feat(report): add dense personal report appendix
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
"""Copy-only tests: these strings are vocabulary cases, not engine fixtures."""
|
||||
|
||||
import pytest
|
||||
|
||||
from scripts.reader_appendix_language import clean_reader_appendix_markdown
|
||||
|
||||
|
||||
@pytest.mark.parametrize("raw,label", [
|
||||
("parameter_sensitive", "参数敏感"),
|
||||
("pyjhora_behavior_only", "仅单一外部参照,未做多引擎核对"),
|
||||
("not_multiengine_parity", "仅单一外部参照,未做多引擎核对"),
|
||||
("unresolved_external_tuple_boundary", "外部边界未对齐"),
|
||||
("partial_verified", "部分核验"),
|
||||
("raw_appendix_only", "仅原始附录可见"),
|
||||
("missing_in_local", "本地暂无"),
|
||||
])
|
||||
def test_status_copy_preserves_rows(raw, label):
|
||||
source = f"| sign_cn | degree_in_sign | nakshatra_lord |\n| 白羊座 | 12.5 | {raw} |\n"
|
||||
result = clean_reader_appendix_markdown(source)
|
||||
assert result == source.replace(raw, label)
|
||||
assert result.count("\n") == source.count("\n")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("raw", ["PyJHora", "JHora", "pyjhora"])
|
||||
def test_external_engine_names(raw):
|
||||
assert clean_reader_appendix_markdown(raw) == "外部参照引擎"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("raw", ["PL9", "PL9 第 83-88 页", "PL9 第 40 / 48 页", "PL9 page 99-100", "PL9.pdf", "PL9 第 43–44 页"])
|
||||
def test_external_product_pages(raw):
|
||||
assert clean_reader_appendix_markdown(raw) == "外部参照资料"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("raw", ["cmd_full_reading", "jyotish_engine.cmd_full_reading", "render_pl9_markdown", "build_professional_report_reference_packet"])
|
||||
def test_internal_functions(raw):
|
||||
assert clean_reader_appendix_markdown(raw) == "本地计算"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("raw,label", [("blocked", "暂不可用"), ("executed", "已执行"), ("available", "可用"), ("computed", "已计算")])
|
||||
def test_ambiguous_words_only_in_whole_cells(raw, label):
|
||||
prose = f"The {raw} result is not a claim.\n| {raw} result | `{raw}` | {raw} |\n"
|
||||
assert clean_reader_appendix_markdown(prose) == f"The {raw} result is not a claim.\n| {raw} result | `{label}` | {label} |\n"
|
||||
assert clean_reader_appendix_markdown(raw) == raw
|
||||
|
||||
|
||||
def test_chart_fence_unchanged_and_copy_idempotent():
|
||||
fence = '```jyotish-chart\n{"title":"parameter_sensitive","id":"D1"}\n```\n'
|
||||
source = "参数敏感\n" + fence + "| parameter_sensitive |\n"
|
||||
result = clean_reader_appendix_markdown(source)
|
||||
assert fence in result
|
||||
assert clean_reader_appendix_markdown(result) == result
|
||||
|
||||
|
||||
def test_public_boundary_only_leaves_internal_renderer_and_json_unchanged():
|
||||
from scripts.professional_report_reference import build_professional_report_reference
|
||||
from types import SimpleNamespace
|
||||
|
||||
packet = {"status": "parameter_sensitive"}
|
||||
engine = SimpleNamespace(
|
||||
build_professional_report_reference_packet=lambda *args: packet,
|
||||
render_pl9_markdown=lambda value: "| parameter_sensitive | cmd_full_reading |",
|
||||
)
|
||||
handler = SimpleNamespace(
|
||||
_high_rigor_birth_payload=lambda body: {"year": 2000, "hour": 0, "minute": 0, "today": "2026-01-01"},
|
||||
_compute_full_reading_for_thematic=lambda birth: {},
|
||||
)
|
||||
assert build_professional_report_reference(handler, {"format": "json"}, engine=engine)["report"] is packet
|
||||
assert build_professional_report_reference(handler, {"format": "markdown"}, engine=engine)["markdown"] == "| parameter_sensitive | cmd_full_reading |"
|
||||
assert engine.render_pl9_markdown(packet) == "| parameter_sensitive | cmd_full_reading |"
|
||||
assert packet == {"status": "parameter_sensitive"}
|
||||
@@ -0,0 +1,89 @@
|
||||
"""Golden facts use deliberately fictional input, never a user birth record."""
|
||||
import copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from scripts.reader_dasha_applicability import BOUNDARY, render_dasha_applicability
|
||||
from scripts.report_fact_table_contract import validate_fact_tables
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def test_golden_applicability_never_relaxes_missing_or_inapplicable_families():
|
||||
packet = json.loads((ROOT / "frontend/tests/fixtures/report-density-fictional-reader.json").read_text(encoding="utf8"))
|
||||
original = copy.deepcopy(packet)
|
||||
output = render_dasha_applicability(packet)
|
||||
families = packet["worksheets"]["timing_and_predictive_systems"]["dasha_master_pack"]["families"]
|
||||
assert len(families) == 15
|
||||
assert output.count(BOUNDARY) == len(families) - 1 + 2
|
||||
for key, value in families.items():
|
||||
if key == "vimshottari":
|
||||
assert "| Vimshottari |" not in output
|
||||
continue
|
||||
name = "Kala Chakra" if key == "kala_chakra" else key.replace("_", "-").title()
|
||||
assert f"| {name} |" in output
|
||||
if value.get("reason"):
|
||||
from scripts.reader_dasha_applicability import REASON_LABELS
|
||||
assert REASON_LABELS[value["reason"]] in output
|
||||
assert value["reason"] not in output
|
||||
assert all(name in output for name in ["Narayana", "Tribhagi-40", "Shattrimshatsama", "Satabdika", "Mudda", "Patyayini"])
|
||||
assert "月宿与月相组合未满足该大运的适用条件" in output
|
||||
assert "本地计算尚未提供可用周期" in output
|
||||
assert packet == original
|
||||
|
||||
|
||||
def test_golden_narayana_periods_and_readable_known_and_unknown_reasons():
|
||||
packet = json.loads((ROOT / "frontend/tests/fixtures/report-density-fictional-reader.json").read_text(encoding="utf8"))
|
||||
timing = packet["worksheets"]["timing_and_predictive_systems"]
|
||||
assert len(timing["narayana_dasha"]["mahadasha_sequence"]) == 12
|
||||
assert len(timing["narayana_dasha"]["dense_boundaries"]) == 156
|
||||
output = render_dasha_applicability(packet)
|
||||
narayana = next(line for line in output.splitlines() if line.startswith("| Narayana |"))
|
||||
assert "本次已返回周期" in narayana
|
||||
assert "这不代表已完成多引擎验证" in narayana
|
||||
assert "缺少可核对周期" not in narayana
|
||||
assert "本次计算未提供帕特雅伊尼年运周期" in output
|
||||
assert "patyayini_dasha_missing" not in output
|
||||
timing["annual_tajika_pack"]["patyayini_dasha"]["reason"] = "unknown_internal_reason_123 arbitrary English detail"
|
||||
unknown = render_dasha_applicability(packet)
|
||||
assert "暂无可核对的详细说明" in unknown
|
||||
assert "unknown_internal_reason_123" not in unknown
|
||||
assert "arbitrary English detail" not in unknown
|
||||
|
||||
|
||||
def test_legacy_applicability_discloses_missing_evidence_without_guessing_families():
|
||||
output = render_dasha_applicability({})
|
||||
assert "缺少本次计算的结构化适用性证据" in output
|
||||
assert BOUNDARY not in output
|
||||
|
||||
|
||||
def test_full_golden_shared_markdown_is_byte_identical_and_reader_copy_is_clean():
|
||||
from types import SimpleNamespace
|
||||
from scripts.professional_report_reference import build_professional_report_reference
|
||||
from scripts.reader_appendix_language import clean_reader_appendix_markdown
|
||||
|
||||
packet = json.loads((ROOT / "frontend/tests/fixtures/report-density-fictional-reader.json").read_text(encoding="utf8"))
|
||||
raw = packet["markdown"]
|
||||
engine = SimpleNamespace(build_professional_report_reference_packet=lambda *args: packet, render_pl9_markdown=lambda value: raw)
|
||||
handler = SimpleNamespace(_high_rigor_birth_payload=lambda body: {"year": 2000, "hour": 12, "minute": 0, "today": "2026-09-22"}, _compute_full_reading_for_thematic=lambda birth: {})
|
||||
for include in (False, True):
|
||||
result = build_professional_report_reference(handler, {"format": "markdown", "include_fact_tables": include}, engine=engine)
|
||||
assert result["markdown"].encode("utf8") == raw.encode("utf8")
|
||||
if include:
|
||||
assert result["reader_dasha_applicability"] == packet["reader_dasha_applicability"]
|
||||
clean = clean_reader_appendix_markdown(raw)
|
||||
assert "pl9" in raw.lower()
|
||||
assert "pl9" not in clean.lower()
|
||||
assert len(clean.splitlines()) == len(raw.splitlines())
|
||||
assert sum(line.startswith("|") for line in clean.splitlines()) == sum(line.startswith("|") for line in raw.splitlines())
|
||||
for field in ("degree_in_sign", "sign_cn", "nakshatra_lord"):
|
||||
assert clean.count(field) == raw.count(field)
|
||||
|
||||
|
||||
def test_optional_table_validator_fails_closed_on_bad_shape():
|
||||
errors = []
|
||||
validate_fact_tables([{"id": "annual"}], lambda *error: errors.append(error))
|
||||
assert errors
|
||||
errors.clear()
|
||||
validate_fact_tables([], lambda *error: errors.append(error))
|
||||
assert not errors
|
||||
Reference in New Issue
Block a user