from __future__ import annotations from datetime import date from scripts.active_rectification_event_engine import ( AUXILIARY_DOMAINS, AUXILIARY_SCORE_FACTOR, DOMAIN_CONFIG, _score_event, compute_candidate_static_contexts, compute_event_candidate_rows, ) from scripts.rectification.contracts import ( is_primary_scoreable_event, is_scoreable_event, normalize_rectification_request, ) from scripts.rectification.scoring_service import public_technique_layers def test_family_event_is_scoreable_with_d12_and_kin_houses() -> None: prefixes, houses = DOMAIN_CONFIG["family"] assert prefixes == ("D12", "D7", "D3") assert houses == (3, 4, 5, 9) event = { "id": "00000000-0000-4000-8000-000000000001", "domain": "family", "event_kind": "family_event", "date_start": "2016-09-15", "date_end": "2016-09-15", "precision": "day", "summary": "家人变化", } assert is_scoreable_event(event) is True assert is_primary_scoreable_event(event) is True coerced = normalize_rectification_request( { "birth_date": "1997-08-08", "start_time": "05:13", "end_time": "05:15", "lat": 36.419, "lon": 114.213, "tz": 8, "events": [{**event, "subject": "self"}], }, today=date(2026, 7, 28), ) assert coerced["events"][0]["subject"] == "family" normalized = normalize_rectification_request( { "birth_date": "1997-08-08", "start_time": "05:13", "end_time": "05:15", "lat": 36.419, "lon": 114.213, "tz": 8, "events": [event], }, today=date(2026, 7, 28), ) assert normalized["events"][0]["subject"] == "family" def test_career_event_always_exposes_d1_tenth_and_d10_layers() -> None: prefixes, houses = DOMAIN_CONFIG["career"] assert prefixes == ("D10",) assert houses == (10,) layers = public_technique_layers("career", ["vim_md_domain_house", "vim_md_domain_varga"]) assert "d1-rashi" in layers assert "d10-dashamsa" in layers scored = _score_event( candidate_time="05:13", event={ "id": "00000000-0000-4000-8000-000000000004", "domain": "career", "event_kind": "career_entry", "date": "2016-09-15", "precision": "day", }, 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 any(item.endswith("_domain_house") for item in scored["rule_ids"]) assert any(item.endswith("_domain_varga") for item in scored["rule_ids"]) def test_appearance_is_auxiliary_first_house_not_primary() -> None: prefixes, houses = DOMAIN_CONFIG["appearance"] assert prefixes == () assert houses == (1,) assert "appearance" in AUXILIARY_DOMAINS assert AUXILIARY_SCORE_FACTOR == 0.4 natal = { "ascendant": {"lon": 10.0, "sign": "Aries"}, "planets": {"Sun": {"house": 1, "lon": 12.0}}, } scored = _score_event( candidate_time="05:13", event={ "id": "00000000-0000-4000-8000-000000000002", "domain": "appearance", "event_kind": "birthmark_or_scar", "date": "2010-01-01", "precision": "year", }, natal_chart=natal, varga_charts=[], vimshottari=("Sun", "Moon", "Mars"), narayana=(None, None), arudha_padas={}, ) assert "appearance_auxiliary_not_primary" in scored["rule_ids"] primary = _score_event( candidate_time="05:13", event={ "id": "00000000-0000-4000-8000-000000000003", "domain": "career", "event_kind": "career_entry", "date": "2010-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"] < primary["points"] assert is_primary_scoreable_event({ "id": "00000000-0000-4000-8000-000000000002", "domain": "appearance", "event_kind": "appearance_note", "date_start": "2010-01-01", "date_end": "2010-12-31", "precision": "year", }) is False def test_family_event_engine_rows_include_d12_and_are_not_skipped() -> None: request = { "birth_date": "1997-08-08", "start_time": "05:13", "end_time": "05:14", "lat": 36.419, "lon": 114.213, "tz": 8.0, "events": [{ "id": "00000000-0000-4000-8000-000000000001", "domain": "family", "event_kind": "family_event", "date": "2016-09-15", "precision": "day", "summary": "家人变化", }], } contexts = compute_candidate_static_contexts(request) assert all("D12" in context["feature"]["available_layers"] for context in contexts) assert all("D7" in context["feature"]["available_layers"] for context in contexts) assert all("D3" in context["feature"]["available_layers"] for context in contexts) assert all(context["varga_charts"]["D12"] is not None for context in contexts) assert all(context["varga_charts"]["D7"] is not None for context in contexts) assert all(context["varga_charts"]["D3"] is not None for context in contexts) assert all(isinstance(context["feature"].get("pada_index"), int) for context in contexts) assert all(0 <= context["feature"]["pada_index"] <= 107 for context in contexts) assert all(isinstance(context["feature"].get("bhava_sign_index"), int) for context in contexts) rows = compute_event_candidate_rows(request, static_contexts=contexts) assert [row["time"] for row in rows] == ["05:13", "05:14"] for row in rows: assert "D12" not in row["missing_layers"] assert "D7" not in row["missing_layers"] assert row["evidence"], "family events must enter scoring instead of being skipped" assert row["evidence"][0]["domain"] == "family" assert "event_kind:family_event" in row["evidence"][0]["rule_ids"] career = { **request, "events": [{ "id": "00000000-0000-4000-8000-000000000004", "domain": "career", "event_kind": "career_entry", "date": "2016-09-15", "precision": "day", }], } career_rows = compute_event_candidate_rows(career, static_contexts=contexts) assert all(row["evidence"] for row in career_rows) career_rules = [rule for row in career_rows for item in row["evidence"] for rule in item["rule_ids"]] layers = public_technique_layers("career", career_rules) assert "d1-rashi" in layers and "d10-dashamsa" in layers family_layers = public_technique_layers("family", ["vim_md_domain_house", "vim_md_domain_varga"]) assert "d12-dwadashamsha" in family_layers and "d7-saptamsha" in family_layers assert "d3-drekkana" in family_layers education_layers = public_technique_layers("education", ["vim_md_domain_house"]) assert "d5-panchamsha" in education_layers and "d24-chaturvimshamsha" in education_layers finance_layers = public_technique_layers("finance", ["vim_md_domain_house"]) 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"]