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>
374 lines
20 KiB
Python
374 lines
20 KiB
Python
"""Sanjay Rath Narayana rules from the astrologer's round-5 ruling (BUG-1218).
|
|
|
|
Constructed charts (synthetic longitudes, no birth data) exercise each of the
|
|
seven start-sign levels, the Indira book-example path, the Saturn / Ketu
|
|
exceptions and the AD start rule. Two cases are ported from upstream
|
|
yinduzhanxing c7117f92: `tests/test_rath_role_strength.py::test_p36_two_roles_for_jupiter_and_one_opposite`
|
|
and the Saturn-seed case of `tests/test_varsha_narayana_native.py`
|
|
(`test_published_muntha_seed_saturn_exception_and_first_five_lengths`, MD part).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import random
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
for entry in (str(ROOT), str(ROOT / "scripts")):
|
|
if entry not in sys.path:
|
|
sys.path.insert(0, entry)
|
|
|
|
from scripts import narayana_dasha as nd # noqa: E402
|
|
from scripts.narayana_rath import ( # noqa: E402
|
|
GRAHAS, INDIRA_EXCEPTION, KETU_FIRST_NOTE, SIGN_LORDS, SIGNS, _role_candidate, antardasha,
|
|
calc_narayana_rath, compare_signs, md_order, mahadasha_sequence,
|
|
)
|
|
|
|
UPSTREAM = Path("/workspace/yinduzhanxing")
|
|
UPSTREAM_SHA = "c7117f92"
|
|
|
|
# (sign A, sign B, longitudes, expected winner, expected level). A/B are Lagna
|
|
# and 7th except level 4: opposite signs always share modality, so level 4 is
|
|
# shown on Aries vs Gemini (the comparator is generic).
|
|
LEVEL_CASES = {
|
|
1: (0, 6, {"Sun": 116.58, "Moon": 54.31, "Mars": 234.34, "Mercury": 26.08, "Jupiter": 192.92,
|
|
"Venus": 131.65, "Saturn": 20.88, "Rahu": 182.68, "Ketu": 2.68}, "Aries"),
|
|
2: (1, 7, {"Sun": 341.18, "Moon": 207.76, "Mars": 142.8, "Mercury": 351.45, "Jupiter": 16.77,
|
|
"Venus": 309.05, "Saturn": 104.26, "Rahu": 51.93, "Ketu": 231.93}, "Scorpio"),
|
|
3: (0, 6, {"Sun": 222.88, "Moon": 43.32, "Mars": 22.23, "Mercury": 245.64, "Jupiter": 191.06,
|
|
"Venus": 173.7, "Saturn": 279.54, "Rahu": 317.96, "Ketu": 137.96}, "Aries"),
|
|
4: (0, 2, {"Sun": 95.0, "Moon": 125.0, "Mars": 35.0, "Mercury": 45.0, "Jupiter": 55.0,
|
|
"Venus": 155.0, "Saturn": 275.0, "Rahu": 305.0, "Ketu": 125.0}, "Gemini"),
|
|
5: (9, 3, {"Sun": 30.94, "Moon": 150.54, "Mars": 86.64, "Mercury": 198.38, "Jupiter": 21.28,
|
|
"Venus": 203.56, "Saturn": 341.08, "Rahu": 227.03, "Ketu": 47.03}, "Capricorn"),
|
|
6: (3, 9, {"Sun": 136.2, "Moon": 178.62, "Mars": 80.38, "Mercury": 202.16, "Jupiter": 260.66,
|
|
"Venus": 355.0, "Saturn": 268.62, "Rahu": 0.8, "Ketu": 180.8}, "Capricorn"),
|
|
# 原值:Sun 195.37(天秤)、Moon 223.57(天蝎)
|
|
# 新值:Sun 223.57、Moon 195.37(日月互换,天秤里换成不落陷的月亮)
|
|
# 原因:BUG-1224(第六轮问 2,p.67)第 3 级把落陷算作弱于无尊贵;太阳落陷天秤会让本例在第 3 级分出,
|
|
# 不再演示第 7 级。互换后前六级仍全平,第 7 级照旧判白羊。
|
|
7: (6, 0, {"Sun": 223.57, "Moon": 195.37, "Mars": 268.44, "Mercury": 0.02, "Jupiter": 349.15,
|
|
"Venus": 328.44, "Saturn": 266.23, "Rahu": 223.1, "Ketu": 43.1}, "Aries"),
|
|
}
|
|
|
|
# Aries vs Libra tie on all seven levels: two occupants each (Venus + Moon in
|
|
# Aries, Mars + Sun in Libra), no role contact, no dignity, lords Mars / Venus
|
|
# both at 10 deg in each other's sign, six years each. Moon 29.5 deg is the AK.
|
|
TIE = {"Sun": 185.0, "Moon": 29.5, "Mars": 190.0, "Mercury": 335.0, "Jupiter": 65.0,
|
|
"Venus": 10.0, "Saturn": 275.0, "Rahu": 85.0, "Ketu": 265.0}
|
|
|
|
|
|
@pytest.mark.parametrize("level", sorted(LEVEL_CASES))
|
|
def test_each_level_decides_and_earlier_levels_tie(level: int) -> None:
|
|
first, second, lons, winner = LEVEL_CASES[level]
|
|
result = compare_signs(first, second, lons)
|
|
assert result["status"] == "selected"
|
|
assert (result["selected_sign"], result["strength_level_decided"]) == (winner, level)
|
|
a, b = (result["candidates"][SIGNS[idx]]["level_values"] for idx in (first, second))
|
|
assert a[:level - 1] == b[:level - 1]
|
|
assert a[level - 1] != b[level - 1]
|
|
|
|
|
|
def test_level_two_counts_roles_not_planets() -> None:
|
|
"""Pisces with Jupiter inside: Jupiter as Jupiter and as lord = two roles."""
|
|
lons = {"Sun": 0.0, "Moon": 30.0, "Mars": 165.0, "Mercury": 120.0, "Jupiter": 345.0,
|
|
"Venus": 60.0, "Saturn": 90.0, "Rahu": 180.0, "Ketu": 240.0}
|
|
result = compare_signs(11, 5, lons)
|
|
assert (result["selected_sign"], result["strength_level_decided"]) == ("Pisces", 2)
|
|
assert [row["role"] for row in result["candidates"]["Pisces"]["role_factors"]] == ["jupiter", "sign_lord"]
|
|
|
|
|
|
def test_ported_upstream_p36_two_roles_for_jupiter_and_one_opposite() -> None:
|
|
# Ported from upstream c7117f92 tests/test_rath_role_strength.py.
|
|
positions = dict(Jupiter=345., Mars=165., Mercury=120., Sun=0., Moon=30.,
|
|
Venus=60., Saturn=90., Rahu=180., Ketu=240.)
|
|
r = calc_narayana_rath(11, positions)
|
|
assert r["seed_sign"] == "Pisces"
|
|
candidates = r["seed_selection"]["candidates"]
|
|
assert candidates["Pisces"]["aspect_factor_count"] == 2
|
|
assert candidates["Virgo"]["aspect_factor_count"] == 1
|
|
assert candidates["Pisces"]["role_factors"] == [
|
|
dict(role="jupiter", planet="Jupiter", contact="conjunction"),
|
|
dict(role="sign_lord", planet="Jupiter", contact="conjunction")]
|
|
# The legacy aspect-only rule (frozen narayana_dasha) picks Virgo here.
|
|
assert nd.select_narayana_seed_sign(11, positions)["selected_sign_idx"] == 5
|
|
|
|
|
|
def _upstream_narayana():
|
|
if not (UPSTREAM / ".git").exists():
|
|
pytest.skip("upstream checkout not on this machine")
|
|
source = subprocess.run(["git", "show", f"{UPSTREAM_SHA}:scripts/narayana_dasha.py"], cwd=UPSTREAM,
|
|
capture_output=True, text=True, check=False)
|
|
if source.returncode != 0:
|
|
pytest.skip("upstream commit not available")
|
|
spec = importlib.util.spec_from_loader("upstream_narayana_c7117f92", loader=None)
|
|
module = importlib.util.module_from_spec(spec)
|
|
exec(compile(source.stdout, "upstream_narayana_dasha.py", "exec"), module.__dict__)
|
|
return module
|
|
|
|
|
|
def test_levels_one_two_equal_upstream_role_profile() -> None:
|
|
"""Levels 1-2 are upstream's rath_p36_role_conjunction_v1 counting."""
|
|
upstream = _upstream_narayana()
|
|
rng = random.Random(1003)
|
|
for _ in range(400):
|
|
lons = {planet: rng.uniform(0, 360) for planet in GRAHAS}
|
|
lagna = rng.randrange(12)
|
|
theirs = upstream.select_narayana_seed_sign(lagna, lons, strength_profile="rath_p36_role_conjunction_v1")
|
|
for idx in (lagna, (lagna + 6) % 12):
|
|
ours = _role_candidate(idx, lons, SIGN_LORDS[SIGNS[idx]])
|
|
for key in ("occupants", "occupant_count", "aspect_factors", "aspect_factor_count", "role_factors"):
|
|
assert ours[key] == theirs["candidates"][idx][key]
|
|
|
|
|
|
def test_full_tie_uses_the_indira_example_only_when_allowed() -> None:
|
|
plain = compare_signs(0, 6, TIE)
|
|
assert plain["status"] == "tie" and plain["strength_level_decided"] is None
|
|
flagged = compare_signs(0, 6, TIE, allow_book_example=True)
|
|
assert (flagged["selected_sign"], flagged["book_example_exception"], flagged["atmakaraka"]) == ("Aries", INDIRA_EXCEPTION, "Moon")
|
|
result = calc_narayana_rath(0, TIE, allow_book_example=True)
|
|
assert result["status"] == "book_example_exception"
|
|
assert result["book_example_exception"] == INDIRA_EXCEPTION
|
|
tied = calc_narayana_rath(0, TIE)
|
|
assert tied["status"] == "parameter_sensitive" and tied["mahadasha"] is None
|
|
assert set(tied["alternatives"]) == {"Aries", "Libra"}
|
|
|
|
|
|
def test_book_example_never_overrides_a_general_level() -> None:
|
|
first, second, lons, winner = LEVEL_CASES[5]
|
|
assert compare_signs(first, second, lons, allow_book_example=True)["book_example_exception"] is None
|
|
|
|
|
|
def _with(lons: dict, **moves: float) -> dict:
|
|
return {**lons, **moves}
|
|
|
|
|
|
BASE = {"Sun": 95.0, "Moon": 125.0, "Mars": 35.0, "Mercury": 45.0, "Jupiter": 55.0,
|
|
"Venus": 155.0, "Saturn": 275.0, "Rahu": 305.0, "Ketu": 125.0}
|
|
|
|
|
|
def test_ketu_reverses_table10_keeping_the_steps() -> None:
|
|
lons = _with(BASE, Rahu=245.0, Ketu=65.0) # Ketu in Gemini (dual: steps of 4/5/... differ from a read-back)
|
|
table10 = nd.build_narayana_sign_sequence(2, profile="rath_general_table10_v1")
|
|
order = md_order(2, lons)
|
|
assert order["direction"] == "table12_ketu_reversed" and order["exception_applied"] == "ketu_in_start_sign"
|
|
assert order["order"] == [2, 6, 10, 11, 3, 7, 8, 0, 4, 5, 9, 1]
|
|
assert [(b - a) % 12 for a, b in zip(order["order"], order["order"][1:])] == \
|
|
[(a - b) % 12 for a, b in zip(table10, table10[1:])]
|
|
assert order["order"] != [2] + table10[:0:-1] # not the table read backwards
|
|
|
|
|
|
def test_saturn_runs_zodiacal_and_ketu_wins_when_both() -> None:
|
|
saturn = md_order(1, _with(BASE, Saturn=40.0))
|
|
assert saturn["direction"] == "table11_saturn_zodiacal"
|
|
assert saturn["order"] == [(1 + i) % 12 for i in range(12)]
|
|
both = md_order(1, _with(BASE, Saturn=40.0, Rahu=215.0, Ketu=35.0))
|
|
assert both["direction"] == "table12_ketu_reversed"
|
|
assert both["method_note"] == KETU_FIRST_NOTE == "PyJHora 兼容:计都优先"
|
|
|
|
|
|
def test_ported_upstream_varsha_saturn_seed_order_and_lengths() -> None:
|
|
# Ported from upstream c7117f92 tests/test_varsha_narayana_native.py: PVR
|
|
# textbook Chart 67 D9 positions; seed Scorpio holds Saturn -> Table 11;
|
|
# first five MD [7, 4, 2, 3, 11] with Ketu as Scorpio's lord.
|
|
rasi = dict(Sun=47+4/60, Moon=184+48/60, Mars=113+53/60, Mercury=64+47/60, Jupiter=160+59/60,
|
|
Venus=1+37/60, Saturn=306+29/60, Rahu=228+38/60, Ketu=48+38/60)
|
|
d9 = {planet: lon * 9 % 360 for planet, lon in rasi.items()}
|
|
seq = mahadasha_sequence(7, d9, cycle_count=1)
|
|
first = seq["mahadasha"][:5]
|
|
assert seq["direction"] == "table11_saturn_zodiacal"
|
|
assert [row["sign_idx"] for row in first] == [7, 8, 9, 10, 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:
|
|
first, second, lons, _ = LEVEL_CASES[1]
|
|
result = calc_narayana_rath(first, lons)
|
|
for key in ("profile", "seed_sign", "seed_basis", "strength_level_decided", "direction", "exception_applied",
|
|
"dual_lord_rule", "mahadasha", "antardasha_start_rule", "source", "status"):
|
|
assert key in result, key
|
|
assert result["profile"] == "sanjay_rath"
|
|
assert result["seed_basis"] == "stronger_of_lagna_seventh:level_1_occupant_count"
|
|
assert len(result["mahadasha"]) == 24
|
|
ad = antardasha(result["mahadasha"][0], lons)
|
|
assert ad["status"] == "selected" and len(ad["rows"]) == 12
|
|
assert ad["antardasha_start_rule"]["strength_level_decided"] in range(1, 8)
|
|
|
|
|
|
# --- BUG-1224: Rahu / Ketu dignity inside Rath Narayana (round-6 ruling Q2,
|
|
# Rath Table 9 p.41-42; Phalita exaltation set) and debilitation at level 3 (p.67).
|
|
|
|
@pytest.mark.parametrize("planet,sign,rank", [
|
|
("Rahu", "Gemini", 3), ("Rahu", "Virgo", 2), ("Rahu", "Aquarius", 1), ("Rahu", "Sagittarius", -1),
|
|
("Rahu", "Taurus", 0), # Taurus / Scorpio exaltation is the Ayur-dasa set, not Phalita
|
|
("Ketu", "Sagittarius", 3), ("Ketu", "Pisces", 2), ("Ketu", "Scorpio", 1), ("Ketu", "Gemini", -1),
|
|
("Ketu", "Scorpio", 1), ("Ketu", "Taurus", 0),
|
|
("Mars", "Capricorn", 3), ("Mars", "Aries", 2), ("Mars", "Scorpio", 1), ("Mars", "Cancer", -1), ("Mars", "Leo", 0),
|
|
])
|
|
def test_node_and_debilitation_ranks(planet: str, sign: str, rank: int) -> None:
|
|
from scripts.narayana_rath import _occupant_dignity
|
|
|
|
assert _occupant_dignity(planet, SIGNS.index(sign)) == rank
|
|
|
|
|
|
def test_p67_debilitated_ketu_is_weaker_than_mars_by_rule_3() -> None:
|
|
"""Rath p.67 (Indira Gandhi, Table 18): "Scorpio: Ketu is debilitated and as
|
|
per Rule (3) of first source of strength is weaker than Mars" — Mars alone
|
|
in Leo, Ketu alone in Gemini. Before BUG-1224 Gemini won at level 4 (dual > fixed)."""
|
|
from tests.test_narayana_rath_book_tables import _chart
|
|
|
|
_asc, lons = _chart("Table 18")
|
|
result = compare_signs(SIGNS.index("Leo"), SIGNS.index("Gemini"), lons)
|
|
assert (result["selected_sign"], result["strength_level_decided"]) == ("Leo", 3)
|
|
assert result["candidates"]["Gemini"]["occupant_dignity"]["ranks"] == {"Ketu": -1}
|
|
assert result["candidates"]["Leo"]["occupant_dignity"]["ranks"] == {"Mars": 0}
|
|
|
|
|
|
def test_constructed_debilitated_ketu_loses_to_plain_mars_at_level_3() -> None:
|
|
lons = {"Sun": 95.0, "Moon": 185.0, "Mars": 135.0, "Mercury": 105.0, "Jupiter": 100.0,
|
|
"Venus": 155.0, "Saturn": 275.0, "Rahu": 245.0, "Ketu": 65.0} # Mars Leo, Ketu Gemini
|
|
result = compare_signs(SIGNS.index("Leo"), SIGNS.index("Gemini"), lons)
|
|
a, b = (result["candidates"][name]["level_values"] for name in ("Leo", "Gemini"))
|
|
assert a[:2] == b[:2]
|
|
assert (result["selected_sign"], result["strength_level_decided"]) == ("Leo", 3)
|
|
|
|
|
|
def test_node_dignity_stays_inside_rath() -> None:
|
|
"""The natal dignity table is untouched (BUG-1211: nodes have no own sign on the D1 path)."""
|
|
import inspect
|
|
import scripts.narayana_rath as rath
|
|
|
|
users = sorted(path.name for path in (ROOT / "scripts").glob("*.py")
|
|
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"])
|
|
|
|
|
|
# --- 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"
|