Files
Jyotisha/scripts/solar_return.py
2026-07-13 22:53:04 +08:00

668 lines
26 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Solar Return (太阳返照盘 / Varshaphala) 计算模块
Jyotish Vedic Astrology Skill — v6.0.18
功能:
1. find_solar_return_ut() — 计算太阳返照精确 UT 时刻
2. calc_solar_return_chart() — 生成完整的太阳返照盘(调用 engine 的 compute_chart_data
3. solar_return_full_report() — 太阳返照盘完整报告(整合 Muntha / Year Lord / Tajika Yogas
依赖:
- swisseph(高精度天文计算,可选)
- 若 swisseph 不可用,使用近似算法(误差 ±1 天以内)
"""
from typing import Dict, Optional, Tuple, List
from datetime import datetime, timedelta
import math
import sys
import os
from ayanamsa_utils import sidereal_flags
# ── swisseph 可用性检测 ─────────────────────────────────────────────
try:
import swisseph as swe
HAS_SWE = True
except ImportError:
HAS_SWE = False
def _gregorian_calendar_flag() -> int:
"""Return the Gregorian calendar flag across swisseph builds."""
return int(getattr(swe, 'GREG_FLAG', 1)) if HAS_SWE else 1
# ── 常量 ──────────────────────────────────────────────────────────
SIGNS = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
SIGN_LORDS = {'Aries':'Mars','Taurus':'Venus','Gemini':'Mercury','Cancer':'Moon',
'Leo':'Sun','Virgo':'Mercury','Libra':'Venus','Scorpio':'Mars',
'Sagittarius':'Jupiter','Capricorn':'Saturn','Aquarius':'Saturn','Pisces':'Jupiter'}
# Sun sign lookup table (approximate, valid ~1950-2100, Lahiri ayanamsa)
# Maps (month, day_range) → sidereal sign index
_SUN_SIGN_LOOKUP = {
1: [(1, 14, 9), (15, 31, 10)], # Jan: Sagittarius → Capricorn
2: [(1, 12, 10), (13, 29, 11)], # Feb: Capricorn → Aquarius
3: [(1, 14, 11), (15, 31, 0)], # Mar: Aquarius → Pisces
4: [(1, 13, 0), (14, 30, 1)], # Apr: Pisces → Aries
5: [(1, 14, 1), (15, 31, 2)], # May: Aries → Taurus
6: [(1, 15, 2), (16, 30, 3)], # Jun: Taurus → Gemini
7: [(1, 16, 3), (17, 31, 4)], # Jul: Gemini → Cancer
8: [(1, 16, 4), (17, 31, 5)], # Aug: Cancer → Leo
9: [(1, 16, 5), (17, 30, 6)], # Sep: Leo → Virgo
10: [(1, 17, 6), (18, 31, 7)], # Oct: Virgo → Libra
11: [(1, 16, 7), (17, 30, 8)], # Nov: Libra → Scorpio
12: [(1, 15, 8), (16, 31, 9)], # Dec: Scorpio → Sagittarius
}
def _estimate_sun_sign(birth_month: int, birth_day: int) -> int:
"""从出生月日估算出生太阳星座(查表法,用于无 swisseph 时的近似计算)。
精度 ±1 星座,对 Muntha 计算足够(Muntha 只关心12年周期)。
返回 sign index 0-11。"""
entries = _SUN_SIGN_LOOKUP.get(birth_month, [(1, 31, 0)])
for day_start, day_end, sign_idx in entries:
if day_start <= birth_day <= day_end:
return sign_idx
return 0 # fallback
# =========================================================================
# 工具函数
# =========================================================================
def _jd_to_datetime(jd_ut: float) -> datetime:
"""Julian Day (UT) → datetime (UTC)"""
# 使用 swisseph 的辅助函数,或手动转换
if HAS_SWE:
# swe.revjul 返回 (year, month, day, hour, minute, second)
rev = swe.revjul(jd_ut, _gregorian_calendar_flag())
if len(rev) == 4:
y, m, d, hour_decimal = rev
h = int(hour_decimal)
minute_decimal = (hour_decimal - h) * 60.0
mn = int(minute_decimal)
s = int(round((minute_decimal - mn) * 60.0))
if s >= 60:
s -= 60
mn += 1
if mn >= 60:
mn -= 60
h += 1
else:
y, m, d, h, mn, s = rev
return datetime(y, m, d, int(h), int(mn), int(s))
else:
# 近似:JD 2440587.5 = 1970-01-01 00:00:00 UT
jd_unix = jd_ut - 2440587.5
ts = int(jd_unix * 86400)
return datetime(1970, 1, 1) + timedelta(seconds=ts)
def _datetime_to_jd_ut(dt: datetime) -> float:
"""datetime (UTC) → Julian Day (UT)"""
if HAS_SWE:
return swe.julday(dt.year, dt.month, dt.day,
dt.hour + dt.minute/60.0 + dt.second/3600.0,
_gregorian_calendar_flag())
else:
# 近似
unix_sec = int((dt - datetime(1970, 1, 1)).total_seconds())
return 2440587.5 + unix_sec / 86400.0
def _get_sun_lon_jd(jd_ut: float, ayanamsa_name: str = 'lahiri') -> Optional[float]:
"""计算给定 JD (UT) 的太阳恒星黄经(Lahiri"""
if not HAS_SWE:
return None
try:
res = swe.calc_ut(jd_ut, swe.SUN, sidereal_flags(swe, ayanamsa_name))
return float(res[0][0] % 360.0)
except Exception:
return None
# =========================================================================
# 核心:计算太阳返照精确时刻
# =========================================================================
def find_solar_return_ut(
birth_jd_ut: float,
birth_sun_lon: float,
target_year: int,
lat: float = 0.0,
lon: float = 0.0,
tz: float = 0.0,
max_iter: int = 30,
tol_deg: float = 0.0003, # ~1 arcsec
ayanamsa_name: str = 'lahiri',
) -> Dict:
"""
计算太阳返照(Solar Return)精确 UT 时刻。
参数:
birth_jd_ut: 出生时刻 JD (UT)
birth_sun_lon: 出生太阳恒星黄经 (0-360)
target_year: 目标年份(计算该年的太阳返照)
lat, lon, tz: 出生地点(仅用于近似计算,精确计算不需要)
max_iter: 最大迭代次数
tol_deg: 收敛精度(度)
返回:
dict: {
'jd_ut': float, # 太阳返照时刻 JD (UT)
'dt_ut': datetime, # 太阳返照 UTC 时间
'dt_local': datetime, # 太阳返照本地时间(近似)
'sun_lon': float, # 返照时太阳经度(应≈birth_sun_lon
'error_deg': float, # 太阳经度误差(度)
'iterations': int, # 实际迭代次数
'method': str, # 'swisseph' or 'approximation'
'note': str,
}
"""
if HAS_SWE:
return _find_solar_return_swe(birth_jd_ut, birth_sun_lon, target_year,
max_iter, tol_deg, ayanamsa_name)
else:
return _find_solar_return_approx(birth_jd_ut, birth_sun_lon, target_year, tz)
def _find_solar_return_swe(
birth_jd_ut: float,
birth_sun_lon: float,
target_year: int,
max_iter: int,
tol_deg: float,
ayanamsa_name: str = 'lahiri',
) -> Dict:
"""使用 swisseph 精确计算太阳返照时刻(Newton 迭代法)"""
# 近似起始点:出生日期在目标年份的同一天
birth_dt = _jd_to_datetime(birth_jd_ut)
try:
approx_dt = datetime(target_year, birth_dt.month, birth_dt.day, 12, 0, 0)
except ValueError:
# 出生是 2月29日 等特殊情况
approx_dt = datetime(target_year, birth_dt.month, 1, 12, 0, 0)
jd_guess = _datetime_to_jd_ut(approx_dt)
prev_diff = None
for i in range(max_iter):
sun_lon = _get_sun_lon_jd(jd_guess, ayanamsa_name=ayanamsa_name)
if sun_lon is None:
return {'error': 'swisseph calc_ut failed', 'method': 'swisseph_failed'}
diff = (sun_lon - birth_sun_lon + 180.0) % 360.0 - 180.0 # -180..180
if abs(diff) < tol_deg:
# 收敛
dt_ut = _jd_to_datetime(jd_guess)
return {
'jd_ut': jd_guess,
'dt_ut': dt_ut,
'dt_local': dt_ut, # 调用方另行加 tz
'sun_lon': sun_lon,
'error_deg': abs(diff),
'iterations': i + 1,
'method': 'swisseph_newton',
'note': f'Newton迭代 {i+1} 步收敛,误差 {abs(diff)*3600:.1f}"',
}
# Newton 步:sun 速度 ~1°/天,步长 = -diff(天)
# 更精确:用瞬时速度(下一小时的速度)
jd_next = jd_guess + 1.0 / 24.0
sun_lon_next = _get_sun_lon_jd(jd_next, ayanamsa_name=ayanamsa_name)
if sun_lon_next is not None:
speed = (sun_lon_next - sun_lon) * 24.0 # °/day
if abs(speed) > 0.01:
step = -diff / speed # days
else:
step = -diff # fallback
else:
step = -diff
jd_guess += step
prev_diff = diff
# 未收敛:返回当前最佳估计
dt_ut = _jd_to_datetime(jd_guess)
sun_lon = _get_sun_lon_jd(jd_guess, ayanamsa_name=ayanamsa_name)
return {
'jd_ut': jd_guess,
'dt_ut': dt_ut,
'dt_local': dt_ut,
'sun_lon': sun_lon,
'error_deg': abs((sun_lon - birth_sun_lon + 180) % 360 - 180) if sun_lon else None,
'iterations': max_iter,
'method': 'swisseph_newton_not_converged',
'note': f'Newton迭代 {max_iter} 步未完全收敛,误差较大,请检查',
'warning': True,
}
def _find_solar_return_approx(
birth_jd_ut: float,
birth_sun_lon: float,
target_year: int,
tz: float,
) -> Dict:
"""
近似计算太阳返照时间(不使用 swisseph)。
误差:±1 天以内(太阳黄经误差 < 1°)。
"""
birth_dt = _jd_to_datetime(birth_jd_ut)
try:
approx_dt_utc = datetime(target_year, birth_dt.month, birth_dt.day, 12, 0, 0)
except ValueError:
approx_dt_utc = datetime(target_year, birth_dt.month, 1, 12, 0, 0)
dt_local = approx_dt_utc + timedelta(hours=tz)
return {
'jd_ut': _datetime_to_jd_ut(approx_dt_utc),
'dt_ut': approx_dt_utc,
'dt_local': dt_local,
'sun_lon': None, # 未知(没有 swisseph
'error_deg': None,
'iterations': 0,
'method': 'approximation_no_swe',
'note': '近似算法(无swisseph),太阳返照时间误差约±1天,太阳经度未知;建议安装swisseph以获精确结果',
'warning': True,
}
# =========================================================================
# 生成完整太阳返照盘
# =========================================================================
def calc_solar_return_chart(
birth_year: int, birth_month: int, birth_day: int,
birth_hour: int, birth_minute: int,
birth_lat: float, birth_lon: float, birth_tz: float,
target_year: int,
ayanamsa_name: str = 'lahiri',
) -> Dict:
"""
计算太阳返照盘(Varshaphala)。
工作流程:
1. 计算精确太阳返照时刻(find_solar_return_ut
2. 以返照时刻调用 compute_chart_data() 生成星盘
3. 返回返照盘数据(planet_lons, houses, ascendant 等)
参数:
birth_*: 出生信息
target_year: 目标年份(计算该年太阳返照)
返回:
dict: {
'solar_return': {...}, # find_solar_return_ut 的结果
'chart': {...}, # compute_chart_data 的返回值
'planet_lons': dict, # 返照盘行星经度
'houses': dict, # 返照盘宫位
'ascendant': dict, # 返照盘上升
'note': str,
}
"""
# Step 1: 获取出生太阳经度(需要先计算出生盘)
# 但 compute_chart_data 需要 swisseph,这里假设调用方已算好 birth_sun_lon
# → 改为由调用方传入 birth_sun_lon,或从出生盘数据里取
# 这里我们先做:调用 engine 的 compute_chart_data 算出生盘
try:
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from jyotish_engine import compute_chart_data, HAS_SWE as ENGINE_HAS_SWE
if not ENGINE_HAS_SWE:
return {
'error': 'swisseph未安装,无法计算星盘',
'hint': '安装swisseph: pip install swisseph',
'solar_return_approx': _find_solar_return_approx(
_datetime_to_jd_ut(datetime(birth_year, birth_month, birth_day,
birth_hour, birth_minute, 0)),
0, # birth_sun_lon unknown
target_year,
birth_tz
),
}
# 算出出生盘的太阳经度
birth_chart, birth_asc_idx, birth_jd, _ = compute_chart_data(
birth_year, birth_month, birth_day,
birth_hour, birth_minute,
birth_lat, birth_lon, birth_tz,
'mean',
ayanamsa_name=ayanamsa_name,
)
if birth_chart is None:
return {'error': '出生盘计算失败(swisseph问题)'}
sun_data = birth_chart.get('planets', {}).get('Sun', {})
birth_sun_lon = sun_data.get('degree_raw', sun_data.get('degree', 0))
birth_jd_ut = birth_jd
# Step 2: 计算太阳返照精确时刻
sr_result = find_solar_return_ut(
birth_jd_ut, birth_sun_lon, target_year,
birth_lat, birth_lon, birth_tz,
ayanamsa_name=ayanamsa_name,
)
if 'error' in sr_result:
return {'error': sr_result['error'], 'solar_return': sr_result}
sr_jd_ut = sr_result['jd_ut']
sr_dt_ut = sr_result['dt_ut']
# Step 3: 以返照时刻计算星盘
# compute_chart_data 接受 year/month/day/hour/minute
sr_year, sr_month, sr_day = sr_dt_ut.year, sr_dt_ut.month, sr_dt_ut.day
sr_hour = sr_dt_ut.hour + sr_dt_ut.minute / 60.0 + sr_dt_ut.second / 3600.0
sr_hour_int = int(sr_hour)
sr_minute_int = int((sr_hour - sr_hour_int) * 60)
sr_chart, sr_asc_idx, sr_jd, sr_ayanamsa = compute_chart_data(
sr_year, sr_month, sr_day,
sr_hour_int, sr_minute_int,
birth_lat, birth_lon, 0, # UT 时间,时区=0
'mean',
ayanamsa_name=ayanamsa_name,
)
if sr_chart is None:
return {'error': '返照盘计算失败', 'solar_return': sr_result}
# 提取行星经度
planet_lons = {}
for pn, pd in sr_chart.get('planets', {}).items():
if isinstance(pd, dict) and 'degree' in pd:
planet_lons[pn] = pd['degree']
houses = sr_chart.get('houses', {})
ascendant = sr_chart.get('ascendant', {})
return {
'solar_return': sr_result,
'chart': sr_chart,
'planet_lons': planet_lons,
'houses': houses,
'ascendant': ascendant,
'asc_sign_idx': sr_asc_idx,
'target_year': target_year,
'birth_year': birth_year,
'age': target_year - birth_year,
'note': f'太阳返照盘 {target_year} 年(年龄 {target_year-birth_year} 岁)',
}
except Exception as e:
return {'error': f'calc_solar_return_chart异常: {e}'}
# =========================================================================
# 完整报告:太阳返照盘 + Muntha + Year Lord + Tajika Yogas
# =========================================================================
def solar_return_full_report(
birth_year: int, birth_month: int, birth_day: int,
birth_hour: int, birth_minute: int,
birth_lat: float, birth_lon: float, birth_tz: float,
target_year: int,
ayanamsa_name: str = 'lahiri',
) -> Dict:
"""
太阳返照盘完整报告(Varshaphala 年运分析)。
整合:
- 太阳返照盘计算
- Muntha(年度主星)
- Year Lord(年守护星)
- Tajika YogasIthasala/Easarapha 等)
- Sahams(特殊点)
- Tri-Pataka(三旗评估)
- Mudda Dasha(年内大运)
v6.0.19 修复:swisseph 不可用时不再提前退出,
而是用查表法估算太阳星座,继续计算 Muntha 和 Year Lord。
"""
result = {'target_year': target_year}
age = target_year - birth_year
degraded = False # True when swisseph unavailable
# 1. 计算太阳返照盘
sr = calc_solar_return_chart(
birth_year, birth_month, birth_day,
birth_hour, birth_minute,
birth_lat, birth_lon, birth_tz,
target_year,
ayanamsa_name=ayanamsa_name,
)
if 'error' in sr:
degraded = True
result['error'] = sr['error']
# Extract approximate solar return data if available
sr_approx = sr.get('solar_return_approx', {})
result['solar_return'] = sr_approx if sr_approx else sr
# Estimate sun sign from birth date for Muntha calculation
approx_sun_sign = _estimate_sun_sign(birth_month, birth_day)
planet_lons = {'Sun': approx_sun_sign * 30 + 15} # mid-sign for computations
asc_sign_idx = None
result['sr_chart_info'] = {
'asc_sign': '未知(无swisseph',
'asc_sign_idx': None,
'age': age,
'note': 'swisseph未安装,Muntha使用近似太阳星座(查表法,±1星座)',
}
else:
result['solar_return'] = sr['solar_return']
result['sr_chart_info'] = {
'asc_sign': SIGNS[sr['asc_sign_idx']],
'asc_sign_idx': sr['asc_sign_idx'],
'age': sr['age'],
}
planet_lons = sr['planet_lons']
asc_sign_idx = sr['asc_sign_idx']
# 2. Muntha(正确算法:从返照盘太阳星座开始数)
try:
# Solar return sun sign = birth sun sign (by definition of solar return)
sr_sun_sign = int(planet_lons.get('Sun', 0) / 30) % 12
muntha_sign = (sr_sun_sign + age) % 12
muntha_note = ''
if degraded:
muntha_note = '(基于查表法估算太阳星座,实际Muntha可能偏差±1星座)'
result['muntha'] = {
'muntha_sign_idx': muntha_sign,
'muntha_sign': SIGNS[muntha_sign],
'muntha_lord': SIGN_LORDS[SIGNS[muntha_sign]],
'sr_sun_sign_idx': sr_sun_sign,
'sr_sun_sign': SIGNS[sr_sun_sign],
'age': age,
'formula': f'(返照盘太阳星座{sr_sun_sign} + 年龄{age}) mod 12 = {muntha_sign}',
'interpretation': _interpret_muntha_sign(muntha_sign),
'note': muntha_note,
}
except Exception as e:
result['muntha'] = {'error': str(e)}
# 3. Year Lord= Muntha 守护星)
try:
muntha_data = result.get('muntha', {})
muntha_sign_val = muntha_data.get('muntha_sign_idx', muntha_sign if 'muntha_sign' in dir() else 0)
year_lord_sign = muntha_sign_val
year_lord = SIGN_LORDS[SIGNS[year_lord_sign]]
result['year_lord'] = {
'year_lord': year_lord,
'year_lord_sign_idx': year_lord_sign,
'year_lord_sign': SIGNS[year_lord_sign],
}
except Exception as e:
result['year_lord'] = {'error': str(e)}
# 4. Tajika Yogas(需完整星盘,降级模式跳过)
if degraded:
result['tajika_yogas'] = {'error': 'swisseph未安装,无法计算Tajika Yogas(需完整星盘)'}
else:
try:
from tajika import calc_tajika_yogas
yogas = calc_tajika_yogas(planet_lons, chart_type='varsha')
result['tajika_yogas'] = yogas
except Exception as e:
result['tajika_yogas'] = {'error': str(e)}
# 5. Sahams(需完整星盘,降级模式跳过)
if degraded:
result['sahams'] = {'error': 'swisseph未安装,无法计算Sahams(需完整星盘)'}
else:
try:
from tajika import calc_all_sahams
asc_lon = sr['ascendant'].get('lon', sr['ascendant'].get('degree', 0))
sr_dt_ut = sr['solar_return']['dt_ut']
sahams = calc_all_sahams(planet_lons, asc_lon, sr_dt_ut, chart_type='varsha')
result['sahams'] = sahams
except Exception as e:
result['sahams'] = {'error': str(e)}
# 6. Tri-Pataka(需完整星盘 + year_lord + muntha,降级模式跳过)
if degraded:
result['tri_pataka'] = {'error': 'swisseph未安装,无法计算Tri-Pataka(需完整星盘)'}
else:
try:
from tajika import calc_tri_pataka
yl_data = result.get('year_lord', {})
yl = yl_data.get('year_lord', '')
muntha_data = result.get('muntha', {})
ms = muntha_data.get('muntha_sign_idx', 0)
tri = calc_tri_pataka(planet_lons, yl, ms)
result['tri_pataka'] = tri
except Exception as e:
result['tri_pataka'] = {'error': str(e)}
# 7. Mudda Dasha(需 asc_sign_idx + year_lord,降级模式跳过)
if degraded:
result['mudda_dasha'] = {'error': 'swisseph未安装,无法计算Mudda Dasha(需完整星盘)'}
else:
try:
from tajika import calc_mudda_dasha
yl_data = result.get('year_lord', {})
yl = yl_data.get('year_lord', '')
mudda = calc_mudda_dasha(asc_sign_idx, yl, birth_month or 1)
result['mudda_dasha'] = mudda
except Exception as e:
result['mudda_dasha'] = {'error': str(e)}
# 8. Harsha / Panchavargiya Bala(年度强度层)
try:
from tajika import calc_tajika_strength_layers
yl_data = result.get('year_lord', {})
yl = yl_data.get('year_lord', '')
if degraded:
asc_lon = float((asc_sign_idx or 0) * 30)
else:
asc = sr.get('ascendant', {})
asc_lon = asc.get('lon', asc.get('degree', float((asc_sign_idx or 0) * 30)))
result['tajika_strength'] = calc_tajika_strength_layers(
planet_lons,
asc_lon=asc_lon,
year_lord=yl,
)
except Exception as e:
result['tajika_strength'] = {'error': str(e)}
# 9. 综合总结
result['summary'] = _make_sr_summary(result)
return result
def _interpret_muntha_sign(sign_idx: int) -> str:
"""Muntha 在12星座的解读"""
interp = {
0: "年度主题:新开始、冒险、独立行动。适合启动项目。",
1: "年度主题:财务稳定、物质积累。投资、储蓄、巩固基础。",
2: "年度主题:学习、沟通、短途旅行。信息处理量大,社交活跃。",
3: "年度主题:家庭、情感、安全感。家庭事务重要,内心探索。",
4: "年度主题:创造力、自我表达、子女。创作项目,娱乐活动。",
5: "年度主题:健康、服务、日常工作。细节管理,身心调整。",
6: "年度主题:关系、合作、伴侣。婚姻/合伙事务活跃。",
7: "年度主题:深入转型、危机处理。可能涉及共享财务、心理探索。",
8: "年度主题:高等学习、长途旅行、哲学。出国、深造、信念探索。",
9: "年度主题:事业成就、社会地位。职业晋升,长期规划结果。",
10: "年度主题:社交网络、创新、团体。朋友活跃,科技相关。",
11: "年度主题:灵性、慈悲服务、潜意识。修心养性,幕后工作。",
}
return interp.get(sign_idx, '')
def _make_sr_summary(result: Dict) -> str:
"""生成太阳返照盘综合总结"""
lines = ["【太阳返照盘综合总结】"]
target = result.get('target_year', '?')
age = result.get('sr_chart_info', {}).get('age', '?')
lines.append(f" 目标年份:{target} 年(年龄 {age} 岁)")
sr = result.get('solar_return', {})
if 'dt_ut' in sr:
lines.append(f" 太阳返照时刻(UT):{sr['dt_ut']}")
if sr.get('method', '').startswith('approx'):
lines.append(" ⚠ 注意:太阳返照时间为近似值,建议安装swisseph获精确时刻")
muntha = result.get('muntha', {})
if 'muntha_sign' in muntha:
lines.append(f" Muntha{muntha['muntha_sign']}(守护星 {muntha['muntha_lord']}")
lines.append(f"{muntha.get('interpretation','')[:60]}")
yl = result.get('year_lord', {})
if 'year_lord' in yl:
lines.append(f" Year Lord{yl['year_lord']}")
tp = result.get('tri_pataka', {})
if 'verdict' in tp:
lines.append(f" Tri-Pataka{tp['verdict']}{tp.get('interpretation','')[:50]}")
yogas = result.get('tajika_yogas', {})
if 'summary' in yogas:
lines.append(f" Tajika Yogas{yogas['summary'][:100]}")
return '\n'.join(lines)
# =========================================================================
# CLI 测试入口
# =========================================================================
if __name__ == '__main__':
print("Solar Return (太阳返照盘) 模块")
print(f" swisseph可用: {HAS_SWE}")
print()
# Generic public smoke fixture, calculating the 2026 solar return.
test_birth_year, test_birth_month, test_birth_day = 1990, 4, 17
test_birth_hour, test_birth_minute = 14, 45
test_lat, test_lon, test_tz = 36.4667, 114.2, 8.0
test_target_year = 2026
print(f"测试:出生 {test_birth_year}-{test_birth_month:02d}-{test_birth_day:02d} "
f"{test_birth_hour:02d}:{test_birth_minute:02d} (tz+{test_tz})")
print(f" 目标年份:{test_target_year}")
print()
result = solar_return_full_report(
test_birth_year, test_birth_month, test_birth_day,
test_birth_hour, test_birth_minute,
test_lat, test_lon, test_tz,
test_target_year
)
print("结果:")
for k, v in result.items():
if k in ('tajika_yogas', 'sahams', 'mudda_dasha'):
print(f" {k}: (见详细输出)")
elif k == 'sr_chart_info':
print(f" {k}: {v}")
elif k == 'solar_return':
sr = v
print(f" solar_return:")
print(f" method: {sr.get('method', '?')}")
print(f" dt_ut: {sr.get('dt_ut', '?')}")
print(f" sun_lon: {sr.get('sun_lon', '?')}")
print(f" error_deg: {sr.get('error_deg', '?')}")
elif k == 'summary':
print(f" summary:\n{v}")
else:
print(f" {k}: {v}")