#!/usr/bin/env python3 """Goel / BVB short graha and rashi strength for the report rashi dashas. Dignity, amsha, and kendradi units follow the short method in Predicting through Jaimini Astrology, printed pages 100-108. This is not Shadbala and not a house-strength table from another program. """ from __future__ import annotations from typing import Dict, Mapping try: from jaimini import FRIENDSHIP, KARAKA_7, SIGNS, calc_chara_karaka_7 from narayana_dasha import SIGN_LORDS, _rasi_aspects except ImportError: # pragma: no cover - package import from scripts.jaimini import FRIENDSHIP, KARAKA_7, SIGNS, calc_chara_karaka_7 from scripts.narayana_dasha import SIGN_LORDS, _rasi_aspects CLASSICAL_PLANETS = ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn") _EXALT_SIGN = { "Sun": 0, "Moon": 1, "Mars": 9, "Mercury": 5, "Jupiter": 3, "Venus": 11, "Saturn": 6, } _DEBIL_SIGN = { "Sun": 6, "Moon": 7, "Mars": 3, "Mercury": 11, "Jupiter": 9, "Venus": 5, "Saturn": 0, } _OWN_SIGNS = { "Sun": {4}, "Moon": {3}, "Mars": {0, 7}, "Mercury": {2, 5}, "Jupiter": {8, 11}, "Venus": {1, 6}, "Saturn": {9, 10}, } # Moolatrikona bands are degree-in-sign, end exclusive. _MOOLATRIKONA = { "Sun": ((4, 0.0, 20.0),), "Moon": ((1, 3.0, 30.0),), "Mars": ((0, 0.0, 12.0),), "Mercury": ((5, 15.0, 20.0),), "Jupiter": ((8, 0.0, 10.0),), "Venus": ((6, 0.0, 15.0),), "Saturn": ((10, 0.0, 20.0),), } _AMSHA_BY_RANK = {1: 70, 2: 60, 3: 50, 4: 40, 5: 30, 6: 20, 7: 10} _CHARA_RASHI = {0: 20, 1: 40, 2: 60} def sign_index(longitude: float) -> int: return int(float(longitude) % 360.0 // 30.0) def degree_in_sign(longitude: float) -> float: return float(longitude) % 30.0 def _in_moolatrikona(planet: str, sign_idx: int, degree: float) -> bool: for sign, start, end in _MOOLATRIKONA.get(planet, ()): if sign == sign_idx and start <= degree < end: return True return False def dignity_units(planet: str, sign_idx: int, degree: float) -> int: """Short-method dignity. Whole exalt and debil signs, except Mercury in Virgo.""" if planet not in CLASSICAL_PLANETS: raise KeyError(planet) if sign_idx == _DEBIL_SIGN[planet]: return 10 if planet == "Mercury" and sign_idx == 5: if degree < 15.0: return 70 if degree < 20.0: return 60 return 50 if sign_idx == _EXALT_SIGN[planet]: return 70 if _in_moolatrikona(planet, sign_idx, degree): return 60 if sign_idx in _OWN_SIGNS[planet]: return 50 lord = SIGN_LORDS[SIGNS[sign_idx]] relation = FRIENDSHIP.get(planet, {}) if lord in relation.get("friend", []): return 40 if lord in relation.get("enemy", []): return 20 return 30 def kendradi_units(sign_idx: int, ak_sign_idx: int) -> int: offset = (int(sign_idx) - int(ak_sign_idx)) % 12 if offset % 3 == 0: return 60 if offset % 3 == 1: return 40 return 20 def karaka_ranks(planet_lons: Mapping[str, float]) -> Dict[str, int]: degrees = { planet: degree_in_sign(planet_lons[planet]) for planet in CLASSICAL_PLANETS if planet in planet_lons } table = calc_chara_karaka_7(degrees).get("karaka_table") or {} ranks = {} for rank, name in KARAKA_7.items(): row = table.get(name) or {} planet = row.get("planet") if planet: ranks[str(planet)] = int(rank) return ranks def graha_strengths(planet_lons: Mapping[str, float]) -> Dict[str, Dict[str, int]]: ranks = karaka_ranks(planet_lons) if "Mars" not in ranks and len(ranks) < 7: raise ValueError("short graha strength needs the seven classical planets") ak_planet = next(planet for planet, rank in ranks.items() if rank == 1) ak_sign = sign_index(planet_lons[ak_planet]) rows = {} for planet in CLASSICAL_PLANETS: sign_idx = sign_index(planet_lons[planet]) degree = degree_in_sign(planet_lons[planet]) dignity = dignity_units(planet, sign_idx, degree) amsha = _AMSHA_BY_RANK[ranks[planet]] kendradi = kendradi_units(sign_idx, ak_sign) rows[planet] = { "sign_index": sign_idx, "dignity": dignity, "amsha": amsha, "kendradi": kendradi, "total": dignity + amsha + kendradi, "karaka_rank": ranks[planet], } return rows def _aspect_units(sign_idx: int, planet_lons: Mapping[str, float], lord: str) -> int: contributors = [] for planet in (lord, "Jupiter", "Mercury"): if planet not in contributors: contributors.append(planet) total = 0 for planet in contributors: if planet not in planet_lons: continue source = sign_index(planet_lons[planet]) if source != sign_idx and not _rasi_aspects(source, sign_idx): continue if planet == lord and planet in {"Jupiter", "Mercury"}: total += 120 else: total += 60 return total def rashi_strengths( planet_lons: Mapping[str, float], graha: Mapping[str, Mapping[str, int]] | None = None, ) -> Dict[int, Dict[str, int]]: graha_rows = graha if graha is not None else graha_strengths(planet_lons) occupants: Dict[int, int] = {index: 0 for index in range(12)} for planet in CLASSICAL_PLANETS: occupants[sign_index(planet_lons[planet])] += 1 rows = {} for sign_idx in range(12): lord = SIGN_LORDS[SIGNS[sign_idx]] chara = _CHARA_RASHI[sign_idx % 3] sthira = 10 * occupants[sign_idx] aspect = _aspect_units(sign_idx, planet_lons, lord) lord_graha = int(graha_rows[lord]["total"]) rows[sign_idx] = { "chara": chara, "sthira": sthira, "aspect": aspect, "lord_graha": lord_graha, "total": chara + sthira + aspect + lord_graha, } return rows def stronger_sign(left: int, right: int, strengths: Mapping[int, Mapping[str, int]], planet_lons: Mapping[str, float]) -> Dict[str, object]: """Pick the stronger sign. Equal totals use the higher chara-karaka lord.""" left_total = int(strengths[left]["total"]) right_total = int(strengths[right]["total"]) if left_total != right_total: selected = left if left_total > right_total else right return {"status": "selected", "sign_index": selected, "tie_break": "rashi_total"} ranks = karaka_ranks(planet_lons) left_lord = SIGN_LORDS[SIGNS[left]] right_lord = SIGN_LORDS[SIGNS[right]] left_rank = ranks.get(left_lord) right_rank = ranks.get(right_lord) if left_rank is None or right_rank is None or left_rank == right_rank: return { "status": "unresolved", "reason": "equal_rashi_strength_and_karaka", "candidates": (left, right), } selected = left if left_rank < right_rank else right return {"status": "selected", "sign_index": selected, "tie_break": "lord_karaka_rank"}