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:
Jesse_Chen
2026-10-04 07:35:11 +08:00
co-authored by Claude Opus 5.5
parent 61bd4425cd
commit 57ef4560ce
7 changed files with 148 additions and 4 deletions
+16
View File
@@ -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
+47
View File
@@ -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"])