"""Round 2 engine-convergence invariants (TASK-rectification-engine-convergence-20260901).""" from __future__ import annotations import unittest from datetime import date, datetime from pathlib import Path from scripts.rectification.candidate_contrast import distinguish_contract_errors from scripts.rectification.contracts import normalize_rectification_request from scripts.rectification.event_probes import ( _apply_prior_ranking, _dominant_existence_prior, _partition_ranked_probes, _vim_start_dates, discriminating_event_probes, event_clarification_probes, ) from scripts.rectification.refinement_packet import build_refinement_packet, window_scan from scripts.rectification.scoring_service import build_event_contribution_matrix REPO_ROOT = Path(__file__).resolve().parents[1] PLANETS = { "Sun": 12.0, "Moon": 100.0, "Mars": 40.0, "Mercury": 20.0, "Jupiter": 80.0, "Venus": 50.0, "Saturn": 200.0, "Rahu": 310.0, "Ketu": 130.0, } GRID_TIMES = [ f"{4 + (45 + offset) // 60:02d}:{(45 + offset) % 60:02d}" for offset in range(31) ] SPLIT_EVENT_ID = "00000000-0000-4000-8000-000000000003" def _varga(asc: int, planet_sign: int) -> dict: return { "Ascendant": {"sign_idx": asc}, **{name: {"sign_idx": planet_sign} for name in PLANETS}, } def _context( time: str, *, moon: float = 100.0, d4_asc: int = 1, d9_asc: int = 1, d10_asc: int = 1, d12_asc: int = 1, d24_asc: int = 1, sun_house: int = 10, ) -> dict: hour, minute = (int(part) for part in time.split(":")) planets = {**PLANETS, "Moon": moon} natal_planets = { name: {"house": sun_house if name != "Moon" else 4, "lon": lon} for name, lon in planets.items() } return { "candidate_at": datetime(1997, 8, 8, hour, minute), "chart": {"ascendant": {"lon": 10.0, "sign": "Aries"}, "planets": natal_planets}, "planet_longitudes": dict(planets), "ascendant_index": 0, "varga_charts": { "D4": _varga(d4_asc, 1), "D9": _varga(d9_asc, 1), "D10": _varga(d10_asc, 1), "D5": _varga(1, 1), "D24": _varga(d24_asc, 1), "D12": _varga(d12_asc, 1), "D7": _varga(1, 1), "D3": _varga(1, 1), }, "arudha_padas": {}, "feature": { "time": time, "ascendant_sign_index": 0, "varga_ascendants": { "D4": d4_asc, "D9": d9_asc, "D10": d10_asc, "D5": 1, "D24": d24_asc, "D12": d12_asc, }, }, } def _accident_events() -> list[dict]: return [ { "id": "00000000-0000-4000-8000-000000000001", "domain": "education", "event_kind": "education_start", "date_start": "2016-01-01", "date_end": "2016-12-31", "precision": "year", "summary": "上大学", }, { "id": "00000000-0000-4000-8000-000000000002", "domain": "relationship", "event_kind": "relationship_start", "date_start": "2024-05-01", "date_end": "2024-05-31", "precision": "month", "summary": "开始认真交往", }, { "id": SPLIT_EVENT_ID, "domain": "relationship", "event_kind": "relationship_end", "date_start": "2024-08-08", "date_end": "2024-08-08", "precision": "day", "summary": "分手", }, { "id": "00000000-0000-4000-8000-000000000004", "domain": "career", "event_kind": "career_entry", "date_start": "2020-04-01", "date_end": "2020-04-30", "precision": "month", "summary": "实习入职", }, { "id": "00000000-0000-4000-8000-000000000005", "domain": "career", "event_kind": "career_exit", "date_start": "2020-10-01", "date_end": "2020-10-31", "precision": "month", "summary": "实习结束离职", }, ] def _accident_request() -> dict: return normalize_rectification_request( { "birth_date": "1997-08-08", "start_time": "04:45", "end_time": "05:15", "lat": 36.420487, "lon": 114.209936, "tz": 8, "events": _accident_events(), }, today=date(2026, 8, 22), ) def _equal_rows(payload: dict) -> list[dict]: event = payload["events"][0] return [ { "time": time, "score": 10, "evidence": [{ "event_id": event["id"], "domain": event["domain"], "candidate_time": time, "rule_ids": ["vim_md_domain_house"], "points": 10, }], "missing_layers": [], } for time in GRID_TIMES ] def _moons_with_split_proximity() -> tuple[float, float]: event_at = date(2024, 8, 8) ranked: list[tuple[int, float]] = [] for moon in (100.0, 100.5, 101.0, 103.0, 110.0): starts = _vim_start_dates("1997-08-08", moon, 2023, 2025) if not starts: continue nearest = min(abs((item - event_at).days) for item in starts) ranked.append((nearest, moon)) ranked.sort() if len(ranked) < 2 or ranked[0][0] == ranked[-1][0]: raise AssertionError("fixture moons do not split AD/PD proximity") return ranked[0][1], ranked[-1][1] def _probe_events(*, education_count: int = 2, d24_split: bool = True) -> tuple[dict, dict]: events = [ { "id": f"00000000-0000-4000-8000-{index:012d}", "domain": "education", "event_kind": "education_start", "summary": "入学", "date": f"{2015 + index}-09-01", "precision": "month", } for index in range(1, education_count + 1) ] events.extend([ { "id": "00000000-0000-4000-8000-000000000011", "domain": "career", "event_kind": "career_entry", "summary": "入职", "date": "2018-07-01", "precision": "month", }, { "id": "00000000-0000-4000-8000-000000000012", "domain": "career", "event_kind": "career_change", "summary": "换岗", "date": "2020-04-01", "precision": "month", }, { "id": "00000000-0000-4000-8000-000000000013", "domain": "relationship", "event_kind": "relationship_start", "summary": "相识", "date": "2021-08-01", "precision": "month", }, ]) late = 2 if d24_split else 1 built = { "static_contexts": [ _context("05:00", d24_asc=1, d12_asc=1, d9_asc=1, d10_asc=1), _context("05:07", d24_asc=late, d12_asc=late, d9_asc=late, d10_asc=late), ] } return {"birth_date": "1997-08-08", "events": events}, built class EngineConvergenceProximityTests(unittest.TestCase): def test_day_event_proximity_splits_adjacent_minutes_in_accident_shape(self) -> None: from scripts.rectification.dasha_transition_proximity import ( DAY_MAX_POINTS, score_transition_proximity, ) closer_moon, farther_moon = _moons_with_split_proximity() event_at = date(2024, 8, 8) closer_starts = _vim_start_dates("1997-08-08", closer_moon, 2023, 2025) farther_starts = _vim_start_dates("1997-08-08", farther_moon, 2023, 2025) closer_delta = min(abs((item - event_at).days) for item in closer_starts) farther_delta = min(abs((item - event_at).days) for item in farther_starts) closer_points = score_transition_proximity( event_date=event_at, precision="day", vim_starts=closer_starts, narayana_starts=[], ) farther_points = score_transition_proximity( event_date=event_at, precision="day", vim_starts=farther_starts, narayana_starts=[], ) self.assertLessEqual(float(closer_points["points"]), DAY_MAX_POINTS) self.assertNotEqual(closer_points["points"], farther_points["points"]) self.assertEqual( closer_points["points"] > farther_points["points"], closer_delta < farther_delta, ) self.assertTrue( any(str(rule).startswith("vim_transition_proximity_") for rule in closer_points["rule_ids"]) ) contexts = [ _context(time, moon=closer_moon if time == "05:00" else farther_moon) for time in GRID_TIMES ] request = _accident_request() built = build_event_contribution_matrix( request, row_provider=_equal_rows, static_contexts=contexts, ) self.assertEqual(len(built["candidate_times"]), 31) cell_0500 = built["matrix"][SPLIT_EVENT_ID]["05:00"] cell_0507 = built["matrix"][SPLIT_EVENT_ID]["05:07"] self.assertNotEqual(cell_0500["points"], cell_0507["points"]) self.assertEqual( cell_0500["points"] > cell_0507["points"], closer_delta < farther_delta, ) self.assertTrue( any("transition_proximity" in str(rule) for rule in cell_0500["rule_ids"]) or any("transition_proximity" in str(rule) for rule in cell_0507["rule_ids"]) ) self.assertLessEqual(abs(cell_0500["points"] - cell_0507["points"]), DAY_MAX_POINTS) def test_algorithm_and_policy_versions_change_with_proximity_semantics(self) -> None: from scripts.rectification.decision_policy import POLICY_VERSION from scripts.rectification.scoring_service import ALGORITHM_VERSION self.assertNotEqual(ALGORITHM_VERSION, "rectification-v5-matrix-scoring-6") self.assertNotEqual(POLICY_VERSION, "rectification-candidate-policy-v2") class EngineConvergenceProbeTests(unittest.TestCase): def test_anchored_quality_outranks_family_existence_and_drops_dominant_priors(self) -> None: request, built = _probe_events() probes = discriminating_event_probes( request, built, scan=window_scan(built), candidate_times=["05:00", "05:07"], representative_time="05:00", today=date(2026, 8, 22), ) quality = [item for item in probes if item.get("source") == "known_event_quality"] self.assertTrue(quality) self.assertEqual(quality[0]["role"], "distinguish") self.assertTrue(quality[0].get("target_evidence_id")) self.assertFalse(distinguish_contract_errors(quality[0])) ranked_family = _apply_prior_ranking({ "domain": "family", "source": "dasha_boundary", "choice_kind": "existence", "information_gain": float(quality[0].get("raw_split_gain") or quality[0].get("information_gain") or 0), "semantic_key": "family.2024.existence", "year": 2024, "window_span_years": 3, "role": "distinguish", "phase": "candidate_discriminator", "candidate_ids": ["05:00", "05:07"], "expected_outcomes": [ {"answer_class": "yes", "supports": ["05:00"], "conflicts": ["05:07"]}, {"answer_class": "no", "supports": ["05:07"], "conflicts": ["05:00"]}, ], }) self.assertTrue(_dominant_existence_prior(ranked_family, ranked_family["answer_priors"])) _, ranked_dropped = _partition_ranked_probes([quality[0], ranked_family]) self.assertTrue( any(item.get("reason") == "dominant_answer_prior" for item in ranked_dropped), ranked_dropped, ) packet = build_refinement_packet( request, built, representative_time="05:00", candidate_times=["05:00", "05:07"], ) dropped = list(packet.get("dropped_probes") or []) + ranked_dropped self.assertTrue( any(item.get("reason") == "dominant_answer_prior" for item in dropped), dropped, ) self.assertFalse( any( item.get("choice_kind") == "existence" and float(max((item.get("answer_priors") or {}).values() or [0])) > 0.8 for item in probes ) ) def test_quality_distinguish_requires_varga_type_split_and_caps_at_two(self) -> None: same_request, same_built = _probe_events(d24_split=False) same_probes = discriminating_event_probes( same_request, same_built, scan=window_scan(same_built), candidate_times=["05:00", "05:07"], representative_time="05:00", today=date(2026, 8, 22), ) self.assertFalse(any(item.get("source") == "known_event_quality" for item in same_probes)) clarification = event_clarification_probes(same_request) self.assertTrue(any(item.get("source") == "known_event_quality" for item in clarification)) split_request, split_built = _probe_events(education_count=4, d24_split=True) split_probes = discriminating_event_probes( split_request, split_built, scan=window_scan(split_built), candidate_times=["05:00", "05:07"], representative_time="05:00", today=date(2026, 8, 22), ) quality = [item for item in split_probes if item.get("source") == "known_event_quality"] self.assertTrue(quality) self.assertLessEqual(len(quality), 2) for item in quality: self.assertEqual(item["role"], "distinguish") self.assertTrue(item.get("target_evidence_id")) self.assertTrue(item.get("display_date_label")) self.assertEqual(item.get("choice_kind"), "event_quality") self.assertFalse(distinguish_contract_errors(item)) class EngineConvergenceAnswerKeyTests(unittest.TestCase): def test_scripts_and_frontend_have_no_answer_key_literals(self) -> None: forbidden = ("target" + "_minute", "pl9_" + "1993", "regression" + "_only") hits: list[str] = [] roots = ( REPO_ROOT / "scripts", REPO_ROOT / "frontend" / "src", REPO_ROOT / "frontend" / "tests", ) skip_parts = {"node_modules", ".next", "dist"} for root in roots: for path in root.rglob("*"): if not path.is_file() or path.suffix not in {".py", ".ts", ".tsx", ".js", ".mjs"}: continue if any(part in skip_parts for part in path.parts): continue text = path.read_text(encoding="utf-8", errors="ignore") for token in forbidden: if token in text: hits.append(f"{path.relative_to(REPO_ROOT)}:{token}") self.assertEqual(hits, []) if __name__ == "__main__": unittest.main()