Files
Jyotisha/tests/test_rectification_refinement_packet.py
T
Jesse_Chen a3196584d2 feat(rectification): add refinement packet without unique-minute claims
Surface D9/D10 change minutes, event-dasha match copy, dual-dasha conflict, and a post-adopt consult handoff so users can keep narrowing or start a reading from a representative time.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-20 07:47:10 +08:00

177 lines
8.1 KiB
Python

from __future__ import annotations
import unittest
from scripts.rectification.decision_policy import build_candidate_decisions, build_decision_receipt
from scripts.rectification.refinement_packet import (
build_refinement_packet,
dasha_agreement,
match_level,
precision_stage,
window_scan,
)
def feature(time: str, *, d1: int | None = None, d9: int | None = None, d10: int | None = None, d4: int | None = None, degree: float | None = None) -> dict:
vargas = {}
if d9 is not None:
vargas["D9"] = d9
if d10 is not None:
vargas["D10"] = d10
if d4 is not None:
vargas["D4"] = d4
row: dict = {"time": time, "varga_ascendants": vargas}
if d1 is not None:
row["ascendant_sign_index"] = d1
if degree is not None:
row["ascendant_degree"] = degree
return {"feature": row}
def request_events() -> dict:
return {
"events": [
{"id": "00000000-0000-4000-8000-000000000001", "domain": "career", "summary": "入职", "event_kind": "career_entry", "precision": "day", "date_start": "2016-09-15", "date_end": "2016-09-15"},
{"id": "00000000-0000-4000-8000-000000000002", "domain": "relationship", "summary": "开始一段关系", "event_kind": "relationship_start", "precision": "day", "date_start": "2018-03-01", "date_end": "2018-03-01"},
{"id": "00000000-0000-4000-8000-000000000003", "domain": "education", "summary": "毕业", "event_kind": "education_completion", "precision": "day", "date_start": "2015-06-01", "date_end": "2015-06-01"},
]
}
class RefinementPacketTest(unittest.TestCase):
def test_window_scan_emits_change_minutes_without_sign_names(self):
built = {
"static_contexts": [
feature("05:13", d1=1, d9=1, d10=4),
feature("05:14", d1=1, d9=7, d10=4),
]
}
scan = window_scan(built)
self.assertEqual(scan["d9_lagna_count"], 2)
self.assertEqual(scan["d10_lagna_count"], 1)
self.assertEqual(scan["d1_lagna_count"], 1)
self.assertEqual(scan["transitions"], [{
"layer": "d9",
"at": "05:14",
"user_meaning": "D9 在 05:14 发生变化",
}])
encoded = str(scan)
self.assertNotIn("白羊", encoded)
self.assertNotIn("Aries", encoded)
self.assertFalse(scan["unique_minute_claim"])
self.assertFalse(scan["confirmation_allowed"])
def test_match_level_and_dual_dasha_conflict(self):
self.assertEqual(match_level(["vim_md_domain_house"]), "strong")
self.assertEqual(match_level(["vim_ad_domain_lord"]), "medium")
self.assertEqual(match_level(["no_domain_activation"]), "none")
built = {
"matrix": {
"00000000-0000-4000-8000-000000000001": {
"05:13": {"points": 8, "rule_ids": ["vim_md_domain_house"]},
"05:14": {"points": 1, "rule_ids": ["vim_ad_domain_house"]},
},
"00000000-0000-4000-8000-000000000002": {
"05:13": {"points": 1, "rule_ids": ["narayana_ad_domain_house"]},
"05:14": {"points": 9, "rule_ids": ["narayana_md_domain_house"]},
},
}
}
agreement = dasha_agreement(built, ["05:13", "05:14"])
self.assertEqual(agreement["status"], "conflict")
self.assertEqual(agreement["vimshottari_top"], "05:13")
self.assertEqual(agreement["narayana_top"], "05:14")
self.assertIn("冲突", agreement["user_meaning"])
def test_precision_stage_walks_d1_then_d9_then_ready(self):
self.assertEqual(precision_stage({"d1_candidates_differ": True}, 2)["current"], "lagna_frame")
self.assertEqual(precision_stage({"d9_candidates_differ": True}, 2)["current"], "d9_refine")
self.assertEqual(precision_stage({"d10_candidates_differ": True}, 2)["current"], "d10_refine")
self.assertEqual(precision_stage({"d4_candidates_differ": True}, 2)["current"], "theme_refine")
ready = precision_stage({}, 3)
self.assertEqual(ready["current"], "ready_to_adopt")
self.assertFalse(ready["unique_minute_claim"])
def test_packet_ledger_oos_and_nakshatra_without_scores(self):
request = request_events()
built = {
"static_contexts": [
feature("05:13", d1=1, d9=1, d10=4, degree=13.1),
feature("05:14", d1=2, d9=1, d10=4, degree=13.2),
],
"matrix": {
request["events"][0]["id"]: {
"05:13": {"points": 6, "rule_ids": ["vim_md_domain_house", "narayana_md_domain_house"]},
"05:14": {"points": 1, "rule_ids": ["no_domain_activation"]},
},
request["events"][1]["id"]: {
"05:13": {"points": 2, "rule_ids": ["vim_ad_domain_house"]},
"05:14": {"points": 2, "rule_ids": ["vim_ad_domain_house"]},
},
request["events"][2]["id"]: {
"05:13": {"points": 0, "rule_ids": ["no_domain_activation"]},
"05:14": {"points": 0, "rule_ids": ["no_domain_activation"]},
},
},
}
packet = build_refinement_packet(
request,
built,
representative_time="05:13",
candidate_times=["05:13", "05:14"],
)
self.assertEqual(packet["event_dasha_ledger"][0]["match"], "strong")
self.assertIn("入职", packet["event_dasha_ledger"][0]["user_meaning"])
self.assertNotIn("points", str(packet["event_dasha_ledger"]))
self.assertEqual(packet["lagna_contrast"]["intervals"][0]["lagna"], "金牛座")
self.assertTrue(packet["nakshatra_boundary"]["near_boundary"])
self.assertEqual(packet["oos_blind_prompts"][0]["domain"], "family")
self.assertFalse(packet["oos_blind_prompts"][0]["used_for_scoring"])
self.assertFalse(packet["confirmation_allowed"])
encoded = str(packet["window_scan"])
self.assertNotIn("热情冲动", encoded)
self.assertNotIn("配偶类型", encoded)
def test_decision_receipt_downgrades_confidence_on_dasha_conflict(self):
request = request_events()
rows = [
{"time": "05:13", "score": 20, "evidence": [], "missing_layers": []},
{"time": "05:14", "score": 8, "evidence": [], "missing_layers": []},
]
decisions = build_candidate_decisions(rows, result_id="00000000-0000-4000-8000-000000000099")
built = {
"missing_layers": [],
"static_contexts": [feature("05:13", d1=1, d9=1, d10=4), feature("05:14", d1=1, d9=1, d10=4)],
"matrix": {
request["events"][0]["id"]: {
"05:13": {"points": 8, "rule_ids": ["vim_md_domain_house"]},
"05:14": {"points": 1, "rule_ids": ["vim_ad_domain_house"]},
},
request["events"][1]["id"]: {
"05:13": {"points": 1, "rule_ids": ["narayana_ad_domain_house"]},
"05:14": {"points": 9, "rule_ids": ["narayana_md_domain_house"]},
},
request["events"][2]["id"]: {
"05:13": {"points": 1, "rule_ids": ["vim_md_domain_varga"]},
"05:14": {"points": 1, "rule_ids": ["vim_md_domain_varga"]},
},
},
}
diagnostics = {
"leave_one_event_out_retention_rate": 1,
"leave_one_domain_out_retention_rate": 1,
"date_sensitivity_retention_rate": 1,
"primary_secondary_margin_percent": 50,
}
receipt = build_decision_receipt(request, decisions, built, diagnostics)
self.assertTrue(receipt["acceptance_allowed"])
self.assertFalse(receipt["confirmation_allowed"])
self.assertEqual(receipt["overall_confidence"], "medium")
self.assertIn("vimshottari_narayana_conflict", receipt["reasons"])
self.assertEqual(receipt["dasha_agreement"]["status"], "conflict")
self.assertEqual(receipt["event_dasha_ledger"][0]["match_label"], "强相关")
if __name__ == "__main__":
unittest.main()