Files
Jyotisha/tests/test_report_reader_main.py
T
jesse-uxandClaude Code 4d801e53c3
Independent Staging Quality Gate / validate (push) Successful in 16m28s
Independent Staging Quality Gate / publish (push) Successful in 3m33s
fix: integrate people archive, report reader and western chart corrections
Validate on Linux Node 22 and PostgreSQL 17: 3894 frontend tests and 64 database tests pass, with no removed test names or new failures. Preserve static Home, bounded gzip, assertion-change records and manual acceptance gaps.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-25 20:41:16 +08:00

295 lines
11 KiB
Python

"""Reader-main export and the KP / Muntha defects behind BUG-1026/1027/1028."""
from __future__ import annotations
import copy
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"
@pytest.fixture(scope="module")
def reader_packet(full_reading):
from scripts.jyotish_engine import build_professional_report_reference_packet
return build_professional_report_reference_packet(full_reading, _class_args(), ["full"])
def test_real_annual_producers_agree_without_identical_metadata(reader_packet) -> None:
years = reader_packet["worksheets"]["timing_and_predictive_systems"]["annual_tajika_series"]["years"]
for annual in years.values():
field = annual["year_lord"]
assert field["status"] == "partial_verified"
left, right = [item["value"] for item in field["producers_checked"]]
assert "year_lord_sign" in left
assert "year_lord_sign" not in right
assert left["year_lord"] == right["year_lord"]
assert field["data"] == left
sections = annual["report_sections"]
assert "冲突" not in repr(sections["executive_summary"]["summary_lines"])
assert "分歧" not in repr(sections["thematic_narrative"]["highlights"])
assert "外部验证已经完成" in repr(sections["thematic_narrative"]["highlights"])
def test_reader_annual_cells_use_real_distinct_years_and_keep_return_dates(reader_packet) -> None:
reader = _pl9_export_markdown_for_edition(reader_packet, "reader_main")
lords = re.findall(r"^\| Varshesha \| ([^|]+) \|$", reader, re.M)
signs = re.findall(r"^\| Muntha sign \| ([^|]+) \|$", reader, re.M)
assert len(lords) == len(signs) == 3
assert "-" not in lords + signs
assert len(set(lords)) >= 2
assert len(set(signs)) >= 2
assert "| Selection basis | Muntha 所在星座的守护星(当前本地口径) |" in reader
assert "| Annual ascendant sign | - |" not in reader
timing = reader_packet["worksheets"]["timing_and_predictive_systems"]
for year in ("2027", "2028"):
annual = timing["annual_tajika_series"]["years"][year]
return_time = annual["solar_return"]["data"]["dt_local"]
section = reader.split(f"### {year} 年度 Varshaphala / Tajika 细项", 1)[1].split("### ", 1)[0]
assert f"| Varshapravesha | {return_time} |" in section
assert _leak_hits(reader) == []
def test_reader_annual_conflict_keeps_both_real_producer_values(reader_packet) -> None:
packet = copy.deepcopy(reader_packet)
timing = packet["worksheets"]["timing_and_predictive_systems"]
annual = timing["annual_tajika_pack"]
current = annual["year_lord"]["data"]
later = timing["annual_tajika_series"]["years"]["2027"]["year_lord"]["data"]
annual["year_lord"] = _field_with_conflict_gate(
"year_lord", {"year_lord": current}, {"year_lord": later},
)
assert annual["year_lord"]["status"] == "conflict"
reader = _pl9_export_markdown_for_edition(packet, "reader_main")
assert "| Varshesha | 太阳返照:火星;Tajika:金星(两者不同) |" in reader
assert "两种计算口径给出的年主不同,暂不合并" in reader
assert "| Muntha sign | 白羊座 |" in reader
assert _leak_hits(reader) == []
def test_reader_omits_missing_name_and_panchanga_machine_keys(reader_packet) -> None:
packet = copy.deepcopy(reader_packet)
packet["birth_info"].pop("name", None)
packet["birth_info"].pop("user_name", None)
reader = _pl9_export_markdown_for_edition(packet, "reader_main")
assert "- 姓名:" not in reader
assert "overall_score" not in reader
assert "吉性_count" not in reader
assert "total_elements" not in reader
assert "| 综合性质 |" in reader
assert "| 吉性要素数量 |" in reader
assert "| 要素总数 |" in reader