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
133 lines
8.1 KiB
Python
133 lines
8.1 KiB
Python
"""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"
|