"""Call-surface and assembly locks for full-mode longform export.""" from __future__ import annotations from types import SimpleNamespace from ayanamsa_utils import ACTIVE_AYANAMSA_NAME, apply_ayanamsa from scripts.jyotish_engine import ( _attach_report_governance_contracts, _build_natal_foundation_modules, _compute_chart_from_args, _native_dasha_master_families, _render_finished_reading_navigation, cmd_kp, render_pl9_markdown, ) from scripts.professional_report_reference import _export_args BEIJING = { "year": 1990, "month": 1, "day": 1, "hour": 12, "minute": 0, "second": 0, "lat": 39.9042, "lon": 116.4074, "tz": 8, "ayanamsa": "raman", } def test_export_args_default_today_target_year_and_age() -> None: args = _export_args({**BEIJING, "today": "2026-09-05"}) assert args.today == "2026-09-05" assert args.target_year == 2026 assert args.age == 36 assert args.hour == 12 assert args.minute == 0 def test_export_args_keeps_explicit_age_and_does_not_duplicate_kwargs() -> None: args = _export_args({**BEIJING, "today": "2026-09-05", "target_year": 2027, "age": 40}) assert args.target_year == 2027 assert args.age == 40 def test_reading_navigation_emits_required_headings() -> None: markdown = "\n".join(_render_finished_reading_navigation({ "report_quality_gate": {"status": "review_required", "checks": []}, "worksheets": { "timing_and_predictive_systems": { "annual_tajika_pack": {"status": "partial_verified"}, "annual_tajika_series": {"years": {"2026": {}, "2027": {}, "2028": {}}}, }, "advanced_systems": {"kp_monthly_report": {"status": "parameter_sensitive", "months": [{}] * 36}}, "strengths_and_scores": {"functional_benefic_malefic": {"status": "partial_verified"}}, "divisional_and_special_charts": {"upagrahas": {"Gulika": {}}}, }, "birth_time_sensitivity": {"status": "not_rectified"}, })) for heading in ( "## 成品阅读导航", "### 先读什么", "### 结论等级规则", "### 专题判读协议", "### 质量验收矩阵", "### 对照覆盖表", ): assert heading in markdown assert "| 三年年度展开 | present |" in markdown assert "| KP 三年流月支持 | present |" in markdown def test_markdown_without_candidate_window_says_not_rectified() -> None: packet = { "schema": "pl9_style_professional_export_v1", "birth_info": BEIJING, "worksheets": {}, "raw_full_reading": {"modules": {}}, "personal_report_producer": {"main_body": {"status": "partial_verified"}}, "reader_engine_boundary_notice": { "primary_text_zh": "多引擎口径说明", "display_rule": "keep_blocked_labels", }, } packet = _attach_report_governance_contracts(packet, SimpleNamespace( **BEIJING, birth_time_accuracy="confirmed", )) markdown = render_pl9_markdown(packet) assert "## 成品阅读导航" in markdown assert "### 出生时间敏感度" in markdown assert "未做校时" in markdown assert packet["birth_time_sensitivity"]["status"] == "not_rectified" def test_candidate_range_keeps_minute_matrix_status() -> None: args = SimpleNamespace( **BEIJING, birth_time_accuracy="provisional", candidate_range={"start_time": "11:50", "end_time": "12:10", "representative_time": "12:00"}, ) packet = _attach_report_governance_contracts( {"worksheets": {}, "raw_full_reading": {"modules": {}}}, args, ) assert packet["birth_time_sensitivity"]["status"] == "candidate_window_only" assert packet["birth_time_sensitivity"]["window"]["candidate_count"] >= 2 def _producer_packet(worksheets: dict) -> dict: return { "schema": "pl9_style_professional_export_v1", "birth_info": BEIJING, "worksheets": worksheets, "full_report_pack": {"sections": {}}, "personal_report_producer": {"main_body": {"status": "partial_verified"}}, "reader_engine_boundary_notice": { "primary_text_zh": "多引擎口径说明", "display_rule": "keep_blocked_labels", }, } def test_native_dasha_families_mark_present_modules_executed() -> None: families = _native_dasha_master_families({ "dasha": {"current_dasha": {"lord": "Saturn"}}, "narayana_dasha": {"status": "computed"}, "yogini_dasha": {"error": "missing"}, "ashtottari_dasha": {}, "kalachakra_dasha": {"status": "blocked", "reason": "not_applicable"}, }) assert families["vimshottari"]["execution_status"] == "executed" assert families["narayana"]["execution_status"] == "executed" assert families["yogini"]["execution_status"] == "blocked" assert families["ashtottari"]["execution_status"] == "blocked" assert families["kala_chakra"]["execution_status"] == "blocked" def test_timing_mainline_renders_from_native_dasha_when_master_pack_has_no_families() -> None: markdown = render_pl9_markdown(_producer_packet({ "timing_and_predictive_systems": { "dasha": {"current_dasha": {"lord": "Saturn"}}, "narayana_dasha": {"status": "computed"}, "dasha_master_pack": {"schema": "dasha_master_report_pack_v1", "status": "blocked"}, "annual_tajika_pack": {"year_lord": {"status": "partial_verified"}}, }, })) assert "### 大运与时间主线" in markdown assert "#### 时间系统证据状态" in markdown assert "| Vimshottari |" in markdown assert "| Narayana |" in markdown assert "| Year Lord |" in markdown def test_kp_upagraha_and_yogakaraka_headings_render_when_worksheets_present() -> None: markdown = render_pl9_markdown(_producer_packet({ "divisional_and_special_charts": { "upagrahas": { "raw": { "Gulika": {"sign_idx": 0, "degree_in_sign": 12.5, "source": "local_formula"}, "Maandi": {"sign_idx": 1, "degree_in_sign": 3.0, "source": "local_formula"}, }, }, }, "advanced_systems": { "kp": { "house_basis": "explicit_cusps", "planets": { "Sun": { "kp_lords": { "sign": "Capricorn", "nakshatra": "Uttara Ashadha", "nakshatra_lord": "Sun", "sub_lord": "Sun", "sub_sub_lord": "Venus", }, "significators": {"A": ["10"], "B": ["10"], "C": [], "D": ["10"]}, }, }, "houses": { 10: { "sign": "Capricorn", "cusp_longitude": 280.1, "kp_lords": { "sign": "Capricorn", "nakshatra": "Uttara Ashadha", "nakshatra_lord": "Sun", "sub_lord": "Moon", "sub_sub_lord": "Mars", }, "significators": {"A": ["Sun"], "B": ["Mercury"], "C": [], "D": []}, }, 2: {"significators": {"A": ["Venus"], "B": ["Jupiter"], "C": [], "D": []}}, 6: {"significators": {"A": ["Saturn"], "B": ["Mars"], "C": [], "D": []}}, 11: {"significators": {"A": ["Moon"], "B": ["Venus"], "C": [], "D": []}}, }, "ruling_planets": { "day_lord": "Monday", "moon_star_lord": "Saturn", "ascendant_star_lord": "Sun", }, }, }, "strengths_and_scores": { "functional_benefic_malefic": { "status": "used", "ascendant": "Aries", "functional_benefics": ["Jupiter", "Sun", "Mars"], "functional_malefics": ["Mercury", "Saturn"], "yogakarakas": ["Mars"], }, "ashtakavarga": {"method": "local", "version": "test"}, }, })) assert "### Upagraha / Sub-Planets" in markdown assert "### KP Lord / Sub 原始表" in markdown assert "#### KP Ruling Planets" in markdown or "#### 行星 KP Lord / Sub" in markdown assert "#### KP 显著星 ABCD" in markdown assert "#### KP 事业宫位核对" in markdown assert "#### 功能性吉凶与 Yogakaraka" in markdown assert "Mars" in markdown or "火星" in markdown def test_cmd_kp_restores_report_ayanamsa() -> None: apply_ayanamsa("raman") args = SimpleNamespace(**BEIJING) result = cmd_kp(args) assert isinstance(result, dict) assert isinstance(result.get("planets"), dict) assert isinstance(result.get("ruling_planets"), dict) assert ACTIVE_AYANAMSA_NAME == "raman" def test_natal_foundation_returns_upagrahas_and_functional_layer() -> None: apply_ayanamsa("raman") args = SimpleNamespace(**BEIJING) chart, _asc_idx, jd, _ayanamsa = _compute_chart_from_args(args) planets = chart["planets"] planet_lons = { name: data.get("degree_raw", data["degree"]) for name, data in planets.items() if isinstance(data, dict) and "degree" in data } asc_lon = chart["ascendant"].get("lon", chart["ascendant"].get("degree")) foundation = _build_natal_foundation_modules(args, planet_lons, asc_lon=asc_lon, jd=jd) assert foundation["upagrahas"].get("raw") assert foundation["functional_benefic_malefic"].get("functional_benefics") or foundation["functional_benefic_malefic"].get("status") assert foundation["functional_benefic_malefic"].get("yogakarakas") is not None assert ACTIVE_AYANAMSA_NAME == "raman"