"""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