Files
Jyotisha/scripts/report_render/audit_chapters.py
jesse-uxandGrok (agent loop) 2e9ca2d73a fix(reports): drop web-verification wording from user-visible report text (BUG-1810)
产品负责人 2026-11-08:用户看到的报告文字不出现「联网 / 外部资料 / 外部核验 /
MEVG / 真实案例 / Real Case / Global Web」,后台记录保留。

- 个人报告写作提示词:依据与边界改为「没有用户经历可对照」,置信度封顶 B 不变
- 年报:技法执行情况跳过 MEVG / Real Case 两行;没有纳入的删掉对应条目
- 合盘:技法执行情况删掉「外部资料与真实案例核对:未进行」整行
- 产品导出审计章节:这两行不再渲染,证据边界同类一句删除,packet 原样保留
- 质量门:只要求 Functional Benefic/Malefic 渲染;新增后台检查,要求 packet
  仍带这两行且它们没有出现在 markdown 里

Tests: 新增 frontend/tests/report-no-web-wording-20261109.test.ts(4)与
tests/test_report_no_web_wording_20261109.py(3);更新年报、合盘、审计章节、
质量门既有断言(原值/新值/原因已注明)。

Co-Authored-By: Grok (agent loop)
2026-11-09 08:20:50 +08:00

464 lines
25 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Technique audit, method differences, evidence boundaries and the D1–D60 ledger.
产品 2026-11-07 占星师方案 item 10: the product export (``render_full_data_markdown``)
keeps what ``render_pl9_markdown`` has and the astrologer's quality gate asks
for: the full Technique Audit Table (with the Functional Benefic/Malefic row),
the parameter and method differences, the evidence boundaries, and the
60-division ledger (20 formal traditional divisions + 40 research D-N rows).
产品 2026-11-08(BUG-1810):用户可见文字不出现「联网 / 外部资料 / 外部核验 /
MEVG / 真实案例」这类说法。审计行与证据边界里的外部核验字样不再渲染;后台
packet 的 ``technique_audit_table`` 原样保留,质量门改为检查后台记录仍在。
Everything is read from the packet. Statuses are written as words, never as
internal codes, so the ordinary projection keeps the chapter.
"""
from __future__ import annotations
import re
from typing import Any
try:
from scripts.report_render.text import SIGNS_CN, _md_cell
except ModuleNotFoundError: # pragma: no cover - direct scripts/ execution
from report_render.text import SIGNS_CN, _md_cell
AUDIT_HEADING = {"zh": "技法审计与证据边界", "en": "Technique Audit and Evidence Boundaries"}
AUDIT_TABLE_HEADING = {"zh": "完整 Technique Audit Table", "en": "Full Technique Audit Table"}
LEDGER_HEADING = {
"zh": "D1–D60 完整原始分盘账本(20 传统 + 40 研究型 D-N)",
"en": "D1-D60 Complete Raw Divisional Ledger (20 formal + 40 research D-N)",
}
REQUIRED_ROWS = ("Functional Benefic/Malefic",)
# 原值:这三行都渲染,缺哪行就补哪行
# 新值:后两行不再渲染(后台 packet 保留);原因:产品负责人 2026-11-08:用户可见文字不出现联网/外部核验字样
HIDDEN_TECHNIQUES = ("MEVG / Global Web Evidence", "Real Case Calibration")
BODIES = ("Ascendant", "Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu")
PLANETS = BODIES[1:]
PLANET_ZH = {
"Ascendant": "上升", "Sun": "太阳", "Moon": "月亮", "Mars": "火星", "Mercury": "水星",
"Jupiter": "木星", "Venus": "金星", "Saturn": "土星", "Rahu": "罗睺", "Ketu": "计都",
}
NATURAL_BENEFICS = {"Jupiter", "Venus", "Mercury", "Moon"}
STATUS = {
"used": ("已调用", "in use"),
"executed": ("已执行", "Ran"),
# Engine status words that are also packet keys; written as reader words (2026-11-07).
"run": ("已执行", "Ran"),
"not_run": ("未执行", "Not run"),
"not run": ("未执行", "Not run"),
"available": ("可用", "available"),
"verified": ("已核验", "verified"),
"blocked": ("阻塞", "Not run"),
"error": ("阻塞", "Not run"),
"parameter_sensitive": ("参数敏感", "parameter-sensitive"),
"partial_verified": ("部分核验", "partly verified"),
"partial": ("部分可用", "partial"),
"not_applicable": ("不适用", "not applicable"),
"conflict": ("方法冲突", "method conflict"),
"candidate_window_only": ("仅候选窗口", "candidate window only"),
}
# Words the ordinary projection treats as engine leakage; the chapter says them another way.
LEAK_WORDS = (
(re.compile(r"\bparity\b", re.I), ("对拍", "cross-check")),
(re.compile(r"\bproducer\b", re.I), ("来源", "source")),
(re.compile(r"\bblocked\b", re.I), ("阻塞", "Not run")),
(re.compile(r"\bexecuted\b", re.I), ("已执行", "Ran")),
(re.compile(r"\bMEVG\b"), ("MEVG", "MEVG")),
)
SNAKE = re.compile(r"\b([A-Za-z][A-Za-z0-9]*)(?:_([A-Za-z0-9]+))+\b")
PY_LIST = re.compile(r"\[([^\[\]{}]*)\]")
PY_DICT = re.compile(r"\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}")
def _lang(language: str) -> int:
return 1 if language == "en" else 0
def _dict(value: Any) -> dict:
return value if isinstance(value, dict) else {}
def _status(value: Any, language: str) -> str:
key = str(value or "").strip().lower()
label = STATUS.get(key)
if label:
return label[_lang(language)]
return _clean(key or ("未声明" if language != "en" else "not declared"), language)
def _planet(name: Any, language: str) -> str:
text = str(name or "")
return text if language == "en" else PLANET_ZH.get(text, text)
def _sign(name: Any, language: str) -> str:
text = str(name or "")
return text if language == "en" else SIGNS_CN.get(text, text)
def _names(values: Any, language: str) -> str:
if not isinstance(values, list) or not values:
return "—"
return ("、" if language != "en" else ", ").join(_planet(item, language) for item in values)
HAN = re.compile(r"[\u3400-\u9fff]")
def _clean(text: Any, language: str) -> str:
"""Engine notes, made readable: no Python reprs, no snake_case keys, no leak words."""
out = str(text or "")
if language == "en" and HAN.search(out):
# The engine wrote this note in Chinese; the English edition must not carry Han text.
return "Engine note in the Chinese edition."
out = PY_DICT.sub("…", out)
out = PY_LIST.sub(lambda match: ("、" if language != "en" else ", ").join(
part.strip().strip("'\"") for part in match.group(1).split(",") if part.strip()
) or "—", out)
out = SNAKE.sub(lambda match: match.group(0).replace("_", " "), out)
for pattern, (zh, en) in LEAK_WORDS:
out = pattern.sub(en if language == "en" else zh, out)
if language != "en":
out = _quote_glue(out)
return _md_cell(out.strip()) or "—"
GLUE_AFTER_HAN = re.compile(r"(?<=[\u4e00-\u9fff])([ \t]*)((?:in|and|or|from|planet|lord)\b[\w\-/.]*)", re.I)
GLUE_BEFORE_HAN = re.compile(r"(?<![`\w])([\w\-/.]*\b(?:in|and|or|from|planet|lord))([ \t]*)(?=[\u4e00-\u9fff])", re.I)
def _quote_glue(text: str) -> str:
"""An English identifier right next to Han text reads as broken prose; quote it as code."""
text = GLUE_AFTER_HAN.sub(lambda m: f"{m.group(1)}`{m.group(2)}`", text)
return GLUE_BEFORE_HAN.sub(lambda m: f"`{m.group(1)}`{m.group(2)}", text)
def _source(value: Any, language: str) -> str:
text = str(value or "")
if text.startswith("references/"):
return f"`{text.split()[0]}`"
return "本地计算" if language != "en" else "local calculation"
def _functional_note(functional: dict, language: str) -> str:
benefic = _names(functional.get("functional_benefics"), language)
malefic = _names(functional.get("functional_malefics"), language)
neutral = _names(functional.get("functional_neutrals"), language)
yogakaraka = _names(functional.get("yogakarakas"), language)
conflicts = _conflicts(functional)
if language == "en":
tail = "; ".join(f"{row[0]} natural {row[1]} / functional {row[2]}" for row in conflicts) or "none"
return (f"Functional benefics {benefic}; functional malefics {malefic}; neutral {neutral}; Yogakaraka {yogakaraka}. "
f"Natural vs functional conflicts: {tail}. A conflicting planet lowers the confidence of the statement it supports.")
word = {"benefic": "吉", "malefic": "凶"}
tail = ";".join(f"{_planet(row[0], language)}(自然{word[row[1]]}、功能{word[row[2]]})" for row in conflicts) or "无"
return (f"功能吉星 {benefic};功能凶星 {malefic};中性 {neutral};Yogakaraka {yogakaraka}。"
f"自然与功能冲突:{tail}。用到冲突星的结论降一级置信度。")
def _conflicts(functional: dict) -> list[tuple[str, str, str]]:
benefic = set(functional.get("functional_benefics") or []) | set(functional.get("yogakarakas") or [])
malefic = set(functional.get("functional_malefics") or [])
rows = []
for planet in PLANETS:
if planet in ("Rahu", "Ketu"):
continue
natural = "benefic" if planet in NATURAL_BENEFICS else "malefic"
role = "benefic" if planet in benefic else "malefic" if planet in malefic else None
if role and role != natural:
rows.append((planet, natural, role))
return rows
def _dual_dasha_note(timing: dict, language: str) -> str:
vim = _dict(_dict(timing.get("dasha")).get("current_dasha"))
nar = _dict(_dict(_dict(timing.get("narayana_dasha")).get("current_dasha")).get("md"))
if language == "en":
return (f"Vimshottari MD {vim.get('lord') or '—'} ({vim.get('start') or '—'} to {vim.get('end') or '—'}); "
f"Narayana MD {nar.get('sign') or '—'} (lord {nar.get('lord') or '—'}, age {nar.get('start_age', '—')}–{nar.get('end_age', '—')}). "
"Both tracks are read together; when they point different ways both are stated.")
return (f"Vimshottari 当前大运 {_planet(vim.get('lord'), language) or '—'}({vim.get('start') or '—'} 至 {vim.get('end') or '—'});"
f"Narayana 当前大运 {_sign(nar.get('sign'), language) or '—'}(宫主 {_planet(nar.get('lord'), language) or '—'},"
f"{nar.get('start_age', '—')}–{nar.get('end_age', '—')} 岁)。双轨同读,方向不同时两面都写。")
def _audit_rows(packet: dict, language: str) -> list[tuple[str, str, str, str]]:
worksheets = _dict(packet.get("worksheets"))
functional = _dict(_dict(worksheets.get("strengths_and_scores")).get("functional_benefic_malefic"))
timing = _dict(worksheets.get("timing_and_predictive_systems"))
table = packet.get("technique_audit_table") if isinstance(packet.get("technique_audit_table"), list) else []
rows: list[tuple[str, str, str, str]] = []
seen = set()
for row in table:
if not isinstance(row, dict) or not row.get("technique"):
continue
name = str(row["technique"])
seen.add(name)
if name in HIDDEN_TECHNIQUES:
# 产品负责人 2026-11-08:这两行只在后台记录里保留,不渲染给用户。
continue
if name == "Functional Benefic/Malefic":
note = _functional_note(functional, language) if functional else _clean(row.get("note"), language)
elif name == "Vimshottari + Narayana Cross-check":
note = _dual_dasha_note(timing, language)
else:
note = _clean(row.get("note"), language)
rows.append((_clean(name, language), _status(row.get("status"), language), _source(row.get("source"), language), note))
if "Functional Benefic/Malefic" not in seen:
status = functional.get("status") or "blocked"
note = _functional_note(functional, language) if functional else (
"功能吉凶层缺失,本报告不能声称完成高严谨解读。" if language != "en"
else "Functional layer missing; this report cannot claim a high-rigor reading.")
rows.append(("Functional Benefic/Malefic", _status(status, language), _source("", language), note))
return rows
def _table(header: tuple[str, ...], rows: list[tuple[str, ...]]) -> list[str]:
lines = ["| " + " | ".join(header) + " |", "|" + "|".join("------" for _ in header) + "|"]
lines.extend("| " + " | ".join(cell or "—" for cell in row) + " |" for row in rows)
return lines
def _functional_table(packet: dict, language: str) -> list[str]:
functional = _dict(_dict(_dict(packet.get("worksheets")).get("strengths_and_scores")).get("functional_benefic_malefic"))
if not functional:
return []
benefic = set(functional.get("functional_benefics") or []) | set(functional.get("yogakarakas") or [])
malefic = set(functional.get("functional_malefics") or [])
owned = _dict(functional.get("owned_houses"))
en = language == "en"
rows = []
for planet in PLANETS:
natural = "benefic" if planet in NATURAL_BENEFICS else "malefic"
if planet in ("Rahu", "Ketu"):
role = "not assigned" if en else "不单独定"
elif planet in set(functional.get("yogakarakas") or []):
role = "Yogakaraka"
elif planet in benefic:
role = "functional benefic" if en else "功能吉"
elif planet in malefic:
role = "functional malefic" if en else "功能凶"
else:
role = "functionally neutral" if en else "中性"
functional_side = "benefic" if planet in benefic else "malefic" if planet in malefic else None
clash = functional_side is not None and functional_side != natural and planet not in ("Rahu", "Ketu")
houses = owned.get(planet)
rows.append((
_planet(planet, language),
(f"natural {natural}" if en else {"benefic": "自然吉", "malefic": "自然凶"}[natural]),
role,
", ".join(str(h) for h in houses) if isinstance(houses, list) and houses else "—",
("yes, confidence down one step" if en else "冲突,降一级置信度") if clash else ("no conflict" if en else "一致"),
))
heading = "### Functional vs natural benefic / malefic" if en else "### 功能吉凶与自然吉凶"
header = ("Planet", "Natural", "Functional", "Houses owned", "Conflict") if en else ("行星", "自然属性", "功能属性", "主管宫", "冲突")
lagna = functional.get("ascendant") or ""
intro = (f"Lagna {lagna}: functional roles come from house lordship for this ascendant. Rahu and Ketu take no separate role."
if en else f"上升 {_sign(lagna, language)}:功能吉凶按本上升的宫主身份判定;罗睺、计都不单独定功能吉凶。")
return [heading, "", intro, "", *_table(header, rows), ""]
PARAMETER_LABELS = {
"lahiri": ("Lahiri", "Lahiri"), "raman": ("Raman", "Raman"), "kp": ("KP", "KP"),
"true_chitra": ("True Chitra", "True Chitra"), "krishnamurti": ("KP", "KP"),
"mean": ("mean node", "平交点"), "true": ("true node", "真交点"),
"whole_sign": ("whole sign", "整宫制"), "placidus": ("Placidus", "Placidus"),
}
def _param(value: Any, language: str) -> str:
key = str(value or "").strip().lower()
label = PARAMETER_LABELS.get(key)
if label:
return label[0 if language == "en" else 1]
return _clean(value, language)
def _difference_rows(packet: dict, language: str) -> list[tuple[str, str, str, str]]:
en = language == "en"
profile = _dict(packet.get("calculation_profile"))
worksheets = _dict(packet.get("worksheets"))
advanced = _dict(worksheets.get("advanced_systems"))
timing = _dict(worksheets.get("timing_and_predictive_systems"))
kp_profile = _dict(_dict(advanced.get("kp")).get("kp_profile"))
rows = [
("Ayanamsa" if en else "岁差 Ayanamsa", _param(profile.get("ayanamsa"), language),
"other ayanamsas shift every sign boundary" if en else "换岁差会移动所有星座与分盘边界", _status("parameter_sensitive", language)),
("Node mode" if en else "交点 Node mode", _param(profile.get("node_mode"), language),
"true vs mean node" if en else "真交点 / 平交点", _status("parameter_sensitive", language)),
("House system" if en else "宫制", _param(profile.get("house_system"), language),
(f"KP uses {_param(kp_profile.get('house_system'), language)} cusps with {_param(kp_profile.get('ayanamsa'), language)} ayanamsa" if en
else f"KP 用 {_param(kp_profile.get('house_system'), language)} 宫头与 {_param(kp_profile.get('ayanamsa'), language)} 岁差,个别行星落宫可能不同"),
_status(kp_profile.get("status") or "parameter_sensitive", language)),
]
narayana = _dict(timing.get("narayana_dasha"))
current = _dict(_dict(narayana.get("current_dasha")).get("md"))
legacy = _dict(_dict(_dict(narayana.get("legacy")).get("current_dasha")).get("md"))
if current:
other = (f"legacy method: {legacy.get('sign')} (age {legacy.get('start_age')}–{legacy.get('end_age')})" if en
else f"旧算法:{_sign(legacy.get('sign'), language)}({legacy.get('start_age')}–{legacy.get('end_age')} 岁)") if legacy else "—"
rows.append((
"Narayana Dasha",
(f"{current.get('sign')} (age {current.get('start_age')}–{current.get('end_age')})" if en
else f"{_sign(current.get('sign'), language)}({current.get('start_age')}–{current.get('end_age')} 岁)"),
other,
_status("conflict" if legacy and legacy.get("sign") != current.get("sign") else narayana.get("status"), language),
))
divergences = _dict(timing.get("transit_multi_reference")).get("divergences")
for row in divergences if isinstance(divergences, list) else []:
if not isinstance(row, dict) or row.get("planet") not in ("Jupiter", "Saturn", "Rahu", "Ketu"):
continue
houses = _dict(row.get("houses"))
names = {"Lagna": ("Lagna", "上升"), "Chandra_Lagna": ("Moon", "月亮"), "Arudha_Lagna": ("Arudha Lagna", "AL"),
"Navamsa_Lagna": ("Navamsa Lagna", "D9 上升")}
text = ("; " if en else ";").join(
f"{names.get(key, (key, key))[0 if en else 1]} {value}" for key, value in houses.items()
)
rows.append((
(f"Transit {row['planet']} by reference point" if en else f"{_planet(row['planet'], language)}行运按参照点"),
text, "houses differ by reference point" if en else "不同参照点落宫不同,需合看", _status("parameter_sensitive", language),
))
return rows
FAMILY_NOTE_ZH = {
"parameter_sensitive": "本地已算出,外部同口径对拍未闭环;可作条件性参考。",
"blocked": "本地没有产出主运周期,本报告不用。",
"not_applicable": "按本盘的适用条件不适用。",
"partial": "只算出部分层级。",
"partial_verified": "部分核验。",
"verified": "已核验。",
}
def _dasha_family_rows(packet: dict, language: str) -> list[tuple[str, str, str]]:
timing = _dict(_dict(packet.get("worksheets")).get("timing_and_predictive_systems"))
families = _dict(_dict(timing.get("dasha_master_pack")).get("families"))
narayana = _dict(timing.get("narayana_dasha"))
rows = []
for name, family in families.items():
if not isinstance(family, dict):
continue
status = str(family.get("status") or "")
if name == "narayana" and _dict(narayana.get("current_dasha")).get("md"):
# The master pack does not run Narayana; the report's Narayana is the timing chapter's.
rows.append(("Narayana", _status(narayana.get("status") or "partial_verified", language),
"Read in the dasha chapter (default method, dated from the birth date plus age)." if language == "en"
else "在大运章节单独计算(默认算法,日期按出生日加年龄)。"))
continue
if language == "en":
reason = family.get("reason")
note = _clean(reason, language) if reason else "—"
else:
# The engine's reasons are English; the Chinese edition says what the status means.
note = FAMILY_NOTE_ZH.get(status, "—")
rows.append((_clean(name, language).title(), _status(status, language), note))
return rows
def _boundaries(packet: dict, language: str) -> list[str]:
en = language == "en"
worksheets = _dict(packet.get("worksheets"))
timing = _dict(worksheets.get("timing_and_predictive_systems"))
double = _dict(timing.get("double_transit_pac"))
ledger = _dict(_dict(_dict(packet.get("full_report_pack")).get("sections")).get("d1_d60_ledger"))
lines = []
if en:
lines.append("- Yogas are rule matches (candidates). Cancellation conditions and real events are not checked, so no yoga is stated as confirmed.")
lines.append("- Timing is stated to the month at most; no statement names an exact day.")
if double:
lines.append("- Double Transit is a reference-date observation, not an event prediction or a calibrated timing result.")
if ledger:
lines.append("- Only the 20 formal divisions may be read, inside their own boundaries; the 40 research D-N rows are raw data.")
else:
lines.append("- Yoga 是按规则匹配出的候选;取消条件与现实事件没有核对,不写成已成立。")
lines.append("- 时间判断最多说到月份,不点具体哪一天。")
if double:
lines.append("- 双重行运(Double Transit)是参考日的观察,不是事件预测,也不是校准过的应期。")
if ledger:
lines.append("- 账本里只有 20 个传统分盘可以在各自边界内交叉阅读;其余 40 个研究型 D-N 只是原始数据,不单独下结论。")
return lines
def _position(value: Any, language: str) -> str:
if not isinstance(value, dict) or not value.get("sign"):
return "—"
degree = value.get("degree_in_sign", value.get("degree"))
if degree is None and isinstance(value.get("lon"), (int, float)):
degree = float(value["lon"]) % 30
sign = _sign(value.get("sign"), language)
return f"{sign} {float(degree):.2f}°" if isinstance(degree, (int, float)) and not isinstance(degree, bool) else sign
def render_d1_d60_ledger(packet: dict, language: str) -> list[str]:
en = language == "en"
ledger = _dict(_dict(_dict(packet.get("full_report_pack")).get("sections")).get("d1_d60_ledger"))
rows = _dict(ledger.get("d1_to_d60"))
if not rows:
return []
summary = _dict(ledger.get("summary"))
header = (("Division", *BODIES, "Tier", "Reading", "Stability") if en
else ("分盘", *(PLANET_ZH[body] for body in BODIES), "技法等级", "解释许可", "稳定性"))
body_rows = []
for division in (f"D{number}" for number in range(1, 61)):
row = _dict(rows.get(division))
raw = _dict(row.get("raw"))
formal = row.get("capability_tier") == "formal_traditional_division"
tier = ("formal division" if formal else "research D-N") if en else ("传统正式" if formal else "研究型 D-N")
allowed = ("bounded cross-reading" if en else "允许受限交叉阅读") if row.get("interpretation_allowed") else ("raw research only" if en else "仅原始研究")
stability = {"not_tested": ("not tested", "未测"), "stable": ("stable", "稳定"), "unstable": ("unstable", "不稳定")}.get(
str(row.get("stability") or "not_tested"), (_clean(row.get("stability"), language),) * 2)[0 if en else 1]
body_rows.append((division, *(_position(raw.get(body), language) for body in BODIES), tier, allowed, stability))
intro = (
f"All 60 divisions as raw positions. Formal traditional divisions: {summary.get('formal_traditional_division_count', '—')}; "
f"research generic D-N: {summary.get('research_generic_dn_division_count', '—')}. Named divisions are read only inside their own "
"evidence boundary; the research rows cannot produce a theme, an event or a chart approval."
if en else
f"保留全部 60 个分割数的原始位置。传统正式分盘 {summary.get('formal_traditional_division_count', '—')} 个;研究型通用 D-N "
f"{summary.get('research_generic_dn_division_count', '—')} 个。传统分盘只在各自的证据边界内进入专题交叉阅读;研究型 D-N 不独立生成主题结论、事件判断或选盘依据。"
)
return [f"## {LEDGER_HEADING['en' if en else 'zh']}", "", intro, "", *_table(header, body_rows), ""]
def render_audit_chapters(packet: dict, language: str) -> str:
language = "en" if language == "en" else "zh"
en = language == "en"
if not isinstance(packet, dict):
return ""
lines = [
f"## {AUDIT_HEADING[language]}",
"",
("This chapter lists what the report actually ran, what it could not run, where parameters or methods change the "
"answer, and the limits of each kind of evidence. Readers can skip it; it stays in every export."
if en else
"本章列出本报告实际调用了哪些技法、哪些没有运行、哪里换参数或换方法结果会变,以及各类证据能说到哪一步。普通阅读可以跳过,导出时始终保留。"),
"",
f"### {AUDIT_TABLE_HEADING[language]}",
"",
*_table(("Technique", "Status", "Source", "Audit note") if en else ("技法", "状态", "依据来源", "审计说明"),
_audit_rows(packet, language)),
"",
*_functional_table(packet, language),
]
differences = _difference_rows(packet, language)
if differences:
lines.extend([
"### Parameters and method differences (parameter-sensitive)" if en else "### 参数与方法差异(参数敏感)",
"",
*_table(("Item", "This report", "Comparison", "Status") if en else ("项目", "本报告取值", "对照", "状态"), differences),
"",
])
families = _dasha_family_rows(packet, language)
if families:
lines.extend([
"### Dasha systems: status" if en else "### 各大运体系状态",
"",
*_table(("System", "Status", "Note") if en else ("体系", "状态", "说明"), families),
"",
])
lines.extend(["### Evidence boundaries" if en else "### 证据边界", "", *_boundaries(packet, language), ""])
lines.extend(render_d1_d60_ledger(packet, language))
return "\n".join(lines).rstrip() + "\n"