Files
Jyotisha/tests/test_report_longform_parity.py
T
Jesse_Chen bab0718700
Independent Staging Quality Gate / validate (push) Failing after 9m41s
Independent Staging Quality Gate / publish (push) Has been skipped
feat(report): ship full-mode longform appendix beside the five-chapter report
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>
2026-09-05 11:10:44 +08:00

254 lines
9.8 KiB
Python

"""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"