fix(report): six parity chapters read real packet records and sit at ## (#91 fix2 F1/F2/F4)

Eleven sections printed one-column tables of 未判定 and packet key names
(summary, p5, stats ...). They now read the nested records: KP profile and
ruling planets, Tara/Chandra combined rows, lagna vargottamamsa flags, LL/7L
P5/P8/exchange checks, Yoga Pinda planets and summary, Sodhita BAV totals and
Ekadhipatya audit, friendship method and bodies, multi-reference houses,
double-transit PAC targets, congregation houses, seven/eight karaka tables.
Argala, Birth Calendar, Tithi, Tajika candidates, Goel dignity and yoga
categories no longer print bare lower-case keys or enums either.

The six chapters are now ## and their sections ###, so the reader counts
six more chapters instead of seventy.

Tests: a section passes only with a real value outside the label column; no
table cell may be a raw key; heading counts equal the base report plus six.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HwMNZEzdzmrbPhuRNmZe3C
This commit is contained in:
Jesse_Chen
2026-10-22 21:44:51 +08:00
co-authored by Claude Opus 5.5
parent 92b4e220f2
commit 00c2b4d498
2 changed files with 889 additions and 152 deletions
File diff suppressed because it is too large Load Diff
+104 -21
View File
@@ -32,19 +32,24 @@ _KAAL_EN = re.compile(r"\d+ of \d+")
def _chapter_text(markdown: str, language: str) -> str:
marker = "# 大运附表" if language == "zh" else "# Dasha Appendix"
marker = "\n## 大运附表\n" if language == "zh" else "\n## Dasha Appendix\n"
assert marker in markdown
return markdown.split(marker, 1)[1]
def _section_body(markdown: str, heading: str) -> str:
marker = f"## {heading}"
marker = f"### {heading}\n"
start = markdown.index(marker) + len(marker)
rest = markdown[start:]
ends = [pos for pos in (rest.find("\n## "), rest.find("\n# ")) if pos >= 0]
ends = [pos for pos in (rest.find("\n### "), rest.find("\n## "), rest.find("\n# ")) if pos >= 0]
return rest[: min(ends)] if ends else rest
def _heading_lines(markdown: str, level: int) -> list[str]:
prefix = "#" * level + " "
return [line[len(prefix):].strip() for line in markdown.splitlines() if line.startswith(prefix)]
def test_chapter_titles_follow_the_task_groups() -> None:
groups = _chapter_sections()
assert len(groups) == len(CHAPTERS) == 6
@@ -73,22 +78,26 @@ def test_empty_packet_chapters_are_headed_and_do_not_print_raw_keys() -> None:
heading = title_zh if language == "zh" else title_en
body = _section_body(text, heading)
assert body.strip(), heading
cursor_next = text.index(f"## {heading}", cursor)
cursor_next = text.index(f"### {heading}\n", cursor)
assert cursor_next >= cursor
cursor = cursor_next
if "|---|" in body or "| --- |" in body:
lines = [line for line in body.splitlines() if line.startswith("|")]
assert len(lines) >= 3, heading
for title in FORBIDDEN_TITLES:
assert f"### {title}" not in text
assert f"## {title}" not in text
assert f"# {title}" not in text
assert _SNAKE.search(text) is None
assert _heading_lines(text, 1) == []
chapters = [chapter_zh if language == "zh" else chapter_en for chapter_en, chapter_zh in CHAPTERS]
assert _heading_lines(text, 2) == chapters
zh = render_parity_chapters({}, "zh")
en = render_parity_chapters({}, "en")
assert zh.index("### 来源感知口径") < zh.index("### 既有口径")
assert "来源感知口径" not in zh.split("### 既有口径", 1)[1]
assert en.index("### Source-aware method") < en.index("### Legacy method")
assert "Source-aware method" not in en.split("### Legacy method", 1)[1]
assert zh.index("#### 来源感知口径") < zh.index("#### 既有口径")
assert "来源感知口径" not in zh.split("#### 既有口径", 1)[1]
assert en.index("#### Source-aware method") < en.index("#### Legacy method")
assert "Source-aware method" not in en.split("#### Legacy method", 1)[1]
assert "印刷页 10" in zh
assert "printed pages 10-17" in en
for banned in ("source_locator", "profile_id", "niryaana_shoola_strength", "source_discrepancy", "sign_idx"):
@@ -120,21 +129,21 @@ def test_three_fictional_charts_render_the_same_chapters(fictional_reports: dict
zh_at = 0
en_at = 0
for title_en, title_zh in SECTIONS:
zh_at = zh.index(f"## {title_zh}", zh_at)
en_at = en.index(f"## {title_en}", en_at)
zh_at = zh.index(f"### {title_zh}\n", zh_at)
en_at = en.index(f"### {title_en}\n", en_at)
for body, heading in ((_section_body(zh, title_zh), title_zh), (_section_body(en, title_en), title_en)):
assert body.strip(), f"{index} {heading}"
for chapter_en, chapter_zh in CHAPTERS:
assert chinese.count(f"# {chapter_zh}") == 1
assert english.count(f"# {chapter_en}") == 1
assert _heading_lines(chinese, 2).count(chapter_zh) == 1
assert _heading_lines(english, 2).count(chapter_en) == 1
for title in FORBIDDEN_TITLES:
assert title not in zh
assert title not in en
assert _SNAKE.search(zh) is None
assert _SNAKE.search(en) is None
assert "印刷页 10" in zh and "printed pages 10-17" in en
assert "### 来源感知口径" in zh and "### 既有口径" in zh
assert "### Source-aware method" in en and "### Legacy method" in en
assert "#### 来源感知口径" in zh and "#### 既有口径" in zh
assert "#### Source-aware method" in en and "#### Legacy method" in en
assert "再小运" in _section_body(zh, "Chara 大运时长")
assert "Pratyantardasha" in _section_body(en, "Chara Dasha Period Durations")
assert "marriage_windows" not in _section_body(zh, "Bhrigu 近似推进序列")
@@ -184,17 +193,40 @@ def _table_blocks(body: str) -> list[list[list[str]]]:
return parsed
_NOT_VALUES = {"未判定", "Not determined", "—", "-", ""}
# A whole cell that looks like a packet key (`summary`, `p5`, `house_7`).
_RAW_KEY_CELL = re.compile(r"^[a-z][a-z0-9]*(_[a-z0-9]+)*$")
def _section_has_real_cell(body: str, undetermined: str) -> bool:
"""A table must carry a value outside its first (label) column.
The first column holds row labels, so it never proves that the packet was
read. A one-column table therefore never passes. A body with no table at all
may be a plain sentence (for example "no boundary within 90 days").
"""
blocks = _table_blocks(body)
if blocks:
return any(
cell and cell != undetermined
cell not in _NOT_VALUES and cell != undetermined
for rows in blocks
for row in rows
for cell in row
for cell in row[1:]
)
prose = [line.strip() for line in body.splitlines() if line.strip()]
return any(line and line != undetermined for line in prose)
prose = [line.strip() for line in body.splitlines() if line.strip() and not line.startswith("#")]
return any(line != undetermined for line in prose)
def _all_cells(markdown: str) -> list[str]:
cells = []
for line in markdown.splitlines():
if not line.startswith("|"):
continue
parts = [cell.strip() for cell in line.strip().strip("|").split("|")]
if parts and all(set(cell) <= set("-: ") and cell for cell in parts):
continue
cells.extend(parts)
return cells
def test_every_section_has_a_real_cell_except_the_four_public_gaps(fictional_reports: dict[int, tuple[str, str]]) -> None:
@@ -214,6 +246,55 @@ def test_every_section_has_a_real_cell_except_the_four_public_gaps(fictional_rep
assert _section_has_real_cell(body, _UNDETERMINED[language]), f"{index} {heading}"
def test_every_section_has_a_real_cell_outside_the_label_column_unit() -> None:
one_column = "| 角色 |\n|---|\n| 未判定 |\n| summary |\n"
assert not _section_has_real_cell(one_column, "未判定")
labels_only = "| 项目 | 内容 |\n|---|---|\n| 状态 | 未判定 |\n| 宫位 | — |\n"
assert not _section_has_real_cell(labels_only, "未判定")
real = "| 项目 | 内容 |\n|---|---|\n| 宫位 | 7 |\n"
assert _section_has_real_cell(real, "未判定")
assert _section_has_real_cell("参考日起 90 天内没有大运或小运交界。", "未判定")
assert not _section_has_real_cell("未判定", "未判定")
def test_no_table_cell_is_a_raw_packet_key(fictional_reports: dict[int, tuple[str, str]]) -> None:
for index, (chinese, english) in fictional_reports.items():
for language, markdown in (("zh", chinese), ("en", english)):
chapter = _chapter_text(markdown, language)
keys = sorted({cell for cell in _all_cells(chapter) if _RAW_KEY_CELL.fullmatch(cell)})
assert keys == [], f"{index} {language} {keys[:10]}"
def test_six_chapters_are_reader_chapters_and_sections_sit_below_them(monkeypatch: pytest.MonkeyPatch) -> None:
"""The reader splits on `## ` and ignores `# `. The six chapters must be `##`."""
import scripts.pl9_full_data_export as export
zh_chapters = [chapter_zh for _chapter_en, chapter_zh in CHAPTERS]
en_chapters = [chapter_en for chapter_en, _chapter_zh in CHAPTERS]
packet = _load_packet(0)
full_zh = render_full_data_markdown(copy.deepcopy(packet))
full_en = _english_edition(copy.deepcopy(packet), True).get("markdown_en") or ""
monkeypatch.setattr(export, "_append_parity_chapters", lambda markdown, _packet, _language: markdown)
base_zh = render_full_data_markdown(copy.deepcopy(packet))
base_en = _english_edition(copy.deepcopy(packet), True).get("markdown_en") or ""
assert base_en
for full, base, chapters in ((full_zh, base_zh, zh_chapters), (full_en, base_en, en_chapters)):
h1 = _heading_lines(full, 1)
assert len(h1) == 1, h1
assert full.startswith("# " + h1[0])
assert _heading_lines(base, 1) == h1
full_h2 = _heading_lines(full, 2)
base_h2 = _heading_lines(base, 2)
assert len(full_h2) == len(base_h2) + len(chapters)
assert full_h2[: len(base_h2)] == base_h2
assert full_h2[len(base_h2):] == chapters
tail = full.split("\n## " + chapters[0] + "\n", 1)[1]
assert _heading_lines(tail, 3)[: len(_chapter_sections()[0])] == [
(dict(SECTIONS)[title] if chapters is zh_chapters else title) for title in _chapter_sections()[0]
]
assert len(_heading_lines(tail, 3)) == len(SECTIONS)
def test_parity_chapters_go_through_local_conventions() -> None:
from scripts.pl9_full_data_export import _apply_local_report_conventions
@@ -236,11 +317,13 @@ def test_parity_chapters_go_through_local_conventions() -> None:
english_packet = copy.deepcopy(packet)
english_packet["report_language"] = "en"
english_packet["worksheets"]["strengths_and_scores"]["vimsopaka"]["Sun"]["category"] = "divorce"
english_packet["worksheets"]["strengths_and_scores"]["vimsopaka"]["Sun"]["category"] = "Divorce risk"
# A bare lower-case word is now treated as an internal key and is not
# printed, so the probe is a reader-style phrase (fix2 F1).
english = render_full_data_markdown(english_packet)
en_body = _section_body(english, "Vimsopaka Contribution")
en_replaced = _apply_local_report_conventions("divorce", language="en")
assert en_replaced == "relationship strain"
en_replaced = _apply_local_report_conventions("Divorce risk", language="en")
assert en_replaced == "relationship strain risk"
assert en_replaced in en_body
assert "divorce" not in en_body.lower()