fix(rectification): restore classic eight methods and observe KP Placidus cusps

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-20 15:58:28 +08:00
parent a1914686e8
commit 84e6191fc2
42 changed files with 1488 additions and 185 deletions
@@ -198,5 +198,57 @@ def test_family_event_engine_rows_include_d12_and_are_not_skipped() -> None:
assert "d2-hora" in finance_layers and "d11-labhamsha" in finance_layers
health_layers = public_technique_layers("health_pressure", ["vim_md_domain_house"])
assert "d30-trimshamsha" in health_layers
occupation_layers = public_technique_layers("occupation", ["vim_md_domain_house"])
assert "d1-rashi" in occupation_layers and "d10-dashamsa" in occupation_layers
assert all("D10" in context["feature"]["available_layers"] for context in contexts)
assert all("D5" in context["feature"]["available_layers"] for context in contexts)
def test_occupation_is_auxiliary_tenth_house_independent_of_career() -> None:
prefixes, houses = DOMAIN_CONFIG["occupation"]
assert prefixes == ("D10",)
assert houses == (10,)
assert "occupation" in AUXILIARY_DOMAINS
event = {
"id": "00000000-0000-4000-8000-000000000005",
"domain": "occupation",
"event_kind": "occupation_note",
"date_start": "2019-01-01",
"date_end": "2019-12-31",
"precision": "year",
"summary": "长期工作",
}
assert is_scoreable_event(event) is True
assert is_primary_scoreable_event(event) is False
scored = _score_event(
candidate_time="05:13",
event={
"id": event["id"],
"domain": "occupation",
"event_kind": "occupation_note",
"date": "2019-01-01",
"precision": "year",
},
natal_chart={"ascendant": {"lon": 10.0, "sign": "Aries"}, "planets": {"Sun": {"house": 10, "lon": 12.0}}},
varga_charts=[{"Ascendant": {"sign_idx": 0}, "Sun": {"sign_idx": 9}}],
vimshottari=("Sun", "Moon", "Mars"),
narayana=(None, None),
arudha_padas={},
)
assert "occupation_auxiliary_not_primary" in scored["rule_ids"]
career = _score_event(
candidate_time="05:13",
event={
"id": "00000000-0000-4000-8000-000000000006",
"domain": "career",
"event_kind": "career_entry",
"date": "2019-01-01",
"precision": "year",
},
natal_chart={"ascendant": {"lon": 10.0, "sign": "Aries"}, "planets": {"Sun": {"house": 10, "lon": 12.0}}},
varga_charts=[{"Ascendant": {"sign_idx": 0}, "Sun": {"sign_idx": 9}}],
vimshottari=("Sun", "Moon", "Mars"),
narayana=(None, None),
arudha_padas={},
)
assert scored["points"] < career["points"]
@@ -0,0 +1,64 @@
from __future__ import annotations
from scripts.rectification.horary_observation import build_horary_observation
def _request(**overrides):
body = {
"birth_date": "1993-04-17",
"start_time": "14:29",
"end_time": "14:30",
"lat": 36.683333,
"lon": 114.35,
"tz": 8.0,
"events": [],
}
body.update(overrides)
return body
def test_horary_without_query_is_skipped() -> None:
result = build_horary_observation(_request(events=[{
"id": "00000000-0000-4000-8000-000000000001",
"domain": "career",
"event_kind": "career_entry",
"date_start": "2019-07-01",
"date_end": "2019-07-01",
"precision": "day",
}]))
assert result["status"] == "skipped"
assert result["unique_minute_claim"] is False
def test_dated_horary_query_is_observation_only() -> None:
result = build_horary_observation(_request(events=[{
"id": "00000000-0000-4000-8000-000000000002",
"domain": "horary",
"event_kind": "horary_query",
"date_start": "2024-01-15",
"date_end": "2024-01-15",
"precision": "day",
"summary": "第一次问起是 14:30",
}]))
assert result["status"] in {"executed", "blocked"}
assert result["unique_minute_claim"] is False
assert "不计分" in result["user_meaning"]
if result["status"] == "executed":
assert result["question_date"] == "2024-01-15"
assert result["question_time"] == "14:30"
assert result.get("lagna")
def test_horary_without_coordinates_is_blocked_not_a_gate_fail() -> None:
result = build_horary_observation({
"events": [{
"id": "00000000-0000-4000-8000-000000000003",
"domain": "horary",
"event_kind": "horary_query",
"date_start": "2024-01-15",
"date_end": "2024-01-15",
"precision": "day",
}],
})
assert result["status"] == "blocked"
assert result["unique_minute_claim"] is False
@@ -0,0 +1,181 @@
from __future__ import annotations
from datetime import datetime
from kp_system import KP_LORDS
from scripts.active_rectification_event_engine import (
build_candidate_static_context,
compute_event_candidate_rows,
)
from scripts.rectification.decision_policy import (
build_candidate_decisions,
build_decision_receipt,
build_technique_audit,
)
from scripts.rectification.kp_cusp_observation import observe_kp_cusps
from scripts.rectification.refinement_packet import window_scan
def _request() -> dict:
return {
"birth_date": "1993-04-17",
"start_time": "14:29",
"end_time": "14:30",
"lat": 36.683333,
"lon": 114.35,
"tz": 8.0,
"events": [{
"id": "5cb071d6-6d99-46be-85dc-a9bf59ef6ac5",
"domain": "career",
"date": "2019-07-01",
"precision": "day",
}],
}
def _score_request() -> 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",
},
{
"id": "00000000-0000-4000-8000-000000000004",
"domain": "family",
"summary": "家人变化",
"event_kind": "family_event",
"precision": "day",
"date_start": "2020-01-01",
"date_end": "2020-01-01",
},
]
}
def _diagnostics() -> dict:
return {
"leave_one_event_out_retention_rate": 1,
"leave_one_domain_out_retention_rate": 1,
"date_sensitivity_retention_rate": 1,
"primary_secondary_margin_percent": 50,
}
def test_observe_kp_cusps_uses_placidus_not_equal_houses() -> None:
context = build_candidate_static_context(_request(), datetime(1993, 4, 17, 14, 29))
feature = context["feature"]
snapshot = feature["kp_cusps"]
natal_houses = context["chart"]["houses"]
assert snapshot["status"] == "executed"
assert snapshot["house_system"] == "placidus"
assert snapshot["ayanamsa"] == "krishnamurti"
assert len(snapshot["houses"]) == 12
tenth = snapshot["houses"]["10"]
equal_tenth = float(natal_houses["house_10"]["cusp_degree"])
assert abs(float(tenth["cusp_degree"]) - equal_tenth) > 0.05
assert tenth["sub_lord"] in KP_LORDS
assert isinstance(feature["kp1_sub_index"], int)
assert 0 <= feature["kp1_sub_index"] <= 8
assert "KP_cusps" not in feature["blocked_layers"]
assert "KP_cusps" in feature["available_layers"]
def test_kp_observation_is_absent_from_scoring_missing_layers() -> None:
request = _request()
context = build_candidate_static_context(request, datetime(1993, 4, 17, 14, 29))
rows = compute_event_candidate_rows(request, static_contexts=[context])
assert rows
assert "KP_cusps" not in rows[0]["missing_layers"]
assert context["feature"]["kp_cusps"]["status"] == "executed"
def test_window_scan_emits_kp_sub_lord_changes_without_confirmation() -> None:
scan = window_scan({
"static_contexts": [
{"feature": {"time": "05:13", "kp1_sub_index": 1, "kp4_sub_index": 2, "kp7_sub_index": 3, "kp10_sub_index": 4}},
{"feature": {"time": "05:14", "kp1_sub_index": 2, "kp4_sub_index": 2, "kp7_sub_index": 3, "kp10_sub_index": 4}},
]
})
meanings = [item["user_meaning"] for item in scan["transitions"]]
assert "KP 1宫子主 在 05:14 发生变化" in meanings
assert "KP 4宫子主 在 05:14 发生变化" not in meanings
encoded = str(scan)
assert "白羊" not in encoded
assert "Aries" not in encoded
assert scan["confirmation_allowed"] is False
assert scan["unique_minute_claim"] is False
def test_technique_audit_kp_executed_when_snapshot_present() -> None:
rows = build_technique_audit(
{
"matrix": {},
"static_contexts": [{
"feature": {
"time": "05:13",
"kp_cusps": {"status": "executed", "house_system": "placidus", "ayanamsa": "krishnamurti"},
}
}],
},
house_table={"time": "05:13", "lagna": "金牛座"},
)
kp = next(row for row in rows if row["technique"] == "KP 宫头")
assert kp["status"] == "executed"
assert "政策跳过" not in kp["note"]
assert "不计分" in kp["note"]
def test_technique_audit_kp_blocked_is_honest_and_does_not_fail_propose() -> None:
rows = build_technique_audit({"matrix": {}, "static_contexts": []}, house_table=None)
kp = next(row for row in rows if row["technique"] == "KP 宫头")
assert kp["status"] == "blocked"
assert "政策跳过" not in kp["note"]
decisions = build_candidate_decisions(
[{"time": "05:13", "score": 20, "evidence": [], "missing_layers": []},
{"time": "05:14", "score": 8, "evidence": [], "missing_layers": []}],
result_id="00000000-0000-4000-8000-000000000099",
)
receipt = build_decision_receipt(
_score_request(),
decisions,
{"missing_layers": [], "matrix": {}, "static_contexts": [{"feature": {"time": "05:13", "kp_cusps": {"status": "blocked"}}}]},
_diagnostics(),
)
assert receipt["gates"]["required_layers"]["passed"] is True
assert receipt["propose_allowed"] is True
audit = next(row for row in receipt["technique_audit_table"] if row["technique"] == "KP 宫头")
assert audit["status"] == "blocked"
assert "政策跳过" not in audit["note"]
def test_observe_kp_cusps_blocks_without_inventing_whole_sign_proxy() -> None:
snapshot = observe_kp_cusps(None, 36.6, 114.3)
assert snapshot["status"] == "blocked"
assert snapshot["houses"] == {}
polar = observe_kp_cusps(2451545.0, 80.0, 0.0)
assert polar["status"] == "blocked"
assert polar["houses"] == {}
+3
View File
@@ -78,6 +78,8 @@ class RectificationV5ServicesTest(unittest.TestCase):
"family": ("family_event",),
"appearance": ("appearance_note",),
"marks": ("birthmark_or_scar",),
"occupation": ("occupation_note",),
"horary": ("horary_query",),
"other": ("other",),
}
@@ -117,6 +119,7 @@ class RectificationV5ServicesTest(unittest.TestCase):
body = request()
body["events"] = [
event(2, "other", "other", precision="year"),
event(3, "horary", "horary_query", precision="day"),
]
normalized = normalize_rectification_request(body, today=date(2026, 7, 28))