Files
Jyotisha/scripts/reader_dasha_applicability.py
T
Jesse_ChenandClaude Opus 5.5 0c06ab1d78 feat(report): static English dictionary for yoga rules and dasha applicability (E3)
yoga_rules.json gains effects_en for every rule and an _en sibling for each
Han-bearing string (combo_template, strength, source, accuracy_note); zh values
untouched. reader_dasha_applicability renders language='en' from the same
packet; the default zh output is byte-identical (pinned by sha256).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
2026-09-29 19:26:00 +08:00

133 lines
8.1 KiB
Python
Raw 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.
"""Reader-only applicability notes; never changes dasha calculations or periods.
``language="en"`` renders the same table from the same packet with the static
English wording below; the default ``"zh"`` output is unchanged.
"""
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.": "该大运要求太阳位于上升星座,本次未满足。",
}
REASON_LABELS_EN = {
"patyayini_dasha_missing": "This calculation did not provide the Patyayini annual periods, so they cannot be used for timing checks yet.",
"The Shodashottari hora-plus-paksha applicability gate is not met.": "The Hora and lunar-fortnight (Paksha) condition for this dasha is not met.",
"This profile requires Navamsa Lagna in Taurus or Libra.": "This dasha requires the Navamsa Lagna in Taurus or Libra; not met here.",
"Dwisaptatisama requires Lagna lord in the 1st or 7th house.": "This dasha requires the Lagna lord in the 1st or 7th house; not met here.",
"Panchottari requires a Cancer Ascendant that is also Cancer in Dwadasamsa.": "This dasha requires a Cancer Lagna in both the birth chart and the Dwadasamsa; not met here.",
"Satabdika requires a vargottama Ascendant (same Rasi and Navamsa sign).": "This dasha requires a Vargottama Lagna (same sign in Rasi and Navamsa); not met here.",
"Chaturshitisama requires the 10th lord to occupy the 10th house.": "This dasha requires the 10th lord in the 10th house; not met here.",
"Shastihayani requires Sun in the Lagna sign.": "This dasha requires the Sun in the Lagna sign; not met here.",
}
_TEXT = {
"zh": {
"boundary": BOUNDARY,
"missing": MISSING,
"ashtottari": "月宿与月相组合未满足该大运的适用条件,不绕过判定。",
"unknown_reason": "计算返回了限制原因,但暂无可核对的详细说明;不据此补写适用条件或日期。",
"heading": "## 辅助时间轴适用性(参数敏感)",
"table_head": ["| 大运族 | 是否适用 / 原因 | 使用边界 |", "| --- | --- | --- |"],
"not_applicable": "本次不适用,不绕过原适用条件,不补写日期。",
"unavailable": "本次不可用:本地计算尚未提供可用周期,不能据此判断时间。",
"has_periods": "本次已返回周期,可作交叉核对;这不代表已完成多引擎验证。",
"unverified": "本次适用性尚未核实,缺少可核对周期,不补写日期。",
"applicable_true": " 原适用性判定:适用。",
"reasons_prefix": " 原因:",
"reasons_joiner": ";",
},
"en": {
"boundary": "Secondary timeline for cross-checking only; does not set event timing on its own",
"missing": "This calculation carries no structured applicability evidence, so it cannot confirm whether each auxiliary timeline applies; no periods or dates are filled in.",
"ashtottari": "The Moon's nakshatra and lunar phase do not meet this dasha's applicability condition; the rule is not bypassed.",
"unknown_reason": "The calculation returned a restriction without a checkable explanation; no condition or date is inferred from it.",
"heading": "## Auxiliary Dasha Applicability (reference only)",
"table_head": ["| Dasha system | Applicable? / Reason | Use |", "| --- | --- | --- |"],
"not_applicable": "Not applicable for this chart; the original condition is not bypassed and no dates are filled in.",
"unavailable": "Unavailable: the local calculation has not produced usable periods, so it cannot be used for timing.",
"has_periods": "Periods were returned and can be used for cross-checking; this does not mean multi-engine verification is complete.",
"unverified": "Applicability not yet verified and no checkable periods are available; no dates are filled in.",
"applicable_true": " Original applicability verdict: applicable.",
"reasons_prefix": " Reason: ",
"reasons_joiner": "; ",
},
}
def _language(value):
return "en" if value == "en" else "zh"
def _reader_reason(value, language="zh"):
labels = REASON_LABELS_EN if language == "en" else REASON_LABELS
if value in labels:
return labels[value]
if value.startswith("Moon Nakshatra '") and "does not qualify for Ashtottari Dasha under " in value:
return _TEXT[language]["ashtottari"]
return _TEXT[language]["unknown_reason"]
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, language="zh"):
text = str(value).replace("\r", " ").replace("\n", " ")
if language == "en":
return text.replace("|", "/").replace("<", "‹").replace(">", "›")
return text.replace("|", "/").replace("<", "<").replace(">", ">")
def render_dasha_applicability(packet, language="zh"):
language = _language(language)
text = _TEXT[language]
timing = packet.get("worksheets", {}).get("timing_and_predictive_systems", {})
master = timing.get("dasha_master_pack", {})
families = master.get("families", {})
lines = ["", text["heading"], ""]
if not isinstance(families, dict) or not families:
return "\n".join(lines + [text["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 += text["table_head"]
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 = text["not_applicable"]
elif status in {"blocked", "missing_in_local", "unavailable"}:
reason = text["unavailable"]
elif _has_periods(key, raw, source):
reason = text["has_periods"]
else:
reason = text["unverified"]
if applicable is True:
reason += text["applicable_true"]
reasons = list(dict.fromkeys(_reader_reason(str(value), language) for value in (raw.get("reason"), source.get("reason")) if value))
if reasons:
reason += text["reasons_prefix"] + text["reasons_joiner"].join(reasons)
name = "Kala Chakra" if key == "kala_chakra" else key.replace("_", "-").title()
lines.append(f"| {_cell(name, language)} | {_cell(reason, language)} | {text['boundary']} |")
return "\n".join(lines) + "\n"