Files
732642856 f83db2fac1 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
2026-06-10 20:50:52 +08:00

186 lines
6.7 KiB
Python

import datetime
from .constants import VIMSHOTTARI_YEARS, DASHA_SEQUENCE, NAK_SPAN
from .nakshatra import get_nakshatra
def _add_years_days(dt, years, days):
"""Add fractional years (as whole years + remaining days) to a datetime."""
total_days = years * 365.2425 + days
return dt + datetime.timedelta(days=total_days)
def _build_sub_periods(start_dt, total_days, starting_lord):
"""
Build sub-periods (Antardasha or Pratyantardasha) within a parent period.
The sub-period sequence starts from the parent lord and cycles through
the Dasha sequence.
"""
seq_start = DASHA_SEQUENCE.index(starting_lord)
periods = []
cursor = start_dt
for i in range(9):
lord = DASHA_SEQUENCE[(seq_start + i) % 9]
proportion = VIMSHOTTARI_YEARS[lord] / 120.0
sub_days = total_days * proportion
end = cursor + datetime.timedelta(days=sub_days)
periods.append({
"planet": lord,
"start": cursor.strftime("%Y-%m-%d"),
"end": end.strftime("%Y-%m-%d"),
"days": round(sub_days, 2),
})
cursor = end
return periods
def calculate_dashas(moon_longitude, birth_dt, query_dt=None):
"""
Compute Vimshottari Dasha timeline from Moon's sidereal longitude at birth.
Parameters
----------
moon_longitude : float
Sidereal longitude of the Moon at birth (0-360).
birth_dt : datetime.datetime
Birth datetime (timezone-aware or naive).
query_dt : datetime.datetime, optional
Date to find active Maha/Antar/Pratyantar for. Defaults to today.
Returns
-------
dict with keys: maha, antar, pratyantar, timeline
"""
if query_dt is None:
query_dt = datetime.datetime.now()
if hasattr(birth_dt, 'tzinfo') and birth_dt.tzinfo:
birth_dt = birth_dt.replace(tzinfo=None)
if hasattr(query_dt, 'tzinfo') and query_dt.tzinfo:
query_dt = query_dt.replace(tzinfo=None)
nak_info = get_nakshatra(moon_longitude)
nak_lord = nak_info["lord"]
elapsed_fraction = nak_info["degree_in_nakshatra"] / NAK_SPAN
remaining_fraction = 1.0 - elapsed_fraction
seq_start = DASHA_SEQUENCE.index(nak_lord)
# Build Mahadasha timeline starting from birth
timeline = []
cursor = birth_dt
first_maha_years = VIMSHOTTARI_YEARS[nak_lord] * remaining_fraction
first_maha_days = first_maha_years * 365.2425
first_end = cursor + datetime.timedelta(days=first_maha_days)
timeline.append({
"planet": nak_lord,
"start": cursor.strftime("%Y-%m-%d"),
"end": first_end.strftime("%Y-%m-%d"),
"years": round(first_maha_years, 4),
"days": round(first_maha_days, 2),
})
cursor = first_end
# Remaining 8 full cycles, then repeat to cover 120+ years
for cycle in range(2):
start_offset = 1 if cycle == 0 else 0
for i in range(start_offset, 9):
lord = DASHA_SEQUENCE[(seq_start + i) % 9]
years = VIMSHOTTARI_YEARS[lord]
days = years * 365.2425
end = cursor + datetime.timedelta(days=days)
timeline.append({
"planet": lord,
"start": cursor.strftime("%Y-%m-%d"),
"end": end.strftime("%Y-%m-%d"),
"years": float(years),
"days": days,
})
cursor = end
active_maha = None
active_antar = None
active_pratyantar = None
active_sukshma = None
active_prana = None
for period in timeline:
p_start = datetime.datetime.strptime(period["start"], "%Y-%m-%d")
p_end = datetime.datetime.strptime(period["end"], "%Y-%m-%d")
if p_start <= query_dt < p_end:
active_maha = period
break
if active_maha:
maha_start = datetime.datetime.strptime(active_maha["start"], "%Y-%m-%d")
maha_days = active_maha["days"]
antars = _build_sub_periods(maha_start, maha_days, active_maha["planet"])
for antar in antars:
a_start = datetime.datetime.strptime(antar["start"], "%Y-%m-%d")
a_end = datetime.datetime.strptime(antar["end"], "%Y-%m-%d")
if a_start <= query_dt < a_end:
active_antar = antar
break
if active_antar:
antar_start = datetime.datetime.strptime(active_antar["start"], "%Y-%m-%d")
antar_days = active_antar["days"]
pratyantars = _build_sub_periods(antar_start, antar_days, active_antar["planet"])
for prat in pratyantars:
pr_start = datetime.datetime.strptime(prat["start"], "%Y-%m-%d")
pr_end = datetime.datetime.strptime(prat["end"], "%Y-%m-%d")
if pr_start <= query_dt < pr_end:
active_pratyantar = prat
break
# Level 4: Sukshma Dasha
if active_pratyantar:
prat_start = datetime.datetime.strptime(active_pratyantar["start"], "%Y-%m-%d")
prat_days = active_pratyantar["days"]
sukshmas = _build_sub_periods(prat_start, prat_days, active_pratyantar["planet"])
for suk in sukshmas:
s_start = datetime.datetime.strptime(suk["start"], "%Y-%m-%d")
s_end = datetime.datetime.strptime(suk["end"], "%Y-%m-%d")
if s_start <= query_dt < s_end:
active_sukshma = suk
break
# Level 5: Prana Dasha
if active_sukshma:
suk_start = datetime.datetime.strptime(active_sukshma["start"], "%Y-%m-%d")
suk_days = active_sukshma["days"]
pranas = _build_sub_periods(suk_start, suk_days, active_sukshma["planet"])
for pra in pranas:
pra_start = datetime.datetime.strptime(pra["start"], "%Y-%m-%d")
pra_end = datetime.datetime.strptime(pra["end"], "%Y-%m-%d")
if pra_start <= query_dt < pra_end:
active_prana = pra
break
# Trim timeline to a reasonable window (birth to ~120 years)
compact_timeline = []
for t in timeline:
compact_timeline.append({
"planet": t["planet"],
"start": t["start"],
"end": t["end"],
})
t_end = datetime.datetime.strptime(t["end"], "%Y-%m-%d")
if t_end > birth_dt + datetime.timedelta(days=120 * 365.2425):
break
return {
"maha": active_maha,
"antar": active_antar,
"pratyantar": active_pratyantar,
"sukshma": active_sukshma,
"prana": active_prana,
"timeline": compact_timeline,
}