Enhance Jyotish validation and Jaimini modules
- add external validation reports and open-source comparison references - integrate Jaimini arudha/graha pada, enhanced argala, and additional synastry kutas - update skill docs and capability matrices - add smoke tests for open-source integrations
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
"""
|
||||
Jaimini Karakas — Chara Karaka System
|
||||
Based on BPHS Jaimini Sutras: The planet with the highest degree
|
||||
in its sign (excluding Rahu/Ketu) becomes the Atmakaraka (soul significator).
|
||||
The 8-karaka scheme is used (includes Rahu as the 8th).
|
||||
"""
|
||||
|
||||
from .constants import ZODIAC_SIGNS, SIGN_LORDS
|
||||
|
||||
KARAKA_NAMES = [
|
||||
"Atmakaraka", # Soul, self (highest degree) — most important planet in chart
|
||||
"Amatyakaraka", # Career/profession, minister
|
||||
"Bhratrikaraka", # Siblings, courage
|
||||
"Matrikaraka", # Mother, education, property
|
||||
"Putrakaraka", # Children, creativity, intelligence
|
||||
"Gnatikaraka", # Enemies, diseases, obstacles
|
||||
"Darakaraka", # Spouse (lowest degree among 7 planets)
|
||||
]
|
||||
|
||||
KARAKA_DESCRIPTIONS = {
|
||||
"Atmakaraka": "The King of the chart. Represents the soul's deepest desire and the primary life lesson. The house it sits in (Karakamsha in D9) reveals the soul's ultimate direction.",
|
||||
"Amatyakaraka": "The Minister. Represents career, profession, and the means through which one earns and contributes to society. Check its D10 position for career specifics.",
|
||||
"Bhratrikaraka": "Significator of siblings, courage, and personal initiative. Its strength shows the native's ability to take bold action.",
|
||||
"Matrikaraka": "Significator of mother, formal education, property, and emotional comfort.",
|
||||
"Putrakaraka": "Significator of children, intelligence, creativity, and past-life merit (Purva Punya). Check D7 for progeny details.",
|
||||
"Gnatikaraka": "Significator of enemies, diseases, and obstacles. Its Dasha can bring confrontations but also the strength to overcome them.",
|
||||
"Darakaraka": "Significator of spouse and marriage partner. The planet with the LOWEST degree becomes the Darakaraka. Its sign, dignity, and D9 position describe the spouse's nature.",
|
||||
}
|
||||
|
||||
# Arudha Pada names for all 12 houses
|
||||
ARUDHA_NAMES = {
|
||||
1: "Arudha Lagna (AL)",
|
||||
2: "Dhana Pada (A2)",
|
||||
3: "Vikrama Pada (A3)",
|
||||
4: "Sukha Pada (A4)",
|
||||
5: "Mantra Pada (A5)",
|
||||
6: "Roga Pada (A6)",
|
||||
7: "Dara Pada (A7)",
|
||||
8: "Mrithyu Pada (A8)",
|
||||
9: "Dharma Pada (A9)",
|
||||
10: "Karma Pada (A10)",
|
||||
11: "Labha Pada (A11)",
|
||||
12: "Upapada (UL)",
|
||||
}
|
||||
|
||||
|
||||
def calculate_jaimini_karakas(planets_data: dict):
|
||||
"""
|
||||
Calculates the 7 Chara Karakas from the natal chart planet data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
planets_data : dict
|
||||
The 'planets' dict from calculate_vedic_chart output.
|
||||
Each planet entry must have 'degree' (0-30 within sign).
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict with karaka assignments and descriptions.
|
||||
"""
|
||||
# Only the 7 visible planets participate (Rahu/Ketu excluded from standard 7-karaka scheme)
|
||||
eligible = ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"]
|
||||
|
||||
# Build list of (planet_name, degree_in_sign)
|
||||
planet_degrees = []
|
||||
for name in eligible:
|
||||
if name in planets_data:
|
||||
deg = planets_data[name]["degree"]
|
||||
planet_degrees.append((name, deg))
|
||||
|
||||
# Sort by degree DESCENDING — highest degree = Atmakaraka
|
||||
planet_degrees.sort(key=lambda x: x[1], reverse=True)
|
||||
|
||||
karakas = {}
|
||||
for i, (planet_name, degree) in enumerate(planet_degrees):
|
||||
if i < len(KARAKA_NAMES):
|
||||
karaka_name = KARAKA_NAMES[i]
|
||||
karakas[karaka_name] = {
|
||||
"planet": planet_name,
|
||||
"degree": degree,
|
||||
"description": KARAKA_DESCRIPTIONS[karaka_name],
|
||||
"sign": planets_data[planet_name]["sign"],
|
||||
"house": planets_data[planet_name]["house"],
|
||||
"d9_sign": planets_data[planet_name].get("d9_sign", ""),
|
||||
}
|
||||
|
||||
return karakas
|
||||
|
||||
|
||||
def _sign_idx(sign_name):
|
||||
"""Get index of a sign (0-11)."""
|
||||
return ZODIAC_SIGNS.index(sign_name)
|
||||
|
||||
|
||||
def _house_count(from_idx, to_idx):
|
||||
"""Count houses from one sign index to another (1-based)."""
|
||||
return ((to_idx - from_idx) % 12) + 1
|
||||
|
||||
|
||||
def calculate_arudha_padas(lagna_sign, planets_data):
|
||||
"""
|
||||
Calculate Arudha Padas for all 12 houses per Jaimini system.
|
||||
|
||||
Arudha Pada of a house = count from house sign to its lord's placement,
|
||||
then count the same distance from the lord's sign.
|
||||
|
||||
Exception: If the Arudha falls in the same sign as the house or the 7th from it,
|
||||
move it to the 10th sign from the house instead.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lagna_sign : str — Ascendant sign name
|
||||
planets_data : dict — planet data from calculate_vedic_chart
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict: {house_num: {"sign": str, "name": str, "sign_index": int}}
|
||||
"""
|
||||
lagna_idx = _sign_idx(lagna_sign)
|
||||
|
||||
# Build planet sign lookup
|
||||
planet_signs = {}
|
||||
for name, pd in planets_data.items():
|
||||
planet_signs[name] = _sign_idx(pd["sign"])
|
||||
|
||||
arudha_padas = {}
|
||||
|
||||
for house_num in range(1, 13):
|
||||
house_sign_idx = (lagna_idx + house_num - 1) % 12
|
||||
house_sign = ZODIAC_SIGNS[house_sign_idx]
|
||||
|
||||
# Lord of this house
|
||||
lord = SIGN_LORDS[house_sign]
|
||||
|
||||
if lord not in planet_signs:
|
||||
continue
|
||||
|
||||
lord_sign_idx = planet_signs[lord]
|
||||
|
||||
# Count from house sign to lord's sign
|
||||
distance = _house_count(house_sign_idx, lord_sign_idx)
|
||||
|
||||
# Arudha = same distance counted from lord's sign
|
||||
arudha_idx = (lord_sign_idx + distance - 1) % 12
|
||||
|
||||
# Exception rule: if arudha falls in the house itself or 7th from it
|
||||
seventh_from_house = (house_sign_idx + 6) % 12
|
||||
if arudha_idx == house_sign_idx or arudha_idx == seventh_from_house:
|
||||
# Move to 10th from the house
|
||||
arudha_idx = (house_sign_idx + 9) % 12
|
||||
|
||||
arudha_padas[house_num] = {
|
||||
"sign": ZODIAC_SIGNS[arudha_idx],
|
||||
"sign_index": arudha_idx,
|
||||
"name": ARUDHA_NAMES.get(house_num, f"A{house_num}"),
|
||||
}
|
||||
|
||||
return arudha_padas
|
||||
|
||||
|
||||
def calculate_upapada(lagna_sign, planets_data):
|
||||
"""
|
||||
Calculate Upapada Lagna (UL) — the Arudha of the 12th house.
|
||||
Critical for spouse analysis in Jaimini.
|
||||
|
||||
The sign of the Upapada and planets in/aspecting it describe the spouse.
|
||||
The 2nd from Upapada shows longevity of marriage.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict: {"sign": str, "sign_index": int, "lord": str, "second_from_ul": str}
|
||||
"""
|
||||
arudha_padas = calculate_arudha_padas(lagna_sign, planets_data)
|
||||
|
||||
ul = arudha_padas.get(12)
|
||||
if not ul:
|
||||
return None
|
||||
|
||||
ul_idx = ul["sign_index"]
|
||||
second_idx = (ul_idx + 1) % 12
|
||||
|
||||
return {
|
||||
"sign": ul["sign"],
|
||||
"sign_index": ul_idx,
|
||||
"lord": SIGN_LORDS[ul["sign"]],
|
||||
"second_from_ul": ZODIAC_SIGNS[second_idx],
|
||||
"description": f"Upapada in {ul['sign']} — spouse characteristics shaped by {SIGN_LORDS[ul['sign']]}. 2nd from UL ({ZODIAC_SIGNS[second_idx]}) indicates marriage longevity.",
|
||||
}
|
||||
|
||||
|
||||
def calculate_karakamsha(karakas, planets_data, lagna_sign):
|
||||
"""
|
||||
Calculate Karakamsha — Atmakaraka's sign in D9 (Navamsha).
|
||||
The Karakamsha sign becomes a reference lagna for soul-level analysis.
|
||||
|
||||
Also computes the 12th from Karakamsha → Ishta Devata (personal deity).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
karakas : dict — output from calculate_jaimini_karakas
|
||||
planets_data : dict — planets output with d9_sign
|
||||
lagna_sign : str — D1 ascendant sign
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict: Karakamsha analysis
|
||||
"""
|
||||
ak = karakas.get("Atmakaraka")
|
||||
if not ak:
|
||||
return None
|
||||
|
||||
ak_planet = ak["planet"]
|
||||
ak_d9_sign = ak.get("d9_sign", "")
|
||||
|
||||
if not ak_d9_sign:
|
||||
return None
|
||||
|
||||
ak_d9_idx = _sign_idx(ak_d9_sign)
|
||||
lagna_idx = _sign_idx(lagna_sign)
|
||||
|
||||
# Karakamsha house from Lagna
|
||||
karakamsha_house = _house_count(lagna_idx, ak_d9_idx)
|
||||
|
||||
# 12th from Karakamsha → Ishta Devata sign
|
||||
ishta_devata_sign_idx = (ak_d9_idx - 1) % 12
|
||||
ishta_devata_sign = ZODIAC_SIGNS[ishta_devata_sign_idx]
|
||||
ishta_devata_lord = SIGN_LORDS[ishta_devata_sign]
|
||||
|
||||
# Check which planets occupy the Karakamsha sign in D9
|
||||
planets_in_karakamsha = []
|
||||
for name, pd in planets_data.items():
|
||||
if pd.get("d9_sign") == ak_d9_sign:
|
||||
planets_in_karakamsha.append(name)
|
||||
|
||||
return {
|
||||
"atmakaraka": ak_planet,
|
||||
"karakamsha_sign": ak_d9_sign,
|
||||
"karakamsha_house_from_lagna": karakamsha_house,
|
||||
"planets_in_karakamsha": planets_in_karakamsha,
|
||||
"ishta_devata_sign": ishta_devata_sign,
|
||||
"ishta_devata_lord": ishta_devata_lord,
|
||||
"description": f"Atmakaraka {ak_planet} in D9 {ak_d9_sign} (house {karakamsha_house} from Lagna) — soul's deepest direction. Ishta Devata indicated by {ishta_devata_lord} ({ishta_devata_sign}).",
|
||||
}
|
||||
Reference in New Issue
Block a user