Port from yinduzhanxing 5fb8379c (MIT): Naisargika, Kaala, Karaka, Tara, Lagnamsaka, Yogardha, Mandooka, Paryaaya and Padanadhamsa dashas; Sarvatobhadra, Kota and Kalanala chakras; Nakshatra Latta; Pachakadi; Raman Amsayu and Pindayu. Producers and their upstream tests are copied; scripts/upstream_technique_layers.py feeds them our natal and transit positions and attaches chart.consultation_native_layers.upstream_techniques. - dasha_registry: kaala and padanadhamsa keys (35 -> 37); the nine ported dashas are served by compute_dasha or fail closed with upstream's reason. - technique_registry.json: 16 partial entries; README badges follow. - Evidence lookup section `upstream_techniques` in the consultation tool. - Boundaries: Kalanala carries no PyJHora (AGPL) grid coordinates; Padanadhamsa/Lagnamsaka run on our Narayana engine (no Rath Table 12, PVR co-lord variants or second cycle). PyJHora observers and the Amsayu worked-example scaffold live in scripts/research (root .py budget). - PyJHora black-box cross-checks: Naisargika (lords/years) and Kaala (seed segment and fraction within 5e-4) on a public chart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DbBXoyn7Hj3LCW3BUs5bZ8
193 lines
12 KiB
Python
193 lines
12 KiB
Python
"""Rangacharya article MD scope using supplied Jaimini Navamsa signs."""
|
|
from copy import deepcopy
|
|
import math
|
|
import json
|
|
from itertools import product
|
|
|
|
PROFILE = 'padanadhamsa_rangacharya_movable_fixed_md_v1'
|
|
SOURCE = 'https://studyres.com/doc/6420886/padanadhamsa-dasa---saptarishis-magazine'
|
|
LORDS = ('Mars','Venus','Mercury','Moon','Sun','Mercury',
|
|
'Venus','Mars','Jupiter','Saturn','Saturn','Jupiter')
|
|
RATH_PROFILE = 'padanadhamsa_rath_single_al_lord_narayana_md_v1'
|
|
RATH_DUAL_PROFILE = 'padanadhamsa_rath_dual_al_sign_strength_md_v1'
|
|
RATH_SOURCE = 'https://scienceoflight.net/wp-content/uploads/extra/Narayana%20Dasa%20by%20Sanjay%20Rath.pdf#page=192'
|
|
RATH_AD_PROFILE = 'padanadhamsa_rath_p50_general_ad_v1'
|
|
RATH_TWO_CYCLE_PROFILE = 'padanadhamsa_rath_two_cycle_md_ad_v1'
|
|
|
|
|
|
# Not ported from upstream (yinduzhanxing 5fb8379c): the PVR conditional
|
|
# co-lord timelines, the PVR co-lord report and the Rath p50 antardasa overlay.
|
|
# They need upstream Narayana entries ours does not carry
|
|
# (calculate_pvr_co_lord_period_candidates, resolve_pvr_co_lord,
|
|
# calculate_narayana_second_cycle_years, Rath Table 12 / Table 25 profiles).
|
|
# Our Narayana engine follows the astrologer's own rulings (BUG-1601 and
|
|
# later); it is not replaced by upstream's variant here.
|
|
|
|
LOCAL_STRENGTH_PROFILE = 'jaimini_lagna_seventh_strength_v1'
|
|
|
|
|
|
def build_padanadhamsa_rath_report(*, arudha_lagna_sign_idx, navamsa_planet_longitudes,
|
|
duration_profile, dual_lord_profile, profile,
|
|
strength_profile='jaimini_lagna_seventh_strength_v1', cycle_count=1):
|
|
"""Rath chapter Special Narayana Dasa: D9 AL-lord sign/seventh seed.
|
|
|
|
Uses existing native Narayana rules, not Rangacharya inclusive durations.
|
|
"""
|
|
try:
|
|
from scripts.narayana_dasha import calc_narayana_mahadasha, select_narayana_seed_sign
|
|
except ModuleNotFoundError: # pragma: no cover - scripts/ on sys.path
|
|
from narayana_dasha import calc_narayana_mahadasha, select_narayana_seed_sign
|
|
if profile not in {RATH_PROFILE, RATH_DUAL_PROFILE}:
|
|
raise ValueError('explicit Rath Padanadhamsa profile required')
|
|
if type(cycle_count) is not int or cycle_count not in (1, 2):
|
|
raise ValueError('cycle_count must be integer 1 or 2')
|
|
if cycle_count != 1:
|
|
raise ValueError('second Padanadhamsa cycle not ported; local Narayana engine has no cycle_count')
|
|
if strength_profile != LOCAL_STRENGTH_PROFILE:
|
|
raise ValueError(f'only {LOCAL_STRENGTH_PROFILE} is available in the local Narayana engine')
|
|
al = _sign(arudha_lagna_sign_idx)
|
|
if al in {7, 10} and profile == RATH_PROFILE:
|
|
raise ValueError('Scorpio/Aquarius AL requires both AL lords and additional D9 strength arbitration')
|
|
required = set(LORDS) | {'Rahu', 'Ketu'}
|
|
if not isinstance(navamsa_planet_longitudes, dict) or set(navamsa_planet_longitudes) != required:
|
|
raise ValueError('all nine D9 planetary longitudes required; no missing-lord fallback')
|
|
for value in navamsa_planet_longitudes.values():
|
|
if isinstance(value, bool) or not isinstance(value, (int,float)) or not math.isfinite(value) or not 0 <= value < 360:
|
|
raise ValueError('D9 longitudes must be finite in [0,360)')
|
|
if duration_profile not in {'jaimini_odd_footed_v1', 'jaimini_odd_footed_dignity_v2'}:
|
|
raise ValueError('explicit native Jaimini duration profile required')
|
|
al_lords = {7: ('Mars', 'Ketu'), 10: ('Saturn', 'Rahu')}.get(al, (LORDS[al],))
|
|
lord_candidates = []
|
|
for lord in al_lords:
|
|
candidate_sign = int(navamsa_planet_longitudes[lord] // 30)
|
|
# Reuse the native score data even if that sign/seventh comparison ties.
|
|
candidate = select_narayana_seed_sign(candidate_sign, navamsa_planet_longitudes)['candidates'][candidate_sign]
|
|
lord_candidates.append({**candidate, 'sign_lord': candidate['lord'],
|
|
'lord': lord, 'sign_idx': candidate_sign})
|
|
winner = lord_candidates[0]
|
|
reason = 'single_al_lord'
|
|
if len(lord_candidates) == 2:
|
|
a, b = lord_candidates
|
|
if a['sign_idx'] == b['sign_idx']:
|
|
reason = 'both_al_lords_in_same_d9_sign'
|
|
else:
|
|
comparison = select_narayana_seed_sign(a['sign_idx'], navamsa_planet_longitudes,
|
|
comparison_sign_idx=b['sign_idx'])
|
|
if comparison['status'] != 'selected':
|
|
raise ValueError('dual AL lord signs tied; later sign-strength rules required')
|
|
winner = next(c for c in lord_candidates if c['sign_idx'] == comparison['selected_sign_idx'])
|
|
for candidate in lord_candidates:
|
|
candidate['strength_comparison'] = comparison
|
|
reason = 'occupant_count_then_' + strength_profile
|
|
anchor = winner['sign_idx']
|
|
selection = select_narayana_seed_sign(anchor, navamsa_planet_longitudes)
|
|
if selection['status'] != 'selected':
|
|
raise ValueError(selection['reason'])
|
|
seed = selection['selected_sign_idx']
|
|
has_ketu = int(navamsa_planet_longitudes['Ketu']//30)==seed
|
|
has_saturn = int(navamsa_planet_longitudes['Saturn']//30)==seed
|
|
if has_ketu and has_saturn:
|
|
raise ValueError('joint Saturn/Ketu seed exception precedence unresolved')
|
|
if has_ketu:
|
|
# Same boundary as narayana_dasha (BUG-1601 family): Rath Table 12 is
|
|
# not a local sequence profile, so a Ketu seed stays blocked.
|
|
raise ValueError('rath_ketu_table12_v1_not_in_local_sequence_profiles')
|
|
sequence_profile = 'rath_saturn_table11_v1' if has_saturn else 'rath_general_table10_v1'
|
|
rows = calc_narayana_mahadasha(seed, navamsa_planet_longitudes,
|
|
duration_profile=duration_profile, dual_lord_profile=dual_lord_profile,
|
|
sequence_profile=sequence_profile, seed_profile='legacy_lagna_v0')
|
|
for row in rows:
|
|
row['active'] = row['years'] > 0
|
|
row.setdefault('cycle_index', 1)
|
|
result = dict(status='parameter_sensitive', variant_profile_id=profile, source=RATH_SOURCE,
|
|
tradition_profile='sanjay_rath_special_narayana', al_lord=winner['lord'],
|
|
al_lord_selection=dict(candidates=lord_candidates, reason=reason,
|
|
selected_sign_idx=anchor, selected_lord=winner['lord']),
|
|
al_lord_navamsa_sign_idx=anchor, seed_selection=selection,
|
|
duration_profile=duration_profile, dual_lord_profile=dual_lord_profile,
|
|
sequence_profile=sequence_profile, strength_profile=strength_profile, cycle_count=cycle_count,
|
|
periods=rows, limits=['Supplied D1 AL and consistently computed D9 longitudes; not computed by this entry',
|
|
'AL-lord and seed comparison follows the explicit strength_profile; unresolved ties and joint Saturn/Ketu precedence remain blocked',
|
|
'Chart47/Table53 first nine published MD durations reproduced; remaining rows not independently verified',
|
|
'First-cycle MD age-years only; no AD, calendar dates or automatic customer export'])
|
|
lines=['# Padanadhamsa — Rath D9 Narayana MD', '', f'Source: {RATH_SOURCE}',
|
|
f'Profile: {profile}', f'Duration: {duration_profile}; dual lord: {dual_lord_profile}',
|
|
f'Sequence: {sequence_profile}; strength: {strength_profile}',
|
|
f'Cycles: {cycle_count}',
|
|
f'AL lords: {list(al_lords)}; anchor decision: {reason}; selected lord: {winner["lord"]}',
|
|
f'AL lord D9 sign: {anchor}; selected seed: {seed}', '',
|
|
]
|
|
lines += ['| Cycle | Sign | Years | Start age | End age | Active |', '| --- | --- | --- | --- | --- | --- |']
|
|
lines += ['| '+' | '.join(str(r[k]) for k in ('cycle_index','sign','years','start_age','end_age','active'))+' |' for r in rows]
|
|
lines += ['', *result['limits']]
|
|
return dict(status=result['status'],customer_export_admitted=False,evidence_appendix=[result],
|
|
exports=dict(markdown='\n'.join(lines),ai_evidence_bundle=deepcopy(result)))
|
|
|
|
|
|
def _sign(value):
|
|
if type(value) is not int or not 0 <= value < 12:
|
|
raise ValueError('sign indices must be integers in [0,12)')
|
|
return value
|
|
|
|
|
|
def calculate_padanadhamsa_duration(*, sign_idx, lord_sign_idx, profile):
|
|
"""Article's inclusive parity rule, usable independently of seed order.
|
|
|
|
The caller supplies the sign of the resolved classical lord in Jaimini
|
|
Navamsa. This also works for dual signs without inventing their MD order.
|
|
"""
|
|
if profile != PROFILE:
|
|
raise ValueError('explicit Rangacharya text-rule profile required')
|
|
sign, lord = _sign(sign_idx), _sign(lord_sign_idx)
|
|
direction = 1 if sign % 2 == 0 else -1
|
|
distance = ((lord - sign) * direction) % 12
|
|
exception = 'own_sign_12' if distance == 0 else 'opposite_sign_10' if distance == 6 else 'none'
|
|
years = 12 if distance == 0 else 10 if distance == 6 else distance + 1
|
|
return dict(status='parameter_sensitive', variant_profile_id=PROFILE,
|
|
source=SOURCE, sign_idx=sign, lord_sign_idx=lord, direction=direction,
|
|
inclusive_count=distance+1, exception=exception, years=years,
|
|
claim_boundary='Article text-rule duration only; published Scorpio example conflict unresolved')
|
|
|
|
|
|
def build_padanadhamsa_report(*, arudha_lagna_sign_idx, jaimini_navamsa_signs,
|
|
navamsa_profile, profile):
|
|
"""Seven classical lords; AL and Jaimini Navamsa supplied, not reconstructed."""
|
|
if profile != PROFILE or navamsa_profile != 'supplied_rangacharya_jaimini_navamsa':
|
|
raise ValueError('explicit Rangacharya and supplied Jaimini Navamsa profiles required')
|
|
al = _sign(arudha_lagna_sign_idx)
|
|
if not isinstance(jaimini_navamsa_signs, dict) or set(jaimini_navamsa_signs) != set(LORDS):
|
|
raise ValueError('exactly seven classical planetary Jaimini Navamsa signs required')
|
|
for value in jaimini_navamsa_signs.values():
|
|
_sign(value)
|
|
seed = jaimini_navamsa_signs[LORDS[al]]
|
|
if seed % 3 == 2:
|
|
raise ValueError('dual-sign trine-group continuation needs an explicit worked sequence')
|
|
direction = 1 if seed % 2 == 0 else -1
|
|
step = 1 if seed % 3 == 0 else 5
|
|
rows, elapsed = [], 0
|
|
for i in range(12):
|
|
sign = (seed + i * step * direction) % 12
|
|
lord = LORDS[sign]
|
|
lord_sign = jaimini_navamsa_signs[lord]
|
|
duration = calculate_padanadhamsa_duration(sign_idx=sign, lord_sign_idx=lord_sign, profile=profile)
|
|
years = duration['years']
|
|
rows.append(dict(sign_idx=sign, lord=lord, lord_sign_idx=lord_sign,
|
|
duration_rule=duration,
|
|
years=years, start_age_years=elapsed, end_age_years=elapsed+years))
|
|
elapsed += years
|
|
result = dict(status='parameter_sensitive', variant_profile_id=PROFILE,
|
|
tradition_profile='iranganti_rangacharya_article', source=SOURCE,
|
|
navamsa_profile=navamsa_profile, seed_sign_idx=seed, seed_lord=LORDS[al],
|
|
periods=rows, limits=['Supplied AL and Jaimini Navamsa, not ordinary Parasara D9',
|
|
'Movable/fixed seed only; seven classical lords, no node co-lord substitution',
|
|
'No birth proration, AD, calendar dates or dual-seed continuation claimed',
|
|
'Article text and Indira table conflict: Scorpio text-rule 4 years versus printed 10; subsequent ages differ by six years',
|
|
'Authored article mirrored by third party; published table reproduction is not event verification'])
|
|
lines=['# Padanadhamsa — Rangacharya movable/fixed MD', '', f'Source: {SOURCE}',
|
|
f'Profile: {PROFILE}', f'Navamsa: {navamsa_profile}', '',
|
|
'| Sign index | Lord | Years | Start age | End age |', '| --- | --- | --- | --- | --- |']
|
|
lines += ['| '+' | '.join(str(r[k]) for k in ('sign_idx','lord','years','start_age_years','end_age_years'))+' |' for r in rows]
|
|
lines += ['', *result['limits']]
|
|
return dict(status=result['status'],customer_export_admitted=False,
|
|
evidence_appendix=[result],exports=dict(markdown='\n'.join(lines),ai_evidence_bundle=deepcopy(result)))
|