Files
Jyotisha/tests/test_report_reader_main.py
T
jesse-ux 3c2f7bd559 fix(report): use the upstream reader edition for new longform bodies
New reports request reader_main from upstream origin/main 23b9609e.
KP and transit no longer copy an empty vars() dict, solar returns keep
birth_asc_sign_idx, and ordinary projection deletes internal lines whole.
2026-09-25 02:21:46 +08:00

221 lines
7.7 KiB
Python

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