feat(narayana-rath): Rahu/Ketu dignity and debilitation at level 3 (BUG-1224)

Round-6 ruling Q2: node dignity by Rath Table 9 p.41-42 (own Aq/Sc, MT Vi/Pi,
Phalita exaltation Ge/Sg), debilitated ranks below none (p.67: debilitated
Ketu weaker than Mars by Rule 3). Rath module only; natal table unchanged.
Book acceptance unchanged (15/17 general, 18 explicit 12/12, 28/30, 24/36).
Goldens recaptured (only rath.source text changes).

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:33:55 +08:00
co-authored by Claude Opus 5.5
parent 7dda1b45c3
commit 61bd4425cd
4 changed files with 99 additions and 13 deletions
+43 -10
View File
@@ -14,8 +14,10 @@ p.63]:
role counted once when the planet is in the sign or casts Rasi Drishti on
it [p.48; no graha drishti]; roles are not de-duplicated by planet
(Jupiter as Jupiter and as lord counts twice) [p.36];
3. occupant dignity: exalted > moolatrikona > own sign (whole sign) — best
rank, then the number of dignified occupants;
3. occupant dignity: exalted > moolatrikona > own sign (whole sign) > none >
debilitated — best rank, then the number of dignified occupants; Rahu /
Ketu by Rath Table 9 (p.41-42, Phalita set; round-6 ruling Q2,
BUG-1224), debilitation below none per p.67;
4. dual > fixed > movable;
5. the lord's degree inside its sign, Chara Karaka sense (higher is
stronger; Rahu / Ketu as 30 - degree);
@@ -98,7 +100,8 @@ SOURCE = {
"md_order": "p.51 Table 10; Saturn p.51-53 Table 11; Ketu p.51, p.53 Table 12",
"ad": "p.48 Arambha Rasi, p.50 note 2; Tables 13 / 11 / 14 (p.54)",
"indira_example": "p.65-66 (AK), Table 18 p.67",
"ruling": "astrologer round 5, 2026-10-03, Q1-Q4; product owner: Ketu first",
"ruling": "astrologer round 5, 2026-10-03, Q1-Q4; product owner: Ketu first; round 6, 2026-10-04, Q2 (node dignity)",
"node_dignity": "Table 9 p.41-42 (Phalita exaltation Rahu Gemini / Ketu Sagittarius); debilitated weaker p.67",
"port": "upstream yinduzhanxing c7117f92 scripts/narayana_dasha.py",
}
SIGNS = nd.SIGNS
@@ -163,16 +166,44 @@ def _role_candidate(sign_idx: int, planet_lons: Dict[str, float], lord: str) ->
}
# Rahu / Ketu dignity inside Rath Narayana only (round-6 ruling Q2, Rath
# Table 9 p.41-42): own sign Rahu Aquarius / Ketu Scorpio; moolatrikona Rahu
# Virgo / Ketu Pisces; exaltation for Phalita dasas such as Narayana Rahu
# Gemini / Ketu Sagittarius (the Taurus / Scorpio pair is for Ayur dasas
# only), debilitation in the opposite signs. Not the natal dignity table
# (BUG-1211 "nodes have no own sign" still holds on the D1 main path).
RATH_NODE_DIGNITY = {
"Rahu": {"exalted": {2}, "moolatrikona": 5, "own": {10}, "debilitated": {8}},
"Ketu": {"exalted": {8}, "moolatrikona": 11, "own": {7}, "debilitated": {2}},
}
DEBILITATED_RANK = -1
def _occupant_dignity(planet: str, sign_idx: int) -> int:
table = nd._NARAYANA_DIGNITY.get(planet)
if table is None: # Rahu / Ketu: no ruling on node dignity; not counted.
return 0
if sign_idx in table["exalted"]:
"""Level 3 rank: exalted 3 > moolatrikona 2 > own 1 > none 0 > debilitated -1.
Debilitated below none: p.67 (Indira, Table 18) "Ketu is debilitated and
as per Rule (3) of first source of strength is weaker than Mars" (Mars in
Leo, no dignity). Applied to all nine bodies (Rule 3 is general).
"""
node = RATH_NODE_DIGNITY.get(planet)
if node is not None:
exalted, moolatrikona, own, debilitated = node["exalted"], node["moolatrikona"], node["own"], node["debilitated"]
else:
table = nd._NARAYANA_DIGNITY.get(planet)
if table is None:
return 0
exalted, debilitated = table["exalted"], table["debilitated"]
moolatrikona = _RATH_CLASSICAL_MOOLATRIKONA_SIGNS[planet]
own = {idx for idx, sign in enumerate(SIGNS) if SIGN_LORDS[sign] == planet}
if sign_idx in exalted:
return 3
if sign_idx == _RATH_CLASSICAL_MOOLATRIKONA_SIGNS[planet]:
if sign_idx == moolatrikona:
return 2
if SIGN_LORDS[SIGNS[sign_idx]] == planet:
if sign_idx in own:
return 1
if sign_idx in debilitated:
return DEBILITATED_RANK
return 0
@@ -189,12 +220,14 @@ def sign_strength(sign_idx: int, planet_lons: Dict[str, float]) -> Dict[str, Any
ranks = {planet: _occupant_dignity(planet, sign_idx) for planet in row["occupants"]}
best = max(ranks.values(), default=0)
dignified = sum(1 for value in ranks.values() if value > 0)
debilitated = sum(1 for value in ranks.values() if value == DEBILITATED_RANK)
lord_sign_idx = nd._get_planet_sign(lord, lons)
if lord_sign_idx is None:
lord_sign_idx = sign_idx
years, _ = nd.calculate_narayana_period_years(sign_idx, lord, lord_sign_idx, profile=DURATION_PROFILE)
row.update({
"occupant_dignity": {"ranks": ranks, "best_rank": best, "dignified_count": dignified},
"occupant_dignity": {"ranks": ranks, "best_rank": best, "dignified_count": dignified,
"debilitated_count": debilitated},
"modality": "dual" if sign_idx in (2, 5, 8, 11) else "fixed" if sign_idx in nd._FIXED_SIGNS else "movable",
"lord_sign": SIGNS[lord_sign_idx],
"lord_degree_in_sign": round(_ck_degree(lord, lons[lord]), 6) if lord in lons else None,