import pytest from scripts import annual_tajika_pack from scripts.annual_tajika_pack import build_annual_tajika_pack def _payload(): return { "birth": { "date": "1990-01-01", "time": "12:00:00", "utc_offset": "+08:00", "place": "Beijing, China", "latitude": 39.9, "longitude": 116.4, "coordinate_precision": "city", }, "settings": { "ayanamsa": "lahiri", "node_mode": "mean", "house_system": "whole_sign", "position_mode": "legacy", "dasha_year_days": 365.25, "solar_return_location_mode": "birth_place", "annual_year_policy": "solar_return_exact", }, "target_year": 2026, "age": 36, } def test_builds_annual_tajika_pack_contract() -> None: pack = build_annual_tajika_pack(_payload()) assert list(pack.keys()) == [ "schema", "profile", "solar_return", "annual_chart", "muntha", "year_lord", "tajika_yogas", "sahams", "mudda_dasha", "patyayini_dasha", "monthly_windows", "external_engine_comparison", "report_sections", "exports", "audit", ] assert pack["schema"] == "jyotish.annual_tajika_pack.v1" assert pack["profile"]["profile_id"] assert pack["solar_return"]["status"] in {"partial_verified", "blocked"} assert pack["annual_chart"]["status"] in {"partial_verified", "blocked"} assert pack["muntha"]["status"] in {"partial_verified", "blocked", "conflict"} assert pack["year_lord"]["status"] in {"partial_verified", "blocked", "conflict"} assert pack["tajika_yogas"]["status"] in {"partial_verified", "blocked"} assert pack["sahams"]["status"] in {"partial_verified", "blocked"} assert isinstance(pack["mudda_dasha"]["periods"], list) assert isinstance(pack["patyayini_dasha"]["periods"], list) assert isinstance(pack["monthly_windows"], list) assert pack["external_engine_comparison"]["status"] in {"blocked", "partial_verified"} assert pack["report_sections"]["executive_summary"]["status"] in {"blocked", "parameter_sensitive"} assert isinstance(pack["report_sections"]["executive_summary"]["quick_takeaways"], list) assert "tajika_yogas" in pack["report_sections"]["evidence_appendix"]["field_briefs"] assert "sahams" in pack["report_sections"]["evidence_appendix"]["field_briefs"] assert pack["report_sections"]["evidence_appendix"]["must_not_claim"] == ["exact_annual_event_prediction"] assert pack["exports"]["json"]["schema"] == pack["schema"] assert pack["exports"]["ai_evidence_bundle"]["profile_id"] == pack["profile"]["profile_id"] unified = pack["exports"]["unified_report_pack_contract"] assert unified["schema"] == "pl9.unified_report_pack_contract.v1" assert unified["pack_id"] == "annual_tajika_pack" assert unified["status"] in {"blocked", "parameter_sensitive"} assert unified["status"] == pack["report_sections"]["executive_summary"]["status"] assert pack["audit"]["technique_audit"][0]["technique"] == "Calculation Profile" def test_annual_tajika_pack_marks_muntha_and_year_lord_conflicts() -> None: pack = build_annual_tajika_pack( _payload(), solar_return_report={ "solar_return": {"dt_ut": "2026-01-01 00:00:00"}, "sr_chart_info": {"ascendant": {"sign": "Leo"}}, "muntha": {"muntha_sign": "Taurus", "muntha_lord": "Venus"}, "year_lord": {"year_lord": "Venus"}, }, tajika_report={ "muntha": {"muntha_sign": "Cancer", "muntha_lord": "Moon"}, "year_lord": {"year_lord": "Moon"}, }, ) assert pack["muntha"]["status"] == "conflict" assert pack["muntha"]["values"] == [ {"producer": "solar_return", "value": {"muntha_sign": "Taurus", "muntha_lord": "Venus"}}, {"producer": "tajika", "value": {"muntha_sign": "Cancer", "muntha_lord": "Moon"}}, ] assert pack["year_lord"]["status"] == "conflict" assert pack["year_lord"]["values"] == [ {"producer": "solar_return", "value": {"year_lord": "Venus"}}, {"producer": "tajika", "value": {"year_lord": "Moon"}}, ] assert pack["report_sections"]["executive_summary"]["status"] == "parameter_sensitive" assert "muntha" in pack["report_sections"]["executive_summary"]["blocked_fields"] assert "year_lord" in pack["report_sections"]["executive_summary"]["blocked_fields"] def test_annual_tajika_pack_year_lord_conflict_exposes_canonical_field_contract() -> None: pack = build_annual_tajika_pack( _payload(), solar_return_report={ "solar_return": {"dt_ut": "2026-01-01 00:00:00"}, "year_lord": {"year_lord": "Venus", "year_lord_sign": "Taurus"}, }, tajika_report={ "year_lord": {"year_lord": "Moon", "year_lord_sign": "Cancer"}, }, ) year_lord = pack["year_lord"] assert year_lord["status"] == "conflict" assert year_lord["field"] == "year_lord" assert year_lord["reason"] == "same_profile_annual_producers_disagree" assert year_lord["values"] == [ {"producer": "solar_return", "value": {"year_lord": "Venus", "year_lord_sign": "Taurus"}}, {"producer": "tajika", "value": {"year_lord": "Moon", "year_lord_sign": "Cancer"}}, ] assert "year_lord" in pack["report_sections"]["executive_summary"]["blocked_fields"] def test_annual_tajika_pack_propagates_nested_blocked_field_statuses() -> None: pack = build_annual_tajika_pack( _payload(), solar_return_report={ "solar_return": {"dt_ut": "2026-01-01 00:00:00"}, "tajika_yogas": {"status": "blocked", "reason": "legacy_tajika_input_lacks_planet_speeds"}, "sahams": {"status": "blocked", "reason": "saham_daynight_requires_wgs84_location_and_timezone"}, }, tajika_report={}, ) assert pack["tajika_yogas"]["status"] == "blocked" assert pack["tajika_yogas"]["reason"] == "legacy_tajika_input_lacks_planet_speeds" assert pack["sahams"]["status"] == "blocked" assert pack["sahams"]["reason"] == "saham_daynight_requires_wgs84_location_and_timezone" assert pack["report_sections"]["evidence_appendix"]["field_briefs"]["tajika_yogas"] == "Tajika Yogas: blocked" assert pack["report_sections"]["evidence_appendix"]["field_briefs"]["sahams"] == "Sahams: blocked" def test_annual_tajika_pack_exposes_tajika_and_sahams_reader_briefs_when_visible() -> None: pack = build_annual_tajika_pack( _payload(), solar_return_report={ "solar_return": {"dt_ut": "2026-01-01 00:00:00"}, "sr_chart_info": {"ascendant": {"sign": "Leo"}}, "muntha": {"muntha_sign": "Taurus", "muntha_lord": "Venus"}, "year_lord": {"year_lord": "Venus"}, "tajika_yogas": {"ithasala": ["Sun/Mars"]}, "sahams": {"punya_saham": {"longitude": 123.4}}, }, tajika_report={}, ) appendix = pack["report_sections"]["evidence_appendix"] assert appendix["field_briefs"]["tajika_yogas"] == "Tajika Yogas: annual candidate structures visible" assert appendix["field_briefs"]["sahams"] == "Sahams: annual sensitive points visible" assert pack["report_sections"]["executive_summary"]["quick_takeaways"] == [ "Muntha: Taurus / Venus", "Year Lord: Venus", ] def test_annual_tajika_pack_promotes_governed_named_yoga_authority_when_motion_inputs_exist() -> None: pack = build_annual_tajika_pack( _payload(), solar_return_report={ "solar_return": {"dt_ut": "2026-01-01 00:00:00"}, "sr_chart_info": {"ascendant": {"sign": "Leo"}}, "muntha": {"muntha_sign": "Taurus", "muntha_lord": "Venus"}, "year_lord": {"year_lord": "Venus"}, "chart": { "planets": { "Sun": {"degree_raw": 0, "speed": 1.0}, "Moon": {"degree_raw": 49, "speed": 13.0}, "Mars": {"degree_raw": 180, "speed": 0.5}, "Mercury": {"degree_raw": 260, "speed": 1.2}, "Jupiter": {"degree_raw": 310, "speed": 0.08}, "Venus": {"degree_raw": 130, "speed": 1.0}, "Saturn": {"degree_raw": 220, "speed": 0.03}, } }, "tajika_yogas": {"legacy_surface": "still_present_but_not_primary"}, }, tajika_report={}, ) tajika_field = pack["tajika_yogas"] authority = tajika_field["data"] appendix = pack["report_sections"]["evidence_appendix"] assert tajika_field["status"] == "partial_verified" assert tajika_field["producer"] == "Tajika Named Yoga Authority" assert tajika_field["data_contract"] == "governed_named_yoga_authority_surface" assert authority["status"] == "authority_ready" assert authority["supported_named_yogas"] == ["Ithasala", "Easarapha"] assert authority["blocked_named_yogas"] == ["Nakta", "Yamaya", "Manahoo", "Kamboola"] assert appendix["field_briefs"]["tajika_yogas"] == "Tajika Yogas: governed authority surface visible for Ithasala/Easarapha (8 rows)" assert pack["audit"]["technique_audit"][5] == {"technique": "Tajika Yogas", "status": "partial_verified"} def test_annual_tajika_pack_accepts_pyjhora_external_replay_result() -> None: replay = { "engine": "PyJHora/JHora", "license_boundary": {"mode": "external_reference_only"}, "input_profile_id": "will_be_replaced_by_builder_check", "mudda_dasha": {"status": "partial_verified", "raw": {"periods": []}}, "patyayini_dasha": {"status": "partial_verified", "raw": {"periods": []}}, "sahams": {"status": "partial_verified", "raw": {}}, "solar_return_boundary": {"status": "partial_verified", "raw": []}, "status": "partial_verified", } pack = build_annual_tajika_pack(_payload(), pyjhora_replay=replay) assert pack["external_engine_comparison"]["status"] == "partial_verified" assert pack["external_engine_comparison"]["pyjhora"]["engine"] == "PyJHora/JHora" assert pack["external_engine_comparison"]["pyjhora"]["license_boundary"]["mode"] == "external_reference_only" assert pack["audit"]["technique_audit"][-1]["technique"] == "PyJHora Annual Replay" assert pack["audit"]["technique_audit"][-1]["status"] == "partial_verified" def test_annual_tajika_pack_preserves_patyayini_replay_tuple_semantics() -> None: replay = { "engine": "PyJHora/JHora", "license_boundary": {"mode": "external_reference_only"}, "status": "partial_verified", "evidence_scope": "pyjhora_behavior_only", "parity_status": "not_multiengine_parity", "patyayini_dasha": { "status": "partial_verified", "raw": {"periods": [((1, "L"), (2022, 2, 17, 5.63431528955698), 0.0438458145826713)]}, }, } pack = build_annual_tajika_pack(_payload(), pyjhora_replay=replay) patyayini = pack["external_engine_comparison"]["pyjhora"]["patyayini_dasha"] assert patyayini["status"] == "partial_verified" assert patyayini["normalized_rows"] == [{ "order": 1, "main_code": 1, "sub_code": "L", "boundary_components": {"year": 2022, "month": 2, "day": 17, "hour_decimal": 5.63431528955698}, "boundary_display": "2022-02-17 05:38:03", "duration_raw": 0.0438458145826713, "timezone_semantics": "not_returned_by_pyjhora_tuple", "boundary_semantics": "unresolved_external_tuple_boundary", "evidence_status": "pyjhora_behavior_only / not_multiengine_parity", }] assert pack["patyayini_dasha"]["periods"] == [] def test_annual_tajika_pack_keeps_runtime_year_lord_replay_as_single_engine_evidence() -> None: replay = { "engine": "PyJHora/JHora", "license_boundary": {"mode": "external_reference_only"}, "status": "partial_verified", "evidence_scope": "pyjhora_behavior_only", "parity_status": "not_multiengine_parity", "year_lord_replay": { "status": "partial_verified", "reason": "pyjhora_tajaka_year_lord_callable_observed", "callable": "jhora.horoscope.transit.tajaka.lord_of_the_year", "pyjhora_version": "4.8.7", "effective_ayanamsa": "LAHIRI", "request_hash": "a" * 64, "node_mode": {"requested": "mean", "status": "unsupported_by_replay_adapter"}, }, } pack = build_annual_tajika_pack(_payload(), pyjhora_replay=replay) external = pack["external_engine_comparison"] assert external["evidence_scope"] == "pyjhora_behavior_only" assert external["parity_status"] == "not_multiengine_parity" assert external["raw_evidence_paths"] == [] assert external["raw_evidence_status"] == "runtime_observation_not_archived" assert external["year_lord_replay"]["node_mode"]["status"] == "unsupported_by_replay_adapter" assert pack["year_lord"]["status"] in {"partial_verified", "blocked", "conflict"} def test_annual_tajika_pack_marks_vedastro_as_base_chart_boundary_not_varshaphala() -> None: vedastro = { "engine": "VedAstro official", "status": "partial_verified_for_base_chart", "chart_core": {"status": "partial_verified", "fields": ["Sun", "Moon", "Ascendant"]}, "dasha_all": {"status": "partial_verified", "range": "2026-01-01/2026-12-31"}, "varshaphala_status": "blocked", "supported_annual_methods": [], "secret_redaction": {"secret_material_included": False}, } pack = build_annual_tajika_pack(_payload(), vedastro_reference=vedastro) comparison = pack["external_engine_comparison"] assert comparison["status"] == "partial_verified" assert comparison["vedastro"]["status"] == "partial_verified_for_base_chart" assert comparison["vedastro"]["varshaphala_status"] == "blocked" assert comparison["vedastro"]["supported_annual_methods"] == [] assert "api_key" not in repr(comparison).lower() assert pack["audit"]["technique_audit"][-1]["technique"] == "VedAstro Annual Boundary" assert pack["audit"]["technique_audit"][-1]["status"] == "partial_verified_for_base_chart" def test_annual_tajika_pack_infers_age_from_target_year_for_tajika_branch(monkeypatch: pytest.MonkeyPatch) -> None: payload = _payload() payload.pop("age") captured = {} def fake_solar_return(args): return { "solar_return": {"dt_ut": "2026-01-01 00:00:00"}, "sr_chart_info": {"ascendant": {"sign": "Leo"}}, "muntha": {"muntha_sign": "Taurus", "muntha_lord": "Venus"}, "year_lord": {"year_lord": "Venus", "year_lord_sign": "Taurus"}, } def fake_tajika(args): captured["age"] = args.age return { "muntha": {"muntha_sign": "Cancer", "muntha_lord": "Moon"}, "year_lord": {"year_lord": "Moon", "year_lord_sign": "Cancer"}, } monkeypatch.setattr(annual_tajika_pack, "_safe_solar_return", fake_solar_return) monkeypatch.setattr(annual_tajika_pack, "_safe_tajika", fake_tajika) pack = build_annual_tajika_pack(payload) assert captured["age"] == 36 assert pack["year_lord"]["status"] == "conflict" assert pack["year_lord"]["values"] == [ {"producer": "solar_return", "value": {"year_lord": "Venus", "year_lord_sign": "Taurus"}}, {"producer": "tajika", "value": {"year_lord": "Moon", "year_lord_sign": "Cancer"}}, ]