fix(dasha): judge antardasha sandhi from the unclipped start

Upstream 3c2dad95, rewritten for this tree. A birth-clipped antardasha start was reported as a transition. The first mahadasha start stays, because BUG-1268 already stores the real pre-birth boundary there. BUG-1382.

Co-Authored-By: Grok (agent loop)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jesse-ux
2026-10-18 16:17:47 +08:00
co-authored by Grok (agent loop) Cursor
parent d11ee6f0dd
commit 5be864a8e4
3 changed files with 82 additions and 3 deletions
+15
View File
@@ -36518,3 +36518,18 @@
- 相关记录:BUG-1268、BUG-1380。
- 复发自:无。
- 修复版本:分支 `codex/upstream-sync8-20261017`。上游提交 `3c2dad95`、`8643bc9a`、`9e04f0db`。
## BUG-1382 | 出生日被当成小运交接
- 状态:resolved
- 首次发现 / 最近更新:2026-10-17 / 2026-10-18
- 来源:issue #80,任务书 T3。上游 `3c2dad95` 的 Sandhi 部分,按我方数据改写。检索「dasha_sandhi」「unclipped_start」,没有更早的同类记录。
- 现象:出生后 30 天内的盘,Sandhi 表把出生日写成一次小运交接。那一天只是第一段小运被裁到出生日,不是两段小运的交界。
- 触发条件:`_calc_dasha_sandhi` 用裁剪后的 `start` 判断小运交接。默认参考时刻还带着当前的时分秒。
- 根因:第一段小运的可见起点被改成了出生日。BUG-1268 之后,第一大运自己的 `start` 已经是出生前的真实交接,不能连大运起点一起跳过。
- 修复:小运起点改用 `unclipped_start_datetime`。第一大运起点仍参与判断。没给参考日期时,参考时刻归零到当天 0 点。
- 验证:`tests/test_dasha_sandhi_birth_clip.py`。虚构盘在出生后 30 天不再把出生日算成小运交接;出生前的大运起点仍在容限内出现。
- 防复发:上述测试。
- 相关记录:BUG-1268、BUG-1380。
- 复发自:无。
- 修复版本:分支 `codex/upstream-sync8-20261017`。上游提交 `3c2dad95`。
+7 -3
View File
@@ -464,7 +464,7 @@ def _calc_sensitive_points(planets):
def _calc_dasha_sandhi(dasha_result, reference_date=None, orb_days=90):
ref = datetime.strptime(reference_date, "%Y-%m-%d") if reference_date else datetime.now()
ref = datetime.strptime(reference_date, "%Y-%m-%d") if reference_date else datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
sandhi = []
timeline = dasha_result.get('timeline', []) if isinstance(dasha_result, dict) else []
for md in timeline:
@@ -486,7 +486,11 @@ def _calc_dasha_sandhi(dasha_result, reference_date=None, orb_days=90):
for boundary_key in ['start', 'end']:
if boundary_key not in ad:
continue
bdt = datetime.strptime(ad[boundary_key], "%Y-%m-%d")
# 被出生裁掉的小运起点不是交接。用完整起点判断,第一大运自己的 start 仍是出生前的真实交接,不跳过。
boundary_date = ad[boundary_key]
if boundary_key == 'start' and ad.get('unclipped_start_datetime'):
boundary_date = datetime.fromisoformat(ad['unclipped_start_datetime']).strftime('%Y-%m-%d')
bdt = datetime.strptime(boundary_date, "%Y-%m-%d")
delta = (bdt - ref).days
if abs(delta) <= orb_days:
sandhi.append({
@@ -494,7 +498,7 @@ def _calc_dasha_sandhi(dasha_result, reference_date=None, orb_days=90):
'mahadasha_lord': md.get('lord'),
'lord': ad.get('lord'),
'boundary': boundary_key,
'date': ad.get(boundary_key),
'date': boundary_date,
'days_from_reference': delta,
'within_orb': True,
})
+60
View File
@@ -0,0 +1,60 @@
"""A clipped birth balance is not an actual Dasha transition.
Upstream ``tests/test_dasha_sandhi_birth_clip.py`` (3c2dad95) skips the first
mahadasha start whenever ``is_balance`` is set. This tree does not: BUG-1268
already stores the real pre-birth mahadasha boundary in ``start``.
| 处 | 原值 | 新值 | 原因 |
| --- | --- | --- | --- |
| 第一大运 ``start`` | ``2000-01-01``,并跳过 ``is_balance`` 起点 | ``1998-01-01``,不跳过 | 我方第一大运起点是出生前的真实交接 |
| 断言 | 跳过起点后窗口为空 | 起点超出容限,窗口仍为空 | 单独证明被裁剪的小运起点不算交接 |
"""
from types import SimpleNamespace
from scripts.jyotish_engine import _calc_dasha_sandhi, cmd_dasha
def test_birth_clip_does_not_create_a_transition():
data = {'timeline': [{'lord': 'Saturn', 'is_balance': True,
'start': '1998-01-01', 'end': '2010-01-01',
'antardasha_timeline': [{'lord': 'Mercury', 'start': '2000-01-01', 'end': '2002-01-01',
'unclipped_start_datetime': '1999-01-01T12:30:00'}]}]}
assert _calc_dasha_sandhi(data, '2000-01-01')['sandhi_windows'] == []
def test_actual_unclipped_start_is_still_found():
data = {'timeline': [{'lord': 'Saturn', 'is_balance': True,
'start': '1998-01-01', 'end': '2010-01-01',
'antardasha_timeline': [{'lord': 'Mercury', 'start': '2000-01-01', 'end': '2002-01-01',
'unclipped_start_datetime': '1999-12-20T12:30:00'}]}]}
windows = _calc_dasha_sandhi(data, '2000-01-01', 30)['sandhi_windows']
assert len(windows) == 1
assert windows[0]['date'] == '1999-12-20'
assert windows[0]['days_from_reference'] == -12
def test_real_boundaries_are_inclusive_at_orb_limit():
data = {'timeline': [{'lord': 'Sun', 'start': '2000-01-01', 'end': '2000-02-01'}]}
windows = _calc_dasha_sandhi(data, '2000-01-02', 1)['sandhi_windows']
assert len(windows) == 1 and windows[0]['date'] == '2000-01-01'
def test_prebirth_mahadasha_start_is_not_discarded():
data = {'timeline': [{'lord': 'Saturn', 'is_balance': True,
'start': '1999-12-01', 'end': '2010-01-01',
'antardasha_timeline': []}]}
windows = _calc_dasha_sandhi(data, '2000-01-01', 90)['sandhi_windows']
assert [row['date'] for row in windows] == ['1999-12-01']
def test_thirty_day_chart_does_not_list_the_birth_day_as_an_antardasha_join():
packet = cmd_dasha(SimpleNamespace(
moon_lon=20 / 3, nakshatra=None, pada=None,
birthdate='2000-01-01', year=2000, month=1, day=1, hour=12, minute=0, second=0,
lat=None, lon=None, tz=0, today='2000-01-31', dasha_year_days=365.25))
windows = _calc_dasha_sandhi(packet, '2000-01-31', 30)['sandhi_windows']
clipped = [
row for row in windows
if row['level'] == 'antardasha' and row['boundary'] == 'start' and row['date'] == '2000-01-01'
]
assert clipped == []