Issue #94 T1. Place goel_jaimini_strength, trikona_raman, sthira_brahma_goel_short_method, and trikona_dasha from upstream 5fb8379c under scripts/. The only local edit is the SOURCE book citation. Raise ROOT_PY_BUDGET from 448 to 452. Co-Authored-By: Grok (agent loop) Co-authored-by: Cursor <cursoragent@cursor.com>
138 lines
8.0 KiB
Python
138 lines
8.0 KiB
Python
"""Goel's full Jaimini strength and special-point profile, not Bhava Bala.
|
|
|
|
Source: Predicting through Jaimini Astrology, printed pp10-17 (PDF pp14-21).
|
|
The published p15 example is the numeric regression. Dignity degree ranges
|
|
reuse the explicitly named Raman manual profile; no universal lineage claim.
|
|
"""
|
|
import math
|
|
from scripts.trikona_raman import CLASSICAL, DIGNITY_PROFILE, _dignity, _validate_positions
|
|
from scripts.narayana_dasha import _rasi_aspects
|
|
from scripts.sthira_brahma_goel_short_method import SIGNS, SIGN_LORDS
|
|
|
|
PROFILE = 'goel_full_jaimini_strength_ch1_v1'
|
|
SOURCE = 'V. P. Goel, Predicting through Jaimini Astrology'
|
|
KARAKA_POINTS = (60, 45, 30, 22.5, 15, 7.5, 3.75)
|
|
MALEFICS = ('Sun', 'Mars', 'Saturn', 'Rahu', 'Ketu')
|
|
|
|
|
|
def calculate_goel_strength(planet_longitudes, *, node_occupancy_policy='include_nodes_text'):
|
|
if node_occupancy_policy not in {'include_nodes_text', 'exclude_nodes_worked_example'}:
|
|
raise ValueError('explicit Goel node occupancy policy required')
|
|
_validate_positions(planet_longitudes)
|
|
signs = {p: int(v // 30) for p, v in planet_longitudes.items()}
|
|
degrees = {p: planet_longitudes[p] % 30 for p in CLASSICAL}
|
|
ranked = sorted(CLASSICAL, key=degrees.get, reverse=True)
|
|
if any(math.isclose(degrees[a], degrees[b], rel_tol=0, abs_tol=1e-10)
|
|
for i, a in enumerate(ranked) for b in ranked[i + 1:]):
|
|
return dict(status='blocked', reason='equal_degree_seven_karaka_rank_requires_finer_positions', profile_id=PROFILE)
|
|
ak = ranked[0]
|
|
karakas = dict(zip(ranked, KARAKA_POINTS))
|
|
|
|
def contacts(sign, candidates):
|
|
return [p for p in candidates if signs[p] == sign or _rasi_aspects(signs[p], sign)]
|
|
|
|
planets = {}
|
|
for p in CLASSICAL:
|
|
sign = signs[p]
|
|
dignity, moola = _dignity(p, planet_longitudes[p])
|
|
kendra = (60, 30, 15)[(sign - signs[ak]) % 3]
|
|
before = [q for q, s in signs.items() if s == (sign - 1) % 12]
|
|
after = [q for q, s in signs.items() if s == (sign + 1) % 12]
|
|
kartari = 60 if sign % 2 == 0 and before and after else 0
|
|
malefic_contacts = contacts(sign, [q for q in MALEFICS if q != p])
|
|
adhipati = 60 if p != 'Moon' and malefic_contacts else 0
|
|
planets[p] = dict(sign_idx=sign, degree_in_sign=degrees[p],
|
|
dignity=dignity, mooltrikona=moola, karaka=karakas[p], kendriya=kendra,
|
|
kartari=kartari, adhipati=adhipati, malefic_contacts=malefic_contacts,
|
|
kartari_before=before, kartari_after=after,
|
|
total=moola + karakas[p] + kendra + kartari + adhipati)
|
|
rows = []
|
|
for sign in range(12):
|
|
lord = SIGN_LORDS[sign]
|
|
occupants = [p for p, s in signs.items() if s == sign]
|
|
permanent = (15, 30, 60)[sign % 3]
|
|
karaka = sum(karakas.get(p, 0) for p in occupants)
|
|
counted = occupants if node_occupancy_policy == 'include_nodes_text' else [p for p in occupants if p in CLASSICAL]
|
|
sthana = 45 + 15 * len(counted) if counted else 0
|
|
# Three roles, not distinct planets: Goel p103 explicitly explains
|
|
# double credit when Mercury/Jupiter is also the sign lord.
|
|
aspect_roles = [role for role, p in [('Jupiter', 'Jupiter'), ('Mercury', 'Mercury'), ('lord', lord)]
|
|
if contacts(sign, [p])]
|
|
aspect = 60 * len(aspect_roles)
|
|
rows.append(dict(sign_idx=sign, sign=SIGNS[sign], lord=lord,
|
|
permanent=permanent, karaka=karaka, sthana=sthana, occupants=occupants, counted_occupants=counted,
|
|
aspect=aspect, aspect_roles=aspect_roles, lord_strength=planets[lord]['total'],
|
|
total=permanent + karaka + sthana + aspect + planets[lord]['total']))
|
|
return dict(status='executed', confidence_status='parameter_sensitive', profile_id=PROFILE,
|
|
source=SOURCE, source_locator='PDF pp14-21; printed pp10-17; lines461-736',
|
|
dignity_profile=DIGNITY_PROFILE, malefic_profile='Sun_Mars_Saturn_Rahu_Ketu_excluding_self',
|
|
node_occupancy_policy=node_occupancy_policy,
|
|
aspect_profile='jaimini_rasi_drishti', aspect_role_policy='distinct_roles',
|
|
atmakaraka=ak, planets=planets, rashi_strengths=rows,
|
|
claim_boundary='Named Goel full method with declared dignity ranges; not PL9 or all-lineage parity.')
|
|
|
|
|
|
def build_goel_chart_evidence(planet_longitudes, ascendant_longitude):
|
|
if (type(ascendant_longitude) not in (int, float)
|
|
or not math.isfinite(ascendant_longitude)
|
|
or not 0 <= ascendant_longitude < 360):
|
|
raise ValueError('ascendant longitude must be a finite number in [0,360)')
|
|
strength = calculate_goel_strength(planet_longitudes)
|
|
result = dict(strength=strength, special_points={})
|
|
if strength['status'] != 'executed':
|
|
result['special_points'] = {
|
|
name: dict(status='blocked', reason=strength['reason'], profile_id=PROFILE)
|
|
for name in ('Brahma', 'Maheshwara', 'Rudra')
|
|
}
|
|
return result
|
|
strength['worked_example_node_exclusion'] = calculate_goel_strength(
|
|
planet_longitudes, node_occupancy_policy='exclude_nodes_worked_example')
|
|
strength['source_discrepancy'] = (
|
|
'Printed p14 includes nodes in Sthana; printed p15 worked totals omit them. '
|
|
'Both numeric alternatives are preserved; the explicit text is the active profile.')
|
|
lagna = int(float(ascendant_longitude) % 360 // 30)
|
|
seventh = (lagna + 6) % 12
|
|
planets = strength['planets']
|
|
rashis = strength['rashi_strengths']
|
|
ak_sign = planets[strength['atmakaraka']]['sign_idx']
|
|
|
|
def choose(candidates, rule):
|
|
candidates = list(dict.fromkeys(candidates))
|
|
scores = {p: planets[p]['total'] for p in candidates}
|
|
winners = [p for p in candidates if scores[p] == max(scores.values())]
|
|
return dict(status='executed' if len(winners) == 1 else 'blocked',
|
|
value=winners[0] if len(winners) == 1 else None,
|
|
candidates=scores, rule=rule,
|
|
reason=None if len(winners) == 1 else 'equal_Goel_strength_no_unique_selector',
|
|
profile_id=PROFILE, confidence_status='parameter_sensitive',
|
|
source=SOURCE, source_locator='PDF pp20-21; printed pp16-17; lines701-736')
|
|
|
|
eighth_ak = SIGN_LORDS[(ak_sign + 7) % 12]
|
|
if planets[eighth_ak]['sign_idx'] == (ak_sign + 7) % 12:
|
|
brahma = choose([eighth_ak], 'eighth_lord_from_AK_occupies_eighth_from_AK')
|
|
elif rashis[lagna]['total'] == rashis[seventh]['total']:
|
|
brahma = dict(status='blocked', reason='equal_Lagna_seventh_Goel_strength', profile_id=PROFILE)
|
|
else:
|
|
anchor = lagna if rashis[lagna]['total'] > rashis[seventh]['total'] else seventh
|
|
brahma = choose([SIGN_LORDS[(anchor + h - 1) % 12] for h in (6, 8, 12)
|
|
if SIGN_LORDS[(anchor + h - 1) % 12] != 'Saturn'],
|
|
'strongest_6_8_12_lord_from_stronger_Lagna_seventh_excluding_Saturn')
|
|
brahma['anchor_sign_idx'] = anchor
|
|
maheswara = choose([eighth_ak] + ([SIGN_LORDS[(ak_sign + 11) % 12]]
|
|
if planets[eighth_ak]['dignity'] == 'exalted' else []),
|
|
'eighth_lord_from_AK_compare_twelfth_if_exalted')
|
|
rudra_candidates = list(dict.fromkeys([SIGN_LORDS[(lagna + 7) % 12], SIGN_LORDS[(lagna + 1) % 12]]))
|
|
afflicted = [p for p in rudra_candidates if planets[p]['malefic_contacts']]
|
|
rudra = choose(afflicted if len(afflicted) == 1 else rudra_candidates,
|
|
'stronger_8th_2nd_lord_unless_only_weaker_has_malefic_contact')
|
|
result['special_points'] = dict(Brahma=brahma, Maheshwara=maheswara, Rudra=rudra)
|
|
result['lagna_seventh_strength'] = dict(lagna=rashis[lagna]['total'],
|
|
seventh=rashis[seventh]['total'], profile_id=PROFILE, source=SOURCE,
|
|
comparison='lagna_vs_seventh')
|
|
# Frozen-export compatibility only. PVR Niryaana is second/eighth Ayur
|
|
# comparison; callers must not route these Goel totals into that selector.
|
|
result['niryaana_shoola_strength'] = dict(result['lagna_seventh_strength'],
|
|
deprecated=True, replacement='lagna_seventh_strength',
|
|
not_for='pvr_niryaana_shoola_ayur_rasi_strength_v1')
|
|
return result
|