"""Reader-main export and the KP / Muntha defects behind BUG-1026/1027/1028.""" from __future__ import annotations import re import pytest from scripts.annual_tajika_pack import _field_brief, _field_with_conflict_gate from scripts.jyotish_engine import ( _copy_call_namespace, _professional_report_reference_boundary_notice, cmd_full_reading, render_pl9_markdown, ) from scripts.pl9_reader_export import _pl9_export_markdown_for_edition from scripts.professional_report_reference import ( REPORT_VERSION_READER, build_professional_report_reference, ) from scripts.shared_full_report_authority import DEFAULT_REPORT_VERSION from scripts.solar_return import calc_solar_return_chart, solar_return_full_report # Clearly fictional. Not a user, not a published celebrity case. FICTIONAL = { "name": "虚构甲", "year": 1991, "month": 4, "day": 7, "hour": 9, "minute": 15, "second": 0, "lat": 30.5728, "lon": 104.0668, "tz": 8.0, "node_mode": "mean", "ayanamsa": "lahiri", "today": "2026-04-07", "transit_date": "2026-04-07", "target_year": 2026, "age": 35, "birth_time_accuracy": "confirmed", } LEAK_PATTERNS = ( re.compile(r"\bblocked\b", re.I), re.compile(r"\bexecuted\b", re.I), re.compile(r"\bpartial_verified\b", re.I), re.compile(r"\bmissing_in_local\b", re.I), re.compile(r"\bproducer\b", re.I), re.compile(r"\bparity\b", re.I), re.compile(r"PL9\s*第\s*\d+\s*页"), re.compile(r"full-report://"), re.compile(r"\bpl9_[A-Za-z0-9_]+\b", re.I), re.compile(r"\b[A-Za-z][A-Za-z0-9]*_unavailable\b"), re.compile(r"\b[A-Za-z][A-Za-z0-9]*_(?:pack|packet)\b", re.I), re.compile(r"object has no attribute"), re.compile(r"\bTraceback\b"), re.compile(r"结论等级规则"), re.compile(r"This reference export"), re.compile(r"producer failed"), re.compile(r"PyJHora"), re.compile(r"\bparameter_sensitive\b"), re.compile(r"\bschema\b", re.I), re.compile(r"\bsource_path\b"), re.compile(r"\{planets\}"), ) CORE_CHAPTERS = ( "出生资料", "Ashtakavarga", "标准大运", "KP", "Year Lord", "Tajika", "Muntha", ) def _class_args(**overrides): fields = {**FICTIONAL, **overrides} return type("Args", (), fields)() def _leak_hits(markdown: str) -> list[str]: return [pattern.pattern for pattern in LEAK_PATTERNS if pattern.search(markdown)] @pytest.fixture(scope="module") def full_reading(): return cmd_full_reading(_class_args()) def test_default_edition_stays_reference_and_reader_requests_v3() -> None: assert DEFAULT_REPORT_VERSION == "pl9_personal_long_report.v3" assert REPORT_VERSION_READER == "pl9_personal_long_report.v3" class Handler: def _high_rigor_birth_payload(self, body): return dict(FICTIONAL) def _compute_full_reading_for_thematic(self, body): return {"modules": {"chart": {}}} class Engine: def build_professional_report_reference_packet(self, full_reading, args, packs): return {"schema": "pl9_style_professional_export_v1", "selected_report_pack_ids": packs} def render_pl9_markdown(self, packet): return "reference-body" handler = Handler() engine = Engine() default = build_professional_report_reference(handler, {"format": "markdown"}, engine=engine) assert default["edition"] == "reference" assert default["markdown"] == "reference-body" reader = build_professional_report_reference( handler, {"format": "markdown", "edition": "reader_main"}, engine=engine, ) assert reader["edition"] == "reader_main" assert reader["report_version"] == "pl9_personal_long_report.v3" def test_reference_body_drops_local_leak_phrases() -> None: packet = { "schema": "pl9_style_professional_export_v1", "birth_info": {"name": "虚构甲"}, "reader_engine_boundary_notice": _professional_report_reference_boundary_notice(), "worksheets": {}, } markdown = render_pl9_markdown(packet) assert "结论等级规则" not in markdown assert "This reference export" not in markdown assert "producer failed" not in markdown assert "多引擎口径说明" in markdown def test_class_args_copy_keeps_year_for_kp_and_transit(full_reading) -> None: args = _class_args() copied = _copy_call_namespace(args) assert copied.year == 1991 assert vars(args) == {} modules = full_reading["modules"] for name in ("kp", "double_transit_pac", "transit_ll7l", "planetary_congregation"): module = modules.get(name) assert isinstance(module, dict), name blob = repr(module) assert "has no attribute" not in blob assert module.get("status") != "blocked", (name, module.get("status"), module.get("reason")) assert "error" not in module or not str(module.get("error", "")).startswith("KP producer failed") def test_reader_main_hides_audit_vocabulary(full_reading) -> None: from scripts.jyotish_engine import build_professional_report_reference_packet packet = build_professional_report_reference_packet(full_reading, _class_args(), ["full"]) reference = _pl9_export_markdown_for_edition(packet, "reference") reader = _pl9_export_markdown_for_edition(packet, "reader_main") assert "结论等级规则" not in reference assert "This reference export" not in reference hits = _leak_hits(reader) assert hits == [], hits for chapter in CORE_CHAPTERS: assert chapter in reader, chapter assert reader.strip().startswith("#") def test_solar_return_restores_birth_asc_and_year_lord_moves() -> None: lords = set() signs = set() for year in (2024, 2026, 2028): chart = calc_solar_return_chart( FICTIONAL["year"], FICTIONAL["month"], FICTIONAL["day"], FICTIONAL["hour"], FICTIONAL["minute"], FICTIONAL["lat"], FICTIONAL["lon"], FICTIONAL["tz"], year, ayanamsa_name="lahiri", ) assert "error" not in chart, chart.get("error") assert isinstance(chart.get("birth_asc_sign_idx"), int) report = solar_return_full_report( FICTIONAL["year"], FICTIONAL["month"], FICTIONAL["day"], FICTIONAL["hour"], FICTIONAL["minute"], FICTIONAL["lat"], FICTIONAL["lon"], FICTIONAL["tz"], year, ayanamsa_name="lahiri", ) muntha = report["muntha"] assert muntha.get("status") != "blocked", muntha assert isinstance(muntha.get("muntha_sign_idx"), int) year_lord = report["year_lord"] assert year_lord.get("status") != "blocked", year_lord lords.add(year_lord["year_lord"]) signs.add(muntha["muntha_sign"]) assert len(signs) >= 2 assert len(lords) >= 2 def test_blocked_side_does_not_invent_a_conflict() -> None: gated = _field_with_conflict_gate( "muntha", {"muntha": {"status": "blocked", "reason": "natal_ascendant_required_for_muntha"}}, {"muntha": {"muntha_sign": "Capricorn", "muntha_lord": "Saturn", "note": "extra"}}, ) assert gated["status"] == "partial_verified" assert "conflict" not in _field_brief("Muntha", gated) same_sign = _field_with_conflict_gate( "year_lord", {"year_lord": {"year_lord": "Mars", "year_lord_sign": "Aries", "trace": "left"}}, {"year_lord": {"year_lord": "Mars", "year_lord_sign": "Aries", "trace": "right"}}, ) assert same_sign["status"] == "partial_verified" disagree = _field_with_conflict_gate( "year_lord", {"year_lord": {"year_lord": "Mars", "year_lord_sign": "Aries"}}, {"year_lord": {"year_lord": "Venus", "year_lord_sign": "Taurus"}}, ) assert disagree["status"] == "conflict"