fix(report): draw charts in the full-data body and clean Chinese residue

New reports draw one north-Indian chart for each ready divisional in the body, so the trailing duplicate section drops. Chinese full-data prose no longer glues English onto Han or leaves listed, background, or native. The birth table starts with the ascendant. The D9 same-sign column says yes or no.

Self-test: tsc exit 0; npm run lint exit 0 (0 errors, 125 existing warnings); three frontend test files 39 pass, 0 fail, 0 cancelled; pytest test_report_chart_blank_columns.py 16 passed; seven related report files 88 passed, exit 0; privacy markers 63 passed.

Co-Authored-By: Grok (agent loop)
This commit is contained in:
jesse-ux
2026-10-13 14:02:26 +08:00
co-authored by Grok (agent loop)
parent 05f8800531
commit a295afa40f
14 changed files with 3466 additions and 10 deletions
+108
View File
@@ -150,6 +150,114 @@ def test_ashtakavarga_house_table_never_labels_the_total_as_sav(editions) -> Non
assert row[5].isdigit()
_CALCULATION_CHART_IDS = {
"D1", "D2", "D3", "D4", "D7", "D9", "D10", "D11", "D12",
"D16", "D20", "D24", "D27", "D30", "D40", "D60",
}
def _section_until_next_h2(markdown: str, heading: str) -> str:
start = markdown.index(heading)
lines = [heading]
for line in markdown[start + len(heading):].splitlines():
if line.startswith("## "):
break
lines.append(line)
return "\n".join(lines)
def test_full_data_chart_section_draws_each_ready_chart_once(editions) -> None:
from scripts.report_chart_block import FENCE_RE
zh, en, _packet = editions
for markdown, heading in (
(zh, "### 本命与分盘北印度图盘"),
(en, "### Natal and Divisional North Indian Charts"),
):
section = _section_until_next_h2(markdown, heading)
blocks = []
for match in FENCE_RE.finditer(section):
block = json.loads(match.group(1))
assert block["layout"] == "north"
blocks.append(block)
ids = [block["id"] for block in blocks]
assert ids
assert len(ids) == len(set(ids))
assert _CALCULATION_CHART_IDS <= set(ids)
assert "MOON" in ids
assert not re.search(r"(?m)^##### .+\n\n+##### ", section)
assert not re.search(r"(?m)^##### .*Bhava", section)
assert "##### Navamsha" not in section and "##### p7 · Navamsha" not in section
def _heading_table(markdown: str, heading: str) -> list[list[str]]:
"""Like _table_after, but the lord table is a #### heading, not ###."""
lines = markdown.splitlines()
start = next(i for i, line in enumerate(lines) if line.startswith("#") and heading in line)
rows = []
for line in lines[start + 1:]:
if line.startswith("#") or (rows and not line.startswith("|")):
break
if line.startswith("|") and not set(line) <= set("|- "):
rows.append([cell.strip() for cell in line.strip("|").split("|")])
return rows
def test_birth_position_table_starts_with_the_ascendant(editions) -> None:
zh, en, packet = editions
asc = packet["core_chart"]["ascendant"]
longitude = asc.get("lon") if isinstance(asc.get("lon"), (int, float)) else asc.get("degree_raw")
fields = _nakshatra_fields(float(longitude))
lord_zh = {
"Sun": "太阳", "Moon": "月亮", "Mars": "火星", "Mercury": "水星",
"Jupiter": "木星", "Venus": "金星", "Saturn": "土星",
"Rahu": "北交点", "Ketu": "南交点",
}
zh_rows = _heading_table(zh, "行星原始位置")
assert zh_rows[1][0] == "上升"
assert fields["nakshatra"] in zh_rows[1][6]
assert zh_rows[1][7] == str(fields["nakshatra_pada"])
assert zh_rows[1][5] == "1"
lords = _heading_table(zh, "行星主星与状态")
assert lords[1][0] == "上升"
assert lords[1][5] == "不适用"
assert lords[1][2] in {fields["nakshatra_lord"], lord_zh.get(fields["nakshatra_lord"])}
en_rows = _heading_table(en, "Planetary Raw Positions")
assert en_rows[1][0] in {"Ascendant", "Lagna", "上升"}
assert fields["nakshatra"] in en_rows[1][6]
assert en_rows[1][7] == str(fields["nakshatra_pada"])
def test_zh_hygiene_rejects_glued_english(editions) -> None:
from scripts.pl9_full_data_export import _pl9_markdown_hygiene_receipt, _zh_body_residue
injected = _pl9_markdown_hygiene_receipt("木星Jupiterexalted在巨蟹座\n", language="zh")
assert injected["status"] == "fail"
assert any(str(item).startswith("glued_latin:") for item in injected["violations"])
residue = _zh_body_residue(editions[0])
assert residue == {"glued_latin": 0, "function_words": 0}
def test_zh_hygiene_is_clean_on_night_and_a_public_chart() -> None:
from scripts.pl9_full_data_export import _zh_body_residue
# Public birth used by tests/smoke_test_runner.py. Not a private user.
public = {
**CASES["day"],
"name": "Albert Einstein",
"year": 1879,
"month": 3,
"day": 14,
"hour": 11,
"minute": 30,
"lat": 48.4,
"lon": 9.98,
"tz": 0.89,
"age": 76,
}
for case in (CASES["night"], public):
packet = _packet(case)
markdown = _pl9_export_markdown_for_edition(copy.deepcopy(packet), "full_data")
assert _zh_body_residue(markdown) == {"glued_latin": 0, "function_words": 0}
def test_chart_section_carries_the_divisional_caveat(editions) -> None:
zh, en, _ = editions
assert re.search(r"### 本命与分盘北印度图盘\n\n" + re.escape(CAVEAT_ZH) + r"\n", zh)