fix(kp): feed sub-lord analysis unrounded cusps, degrees and time (BUG-1543, upstream 26d5a52d)

cmd_kp handed calc_kp_analysis the cusps from calculate_bhava_boundaries
(rounded to 4 decimals), degree_in_sign (4 decimals) and the chart's
display julian_day (6 decimals). KP star, sub and sub-sub boundaries are
cut on raw longitude, so a body or cusp sitting on a boundary could land
one cell off. The fix recomputes the JD from the civil instant with the
compute_chart_data formula, uses calculate_cusps directly, and uses
degree_raw % 30.

Only the KP part of 26d5a52d is ported. Its Vivaha Saham day/night change
and Yoga status pass-through are left for a separate decision. Test ported
from upstream tests/test_kp_unrounded_inputs.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
This commit is contained in:
2026-11-04 12:01:47 +08:00
co-authored by Claude Opus 5.5
parent 8137c692b1
commit 9d69cd5625
3 changed files with 78 additions and 3 deletions
+17
View File
@@ -21240,3 +21240,20 @@
- 关联记录:BUG-1541、BUG-1220(年盘保留秒)。
- 复发自:无
- 修复版本:分支 `codex/upstream-skill-sync-20261104`,待合并。
## BUG-1543 | KP 子主星与宫头用的是显示用的四舍五入值
- 状态:resolved(代码 + 回归测试;待部署)
- 首次发现 / 最近更新:2026-11-04(上游同步第十轮核对上游 `26d5a52d`)/ 2026-11-04
- 影响面:`scripts/jyotish_engine.py` `cmd_kp`(命令 `kp`、`/api/kp` 一类入口)。KP 在商业覆盖层仍是 `reference_only`。
- 现象:行星落在星主 / 子主 / 子子主分界附近、或宫头附近时,KP 表可能差一格。
- 触发条件:行星度数或宫头离 KP 分界小于约 0.0001 度;出生时间带秒。
- 根因:`cmd_kp` 把 `calculate_bhava_boundaries()` 返回的宫头(保留 4 位小数)、行星的 `degree_in_sign`(保留 4 位小数)和星盘结果里的 `julian_day`(保留 6 位小数,约 0.09 秒)交给 KP 分析,KP 分界是按原始经度切的。上游同一缺陷,10-16 修复,第九轮没核对。
- 修复:按出生时刻重算儒略日(与 `compute_chart_data` 同一公式,含秒);宫头改用 `calculate_cusps()` 原值;行星度数用 `degree_raw % 30`,没有原值时才退回显示值。
- 验证:新测试 `tests/test_upstream_sync10_kp_unrounded_inputs.py`(移植上游 `tests/test_kp_unrounded_inputs.py`):儒略日、宫头、行星度数都是原值;改前失败、改后通过。`test_kp_system.py`、`test_kp_cusp_house_assignment.py`、`test_kp_monthly_report_packet.py` 5 条通过、2 条跳过;公开名人盘实跑 `cmd_kp` 正常返回。
- 影响输出:只有贴着分界的行星或宫头会换一格,其余逐字节相同。不涉及冻结身份文件。
- 未搬:同一上游提交里的 Vivaha Saham 昼夜公式与 Yoga 燃烧状态透传,见本轮进度记录。
- 防复发:上述测试;显示用的取整值不进计算。
- 关联记录:BUG-1383(KP 边界,第八轮)、BUG-1541、BUG-1542。
- 复发自:无
- 修复版本:分支 `codex/upstream-skill-sync-20261104`,待合并。
+13 -3
View File
@@ -13749,6 +13749,12 @@ def cmd_kp(args):
_apply_ayanamsa(requested_report_ayanamsa)
return {"error": "swisseph未安装"}
# The chart's julian_day is rounded for display. KP cusp and sub-lord
# boundaries use the civil instant itself, seconds included (upstream
# 26d5a52d, BUG-1543). Same formula as compute_chart_data.
jd = swe.julday(args.year, args.month, args.day,
_birth_hour_decimal(args.hour, args.minute, _arg_second(args)) - args.tz)
asc_sign = chart.get("ascendant", {}).get("sign", "Aries")
planets = chart.get("planets", {})
planet_lons = {
@@ -13762,7 +13768,8 @@ def cmd_kp(args):
from bhava_chalit import BhavaChalitCalculator
kp_house_calculator = BhavaChalitCalculator()
asc_lon = chart.get('ascendant', {}).get('degree_raw', chart.get('ascendant', {}).get('degree', 0.0))
boundaries = kp_house_calculator.calculate_bhava_boundaries(
# Unrounded cusps; calculate_bhava_boundaries rounds them to 4 decimals.
raw_cusps = kp_house_calculator.calculate_cusps(
float(asc_lon), 0.0, 'placidus', jd, args.lat, args.lon,
)
bhava_chart = kp_house_calculator.get_bhava_chalit_chart(
@@ -13776,16 +13783,19 @@ def cmd_kp(args):
for pname, pdata in planets.items():
if isinstance(pdata, dict) and 'sign' in pdata:
bhava_row = bhava_planets.get(pname) if isinstance(bhava_planets.get(pname), dict) else {}
raw_lon = planet_lons.get(pname)
planet_positions[pname] = {
'sign': pdata['sign'],
'degree': pdata.get('degree_in_sign', pdata.get('degree', 0) % 30),
# degree_in_sign is rounded to 4 decimals for display.
'degree': (float(raw_lon) % 30 if raw_lon is not None
else pdata.get('degree_in_sign', pdata.get('degree', 0) % 30)),
'house': bhava_row.get('bhava_house', pdata.get('house', 1)),
}
result = calc_kp_analysis(
planet_positions,
asc_sign,
house_cusps=boundaries.get('cusps'),
house_cusps=raw_cusps,
house_occupancy_basis='cusp_to_cusp',
)
moon_lon = planets.get('Moon', {}).get('degree_raw', planets.get('Moon', {}).get('degree', 0.0)) if isinstance(planets.get('Moon'), dict) else 0.0
@@ -0,0 +1,48 @@
"""KP must not reuse display-rounded time, cusps or planet degrees (BUG-1543).
Ported from upstream yinduzhanxing 26d5a52d tests/test_kp_unrounded_inputs.py
(sync 10). Synthetic chart only.
"""
import sys
from pathlib import Path
from types import SimpleNamespace
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'scripts'))
import jyotish_engine as engine # noqa: E402
from bhava_chalit import BhavaChalitCalculator # noqa: E402
def test_kp_passes_raw_time_cusps_and_longitudes(monkeypatch):
args = SimpleNamespace(year=2000, month=1, day=2, hour=3, minute=4, second=5,
lat=20., lon=75., tz=5.5, ayanamsa='raman')
chart = {'ascendant': {'sign': 'Aries', 'degree_raw': 12.123456789},
'planets': {'Sun': {'sign': 'Aries', 'degree_raw': 1.234567891, 'degree_in_sign': 1.2346},
'Moon': {'sign': 'Taurus', 'degree_raw': 42.987654321, 'degree_in_sign': 12.9877}}}
raw_cusps = [12.123456789 + 30 * i for i in range(12)]
seen = {}
monkeypatch.setattr(engine, '_compute_chart_from_args', lambda a: (chart, 0, 2451545., 23.))
def cusps(self, asc, mc, hs, jd, lat, lon):
seen['jd'] = jd
return raw_cusps
monkeypatch.setattr(BhavaChalitCalculator, 'calculate_cusps', cusps)
monkeypatch.setattr(BhavaChalitCalculator, 'get_bhava_chalit_chart',
lambda *a, **k: {'planets': {'Sun': {'bhava_house': 12}, 'Moon': {'bhava_house': 2}}})
def analyze(planets, asc, **kwargs):
seen['planets'] = planets
seen.update(kwargs)
return {}
monkeypatch.setattr(engine, 'calc_kp_analysis', analyze)
monkeypatch.setattr(engine, 'attach_calculation_profile', lambda result, args: result)
monkeypatch.setattr(engine, '_build_response_envelope', lambda command, result, **k: result)
engine.cmd_kp(args)
expected = engine.swe.julday(2000, 1, 2, 3 + 4 / 60 + 5 / 3600 - 5.5)
assert seen['jd'] == expected and seen['jd'] != round(expected, 6)
assert seen['house_cusps'] == raw_cusps
assert seen['planets']['Sun']['degree'] == 1.234567891
assert seen['planets']['Moon']['degree'] == 42.987654321 % 30
assert seen['house_occupancy_basis'] == 'cusp_to_cusp'