"""BUG-1720: rectification scoring uses the birth moment and the float sub-period split. 原因:产品 2026-11-08 批准;占星师要求整条调用链改浮点切分;BUG-1720。 """ import sys from datetime import datetime, timedelta from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "scripts")) from chart_dasha import birth_moment # noqa: E402 from scripts import active_rectification_event_engine as engine # noqa: E402 from scripts.rectification import event_probes # noqa: E402 BIRTH = datetime(1997, 8, 8, 17, 45) MOON = 100.0 def _float_chain(birth_at: datetime, moon: float, event_at: datetime) -> tuple[str, str, str]: timeline, _, _, _ = birth_moment.mahadashas(birth_at, moon) major = next(p for p in timeline if p["start"] <= event_at < p["end"]) minor = next(p for p in birth_moment.split_period(major) if p["start"] <= event_at < p["end"]) prat = next(p for p in birth_moment.split_period(minor) if p["start"] <= event_at < p["end"]) return major["lord"], minor["lord"], prat["lord"] def test_timeline_starts_from_the_birth_moment_not_midnight(): # 原值:dasha_analyzer.build_dasha_timeline(birth_date) —— 出生日 0 点起算 # 新值:birth_moment.mahadashas(出生时刻),与 cmd_dasha / 星盘页一致 timeline = engine._vimshottari_timeline(BIRTH, MOON) expected, _, _, _ = birth_moment.mahadashas(BIRTH, MOON) assert timeline == expected midnight = engine._vimshottari_timeline(BIRTH.replace(hour=0, minute=0), MOON) assert timeline[0]["start"] - midnight[0]["start"] == timedelta(hours=17, minutes=45) def test_active_lords_follow_the_float_split_around_every_pd_boundary(): # 原值:AD / PD 由整数天的 build_antardasha 切,末段最多早一天结束 # 新值:birth_moment.split_period,末段钉到父段终点 timeline, _, _, _ = birth_moment.mahadashas(BIRTH, MOON) major = next(p for p in timeline if p["start"] <= datetime(2020, 1, 1) < p["end"]) for minor in birth_moment.split_period(major): for prat in birth_moment.split_period(minor): for at in (prat["start"], prat["end"] - timedelta(minutes=1)): assert engine._active_vimshottari(BIRTH, MOON, at) == _float_chain(BIRTH, MOON, at) def test_last_pratyantar_runs_to_the_parent_end(): # 她的复现形状:整数天切分让最后一段提前结束;浮点切分到父段终点前一分钟仍是最后一段 timeline, _, _, _ = birth_moment.mahadashas(BIRTH, MOON) major = timeline[2] last_minor = birth_moment.split_period(major)[-1] at = last_minor["end"] - timedelta(minutes=1) md, ad, pd = engine._active_vimshottari(BIRTH, MOON, at) assert (md, ad) == (major["lord"], last_minor["lord"]) assert pd == birth_moment.split_period(last_minor)[-1]["lord"] def test_a_bare_date_string_is_rejected(): # 原值:第一个参数是 "YYYY-MM-DD" 字符串 # 新值:必须是候选出生时刻 datetime,避免悄悄退回 0 点起算 with pytest.raises(TypeError): engine._active_vimshottari("1997-08-08", MOON, datetime(2020, 1, 1)) with pytest.raises(TypeError): event_probes._vim_start_dates("1997-08-08", MOON, 2005, 2025) def test_probe_boundaries_are_the_float_split_starts(): starts = event_probes._vim_start_dates(BIRTH, MOON, 2005, 2025, include_pratyantar=True) timeline, _, _, _ = birth_moment.mahadashas(BIRTH, MOON) expected = [] for major in timeline: if 2005 <= major["start"].year <= 2025: expected.append(major["start"].date()) for minor in birth_moment.split_period(major): if 2005 <= minor["start"].year <= 2025: expected.append(minor["start"].date()) for prat in birth_moment.split_period(minor): if 2005 <= prat["start"].year <= 2025: expected.append(prat["start"].date()) assert starts == expected def test_no_rectification_caller_uses_the_midnight_timeline(): callers = set() for path in (ROOT / "scripts").rglob("*.py"): text = path.read_text(encoding="utf-8", errors="ignore") code = "\n".join(line for line in text.splitlines() if not line.lstrip().startswith("#")) if "build_dasha_timeline(" in code.replace("def build_dasha_timeline(", ""): callers.add(path.relative_to(ROOT).as_posix()) rectification = {c for c in callers if "rectif" in c or "chart_choice" in c or "nadi_seconds" in c} assert not rectification, rectification