feat(narayana-rath): follow book p.43 for dual lords and node years (BUG-1227~1229)
Round-7 option A is text-first, so Scorpio and Aquarius lords, the Ketu-only degree reversal, and node exaltation years now live in the Rath display layer. Scoring files stay unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -94,9 +94,9 @@ def test_reference_edition_shows_both_and_english_terms_cover_the_rath_lines() -
|
||||
packet = _packet(CASES["day"])
|
||||
zh = _pl9_export_markdown_for_edition(copy.deepcopy(packet), "reference")
|
||||
legacy = zh.index(f"- 算法:{LEGACY_NOTE_ZH}")
|
||||
rath = zh.index("- 并列:Sanjay Rath 版(并列参考,未进打分与时间轴结论):起运宫 ")
|
||||
rath = zh.index("- 并列:Sanjay Rath 版(并列参考,未进打分与时间轴结论;按原书 p.43 正文(第七轮裁定)):起运宫 ")
|
||||
assert legacy < rath
|
||||
assert "| Rath MD | 星座 | 宫主 | 起始年龄 | 结束年龄 | 年数 |" in zh and "| Rath AD | 星座 | 起始年龄 | 结束年龄 |" in zh
|
||||
assert "| Rath MD | 星座 | 宫主 | 起始年龄 | 结束年龄 | 基本年数 | 修正 | 最终 |" in zh and "| Rath AD | 星座 | 起始年龄 | 结束年龄 |" in zh
|
||||
view = packet["worksheets"]["timing_and_predictive_systems"]["narayana_dasha"]["rath"]
|
||||
for line in rath_report_lines_zh(view):
|
||||
text = line
|
||||
|
||||
@@ -70,7 +70,11 @@ def test_book_start_sign_reproduces_order_and_years(table: str) -> None:
|
||||
asc, lons = _chart(table)
|
||||
result = calc_narayana_rath(asc, lons, seed_sign_idx=SIGNS.index(book["start_sign"]), cycle_count=1)
|
||||
assert [row["sign"] for row in result["mahadasha"]] == [row[0] for row in book["rows"]]
|
||||
assert [row["years"] for row in result["mahadasha"]] == [row[1] for row in book["rows"]]
|
||||
# 原值:年数逐行等于书印值
|
||||
# 新值:Table 15 仍等于书印值;Table 17 / 18 见 NEW_EXPLICIT_YEARS(年数不再作通过线)
|
||||
# 原因:BUG-1227 第七轮选 A,加上 BUG-1229 罗计旺陷 ±1
|
||||
expected_years = NEW_EXPLICIT_YEARS.get(table, [row[1] for row in book["rows"]])
|
||||
assert [row["years"] for row in result["mahadasha"]] == expected_years
|
||||
|
||||
|
||||
def test_saturn_in_the_start_sign_gives_the_zodiacal_order() -> None:
|
||||
@@ -96,7 +100,10 @@ def test_start_sign_by_the_seven_level_rule() -> None:
|
||||
assert [row["role"] for row in pisces["role_factors"]] == ["jupiter", "sign_lord"]
|
||||
assert result["seed_selection"]["candidates"]["Virgo"]["aspect_factor_count"] == 1
|
||||
assert [row["sign"] for row in result["mahadasha"]] == [row[0] for row in _book("Table 17")["rows"]]
|
||||
assert [row["years"] for row in result["mahadasha"]] == [row[1] for row in _book("Table 17")["rows"]]
|
||||
# 原值:年数 12/12 等于 Table 17 书印值(天蝎 10、水瓶 8)
|
||||
# 新值:10/12;天蝎计都 2(基本 1,入旺 +1),水瓶土星 1。Chart 3 水瓶印 9
|
||||
# 原因:BUG-1227 第七轮选 A,年数不再作通过线;BUG-1229
|
||||
assert [row["years"] for row in result["mahadasha"]] == NEW_EXPLICIT_YEARS["Table 17"]
|
||||
asc, lons = _chart("Table 18")
|
||||
for allow in (False, True):
|
||||
result = calc_narayana_rath(asc, lons, cycle_count=1, allow_book_example=allow)
|
||||
@@ -138,7 +145,10 @@ def test_table18_only_with_the_explicit_cancer_seed_and_flagged() -> None:
|
||||
assert result["book_example_exception"] == "rath_p66_indira_ak"
|
||||
assert result["book_example"]["book_internal_contradiction"] is True
|
||||
assert [row["sign"] for row in result["mahadasha"]] == [row[0] for row in book["rows"]]
|
||||
assert [row["years"] for row in result["mahadasha"]] == [row[1] for row in book["rows"]]
|
||||
# 原值:显式巨蟹起运后年数等于书印值(水瓶 2)
|
||||
# 新值:水瓶罗睺 1(基本 2,落陷 −1),其余 11 段不变
|
||||
# 原因:BUG-1229,罗计按 Phalita 旺陷加减;顺序仍是 12/12
|
||||
assert [row["years"] for row in result["mahadasha"]] == NEW_EXPLICIT_YEARS["Table 18"]
|
||||
# The general rule's own verdict travels with it, unchanged.
|
||||
assert (result["general_rule_selection"]["selected_sign"], result["general_rule_selection"]["strength_level_decided"]) == ("Capricorn", 5)
|
||||
with pytest.raises(ValueError):
|
||||
@@ -168,20 +178,25 @@ VERDICT_TABLES = {
|
||||
# software used Rule 8 at the seventh level — a software special case; keep
|
||||
# the general result, flagged `book_software_rule8_exception` (BUG-1226).
|
||||
EXPECTED_SIGN_MISSES = {("Table 25", "Gemini", "Sagittarius"), ("Table 25", "Virgo", "Pisces")}
|
||||
# Dual lords (Aquarius, Scorpio): book Table 23 Aquarius = Saturn (ours Rahu),
|
||||
# Table 25 Scorpio = Ketu (ours Mars). Round-6 ruling Q6a (p.43 (d): compare
|
||||
# the signs the two lords occupy) was tried in batch 5 (BUG-1227) and NOT
|
||||
# landed: it gives 9/10 here (new miss Table 23 Scorpio) but drops the Table 17
|
||||
# years from 12/12 to 10/12 (Scorpio 10 -> 1 by Ketu, Aquarius 8 -> 1 by
|
||||
# Saturn) — the book's standard nativity (Table 17 / Chart 3, and the p.43 (e)
|
||||
# example) conflicts with its own Table 23 verdict. Kept at 8/10 (red line).
|
||||
EXPECTED_CO_LORD_MISSES = {("Table 23", "Aquarius"), ("Table 25", "Scorpio")}
|
||||
# Dual lords after round-7 option A (BUG-1227 resolved). 9/10: the one miss is
|
||||
# Table 23 Scorpio (book Mars, p.43 (d) level 2 takes Ketu because Sagittarius
|
||||
# has two Jupiter roles). Table 17 years are no longer a pass line:
|
||||
# Scorpio book 10 -> Ketu 2 (base 1, exalted +1); Aquarius book 8 -> Saturn 1.
|
||||
# Chart 3 prints Aquarius 9 (Rahu 8+1). Table 18 Aquarius book 2 -> Rahu 1
|
||||
# (base 2, debilitated -1).
|
||||
EXPECTED_CO_LORD_MISSES = {("Table 23", "Scorpio")}
|
||||
# Explicit-seed years that no longer match the printed book rows. Order is unchanged.
|
||||
NEW_EXPLICIT_YEARS = {
|
||||
"Table 17": [12, 5, 2, 3, 5, 1, 1, 12, 2, 2, 9, 1],
|
||||
"Table 18": [6, 9, 10, 2, 9, 5, 6, 1, 10, 4, 7, 5],
|
||||
}
|
||||
|
||||
|
||||
def test_book_software_verdicts_28_of_30_and_co_lords_8_of_10() -> None:
|
||||
from scripts.narayana_rath import _lord, compare_signs
|
||||
def test_book_software_verdicts_28_of_30_and_co_lords_9_of_10() -> None:
|
||||
from scripts.narayana_rath import compare_signs, resolve_dual_lord
|
||||
|
||||
sign_misses, co_lord_misses, pairs = set(), set(), 0
|
||||
decisions = []
|
||||
for table, (birth, co_lords, stronger) in VERDICT_TABLES.items():
|
||||
chart, _asc, *_ = compute_chart_data(*birth, ayanamsa_name="lahiri")
|
||||
lons = {name: row["degree"] for name, row in chart["planets"].items()}
|
||||
@@ -195,11 +210,17 @@ def test_book_software_verdicts_28_of_30_and_co_lords_8_of_10() -> None:
|
||||
assert result["strength_level_decided"] == 5
|
||||
sign_misses.add((table, SIGNS[winner], SIGNS[second if winner == first else first]))
|
||||
for sign_idx, want in zip((10, 7), co_lords):
|
||||
if _lord(sign_idx, lons) != want:
|
||||
decision = resolve_dual_lord(sign_idx, lons)
|
||||
decisions.append((table, SIGNS[sign_idx], want, decision))
|
||||
if decision.get("lord") != want:
|
||||
co_lord_misses.add((table, SIGNS[sign_idx]))
|
||||
assert pairs == 30
|
||||
assert sign_misses == EXPECTED_SIGN_MISSES # 28/30
|
||||
assert co_lord_misses == EXPECTED_CO_LORD_MISSES # 8/10
|
||||
assert sign_misses == EXPECTED_SIGN_MISSES # 28/30, same two Wadiyar pairs
|
||||
assert co_lord_misses == EXPECTED_CO_LORD_MISSES # 9/10
|
||||
assert len(decisions) == 10
|
||||
assert all(row[3]["rule"] == "d" and row[3]["status"] == "selected" for row in decisions)
|
||||
scorpio = next(row[3] for row in decisions if row[0] == "Table 23" and row[1] == "Scorpio")
|
||||
assert (scorpio["lord"], scorpio["strength_level_decided"]) == ("Ketu", 2)
|
||||
|
||||
|
||||
# Table 25 (Wadiyar) sub-periods for the first three MDs, from upstream
|
||||
|
||||
@@ -183,8 +183,12 @@ def test_ported_upstream_varsha_saturn_seed_order_and_lengths() -> None:
|
||||
first = seq["mahadasha"][:5]
|
||||
assert seq["direction"] == "table11_saturn_zodiacal"
|
||||
assert [row["sign_idx"] for row in first] == [7, 8, 9, 10, 11]
|
||||
assert [row["years"] for row in first] == [7, 4, 2, 3, 11]
|
||||
# 原值:第一段 7 年(计都在双子,年数不减落陷)
|
||||
# 新值:第一段 6 年(基本 7,计都在双子落陷 −1);主星仍是计都,第 1 级
|
||||
# 原因:BUG-1229,第七轮 p.40 (a),罗计按 Phalita 旺陷加减
|
||||
assert [row["years"] for row in first] == [6, 4, 2, 3, 11]
|
||||
assert first[0]["lord"] == "Ketu"
|
||||
assert (first[0]["base_years"], first[0]["dignity_adjustment"], first[0]["adjustment_source"]) == (7, -1, "p40a_phalita_nodes")
|
||||
|
||||
|
||||
def test_output_fields() -> None:
|
||||
@@ -295,3 +299,75 @@ def test_book_divergence_lists_the_two_other_statements() -> None:
|
||||
p47 = direction["book_divergence"][0]
|
||||
assert p47["direction"] == "reverse" # Taurus is an even sign
|
||||
assert p47["differs"] == (p47["direction"] != direction["direction"])
|
||||
|
||||
|
||||
# --- BUG-1227 / BUG-1228 / BUG-1229: round-7 ruling (p.43 option A, only Ketu
|
||||
# reversed, node exaltation ±1). Synthetic longitudes, no birth data.
|
||||
|
||||
|
||||
def _nine(**placed: float) -> dict:
|
||||
chart = {"Sun": 5.0, "Moon": 6.0, "Mars": 7.0, "Mercury": 8.0, "Jupiter": 9.0,
|
||||
"Venus": 11.0, "Saturn": 12.0, "Rahu": 13.0, "Ketu": 193.0}
|
||||
chart.update(placed)
|
||||
return chart
|
||||
|
||||
|
||||
def test_mars_in_aries_and_saturn_in_capricorn_do_not_trigger_rule_c() -> None:
|
||||
"""附问:火星在白羊、土星在摩羯不是 p.43 (c) 的「在本宫」。若误触发,会改取外面那颗。"""
|
||||
from scripts.narayana_rath import resolve_dual_lord
|
||||
|
||||
scorpio = resolve_dual_lord(SIGNS.index("Scorpio"), _nine(
|
||||
Mars=10.0, Sun=11.0, Moon=12.0, Ketu=65.0, Rahu=245.0))
|
||||
assert (scorpio["rule"], scorpio["lord"]) == ("d", "Mars")
|
||||
aquarius = resolve_dual_lord(SIGNS.index("Aquarius"), _nine(
|
||||
Saturn=280.0, Sun=281.0, Moon=282.0, Rahu=70.0, Ketu=250.0))
|
||||
assert (aquarius["rule"], aquarius["lord"]) == ("d", "Saturn")
|
||||
|
||||
|
||||
def test_same_sign_degree_reverses_only_ketu_and_ties_stay_undecided() -> None:
|
||||
from scripts.narayana_rath import _strength_degree, resolve_dual_lord
|
||||
|
||||
assert _strength_degree("Ketu", 0.0) == 30.0
|
||||
assert _strength_degree("Ketu", 27.0) == 3.0
|
||||
assert _strength_degree("Rahu", 27.0) == 27.0
|
||||
assert _strength_degree("Saturn", 14.0) == 14.0
|
||||
rahu = resolve_dual_lord(SIGNS.index("Aquarius"), _nine(Saturn=44.0, Rahu=57.0, Ketu=237.0))
|
||||
assert (rahu["status"], rahu["rule"], rahu["identity_rule"], rahu["lord"]) == ("selected", "b", "p116_longitude", "Rahu")
|
||||
mars = resolve_dual_lord(SIGNS.index("Scorpio"), _nine(Mars=134.0, Ketu=147.0, Rahu=327.0))
|
||||
assert (mars["status"], mars["rule"], mars["lord"]) == ("selected", "b", "Mars")
|
||||
ketu_zero = resolve_dual_lord(SIGNS.index("Scorpio"), _nine(Mars=29.0, Ketu=0.0, Rahu=180.0))
|
||||
assert (ketu_zero["rule"], ketu_zero["lord"]) == ("b", "Ketu")
|
||||
tied = resolve_dual_lord(SIGNS.index("Scorpio"), _nine(Mars=10.0, Ketu=20.0, Rahu=200.0))
|
||||
assert (tied["status"], tied["lord"], tied["reason"]) == ("undecided", None, "equal_degree")
|
||||
|
||||
|
||||
def test_rahu_degree_at_level_five_is_not_reversed() -> None:
|
||||
from scripts.narayana_rath import sign_strength
|
||||
|
||||
row = sign_strength(SIGNS.index("Aquarius"), _nine(Saturn=44.0, Rahu=57.0, Ketu=237.0))
|
||||
assert row["lord"] == "Rahu"
|
||||
assert row["lord_degree_in_sign"] == 27.0
|
||||
|
||||
|
||||
def test_node_dignity_adjusts_the_period_and_clamps() -> None:
|
||||
from scripts.narayana_rath import rath_period_years
|
||||
|
||||
ketu = rath_period_years(SIGNS.index("Scorpio"), "Ketu", SIGNS.index("Sagittarius"))
|
||||
assert (ketu["base_years"], ketu["dignity_adjustment"], ketu["years"], ketu["adjustment_source"]) == (1, 1, 2, "p40a_phalita_nodes")
|
||||
rahu = rath_period_years(SIGNS.index("Aquarius"), "Rahu", SIGNS.index("Gemini"))
|
||||
assert (rahu["base_years"], rahu["dignity_adjustment"], rahu["years"]) == (8, 1, 9)
|
||||
mars = rath_period_years(SIGNS.index("Aries"), "Mars", SIGNS.index("Capricorn"))
|
||||
assert mars["adjustment_source"] == "p40a_grahas" and mars["dignity_adjustment"] == 1
|
||||
|
||||
|
||||
def test_dual_lord_cycle_stays_undecided() -> None:
|
||||
"""Scorpio's lords sit in Aquarius and Taurus (tied). Aquarius's lords then
|
||||
sit in Leo and Scorpio (tied), which asks for Scorpio again."""
|
||||
from scripts.narayana_rath import resolve_dual_lord
|
||||
|
||||
decision = resolve_dual_lord(SIGNS.index("Scorpio"), _nine(
|
||||
Mars=305.0, Ketu=35.0, Rahu=215.0, Saturn=125.0,
|
||||
Sun=5.0, Moon=6.0, Mercury=7.0, Jupiter=8.0, Venus=9.0))
|
||||
assert decision["status"] == "undecided"
|
||||
assert decision["lord"] is None
|
||||
assert decision["rule"] == "d"
|
||||
|
||||
Reference in New Issue
Block a user