From f02d27a73f49c47fee5fb0e20a5b48ce4f33c3b6 Mon Sep 17 00:00:00 2001 From: jesse-ux Date: Wed, 7 Oct 2026 17:36:52 +0800 Subject: [PATCH] fix(solar-return): thread the birth second into the return instant (BUG-1267, upstream e4a6ac01) --- docs/BUG_HISTORY.md | 16 ++++++++++ scripts/cmd_solar_return.py | 1 + scripts/jyotish_api_server.py | 2 +- scripts/jyotish_engine.py | 4 ++- scripts/solar_return.py | 6 +++- tests/test_solar_return_birth_second.py | 39 +++++++++++++++++++++++++ 6 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 tests/test_solar_return_birth_second.py diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index e91c9e59..533f422d 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -17030,3 +17030,19 @@ - 相关记录:无 - 复发自:无 - 修复版本:本分支待发布 + +## BUG-1267 | 太阳返照丢掉出生秒 + +- 状态:resolved +- 首次发现:2026-10-07 +- 最近更新:2026-10-07 +- 影响面:`scripts/solar_return.py`、`scripts/cmd_solar_return.py`、全盘与年运接口里的太阳返照 +- 用户现象:出生秒不是 0 时,返照时刻仍按整分计算 +- 触发条件:计算太阳返照盘或 Varshaphala 完整报告 +- 根因:`calc_solar_return_chart` 与 `solar_return_full_report` 的出生时刻只接到分。上游 `e4a6ac01` 把秒传进出生盘。同一提交里的视位置拟合、减 5 秒和交点修正没有启用。Shadbala 方法声明函数只在未移植的 `evidence_labeled_reporting.py`,本仓没有对应函数,文案未改 +- 修复:上述两个函数、命令行、全盘两处调用和年运接口都传入 `birth_second`。年运接口把秒写在原有参数行上,不增加行数。近似无星历时,秒也进入出生时刻 +- 验证:`tests/test_solar_return_birth_second.py`。虚构盘秒 40 与秒 0 的返照儒略日相差超过 20 秒,且与上游 `23be1807` 的返照时刻一致 +- 防复发:上述测试钉住上游时刻 +- 相关记录:BUG-1220 +- 复发自:无 +- 修复版本:本分支待发布 diff --git a/scripts/cmd_solar_return.py b/scripts/cmd_solar_return.py index e38eb714..37e94da9 100644 --- a/scripts/cmd_solar_return.py +++ b/scripts/cmd_solar_return.py @@ -43,6 +43,7 @@ def cmd_solar_return(args: Any) -> Dict[str, Any]: args.lat, args.lon, args.tz, args.target_year, ayanamsa_name=getattr(args, 'ayanamsa', None) or DEFAULT_AYANAMSA_NAME, + birth_second=int(getattr(args, 'second', 0) or 0), **solar_return_location_kwargs(args), ) diff --git a/scripts/jyotish_api_server.py b/scripts/jyotish_api_server.py index 67005a8a..92120774 100644 --- a/scripts/jyotish_api_server.py +++ b/scripts/jyotish_api_server.py @@ -8240,7 +8240,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler, VedastroEvidenceMixin, SynastryM lon, tz, target_year, - ayanamsa_name=_request_ayanamsa(body), + ayanamsa_name=_request_ayanamsa(body), birth_second=birth_dt.second, ) return {'success': True, 'endpoint': 'annual', 'report': report} diff --git a/scripts/jyotish_engine.py b/scripts/jyotish_engine.py index b9449dee..3d0c814b 100644 --- a/scripts/jyotish_engine.py +++ b/scripts/jyotish_engine.py @@ -14671,7 +14671,8 @@ def _tajika_annual_core(args, planet_lons, asc_si, age, mode='all'): sr_cache['sr'] = calc_solar_return_chart( int(args.year), int(args.month), int(args.day), int(args.hour), int(args.minute), float(args.lat), float(args.lon), float(args.tz), int(args.year) + int(age), - ayanamsa_name=sr_cache['ayanamsa'], **solar_return_location_kwargs(args), + ayanamsa_name=sr_cache['ayanamsa'], birth_second=_arg_second(args), + **solar_return_location_kwargs(args), ) return sr_cache['sr'] @@ -16144,6 +16145,7 @@ def cmd_full_reading(args): args.lat, args.lon, args.tz, args.target_year, ayanamsa_name=_current_ayanamsa_name(args), + birth_second=_arg_second(args), **_solar_return_location_kwargs(args), ) report['modules']['solar_return'] = sr_result diff --git a/scripts/solar_return.py b/scripts/solar_return.py index cd25d148..dd92657d 100644 --- a/scripts/solar_return.py +++ b/scripts/solar_return.py @@ -314,6 +314,7 @@ def calc_solar_return_chart( return_lat: float | None = None, return_lon: float | None = None, return_tz: float | None = None, + birth_second: int = 0, ) -> Dict: """ 计算太阳返照盘(Varshaphala)。 @@ -358,7 +359,7 @@ def calc_solar_return_chart( '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)), + birth_hour, birth_minute, birth_second)), 0, # birth_sun_lon unknown target_year, annual_tz, @@ -372,6 +373,7 @@ def calc_solar_return_chart( birth_hour, birth_minute, birth_lat, birth_lon, birth_tz, 'mean', + second=birth_second, ayanamsa_name=ayanamsa_name, ) if birth_chart is None: @@ -474,6 +476,7 @@ def solar_return_full_report( return_lat: float | None = None, return_lon: float | None = None, return_tz: float | None = None, + birth_second: int = 0, ) -> Dict: """ 太阳返照盘完整报告(Varshaphala 年运分析)。 @@ -503,6 +506,7 @@ def solar_return_full_report( ayanamsa_name=ayanamsa_name, local_timezone_id=local_timezone_id, return_lat=return_lat, return_lon=return_lon, return_tz=return_tz, + birth_second=birth_second, ) if 'error' in sr: diff --git a/tests/test_solar_return_birth_second.py b/tests/test_solar_return_birth_second.py new file mode 100644 index 00000000..84e59351 --- /dev/null +++ b/tests/test_solar_return_birth_second.py @@ -0,0 +1,39 @@ +"""Solar return keeps the birth second (BUG-1267). + +The expected instant is the return computed from upstream 23be1807 for this +fictional chart. Apparent-position fitting and the -5 second offset stay off. +""" + +from __future__ import annotations + +from types import SimpleNamespace + +from scripts.cmd_solar_return import cmd_solar_return +from scripts.solar_return import calc_solar_return_chart + + +FICTIONAL = dict( + birth_year=1980, birth_month=6, birth_day=15, + birth_hour=8, birth_minute=30, + birth_lat=28.6, birth_lon=77.2, birth_tz=5.5, +) +CMD_ARGS = dict(year=1980, month=6, day=15, hour=8, minute=30, lat=28.6, lon=77.2, tz=5.5) +TARGET_YEAR = 2010 +# Upstream 23be1807 calc_solar_return_chart for this fictional chart, second 40, Lahiri. +UPSTREAM_RETURN_ISO = "2010-06-15 19:25:43" + + +def test_birth_second_moves_the_return_instant() -> None: + base = calc_solar_return_chart(**FICTIONAL, target_year=TARGET_YEAR, ayanamsa_name="lahiri", birth_second=0) + shifted = calc_solar_return_chart(**FICTIONAL, target_year=TARGET_YEAR, ayanamsa_name="lahiri", birth_second=40) + assert "error" not in base and "error" not in shifted + assert base["solar_return"]["jd_ut"] != shifted["solar_return"]["jd_ut"] + assert abs(shifted["solar_return"]["jd_ut"] - base["solar_return"]["jd_ut"]) > (20 / 86400) + + +def test_cmd_solar_return_threads_the_second() -> None: + direct = calc_solar_return_chart(**FICTIONAL, target_year=TARGET_YEAR, ayanamsa_name="lahiri", birth_second=40) + via_cmd = cmd_solar_return(SimpleNamespace(**CMD_ARGS, second=40, target_year=TARGET_YEAR, ayanamsa="lahiri")) + assert via_cmd["solar_return"]["jd_ut"] == direct["solar_return"]["jd_ut"] + if UPSTREAM_RETURN_ISO is not None: + assert str(via_cmd["solar_return"]["dt_ut"]) == UPSTREAM_RETURN_ISO