fix(rectification): ask reverse-inference probes at engine dasha months
Independent Staging Quality Gate / validate (push) Failing after 8m39s
Independent Staging Quality Gate / publish (push) Has been skipped

Keep Vimshottari/Narayana start dates instead of truncating to year, so
same-year month splits can appear on the choice card.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-28 10:20:05 +08:00
parent 4767b34ff0
commit 68a78af9e1
14 changed files with 359 additions and 84 deletions
+97 -16
View File
@@ -287,6 +287,87 @@ class EventProbesTest(unittest.TestCase):
probes = _probes(_request(), built, ["05:13", "05:40"], "05:13", precision_current="d4_refine")
self.assertTrue(all(item["source"] != "dasha_boundary" for item in probes))
def test_same_year_month_apart_boundary_uses_engine_month(self) -> None:
from unittest.mock import patch
from scripts.rectification import event_probes as probes_mod
built = {
"static_contexts": [
_context("05:13", d4_asc=0, sun_house=4, sun_varga_sign=3, moon=100.0),
_context("05:40", d4_asc=1, sun_house=10, sun_varga_sign=9, moon=101.0),
]
}
def fake_vim(_birth_date: str, moon: float, _lo: int, _hi: int) -> list[date]:
return [date(2018, 3, 15)] if moon <= 100.0 else [date(2018, 9, 20)]
def fake_narayana(_asc: int, planets: dict, _birth_date: str, _lo: int, _hi: int) -> list[date]:
moon = float(planets.get("Moon") or 0)
return [date(2018, 3, 15)] if moon <= 100.0 else [date(2018, 9, 20)]
def fake_score(context: dict, *, birth_date: str, domain: str, year: int, month: int | None = None) -> dict:
del birth_date, domain
early = probes_mod._context_time(context) == "05:13"
if year == 2018 and month == 3:
return {"rule_ids": ["vim_ad_domain_lord"] if early else ["no_domain_activation"]}
if year == 2018 and month == 9:
return {"rule_ids": ["vim_md_domain_house"] if early else ["no_domain_activation"]}
return {"rule_ids": ["no_domain_activation"]}
with (
patch.object(probes_mod, "_vim_start_dates", side_effect=fake_vim),
patch.object(probes_mod, "_narayana_start_dates", side_effect=fake_narayana),
patch.object(probes_mod, "_score_year", side_effect=fake_score),
):
probes = _probes(_request(), built, ["05:13", "05:40"], "05:13")
row = next(item for item in probes if item["domain"] == "relocation")
self.assertEqual(row["source"], "dasha_boundary")
self.assertEqual(row["year"], 2018)
self.assertIn(row["month"], {3, 9})
self.assertRegex(row["year_label"], r"2018 年 [39] 月前后")
self.assertIn("2018 年", row["user_meaning"])
self.assertIn("月前后", row["user_meaning"])
self.assertNotIn("points", str(row))
def test_vim_start_dates_keep_engine_month(self) -> None:
from scripts.rectification import event_probes as probes_mod
starts = probes_mod._vim_start_dates("1997-08-08", 100.0, 2005, 2025)
self.assertTrue(starts)
self.assertTrue(all(isinstance(item, date) and not isinstance(item, datetime) for item in starts))
self.assertTrue(any(item.month != 7 or item.day != 1 for item in starts))
def test_age_band_fallback_stays_year_precision(self) -> None:
from unittest.mock import patch
from scripts.rectification import event_probes as probes_mod
built = {
"static_contexts": [
_context("05:13", d4_asc=0, sun_house=4, sun_varga_sign=3, moon=100.0),
_context("05:40", d4_asc=1, sun_house=10, sun_varga_sign=9, moon=100.0),
]
}
def fake_score(context: dict, *, birth_date: str, domain: str, year: int, month: int | None = None) -> dict:
del birth_date, domain, year, month
return {
"rule_ids": ["vim_md_domain_house"]
if probes_mod._context_time(context) == "05:13"
else ["no_domain_activation"]
}
with (
patch.object(probes_mod, "_vim_start_dates", return_value=[]),
patch.object(probes_mod, "_narayana_start_dates", return_value=[]),
patch.object(probes_mod, "_score_year", side_effect=fake_score),
):
probes = _probes(_request(), built, ["05:13", "05:40"], "05:13")
row = next(item for item in probes if item["source"] == "dasha_activation")
self.assertNotIn("month", row)
self.assertRegex(row["year_label"], r"^\d{4} 年前后$")
def test_missing_narayana_inputs_do_not_claim_dasha_year(self) -> None:
built = {
"static_contexts": [
@@ -402,15 +483,15 @@ class EventProbesTest(unittest.TestCase):
]
}
def fake_vim(_birth_date: str, moon: float, _lo: int, _hi: int) -> list[int]:
return [2010, 2020] if moon <= 100.0 else [2009, 2019]
def fake_vim(_birth_date: str, moon: float, _lo: int, _hi: int) -> list[date]:
return [date(2010, 3, 15), date(2020, 3, 15)] if moon <= 100.0 else [date(2009, 9, 15), date(2019, 9, 15)]
def fake_narayana(_asc: int, planets: dict, _birth_date: str, _lo: int, _hi: int) -> list[int]:
def fake_narayana(_asc: int, planets: dict, _birth_date: str, _lo: int, _hi: int) -> list[date]:
moon = float(planets.get("Moon") or 0)
return [2010, 2020] if moon <= 100.0 else [2009, 2019]
return [date(2010, 3, 15), date(2020, 3, 15)] if moon <= 100.0 else [date(2009, 9, 15), date(2019, 9, 15)]
def fake_score(context: dict, *, birth_date: str, domain: str, year: int) -> dict:
del birth_date, domain
def fake_score(context: dict, *, birth_date: str, domain: str, year: int, month: int | None = None) -> dict:
del birth_date, domain, month
early = probes_mod._context_time(context) == "05:13"
if year == 2010:
return {"rule_ids": ["vim_ad_domain_lord"] if early else ["no_domain_activation"]}
@@ -419,8 +500,8 @@ class EventProbesTest(unittest.TestCase):
return {"rule_ids": ["no_domain_activation"]}
with (
patch.object(probes_mod, "_vim_start_years", side_effect=fake_vim),
patch.object(probes_mod, "_narayana_start_years", side_effect=fake_narayana),
patch.object(probes_mod, "_vim_start_dates", side_effect=fake_vim),
patch.object(probes_mod, "_narayana_start_dates", side_effect=fake_narayana),
patch.object(probes_mod, "_score_year", side_effect=fake_score),
):
probes = _probes(_request(), built, ["05:13", "05:40"], "05:13")
@@ -441,15 +522,15 @@ class EventProbesTest(unittest.TestCase):
]
}
def fake_vim(_birth_date: str, moon: float, _lo: int, _hi: int) -> list[int]:
return [2003, 2019] if moon <= 100.0 else [2002, 2018]
def fake_vim(_birth_date: str, moon: float, _lo: int, _hi: int) -> list[date]:
return [date(2003, 3, 15), date(2019, 3, 15)] if moon <= 100.0 else [date(2002, 9, 15), date(2018, 9, 15)]
def fake_narayana(_asc: int, planets: dict, _birth_date: str, _lo: int, _hi: int) -> list[int]:
def fake_narayana(_asc: int, planets: dict, _birth_date: str, _lo: int, _hi: int) -> list[date]:
moon = float(planets.get("Moon") or 0)
return [2003, 2019] if moon <= 100.0 else [2002, 2018]
return [date(2003, 3, 15), date(2019, 3, 15)] if moon <= 100.0 else [date(2002, 9, 15), date(2018, 9, 15)]
def fake_score(context: dict, *, birth_date: str, domain: str, year: int) -> dict:
del birth_date, domain, year
def fake_score(context: dict, *, birth_date: str, domain: str, year: int, month: int | None = None) -> dict:
del birth_date, domain, year, month
return {
"rule_ids": ["vim_md_domain_house"]
if probes_mod._context_time(context) == "05:13"
@@ -457,8 +538,8 @@ class EventProbesTest(unittest.TestCase):
}
with (
patch.object(probes_mod, "_vim_start_years", side_effect=fake_vim),
patch.object(probes_mod, "_narayana_start_years", side_effect=fake_narayana),
patch.object(probes_mod, "_vim_start_dates", side_effect=fake_vim),
patch.object(probes_mod, "_narayana_start_dates", side_effect=fake_narayana),
patch.object(probes_mod, "_score_year", side_effect=fake_score),
):
probes = _probes(_request(), built, ["05:13", "05:40"], "05:13")