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>
149 lines
4.9 KiB
Python
149 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Source-bounded Tribhagi Dasha producer.
|
|
|
|
Tribhagi divides each Vimshottari lord's years across its three nakshatras.
|
|
The full 27-nakshatra traversal is 120 years; each nine-nakshatra bhaga is
|
|
40 years. This module emits birth-forward major rows only.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timedelta
|
|
from typing import Any
|
|
|
|
|
|
YEAR_DAYS = 365.25636
|
|
NAKSHATRA_SPAN = 360.0 / 27.0
|
|
VIMSHOTTARI_SEQUENCE = (
|
|
("Ketu", 7),
|
|
("Venus", 20),
|
|
("Sun", 6),
|
|
("Moon", 10),
|
|
("Mars", 7),
|
|
("Rahu", 18),
|
|
("Jupiter", 16),
|
|
("Saturn", 19),
|
|
("Mercury", 17),
|
|
)
|
|
NAKSHATRA_NAMES = (
|
|
"Ashwini",
|
|
"Bharani",
|
|
"Krittika",
|
|
"Rohini",
|
|
"Mrigashira",
|
|
"Ardra",
|
|
"Punarvasu",
|
|
"Pushya",
|
|
"Ashlesha",
|
|
"Magha",
|
|
"Purva Phalguni",
|
|
"Uttara Phalguni",
|
|
"Hasta",
|
|
"Chitra",
|
|
"Swati",
|
|
"Vishakha",
|
|
"Anuradha",
|
|
"Jyeshtha",
|
|
"Mula",
|
|
"Purva Ashadha",
|
|
"Uttara Ashadha",
|
|
"Shravana",
|
|
"Dhanishtha",
|
|
"Shatabhisha",
|
|
"Purva Bhadrapada",
|
|
"Uttara Bhadrapada",
|
|
"Revati",
|
|
)
|
|
FULL_CYCLE_YEARS = 120
|
|
BHAGA_CYCLE_YEARS = 40
|
|
|
|
|
|
def _nakshatra_index(moon_longitude: float) -> int:
|
|
return int(float(moon_longitude) % 360.0 // NAKSHATRA_SPAN)
|
|
|
|
|
|
def _nakshatra_fraction_elapsed(moon_longitude: float) -> float:
|
|
return (float(moon_longitude) % NAKSHATRA_SPAN) / NAKSHATRA_SPAN
|
|
|
|
|
|
def _lord_for_nakshatra(index: int) -> tuple[str, float]:
|
|
lord, years = VIMSHOTTARI_SEQUENCE[index % len(VIMSHOTTARI_SEQUENCE)]
|
|
return lord, years / 3.0
|
|
|
|
|
|
def _cycle_indices(start: int, mode: str) -> list[int]:
|
|
if mode == "full_120":
|
|
return [(start + offset) % 27 for offset in range(27)]
|
|
if mode == "single_bhaga_40":
|
|
bhaga_start = (start // 9) * 9
|
|
return [(start + offset - bhaga_start) % 9 + bhaga_start for offset in range(9)]
|
|
raise ValueError("mode must be full_120 or single_bhaga_40")
|
|
|
|
|
|
def calculate_tribhagi_dasha(birth_info: dict[str, Any]) -> dict[str, Any]:
|
|
"""Return birth-forward Tribhagi rows; child-period boundaries stay blocked."""
|
|
required = ("birth_datetime", "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": [],
|
|
}
|
|
mode = str(birth_info.get("mode") or "full_120")
|
|
try:
|
|
indices = _cycle_indices(_nakshatra_index(float(birth_info["moon_longitude"])), mode)
|
|
except ValueError as exc:
|
|
return {
|
|
"applicable": False,
|
|
"execution_status": "blocked",
|
|
"confidence_status": "blocked",
|
|
"reason": str(exc),
|
|
"major": [],
|
|
}
|
|
birth = birth_info["birth_datetime"]
|
|
if isinstance(birth, str):
|
|
birth = datetime.fromisoformat(birth)
|
|
year_days = float(birth_info.get("dasha_year_days") or YEAR_DAYS)
|
|
fraction_elapsed = _nakshatra_fraction_elapsed(float(birth_info["moon_longitude"]))
|
|
rows = []
|
|
cursor = birth
|
|
for offset, nakshatra_index in enumerate(indices):
|
|
lord, full_years = _lord_for_nakshatra(nakshatra_index)
|
|
years = full_years * (1.0 - fraction_elapsed) if offset == 0 else full_years
|
|
end = cursor + timedelta(days=years * year_days)
|
|
rows.append(
|
|
{
|
|
"level": "mahadasha",
|
|
"lord": lord,
|
|
"planet": lord,
|
|
"nakshatra_index": nakshatra_index,
|
|
"nakshatra": NAKSHATRA_NAMES[nakshatra_index],
|
|
"years": years,
|
|
"full_years": full_years,
|
|
"is_birth_balance": offset == 0,
|
|
"start_date": cursor.isoformat(timespec="seconds"),
|
|
"end_date": end.isoformat(timespec="seconds"),
|
|
}
|
|
)
|
|
cursor = end
|
|
return {
|
|
"schema": "tribhagi_dasha_v1",
|
|
"applicable": True,
|
|
"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",
|
|
"mode": mode,
|
|
"total_cycle": FULL_CYCLE_YEARS if mode == "full_120" else BHAGA_CYCLE_YEARS,
|
|
"dasha_year_days": year_days,
|
|
"starting_nakshatra_index": indices[0],
|
|
"starting_lord": rows[0]["lord"] if rows else None,
|
|
"dasha_balance_at_birth_years": rows[0]["years"] if rows else 0.0,
|
|
"major": rows,
|
|
"reason": "Local source-bounded Tribhagi MD calculation; AD/PD and PL9 semantic mapping remain blocked pending row-level replay.",
|
|
"source_locator": "Public Tribhagi explanations: Vimshottari lord years divided across three ruled nakshatras; full 120-year cycle or single 40-year bhaga view.",
|
|
}
|