feat(narayana-rath): AD direction checked against p.51, book divergence listed (BUG-1225)
Round-6 ruling Q7: default by the AD start sign's odd/even (Table 13, as in Table 25); Saturn in the MD sign -> always zodiacal (Table 11); Ketu -> reversed (Table 14); both -> Ketu first. Each AD rule now carries direction.book_divergence (p.47 J.S. 1.1.34; J.S. 2.4.31-32), not applied; reference report shows one line, English twins added. Table 25 Taurus 12/12. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
61bd4425cd
commit
57ef4560ce
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -162,6 +162,7 @@ def rath_report_lines_zh(rath: Any) -> List[str]:
|
||||
"",
|
||||
f"- 当前大运 {md.get('sign')} 的子运:比较 {compared[0]} 与 {compared[-1]},较强 {rule.get('stronger_sign')}"
|
||||
f"(第 {rule.get('strength_level_decided')} 级),从宫主 {rule.get('lord')} 所在 {rule.get('start_sign')} 起,12 等分",
|
||||
*_ad_direction_lines_zh(rule.get("direction")),
|
||||
"",
|
||||
"| Rath AD | 星座 | 起始年龄 | 结束年龄 |",
|
||||
"|---------|------|----------|----------|",
|
||||
@@ -171,6 +172,21 @@ def rath_report_lines_zh(rath: Any) -> List[str]:
|
||||
return lines
|
||||
|
||||
|
||||
AD_DIRECTION_WORD_ZH = {"zodiacal": "顺行", "reverse": "逆行"}
|
||||
|
||||
|
||||
def _ad_direction_lines_zh(direction: Any) -> List[str]:
|
||||
"""AD direction applied + the book's two other statements, flagged as a
|
||||
book divergence and not applied (round-6 ruling Q7, BUG-1225)."""
|
||||
if not isinstance(direction, dict) or not direction.get("rule_text"):
|
||||
return []
|
||||
others = ";".join(
|
||||
f"{row.get('rule_text')} → {AD_DIRECTION_WORD_ZH.get(row.get('direction'), '-')}"
|
||||
for row in direction.get("book_divergence") or [] if isinstance(row, dict))
|
||||
line = f"- 子运方向:{direction['rule_text']},{AD_DIRECTION_WORD_ZH.get(direction.get('direction'), '-')}"
|
||||
return [line + (f"(书内分歧,未采用:{others})" if others else "")]
|
||||
|
||||
|
||||
def rath_summary_line_zh(rath: Any) -> Optional[str]:
|
||||
"""One report line naming the Rath start sign and running MD (BUG-1218)."""
|
||||
if not isinstance(rath, dict) or not rath:
|
||||
|
||||
@@ -42,8 +42,12 @@ exception, exalted +1 / debilitated -1); Scorpio / Aquarius lords by
|
||||
AD [p.48 Arambha Rasi, p.50 note 2]: compare the MD sign and its 7th with the
|
||||
same seven levels, take the stronger, start from the sign holding that sign's
|
||||
lord (dual lords by the same rule), twelve equal parts. Order: Table 13 from
|
||||
the start sign; Saturn in the MD sign -> Table 11 (zodiacal); Ketu in the MD
|
||||
sign -> Table 14 (Ketu first when both).
|
||||
the start sign (direction by the start sign's odd/even, as in Table 25;
|
||||
round-6 ruling Q7); Saturn in the MD sign -> Table 11 (zodiacal); Ketu in the
|
||||
MD sign -> Table 14 (Ketu first when both) [p.51]. The book's other two
|
||||
statements (p.47 J.S. 1.1.34: by the MD sign; J.S. 2.4.31-32: by the MD sign
|
||||
or its lord's sign) disagree with Table 25; each AD rule carries them as
|
||||
`direction.book_divergence` (BUG-1225), not applied.
|
||||
|
||||
Ported verbatim from upstream yinduzhanxing c7117f92 `scripts/narayana_dasha.py`:
|
||||
`_rath_ketu_table12_sequence` (the `rath_ketu_table12_v1` branch of
|
||||
@@ -318,6 +322,52 @@ def _ad_order_profile(md_sign_idx: int, planet_lons: Dict[str, float]) -> Tuple[
|
||||
return "rath_table13_v1", None
|
||||
|
||||
|
||||
# AD direction (round-6 ruling Q7): default by the odd/even of the AD start
|
||||
# sign (Table 13 practice, as the book's Table 25 shows); exceptions p.51:
|
||||
# Saturn in the MD sign -> always zodiacal (Table 11); Ketu in the MD sign ->
|
||||
# reversed (Table 14); both -> Ketu first (product owner 2026-10-03). Two
|
||||
# other statements in the book disagree with Table 25 and are reported, not
|
||||
# applied (`book_divergence`).
|
||||
AD_DIRECTION_RULE = {
|
||||
"rath_table13_v1": ("按子运起点星座奇偶(Table 13)", "by the odd/even of the AD start sign (Table 13)"),
|
||||
"rath_saturn_table11_v1": ("大运星座有土星:一律顺行(Table 11)", "Saturn in the MD sign: always zodiacal (Table 11)"),
|
||||
"rath_ketu_table14_v1": ("大运星座有计都:反向(Table 14)", "Ketu in the MD sign: reversed (Table 14)"),
|
||||
}
|
||||
AD_BOOK_DIVERGENCE = (
|
||||
("rath_p47_js_1_1_34", "Rath p.47 (J.S. 1.1.34)",
|
||||
"p.47(J.S. 1.1.34)看大运星座奇偶", "p.47 (J.S. 1.1.34) by the odd/even of the MD sign"),
|
||||
("js_2_4_31_32", "J.S. 2.4.31-32 (Rath p.47-48)",
|
||||
"J.S. 2.4.31–32 看大运星座或其宫主所落星座", "J.S. 2.4.31-32 by the MD sign or the sign its lord occupies"),
|
||||
)
|
||||
|
||||
|
||||
def _is_odd_sign(sign_idx: int) -> bool:
|
||||
return sign_idx % 2 == 0 # Aries (index 0) is the first, odd sign
|
||||
|
||||
|
||||
def ad_direction(md_sign_idx: int, start_sign_idx: int, profile: str, planet_lons: Dict[str, float]) -> Dict[str, Any]:
|
||||
"""Applied AD direction plus the two book statements it does not follow."""
|
||||
order = nd.build_narayana_antardasha_order_sequence(start_sign_idx, profile=profile)
|
||||
applied = "zodiacal" if order[1] == (start_sign_idx + 1) % 12 else "reverse"
|
||||
lord_sign = nd._get_planet_sign(_lord(md_sign_idx, planet_lons), planet_lons)
|
||||
by_rule = {
|
||||
"rath_p47_js_1_1_34": "zodiacal" if _is_odd_sign(md_sign_idx) else "reverse",
|
||||
"js_2_4_31_32": "zodiacal" if _is_odd_sign(md_sign_idx) or (lord_sign is not None and _is_odd_sign(lord_sign)) else "reverse",
|
||||
}
|
||||
zh, en = AD_DIRECTION_RULE[profile]
|
||||
return {
|
||||
"direction": applied,
|
||||
"rule": profile,
|
||||
"rule_text": zh,
|
||||
"rule_text_en": en,
|
||||
"book_divergence": [
|
||||
{"id": key, "source": source, "rule_text": rule_zh, "rule_text_en": rule_en,
|
||||
"direction": by_rule[key], "differs": by_rule[key] != applied, "applied": False}
|
||||
for key, source, rule_zh, rule_en in AD_BOOK_DIVERGENCE
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def mahadasha_sequence(seed_sign_idx: int, planet_lons: Dict[str, float],
|
||||
*, start_age: float = 0.0, cycle_count: int = 2) -> Dict[str, Any]:
|
||||
"""Rath MD rows from one start sign (cycle 2 = twelve minus cycle 1)."""
|
||||
@@ -384,6 +434,7 @@ def antardasha(md_row: Dict[str, Any], planet_lons: Dict[str, float]) -> Dict[st
|
||||
"start_sign": SIGNS[start],
|
||||
"order_profile": profile,
|
||||
"method_note": note,
|
||||
"direction": ad_direction(md_sign_idx, start, profile, planet_lons),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -174,6 +174,20 @@ _PHRASES: dict[str, str] = {
|
||||
"| Rath MD | 星座 | 宫主 | 起始年龄 | 结束年龄 | 年数 |": "| Rath MD | Sign | Lord | Start age | End age | Years |",
|
||||
"| Rath AD | 星座 | 起始年龄 | 结束年龄 |": "| Rath AD | Sign | Start age | End age |",
|
||||
" 的子运:比较 ": " sub-periods: compare ",
|
||||
# BUG-1225: narayana_legacy_label._ad_direction_lines_zh (AD direction + book divergence)
|
||||
"- 子运方向:": "- AD direction: ",
|
||||
"按子运起点星座奇偶(Table 13)": "by the odd/even of the AD start sign (Table 13)",
|
||||
"大运星座有土星:一律顺行(Table 11)": "Saturn in the MD sign: always zodiacal (Table 11)",
|
||||
"大运星座有计都:反向(Table 14)": "Ketu in the MD sign: reversed (Table 14)",
|
||||
"(书内分歧,未采用:": " (book divergence, not applied: ",
|
||||
"p.47(J.S. 1.1.34)看大运星座奇偶": "p.47 (J.S. 1.1.34) by the odd/even of the MD sign",
|
||||
"J.S. 2.4.31–32 看大运星座或其宫主所落星座": "J.S. 2.4.31-32 by the MD sign or the sign its lord occupies",
|
||||
" → 顺行;": " -> zodiacal; ",
|
||||
" → 逆行;": " -> reverse; ",
|
||||
" → 顺行)": " -> zodiacal)",
|
||||
" → 逆行)": " -> reverse)",
|
||||
",顺行": ", zodiacal",
|
||||
",逆行": ", reverse",
|
||||
",12 等分": ", twelve equal parts",
|
||||
# BUG-1214: tajika_year_lord.selection_basis_text (zh) → its en twin.
|
||||
"年盘不全,暂用 Muntha 主星": "Annual chart incomplete; Muntha sign lord used for now",
|
||||
|
||||
@@ -248,3 +248,19 @@ def _production_files(record) -> list:
|
||||
for value in record:
|
||||
found.extend(_production_files(value))
|
||||
return found
|
||||
|
||||
|
||||
def test_table25_taurus_md_is_the_saturn_exception_and_carries_the_book_divergence() -> None:
|
||||
"""Round-6 ruling Q7 / BUG-1225: Saturn in the Taurus MD sign -> ADs always
|
||||
zodiacal (p.51), 12/12 with the book; the p.47 / J.S. 2.4.31-32 statements
|
||||
are listed as a book divergence, not applied."""
|
||||
chart, asc, *_ = compute_chart_data(*VERDICT_TABLES["Table 25"][0], ayanamsa_name="lahiri")
|
||||
lons = {name: row["degree"] for name, row in chart["planets"].items()}
|
||||
taurus = calc_narayana_rath(asc, lons, seed_sign_idx=SIGNS.index("Cancer"), cycle_count=1)["mahadasha"][2]
|
||||
ad = antardasha(taurus, lons)
|
||||
rule = ad["antardasha_start_rule"]
|
||||
assert taurus["sign"] == "Taurus" and rule["order_profile"] == "rath_saturn_table11_v1"
|
||||
assert [row["sign"] for row in ad["rows"]] == TABLE25_AD["Taurus"]
|
||||
assert rule["direction"]["direction"] == "zodiacal"
|
||||
p47, js = rule["direction"]["book_divergence"]
|
||||
assert (p47["direction"], p47["differs"]) == ("reverse", True) # Taurus is even: p.47 would run the ADs backwards
|
||||
|
||||
@@ -248,3 +248,50 @@ def test_node_dignity_stays_inside_rath() -> None:
|
||||
if path.name != "narayana_rath.py" and "RATH_NODE_DIGNITY" in path.read_text(encoding="utf-8"))
|
||||
assert users == []
|
||||
assert "RATH_NODE_DIGNITY" in inspect.getsource(rath._occupant_dignity)
|
||||
|
||||
|
||||
# --- BUG-1225: AD direction vs p.51 exceptions and the book divergence (round-6 ruling Q7).
|
||||
|
||||
def _md(sign: str, years: float = 6.0) -> dict:
|
||||
return {"sign": sign, "sign_idx": SIGNS.index(sign), "years": years, "start_age": 0.0, "end_age": years}
|
||||
|
||||
|
||||
def test_ad_tables_match_the_p51_exceptions_for_every_start_sign() -> None:
|
||||
for start in range(12):
|
||||
table13 = nd.build_narayana_antardasha_order_sequence(start, profile="rath_table13_v1")
|
||||
table14 = nd.build_narayana_antardasha_order_sequence(start, profile="rath_ketu_table14_v1")
|
||||
table11 = nd.build_narayana_antardasha_order_sequence(start, profile="rath_saturn_table11_v1")
|
||||
forward = [(start + i) % 12 for i in range(12)]
|
||||
backward = [(start - i) % 12 for i in range(12)]
|
||||
assert table13 == (forward if start % 2 == 0 else backward) # default: start sign odd -> zodiacal
|
||||
assert table14 == (backward if start % 2 == 0 else forward) # Ketu: the opposite
|
||||
assert table11 == forward # Saturn: always zodiacal
|
||||
|
||||
|
||||
def test_ketu_in_the_md_sign_reverses_the_ads() -> None:
|
||||
lons = _with(BASE, Rahu=215.0, Ketu=35.0) # Ketu in Taurus
|
||||
ad = antardasha(_md("Taurus"), lons)
|
||||
rule = ad["antardasha_start_rule"]
|
||||
assert rule["order_profile"] == "rath_ketu_table14_v1" and rule["method_note"] is None
|
||||
start = SIGNS.index(rule["start_sign"])
|
||||
plain = nd.build_narayana_antardasha_order_sequence(start, profile="rath_table13_v1")
|
||||
rows = [SIGNS.index(row["sign"]) for row in ad["rows"]]
|
||||
assert rows[0] == plain[0] == start
|
||||
assert (rows[1] - start) % 12 == (start - plain[1]) % 12 # same first step, opposite direction
|
||||
direction = rule["direction"]
|
||||
assert direction["rule"] == "rath_ketu_table14_v1"
|
||||
assert direction["direction"] == ("reverse" if start % 2 == 0 else "zodiacal")
|
||||
both = antardasha(_md("Taurus"), _with(lons, Saturn=40.0))
|
||||
assert both["antardasha_start_rule"]["order_profile"] == "rath_ketu_table14_v1"
|
||||
assert both["antardasha_start_rule"]["method_note"] == KETU_FIRST_NOTE
|
||||
|
||||
|
||||
def test_book_divergence_lists_the_two_other_statements() -> None:
|
||||
lons = _with(BASE, Rahu=215.0, Ketu=35.0)
|
||||
direction = antardasha(_md("Taurus"), lons)["antardasha_start_rule"]["direction"]
|
||||
ids = [row["id"] for row in direction["book_divergence"]]
|
||||
assert ids == ["rath_p47_js_1_1_34", "js_2_4_31_32"]
|
||||
assert all(row["applied"] is False and row["rule_text"] and row["rule_text_en"] for row in direction["book_divergence"])
|
||||
p47 = direction["book_divergence"][0]
|
||||
assert p47["direction"] == "reverse" # Taurus is an even sign
|
||||
assert p47["differs"] == (p47["direction"] != direction["direction"])
|
||||
|
||||
Reference in New Issue
Block a user