feat(report): add dense personal report appendix

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
jesse-ux
2026-09-23 10:42:17 +08:00
co-authored by Claude Code
parent a7f82dc2a6
commit bbd96d3b6b
44 changed files with 48833 additions and 50 deletions
+73
View File
@@ -0,0 +1,73 @@
"""Reader-only applicability notes; never changes dasha calculations or periods."""
BOUNDARY = "第二时间轴,仅供交叉核对,不单独生成应期"
MISSING = "缺少本次计算的结构化适用性证据,无法确认各辅助时间轴是否适用;不补算、不补写日期。"
REASON_LABELS = {
"patyayini_dasha_missing": "本次计算未提供帕特雅伊尼年运周期,暂不能用于时间核对。",
"The Shodashottari hora-plus-paksha applicability gate is not met.": "未满足该大运对时辰分区与月相组合的适用条件。",
"This profile requires Navamsa Lagna in Taurus or Libra.": "该大运要求九分盘上升落在金牛座或天秤座,本次未满足。",
"Dwisaptatisama requires Lagna lord in the 1st or 7th house.": "该大运要求上升主星位于第一宫或第七宫,本次未满足。",
"Panchottari requires a Cancer Ascendant that is also Cancer in Dwadasamsa.": "该大运要求本命及十二分盘上升均为巨蟹座,本次未满足。",
"Satabdika requires a vargottama Ascendant (same Rasi and Navamsa sign).": "该大运要求本命及九分盘上升同星座,本次未满足。",
"Chaturshitisama requires the 10th lord to occupy the 10th house.": "该大运要求第十宫主星位于第十宫,本次未满足。",
"Shastihayani requires Sun in the Lagna sign.": "该大运要求太阳位于上升星座,本次未满足。",
}
def _reader_reason(value):
if value in REASON_LABELS:
return REASON_LABELS[value]
if value.startswith("Moon Nakshatra '") and "does not qualify for Ashtottari Dasha under " in value:
return "月宿与月相组合未满足该大运的适用条件,不绕过判定。"
return "计算返回了限制原因,但暂无可核对的详细说明;不据此补写适用条件或日期。"
def _has_periods(key, raw, source):
fields = ("periods", "major") + (("mahadasha_sequence", "dense_boundaries") if key == "narayana" else ())
return any(isinstance(data.get(field), (list, dict)) and bool(data[field]) for data in (raw, source) for field in fields)
def _cell(value):
return str(value).replace("|", "/").replace("\r", " ").replace("\n", " ").replace("<", "<").replace(">", ">")
def render_dasha_applicability(packet):
timing = packet.get("worksheets", {}).get("timing_and_predictive_systems", {})
master = timing.get("dasha_master_pack", {})
families = master.get("families", {})
lines = ["", "## 辅助时间轴适用性(参数敏感)", ""]
if not isinstance(families, dict) or not families:
return "\n".join(lines + [MISSING, ""])
families = dict(families)
annual = timing.get("annual_tajika_pack", {})
for key, value in annual.items():
if key.endswith("_dasha"):
families.setdefault(key.removesuffix("_dasha"), value)
lines += ["| 大运族 | 是否适用 / 原因 | 使用边界 |", "| --- | --- | --- |"]
for key in sorted(families):
if key == "vimshottari":
continue
raw = families[key] if isinstance(families[key], dict) else {}
source_key = "kalachakra_dasha" if key == "kala_chakra" else f"{key}_dasha"
source = timing.get(source_key, {})
source = source if isinstance(source, dict) else {}
status = raw.get("status", raw.get("execution_status"))
applicable = source.get("applicable", raw.get("applicable"))
if applicable is False or status == "not_applicable":
reason = "本次不适用,不绕过原适用条件,不补写日期。"
elif status in {"blocked", "missing_in_local", "unavailable"}:
reason = "本次不可用:本地计算尚未提供可用周期,不能据此判断时间。"
elif _has_periods(key, raw, source):
reason = "本次已返回周期,可作交叉核对;这不代表已完成多引擎验证。"
else:
reason = "本次适用性尚未核实,缺少可核对周期,不补写日期。"
if applicable is True:
reason += " 原适用性判定:适用。"
reasons = list(dict.fromkeys(_reader_reason(str(value)) for value in (raw.get("reason"), source.get("reason")) if value))
if reasons:
reason += " 原因:" + ";".join(reasons)
name = "Kala Chakra" if key == "kala_chakra" else key.replace("_", "-").title()
lines.append(f"| {_cell(name)} | {_cell(reason)} | {BOUNDARY} |")
return "\n".join(lines) + "\n"