"""Golden tests for SECTOR_EXPOSURE_GRAPH_V1 / LEADER_LIFECYCLE_GATE_V1 (governance/todo/v8_9_p1_adoption_plan.yaml P1-A.5). Maps to v8.9 proposal golden cases V89_044 (sector_overlap), V89_045 (ETF_direct_overlap), V89_046 (leader_distribution). """ from __future__ import annotations import importlib.util from pathlib import Path ROOT = Path(__file__).resolve().parents[3] MODULE_PATH = ROOT / "tools" / "build_sector_exposure_graph_v1.py" def _load_module(): spec = importlib.util.spec_from_file_location("build_sector_exposure_graph_v1", MODULE_PATH) module = importlib.util.module_from_spec(spec) assert spec.loader is not None spec.loader.exec_module(module) return module def test_v89_044_etf_lookthrough_adds_to_direct_weight() -> None: mod = _load_module() position = { "direct_weight_pct": 20.0, "etf_constituents_json": [{"ticker": "X", "weight_pct": 50, "sector_id": "EQ:TECH:SEMIS:HBM"}], "etf_weight_pct": 10.0, "sector_id": "EQ:TECH:SEMIS:HBM", } result = mod.sector_exposure(position) assert result["sector_family_total_pct"] == 25.0 assert result["gate"] == "PASS" def test_v89_045_missing_constituents_blocks_not_zero_estimate() -> None: mod = _load_module() result = mod.sector_exposure({"direct_weight_pct": 10.0, "sector_id": "EQ:TECH:SEMIS:HBM"}) assert result["gate"] == "ETF_BUY_BLOCKED" assert result["sector_family_total_pct"] is None def test_v89_046_captain_distribution_break_demotes_immediately() -> None: mod = _load_module() result = mod.evaluate_leader_role( { "current_role": "CAPTAIN", "above_ma60_or_reclaim_confirmed": False, "institutional_flow_status": "distribution", "earnings_revision_status": "neutral", "relative_strength_leads_sector": False, "volume_quality_confirmed": False, } ) assert result["leader_role"] == "DISTRIBUTION_RISK" assert result["role_changed"] is True def test_missing_role_inputs_keeps_current_role_not_arbitrary() -> None: mod = _load_module() result = mod.evaluate_leader_role({"current_role": "ENABLER"}) assert result["leader_role"] == "ENABLER" assert result["role_transition_reason"] == "DATA_MISSING"