Files
Jyotisha/scripts/shattrimshatsama_dasha.py
T
Jesse_ChenandCursor 04ad3325d5
Independent Staging Quality Gate / validate (push) Successful in 11m26s
Independent Staging Quality Gate / publish (push) Successful in 13m24s
fix(relationship): freeze marriage event classes and add conditional dashas (BUG-608)
Venus/Jupiter/Moon periods no longer collapse into one marriage-opportunity line. Full reading reports the ten BPHS conditional dasha families; Skill 6.9.16.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 16:04:52 +08:00

121 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""Source-bounded Shattrimshatsama (36-year) Mahadasha producer."""
from __future__ import annotations
from datetime import datetime, timedelta
from typing import Any
YEAR_DAYS = 365.25636
NAKSHATRA_SPAN = 360.0 / 27.0
SRAVANA_INDEX = 21
TOTAL_CYCLE = 36
DASHA_SEQUENCE = (
("Moon", 1),
("Sun", 2),
("Jupiter", 3),
("Mars", 4),
("Mercury", 5),
("Saturn", 6),
("Venus", 7),
("Rahu", 8),
)
_YEARS = dict(DASHA_SEQUENCE)
def shattrimshatsama_applicability(*, birth_is_daytime: bool, ascendant_hora_lord: str) -> dict[str, Any]:
"""Apply the day/Sun-Hora or night/Moon-Hora condition."""
hora_lord = str(ascendant_hora_lord).strip().title()
applicable = (bool(birth_is_daytime) and hora_lord == "Sun") or (
not bool(birth_is_daytime) and hora_lord == "Moon"
)
return {
"applicable": applicable,
"birth_is_daytime": bool(birth_is_daytime),
"ascendant_hora_lord": hora_lord,
"rule_family": "bphs_shattrimshatsama_day_sun_hora_or_night_moon_hora",
"reason": (
"Birth matches the day/Sun-Hora or night/Moon-Hora condition."
if applicable
else "Shattrimshatsama requires day birth with Sun Hora, or night birth with Moon Hora."
),
}
def _sequence_from(lord: str) -> list[tuple[str, int]]:
start = next(index for index, (item, _years) in enumerate(DASHA_SEQUENCE) if item == lord)
return list(DASHA_SEQUENCE[start:] + DASHA_SEQUENCE[:start])
def _birth_lord_and_balance(moon_longitude: float) -> tuple[str, float, int]:
moon = float(moon_longitude) % 360.0
nakshatra = int(moon // NAKSHATRA_SPAN)
lord = DASHA_SEQUENCE[((nakshatra - SRAVANA_INDEX) % 27) % len(DASHA_SEQUENCE)][0]
balance = _YEARS[lord] * (1.0 - (moon % NAKSHATRA_SPAN) / NAKSHATRA_SPAN)
return lord, balance, nakshatra
def calculate_shattrimshatsama_dasha(birth_info: dict[str, Any]) -> dict[str, Any]:
"""Return birth-forward MD rows; child-period boundaries stay blocked."""
required = ("birth_datetime", "birth_is_daytime", "ascendant_hora_lord", "moon_longitude")
missing = [field for field in required if birth_info.get(field) is None]
if missing:
return {
"applicable": False,
"execution_status": "blocked",
"confidence_status": "blocked",
"reason": "Missing required input: " + ", ".join(missing),
"major": [],
}
birth = birth_info["birth_datetime"]
if isinstance(birth, str):
birth = datetime.fromisoformat(birth)
applicability = shattrimshatsama_applicability(
birth_is_daytime=bool(birth_info["birth_is_daytime"]),
ascendant_hora_lord=str(birth_info["ascendant_hora_lord"]),
)
if not applicability["applicable"]:
return {
**applicability,
"execution_status": "not_applicable",
"confidence_status": "not_applicable",
"total_cycle": TOTAL_CYCLE,
"major": [],
}
year_days = float(birth_info.get("dasha_year_days") or YEAR_DAYS)
lord, balance, nakshatra = _birth_lord_and_balance(float(birth_info["moon_longitude"]))
cursor = birth
rows = []
for index, (row_lord, full_years) in enumerate(_sequence_from(lord)):
years = balance if index == 0 else float(full_years)
end = cursor + timedelta(days=years * year_days)
rows.append(
{
"level": "mahadasha",
"lord": row_lord,
"planet": row_lord,
"years": years,
"full_years": full_years,
"is_birth_balance": index == 0,
"start_date": cursor.isoformat(timespec="seconds"),
"end_date": end.isoformat(timespec="seconds"),
}
)
cursor = end
return {
**applicability,
"execution_status": "executed",
"confidence_status": "parameter_sensitive",
"verification_status": "source_bounded_local_candidate",
"period_depth_status": "mahadasha_executed_ad_pd_blocked_pending_row_level_replay",
"total_cycle": TOTAL_CYCLE,
"dasha_year_days": year_days,
"moon_nakshatra_index": nakshatra,
"starting_lord": lord,
"dasha_balance_at_birth_years": balance,
"major": rows,
"reason": "Local source-bounded MD calculation; AD/PD are intentionally blocked pending row-level parity and runtime wiring.",
"source_locator": "BPHS conditional Dasha passages: day/Sun-Hora or night/Moon-Hora gate, Sravana count, Moon-to-Rahu order, and 1-8 year allotments.",
}