feat(narayana): Rath start-sign seven-level rule, Ketu/Saturn exceptions, AD start rule; Table 18 not reproduced, not wired (BUG-1218)
Astrologer round-5 Q1-Q4 in scripts/narayana_rath.py (frozen narayana_dasha.py untouched). Levels 1-2 are upstream c7117f92's rath_p36_role_conjunction_v1 counting (test compares with the upstream function); Ketu reversal is the upstream rath_ketu_table12_v1 branch; Ketu first when Saturn shares the seed. Book tables: 15 Cancer (level 1) and 17 Pisces (level 2) reproduce; 18 gives Capricorn at level 5, so the Indira AK example (full tie only) is never reached -> red line 3: no T4 wiring, no T5 trial. 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
1c39988b3c
commit
c41555f69e
+301
-64
@@ -1,77 +1,270 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Narayana Dasha, Sanjay Rath profile (`sanjay_rath`) — research only (BUG-1215).
|
||||
"""Narayana Dasha, Sanjay Rath profile (`sanjay_rath`) — research only (BUG-1215 / BUG-1218).
|
||||
|
||||
Astrologer round-4 ruling 2026-10-03, Q4: the second time axis should be Sanjay
|
||||
Rath's Narayana Dasha (start from the stronger of Lagna and 7th, order by the
|
||||
start sign, dual lords by the traditional profile). The current product axis
|
||||
(`narayana_dasha.calc_narayana_mahadasha`) always starts from the Lagna and runs
|
||||
zodiacally; it is labelled `legacy_reference_only` on user-visible surfaces.
|
||||
Astrologer round-4 ruling (Q4): the second time axis should be Sanjay Rath's
|
||||
Narayana Dasha. Round-5 ruling 2026-10-03 fixed the open points (book pages
|
||||
in brackets; Sanjay Rath, *Narayana Dasa*):
|
||||
|
||||
This module builds the Rath sequence from the rule ledger ported from upstream
|
||||
yinduzhanxing c7117f92 (`references/oracle/sanjay_rath_narayana_book_ledger_2026_08_28.json`,
|
||||
`docs/research/sanjay_rath_narayana_book_ledger_2026_08_28.md`):
|
||||
Start sign = the stronger of Lagna and 7th [p.48]. General rule, seven levels,
|
||||
stop at the first level that separates the two signs, no weighting [p.35-36,
|
||||
p.63]:
|
||||
|
||||
- start sign: stronger of Lagna / 7th (`rath_md_start_stronger_lagna_seventh`),
|
||||
decided by the existing `narayana_dasha.select_narayana_seed_sign`; a tie
|
||||
(`later_narayana_sign_strength_rules_required`) gives
|
||||
`status: parameter_sensitive` and both sequences;
|
||||
- MD order: Table 10 by the start sign (`rath_md_general_order_table10`); Saturn
|
||||
in the start sign -> Table 11, zodiacal (`rath_saturn_start_exception`); Ketu
|
||||
in the start sign (no Saturn) -> Table 10 direction reversed (the reversal
|
||||
rule, `rath_ketu_reversal_exception`; the ledger tables only the AD form);
|
||||
- years: `jaimini_odd_footed_dignity_v2` (Upadesa 1.1.25-27 odd/even-footed
|
||||
count with the fixed-sign exception, exalted +1 / debilitated -1, ledger
|
||||
`upadesa_1125_1126_odd_even_count`, `upadesa_1127_fixed_sign_exception`,
|
||||
`upadesa_bphs_exalt_debil_adjustment`); Scorpio / Aquarius lords by
|
||||
`jaimini_dual_lord_source_v1` (`upadesa_dual_lord_strength_chain`, partial);
|
||||
- AD: twelve equal parts from the MD sign, Table 13 order; Saturn in the MD sign
|
||||
-> Table 11, Ketu -> Table 14 (`rath_ad_equal_twelfths`, `rath_ad_order_table13`).
|
||||
1. more occupants (actual bodies, Rahu/Ketu included);
|
||||
2. more support from three roles — Jupiter, Mercury and the sign lord — each
|
||||
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;
|
||||
4. dual > fixed > movable;
|
||||
5. the lord's degree inside its sign, Chara Karaka sense (higher is
|
||||
stronger; Rahu / Ketu as 30 - degree);
|
||||
6. the lord placed in a sign of the opposite parity to the sign it rules;
|
||||
7. the sign giving the longer dasha.
|
||||
"Contains the AK" / "lord is the AK" are not general rules [p.35]. The
|
||||
Indira example (p.65-66) decides Cancer by the AK; it is kept as a flagged
|
||||
book example (`book_example_exception: rath_p66_indira_ak`), used only when
|
||||
all seven levels tie.
|
||||
|
||||
Not wired into scoring, consultation or reports: the three book tables
|
||||
(Table 15 / 17 / 18) are not all reproduced (see
|
||||
`docs/tasks/PROGRESS-astrologer-rulings-batch2-20261003.md`, task red line 2).
|
||||
MD order: Table 10 by the start sign [p.51]; Saturn in the start sign -> zodiacal,
|
||||
sign by sign (Table 11) [p.51-53]; Ketu in the start sign -> Table 10 direction
|
||||
reversed, keeping the fixed / dual stepping (not the whole table read
|
||||
backwards) [p.51, p.53 Table 12; p.54 Table 14 is the AD table]. Saturn and
|
||||
Ketu both in the start sign: Ketu first (PyJHora's practice, product owner
|
||||
ruling 2026-10-03, `method_note`).
|
||||
|
||||
Years: `jaimini_odd_footed_dignity_v2` (odd/even-footed count, fixed-sign
|
||||
exception, exalted +1 / debilitated -1); Scorpio / Aquarius lords by
|
||||
`jaimini_dual_lord_source_v1`. Cycle 2 = 12 minus cycle 1.
|
||||
|
||||
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).
|
||||
|
||||
Ported verbatim from upstream yinduzhanxing c7117f92 `scripts/narayana_dasha.py`:
|
||||
`_rath_ketu_table12_sequence` (the `rath_ketu_table12_v1` branch of
|
||||
`build_narayana_sign_sequence`), `_RATH_CLASSICAL_MOOLATRIKONA_SIGNS`, and the
|
||||
level 1-2 counting of `select_narayana_seed_sign(strength_profile=
|
||||
"rath_p36_role_conjunction_v1")` (`_role_candidate`; tests compare it with the
|
||||
upstream function). Tables 10 / 11 / 13 / 14, `_rasi_aspects`, the year and
|
||||
dual-lord functions are the same in both repositories and are reused from the
|
||||
frozen local `narayana_dasha.py`, which this module does not modify.
|
||||
|
||||
Not wired into scoring, consultation or reports (task red line 3: Table 18 is
|
||||
not reproduced by the general rule, see tests/test_narayana_rath_book_tables.py).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
try: # scripts/ on sys.path
|
||||
import narayana_dasha as nd
|
||||
from jaimini import calc_chara_karaka_8
|
||||
except ImportError: # pragma: no cover - package import
|
||||
from scripts import narayana_dasha as nd
|
||||
from scripts.jaimini import calc_chara_karaka_8
|
||||
|
||||
PROFILE = "sanjay_rath"
|
||||
DURATION_PROFILE = "jaimini_odd_footed_dignity_v2"
|
||||
DUAL_LORD_PROFILE = "jaimini_dual_lord_source_v1"
|
||||
SOURCE = ("Sanjay Rath, Narayana Dasa — rule ledger and Tables 10/11/13/14 "
|
||||
"(ported from upstream yinduzhanxing c7117f92)")
|
||||
ROLE_PROFILE = "rath_p36_role_conjunction_v1"
|
||||
INDIRA_EXCEPTION = "rath_p66_indira_ak"
|
||||
KETU_FIRST_NOTE = "PyJHora 兼容:计都优先"
|
||||
KETU_FIRST_NOTE_EN = "PyJHora-compatible: Ketu takes precedence"
|
||||
SOURCE = {
|
||||
"book": "Sanjay Rath, Narayana Dasa",
|
||||
"start_sign": "p.48 (stronger of Lagna / 7th); levels p.35-36, p.63; Rasi Drishti p.48",
|
||||
"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",
|
||||
"port": "upstream yinduzhanxing c7117f92 scripts/narayana_dasha.py",
|
||||
}
|
||||
SIGNS = nd.SIGNS
|
||||
SIGN_LORDS = nd.SIGN_LORDS
|
||||
GRAHAS = ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu")
|
||||
LEVELS = (
|
||||
"occupant_count",
|
||||
"role_conjunction_or_rasi_aspect",
|
||||
"occupant_dignity",
|
||||
"modality",
|
||||
"lord_degree_in_sign",
|
||||
"lord_opposite_parity",
|
||||
"longer_dasha",
|
||||
)
|
||||
|
||||
# Ported from upstream c7117f92 (Rath p80, whole-sign scope).
|
||||
_RATH_CLASSICAL_MOOLATRIKONA_SIGNS = dict(Sun=4, Moon=1, Mars=0,
|
||||
Mercury=5, Jupiter=8, Venus=6, Saturn=10) # Rath p80, whole-sign scope.
|
||||
|
||||
|
||||
def _rath_ketu_table12_sequence(seed_sign_idx: int) -> List[int]:
|
||||
# Ported from upstream c7117f92 build_narayana_sign_sequence,
|
||||
# profile "rath_ketu_table12_v1".
|
||||
# Rath pp.53-54 Table12 note: reverse Table10 direction, retain motion.
|
||||
return [(2 * seed_sign_idx - sign) % 12
|
||||
for sign in nd._NARAYANA_RATH_GENERAL_SEQUENCE[seed_sign_idx]]
|
||||
|
||||
|
||||
def _grahas(planet_lons: Dict[str, float]) -> Dict[str, float]:
|
||||
return {planet: float(planet_lons[planet]) for planet in GRAHAS if planet_lons.get(planet) is not None}
|
||||
|
||||
|
||||
def _lord(sign_idx: int, planet_lons: Dict[str, float]) -> str:
|
||||
return nd._resolve_narayana_lord(sign_idx, planet_lons, dual_lord_profile=DUAL_LORD_PROFILE)
|
||||
|
||||
|
||||
def _role_candidate(sign_idx: int, planet_lons: Dict[str, float], lord: str) -> Dict[str, Any]:
|
||||
"""Levels 1-2 for one sign: upstream `select_narayana_seed_sign` with
|
||||
strength_profile="rath_p36_role_conjunction_v1" (c7117f92), per sign."""
|
||||
occupants = sorted(
|
||||
planet
|
||||
for planet in planet_lons
|
||||
if nd._get_planet_sign(planet, planet_lons) == sign_idx
|
||||
)
|
||||
aspect_factors = []
|
||||
role_factors = []
|
||||
roles = (("jupiter", "Jupiter"), ("mercury", "Mercury"), ("sign_lord", lord))
|
||||
for role, planet in roles:
|
||||
planet_sign_idx = nd._get_planet_sign(planet, planet_lons)
|
||||
conjunction = planet_sign_idx == sign_idx
|
||||
if planet_sign_idx is not None and (conjunction or nd._rasi_aspects(planet_sign_idx, sign_idx)):
|
||||
aspect_factors.append(planet)
|
||||
role_factors.append(dict(role=role, planet=planet, contact="conjunction" if conjunction else "rasi_aspect"))
|
||||
return {
|
||||
"sign": SIGNS[sign_idx],
|
||||
"lord": lord,
|
||||
"occupants": occupants,
|
||||
"occupant_count": len(occupants),
|
||||
"aspect_factors": aspect_factors,
|
||||
"aspect_factor_count": len(aspect_factors),
|
||||
"role_factors": role_factors,
|
||||
}
|
||||
|
||||
|
||||
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"]:
|
||||
return 3
|
||||
if sign_idx == _RATH_CLASSICAL_MOOLATRIKONA_SIGNS[planet]:
|
||||
return 2
|
||||
if SIGN_LORDS[SIGNS[sign_idx]] == planet:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def _ck_degree(planet: str, lon: float) -> float:
|
||||
degree = float(lon) % 30.0
|
||||
return 30.0 - degree if planet in ("Rahu", "Ketu") else degree
|
||||
|
||||
|
||||
def sign_strength(sign_idx: int, planet_lons: Dict[str, float]) -> Dict[str, Any]:
|
||||
"""All seven level values for one sign (see module docstring)."""
|
||||
lons = _grahas(planet_lons)
|
||||
lord = _lord(sign_idx, lons)
|
||||
row = _role_candidate(sign_idx, lons, lord)
|
||||
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)
|
||||
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},
|
||||
"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,
|
||||
"lord_opposite_parity": lord_sign_idx % 2 != sign_idx % 2,
|
||||
"dasha_years": years,
|
||||
})
|
||||
row["level_values"] = [
|
||||
row["occupant_count"],
|
||||
row["aspect_factor_count"],
|
||||
(best, dignified),
|
||||
{"movable": 0, "fixed": 1, "dual": 2}[row["modality"]],
|
||||
row["lord_degree_in_sign"] if row["lord_degree_in_sign"] is not None else -1.0,
|
||||
int(row["lord_opposite_parity"]),
|
||||
years,
|
||||
]
|
||||
return row
|
||||
|
||||
|
||||
def _atmakaraka(planet_lons: Dict[str, float]) -> Optional[str]:
|
||||
lons = {planet: lon for planet, lon in _grahas(planet_lons).items() if planet != "Ketu"}
|
||||
table = calc_chara_karaka_8(lons).get("karaka_table_8") or {}
|
||||
for row in table.values():
|
||||
if row.get("rank") == 1:
|
||||
return row.get("planet")
|
||||
return None
|
||||
|
||||
|
||||
def compare_signs(first_idx: int, second_idx: int, planet_lons: Dict[str, float],
|
||||
*, allow_book_example: bool = False) -> Dict[str, Any]:
|
||||
"""Stronger of two signs by the seven general levels.
|
||||
|
||||
`allow_book_example`: only when all seven tie, the Indira example's AK
|
||||
tie-break may decide; the result then carries `book_example_exception`.
|
||||
"""
|
||||
candidates = {first_idx: sign_strength(first_idx, planet_lons), second_idx: sign_strength(second_idx, planet_lons)}
|
||||
base = {"candidates": {SIGNS[idx]: row for idx, row in candidates.items()},
|
||||
"book_example_exception": None}
|
||||
for level, name in enumerate(LEVELS):
|
||||
a = candidates[first_idx]["level_values"][level]
|
||||
b = candidates[second_idx]["level_values"][level]
|
||||
if a != b:
|
||||
winner = first_idx if a > b else second_idx
|
||||
return {**base, "status": "selected", "selected_sign_idx": winner, "selected_sign": SIGNS[winner],
|
||||
"strength_level_decided": level + 1, "strength_level_name": name}
|
||||
if allow_book_example:
|
||||
ak = _atmakaraka(planet_lons)
|
||||
holders = [idx for idx in (first_idx, second_idx) if ak and nd._get_planet_sign(ak, planet_lons) == idx]
|
||||
if len(holders) == 1:
|
||||
winner = holders[0]
|
||||
return {**base, "status": "selected", "selected_sign_idx": winner, "selected_sign": SIGNS[winner],
|
||||
"strength_level_decided": None, "strength_level_name": "book_example_atmakaraka",
|
||||
"book_example_exception": INDIRA_EXCEPTION, "atmakaraka": ak}
|
||||
return {**base, "status": "tie", "reason": "all_seven_general_levels_tie",
|
||||
"strength_level_decided": None, "strength_level_name": None}
|
||||
|
||||
|
||||
def md_order(seed_sign_idx: int, planet_lons: Dict[str, float]) -> Dict[str, Any]:
|
||||
"""MD sign order for one start sign, with the rule that decided it."""
|
||||
"""MD sign order for one start sign, with the exception that decided it."""
|
||||
saturn = nd._get_planet_sign("Saturn", planet_lons) == seed_sign_idx
|
||||
ketu = nd._get_planet_sign("Ketu", planet_lons) == seed_sign_idx
|
||||
if saturn:
|
||||
method_note = None
|
||||
if ketu:
|
||||
order = _rath_ketu_table12_sequence(seed_sign_idx)
|
||||
direction = "table12_ketu_reversed"
|
||||
exception = "ketu_in_start_sign"
|
||||
if saturn:
|
||||
method_note = KETU_FIRST_NOTE
|
||||
elif saturn:
|
||||
order = nd.build_narayana_sign_sequence(seed_sign_idx, profile="rath_saturn_table11_v1")
|
||||
direction = "table11_saturn_zodiacal"
|
||||
elif ketu:
|
||||
table10 = nd.build_narayana_sign_sequence(seed_sign_idx, profile="rath_general_table10_v1")
|
||||
order = [(2 * seed_sign_idx - sign) % 12 for sign in table10]
|
||||
direction = "table10_reversed_ketu"
|
||||
exception = "saturn_in_start_sign"
|
||||
else:
|
||||
order = nd.build_narayana_sign_sequence(seed_sign_idx, profile="rath_general_table10_v1")
|
||||
direction = "table10_general"
|
||||
return {"order": order, "direction": direction, "saturn_in_seed": saturn, "ketu_in_seed": ketu}
|
||||
exception = None
|
||||
return {"order": order, "direction": direction, "exception_applied": exception,
|
||||
"method_note": method_note, "method_note_en": KETU_FIRST_NOTE_EN if method_note else None,
|
||||
"saturn_in_seed": saturn, "ketu_in_seed": ketu}
|
||||
|
||||
|
||||
def _ad_order_profile(md_sign_idx: int, planet_lons: Dict[str, float]) -> str:
|
||||
if nd._get_planet_sign("Saturn", planet_lons) == md_sign_idx:
|
||||
return "rath_saturn_table11_v1"
|
||||
if nd._get_planet_sign("Ketu", planet_lons) == md_sign_idx:
|
||||
return "rath_ketu_table14_v1"
|
||||
return "rath_table13_v1"
|
||||
def _ad_order_profile(md_sign_idx: int, planet_lons: Dict[str, float]) -> Tuple[str, Optional[str]]:
|
||||
ketu = nd._get_planet_sign("Ketu", planet_lons) == md_sign_idx
|
||||
saturn = nd._get_planet_sign("Saturn", planet_lons) == md_sign_idx
|
||||
if ketu:
|
||||
return "rath_ketu_table14_v1", (KETU_FIRST_NOTE if saturn else None)
|
||||
if saturn:
|
||||
return "rath_saturn_table11_v1", None
|
||||
return "rath_table13_v1", None
|
||||
|
||||
|
||||
def mahadasha_sequence(seed_sign_idx: int, planet_lons: Dict[str, float],
|
||||
@@ -81,7 +274,7 @@ def mahadasha_sequence(seed_sign_idx: int, planet_lons: Dict[str, float],
|
||||
rows: List[Dict[str, Any]] = []
|
||||
cursor = float(start_age)
|
||||
for position, sign_idx in enumerate(order["order"], start=1):
|
||||
lord = nd._resolve_narayana_lord(sign_idx, planet_lons, dual_lord_profile=DUAL_LORD_PROFILE)
|
||||
lord = _lord(sign_idx, planet_lons)
|
||||
lord_sign_idx = nd._get_planet_sign(lord, planet_lons)
|
||||
if lord_sign_idx is None:
|
||||
lord_sign_idx = sign_idx
|
||||
@@ -104,49 +297,93 @@ def mahadasha_sequence(seed_sign_idx: int, planet_lons: Dict[str, float],
|
||||
return {**order, "seed_sign": SIGNS[seed_sign_idx], "seed_sign_idx": seed_sign_idx, "mahadasha": rows}
|
||||
|
||||
|
||||
def antardasha(md_row: Dict[str, Any], planet_lons: Dict[str, float]) -> List[Dict[str, Any]]:
|
||||
"""Twelve equal ADs from the MD sign (Table 13 / 11 / 14)."""
|
||||
profile = _ad_order_profile(md_row["sign_idx"], planet_lons)
|
||||
def antardasha(md_row: Dict[str, Any], planet_lons: Dict[str, float]) -> Dict[str, Any]:
|
||||
"""Twelve equal ADs: start from the sign of the lord of the stronger of
|
||||
the MD sign and its 7th (round-5 Q4); order Table 13 / 11 / 14."""
|
||||
md_sign_idx = md_row["sign_idx"]
|
||||
seventh = (md_sign_idx + 6) % 12
|
||||
comparison = compare_signs(md_sign_idx, seventh, planet_lons)
|
||||
profile, note = _ad_order_profile(md_sign_idx, planet_lons)
|
||||
if comparison["status"] != "selected":
|
||||
return {"status": "blocked", "reason": comparison.get("reason"), "rows": [],
|
||||
"antardasha_start_rule": {"rule": "stronger_of_md_sign_and_seventh_then_its_lord_sign",
|
||||
"compared": [SIGNS[md_sign_idx], SIGNS[seventh]],
|
||||
"strength_level_decided": None}}
|
||||
stronger = comparison["selected_sign_idx"]
|
||||
lord = _lord(stronger, planet_lons)
|
||||
start = nd._get_planet_sign(lord, planet_lons)
|
||||
if start is None:
|
||||
return {"status": "blocked", "reason": "stronger_sign_lord_longitude_missing", "rows": []}
|
||||
rows = nd.subdivide_narayana_equal_order(
|
||||
md_row, start_sign_idx=md_row["sign_idx"], order_profile=profile, level="AD",
|
||||
md_row, start_sign_idx=start, order_profile=profile, level="AD",
|
||||
parent_key="parent_md", parent_name=md_row["sign"],
|
||||
)
|
||||
return rows
|
||||
for row in rows:
|
||||
row["start_sign_source"] = "lord_of_stronger_md_or_seventh_sign"
|
||||
return {
|
||||
"status": "selected",
|
||||
"rows": rows,
|
||||
"antardasha_start_rule": {
|
||||
"rule": "stronger_of_md_sign_and_seventh_then_its_lord_sign",
|
||||
"compared": [SIGNS[md_sign_idx], SIGNS[seventh]],
|
||||
"stronger_sign": SIGNS[stronger],
|
||||
"strength_level_decided": comparison["strength_level_decided"],
|
||||
"lord": lord,
|
||||
"dual_lord_rule": DUAL_LORD_PROFILE if stronger in (7, 10) else None,
|
||||
"start_sign": SIGNS[start],
|
||||
"order_profile": profile,
|
||||
"method_note": note,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def calc_narayana_rath(lagna_sign_idx: int, planet_lons: Dict[str, float],
|
||||
*, seed_sign_idx: Optional[int] = None, cycle_count: int = 2) -> Dict[str, Any]:
|
||||
*, seed_sign_idx: Optional[int] = None, cycle_count: int = 2,
|
||||
allow_book_example: bool = False) -> Dict[str, Any]:
|
||||
"""Rath Narayana Dasha for one chart.
|
||||
|
||||
`seed_sign_idx` overrides the start sign (book-table replay only).
|
||||
`allow_book_example` lets the Indira AK example decide a full seven-level
|
||||
tie (flagged in the output).
|
||||
"""
|
||||
if seed_sign_idx is not None:
|
||||
selection = {"status": "explicit", "selected_sign_idx": seed_sign_idx}
|
||||
else:
|
||||
selection = nd.select_narayana_seed_sign(lagna_sign_idx, planet_lons)
|
||||
base = {
|
||||
"profile": PROFILE,
|
||||
"duration_profile": DURATION_PROFILE,
|
||||
"dual_lord_rule": DUAL_LORD_PROFILE,
|
||||
"source": SOURCE,
|
||||
"lagna_sign": SIGNS[lagna_sign_idx],
|
||||
"seed_selection": selection,
|
||||
"antardasha_start_rule": "stronger_of_md_sign_and_seventh_then_its_lord_sign (p.48, p.50 note 2)",
|
||||
}
|
||||
if seed_sign_idx is not None:
|
||||
selection = {"status": "explicit", "selected_sign_idx": seed_sign_idx,
|
||||
"strength_level_decided": None, "book_example_exception": None}
|
||||
else:
|
||||
selection = compare_signs(lagna_sign_idx, (lagna_sign_idx + 6) % 12, planet_lons,
|
||||
allow_book_example=allow_book_example)
|
||||
base["seed_selection"] = selection
|
||||
base["strength_level_decided"] = selection.get("strength_level_decided")
|
||||
base["book_example_exception"] = selection.get("book_example_exception")
|
||||
if selection["status"] in ("selected", "explicit"):
|
||||
seq = mahadasha_sequence(selection["selected_sign_idx"], planet_lons, cycle_count=cycle_count)
|
||||
return {**base, "status": "partial_verified" if selection["status"] == "selected" else "explicit_seed",
|
||||
"seed_sign": seq["seed_sign"],
|
||||
"seed_basis": ("explicit_seed" if selection["status"] == "explicit"
|
||||
else "stronger_of_lagna_seventh:" + "+".join(selection.get("selection_levels", []))),
|
||||
"direction": seq["direction"], "mahadasha": seq["mahadasha"]}
|
||||
if selection["status"] == "explicit":
|
||||
status, basis = "explicit_seed", "explicit_seed"
|
||||
elif selection.get("book_example_exception"):
|
||||
status, basis = "book_example_exception", f"book_example:{selection['book_example_exception']}"
|
||||
else:
|
||||
status = "partial_verified"
|
||||
basis = f"stronger_of_lagna_seventh:level_{selection['strength_level_decided']}_{selection['strength_level_name']}"
|
||||
return {**base, "status": status, "seed_sign": seq["seed_sign"], "seed_basis": basis,
|
||||
"direction": seq["direction"], "exception_applied": seq["exception_applied"],
|
||||
"method_note": seq["method_note"], "method_note_en": seq["method_note_en"],
|
||||
"mahadasha": seq["mahadasha"]}
|
||||
alternatives = {
|
||||
SIGNS[idx]: mahadasha_sequence(idx, planet_lons, cycle_count=cycle_count)
|
||||
for idx in (lagna_sign_idx, (lagna_sign_idx + 6) % 12)
|
||||
}
|
||||
lagna_seq = alternatives[SIGNS[lagna_sign_idx]]
|
||||
return {**base, "status": "parameter_sensitive", "reason": selection.get("reason"),
|
||||
"seed_sign": None, "seed_basis": "tie_lagna_seventh_both_listed",
|
||||
"direction": None, "mahadasha": None,
|
||||
"direction": None, "exception_applied": None, "method_note": None, "method_note_en": None,
|
||||
"mahadasha": None,
|
||||
"alternatives": {sign: {"seed_sign": seq["seed_sign"], "direction": seq["direction"],
|
||||
"mahadasha": seq["mahadasha"]} for sign, seq in alternatives.items()},
|
||||
"lagna_start_mahadasha": lagna_seq["mahadasha"]}
|
||||
"exception_applied": seq["exception_applied"],
|
||||
"mahadasha": seq["mahadasha"]} for sign, seq in alternatives.items()}}
|
||||
|
||||
Reference in New Issue
Block a user