bab0718700
Web export now calls the same full pack as the long skill report and caches an owner-only Markdown download. Appendix failure stays unavailable and does not change the main report status. Co-authored-by: Cursor <cursoragent@cursor.com>
344 lines
13 KiB
Python
Executable File
344 lines
13 KiB
Python
Executable File
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"]
|