from __future__ import annotations from scripts.full_report_quality_gate import evaluate_full_report from scripts.jyotish_engine import render_pl9_markdown def _complete_markdown() -> str: return "\n".join( [ "# 个人印度占星报告", "## 摘要", "### 基础资料表", "#### Birth Particulars / Calculation Details", "#### 出生 Panchanga(本地计算)", "#### D1 — Rashi Chart(本命盘)", "### Vargas I", "#### D1–D60 完整原始分盘账本(20 传统 + 40 研究型 D-N)", "### 本命主轴", "### 2026 年度重点", "#### 年度专题证据状态", "### 力量、Ashtakavarga 与功能性吉凶", "#### 功能性吉凶与 Yogakaraka", "#### Bhava Lagna", "#### Hora Lagna", "#### Ghati Lagna", "#### ViGhati Lagna", "#### Sree Lagna", "#### Indu Lagna", "本章已调用依据:D2(Hora 财富分盘)、D11(Rudramsa 收益分盘)。", "### 大运与时间主线", "#### KP 三年流月支持", "### 2026 KP 月度支持", "### 2027 KP 月度支持", "### 2028 KP 月度支持", "#### KP 使用边界", "#### Patyayini Dasha", "#### Narayana Rashi Dasha", "### 专业支持专题:第二证据轴", "#### 辅助大运族:Yogini / Ashtottari / Kala Chakra", "#### 本命补充因子:Sahams / Avasthas / Upagrahas", "#### 未闭环专业层:可见但不入判断", "### 事业与外部发展", "### 财运与资源使用", "### 关系与合作模式", "## Blocked / Audit Appendix", "## Audit Appendix", ] ) def _complete_packet() -> dict: return { "schema": "pl9_style_professional_export_v1", "calculation_profile_id": "profile-1993", "calculation_profile": { "profile_id": "profile-1993", "profile_version": "1.0", "algorithm": "sidereal_natal_chart", "ayanamsa": "lahiri", "node_mode": "mean", "input_hash": "input-hash", "engine": {"ephemeris_provider": "local"}, }, "result_hash": "result-hash", "result_binding": { "input_hash": "input-hash", "result_hash": "result-hash", }, "chart_identity": { "chart_profile_id": "profile-1993", "birth_data_status": "user_provided", "rectification_status": "not_reviewed", "approval_status": "not_approved", }, "full_report_pack": { "schema": "pl9.full_report_pack.v1", "sections": { key: {"status": "verified"} for key in ( "base", "strength", "dasha", "annual", "transit", "professional_support", "audit_appendix", ) } | { "d1_d60_ledger": { "status": "available", "d1_to_d60": {f"D{number}": {"raw": {}} for number in range(1, 61)}, "summary": { "formal_traditional_division_count": 20, "research_generic_dn_division_count": 40, }, } }, }, "worksheets": { "divisional_and_special_charts": { "special_lagnas": { "Bhava_Lagna": {"sign": "Aries"}, "Hora_Lagna": {"sign": "Aries"}, "Ghati_Lagna": {"sign": "Aries"}, "ViGhati_Lagna": {"sign": "Aries"}, "Sree_Lagna": {"sign": "Aries"}, "Indu_Lagna": {"sign": "Aries"}, }, "varga_full": { "D2_Hora": {"Ascendant": {"sign": "Aries"}}, "D11_Rudramsa": {"Ascendant": {"sign": "Taurus"}}, }, }, "strengths_and_scores": { "functional_benefic_malefic": {"status": "used"}, }, "timing_and_predictive_systems": { "narayana_dasha": {"status": "executed"}, "annual_tajika_pack": { "patyayini": {"status": "executed"}, "external_engine_comparison": { "pyjhora": { "patyayini_dasha": { "status": "partial_verified", "normalized_rows": [{"order": 1}], } } }, "audit": {"technique_audit": [{"technique": "Annual", "status": "partial_verified"}]}, "report_sections": {"evidence_appendix": {"status": "partial_verified"}}, }, "dasha_master_pack": { "families": {"yogini": {"periods": [{"lord": "Sun"}]}} }, }, "advanced_systems": { "sahams": {"Punya": {"sign": "Aries"}}, "kp_monthly_report": { "maturity_profile": {"claim_status": "observation_only_truth_blocked"}, "must_not_claim": ["exact_event_timing"], }, }, }, } def test_complete_packet_returns_passed_with_traceable_professional_coverage() -> None: result = evaluate_full_report(_complete_packet(), _complete_markdown()) assert result["schema_version"] == "jyotish.full_report_quality_gate.v1" assert result["status"] == "passed" assert result["professional_coverage_manifest"] assert { "material_id", "source_reference", "admission_tier", "report_role", "surface_location", "status", "limitation_reference", }.issubset(result["professional_coverage_manifest"][0]) assert not result["blocking_reasons"] assert { item["material_id"]: item["surface_location"] for item in result["professional_coverage_manifest"] }["special_lagnas"] == "professional_support_cross_reference" def test_missing_provenance_blocks_full_report() -> None: packet = _complete_packet() packet.pop("result_hash") result = evaluate_full_report(packet, _complete_markdown()) assert result["status"] == "blocked" assert "provenance_missing:result_hash" in result["blocking_reasons"] def test_missing_chart_identity_blocks_full_report() -> None: packet = _complete_packet() packet.pop("chart_identity") result = evaluate_full_report(packet, _complete_markdown()) assert result["status"] == "blocked" assert "chart_identity_missing:chart_profile_id" in result["blocking_reasons"] def test_invalid_result_binding_blocks_full_report() -> None: packet = _complete_packet() packet["result_binding"]["result_hash"] = "other-result" result = evaluate_full_report(packet, _complete_markdown()) assert result["status"] == "blocked" assert "provenance_lineage_binding_invalid" in result["blocking_reasons"] def test_missing_or_incomplete_d1_d60_ledger_blocks_full_report() -> None: packet = _complete_packet() packet["full_report_pack"]["sections"].pop("d1_d60_ledger") missing = evaluate_full_report(packet, _complete_markdown()) assert missing["status"] == "blocked" assert "required_pack_section_missing:d1_d60_ledger" in missing["blocking_reasons"] packet = _complete_packet() packet["full_report_pack"]["sections"]["d1_d60_ledger"]["d1_to_d60"].pop("D60") incomplete = evaluate_full_report(packet, _complete_markdown()) assert incomplete["status"] == "blocked" assert "d1_d60_ledger_incomplete" in incomplete["blocking_reasons"] def test_executed_required_support_missing_from_rendering_requires_review() -> None: markdown = _complete_markdown().replace("#### Hora Lagna\n", "") result = evaluate_full_report(_complete_packet(), markdown) assert result["status"] == "review_required" assert "special_lagna_not_rendered:Hora_Lagna" in result["review_reasons"] def test_generated_d11_gains_chart_missing_from_rendering_requires_review() -> None: markdown = _complete_markdown().replace("D11(Rudramsa 收益分盘)", "收益分盘已省略") result = evaluate_full_report(_complete_packet(), markdown) assert result["status"] == "review_required" assert "required_support_not_rendered:wealth_d11_gains_chart" in result["review_reasons"] def test_missing_professional_support_section_requires_review() -> None: markdown = _complete_markdown().replace("### 专业支持专题:第二证据轴\n", "") result = evaluate_full_report(_complete_packet(), markdown) assert result["status"] == "review_required" assert "reader_section_missing:professional_support" in result["review_reasons"] def test_missing_restricted_materials_register_requires_review() -> None: markdown = _complete_markdown().replace("#### 未闭环专业层:可见但不入判断\n", "") result = evaluate_full_report(_complete_packet(), markdown) assert result["status"] == "review_required" assert "reader_section_missing:restricted_materials" in result["review_reasons"] def test_missing_one_kp_year_requires_review() -> None: markdown = _complete_markdown().replace("### 2028 KP 月度支持\n", "") result = evaluate_full_report(_complete_packet(), markdown) assert result["status"] == "review_required" assert "three_year_kp_monthly_incomplete" in result["review_reasons"] def test_missing_special_lagna_value_requires_review() -> None: packet = _complete_packet() packet["worksheets"]["divisional_and_special_charts"]["special_lagnas"].pop("Sree_Lagna") result = evaluate_full_report(packet, _complete_markdown()) assert result["status"] == "review_required" assert "special_lagna_missing:Sree_Lagna" in result["review_reasons"] def test_empty_d11_structure_blocks_full_report() -> None: packet = _complete_packet() packet["worksheets"]["divisional_and_special_charts"]["varga_full"]["D11_Rudramsa"] = {} result = evaluate_full_report(packet, _complete_markdown()) assert result["status"] == "blocked" assert "wealth_varga_structure_missing:D11" in result["blocking_reasons"] def test_missing_kp_boundary_status_requires_review() -> None: packet = _complete_packet() packet["worksheets"]["advanced_systems"]["kp_monthly_report"].pop("must_not_claim") result = evaluate_full_report(packet, _complete_markdown()) assert result["status"] == "review_required" assert "kp_evidence_status_missing" in result["review_reasons"] def test_executed_auxiliary_support_missing_from_rendering_requires_review() -> None: markdown = _complete_markdown().replace("#### 辅助大运族:Yogini / Ashtottari / Kala Chakra\n", "") result = evaluate_full_report(_complete_packet(), markdown) assert result["status"] == "review_required" assert "required_support_not_rendered:auxiliary_dasha_support" in result["review_reasons"] def test_restricted_material_cannot_be_promoted_as_confirmed() -> None: packet = _complete_packet() packet["professional_coverage_overrides"] = { "tajika_named_yoga": {"status": "confirmed"}, } result = evaluate_full_report(packet, _complete_markdown()) assert result["status"] == "blocked" assert "restricted_material_promoted:tajika_named_yoga" in result["blocking_reasons"] def test_missing_patyayini_normalized_rows_requires_review() -> None: packet = _complete_packet() packet["worksheets"]["timing_and_predictive_systems"]["annual_tajika_pack"]["external_engine_comparison"][ "pyjhora" ]["patyayini_dasha"]["normalized_rows"] = [] result = evaluate_full_report(packet, _complete_markdown()) assert result["status"] == "review_required" assert "patyayini_normalized_rows_missing" in result["review_reasons"] def test_quality_gate_reads_product_markdown_markers() -> None: result = evaluate_full_report(_complete_packet(), "\n".join([ "# 个人印度占星报告", "## 成品阅读导航", "### 基础资料表", "#### Birth Particulars / Calculation Details", "#### D1 — Rashi Chart(本命盘)", "### Vargas I", "#### D1–D60 完整原始分盘账本(20 传统 + 40 研究型 D-N)", "### 大运与时间主线", "#### KP 三年流月支持", "### 2026 年度重点", "### 专业支持专题:第二证据轴", "#### 未闭环专业层:可见但不入判断", "## Blocked / Audit Appendix", "## Audit Appendix", ])) markdown = render_pl9_markdown(_complete_packet()) assert "# 个人印度占星报告" in markdown assert "## 成品阅读导航" in markdown assert result["schema_version"] == "jyotish.full_report_quality_gate.v1" assert "rendered_markdown_not_supplied" not in result["warning_reasons"]