The first mahadasha starts before birth, so the nine periods end before the 120th birthday. Historical charts past that point had no covering period: cmd_dasha's current_dasha was null and the consultation card's timing.vimshottari was empty (Frida Kahlo at 2026-09-27). next_cycle_mahadashas (chart_dasha/birth_moment.py) continues with the same lords, order and full 365.25-day years until a period covers the reference day; cmd_dasha, the chart surfaces and the consultation sub-periods share it. Nothing is added inside the first cycle. The frozen rectification identity (dasha_analyzer, domain_calculation_service) is untouched, so no re-freeze. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y
131 lines
6.2 KiB
Python
131 lines
6.2 KiB
Python
"""BUG-1556: Vimshottari past the first 120-year cycle.
|
|
|
|
The first mahadasha starts before birth, so the nine periods end less than 120
|
|
years after birth. Old (historical / public) charts then had no period covering
|
|
the reference date: the engine's current_dasha was null and the consultation
|
|
card's timing.vimshottari was empty. The timeline now continues with the same
|
|
lords, order and full years (365.25-day years, as the first cycle) until a
|
|
period covers the reference date; inside the first cycle nothing changes.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from types import SimpleNamespace
|
|
|
|
from scripts.chart_dasha import birth_moment
|
|
from scripts.jyotish_engine import cmd_dasha
|
|
import domain_calculation_service as dcs
|
|
import jyotish_api_server as api
|
|
|
|
# Frida Kahlo, Rodden AA: 1907-07-06 08:30, Mexico City (public chart).
|
|
KAHLO = dict(year=1907, month=7, day=6, hour=8, minute=30, second=0, lat=19.3333, lon=-99.1667, tz=-6.6167)
|
|
KAHLO_BIRTH = datetime(1907, 7, 6, 8, 30)
|
|
REFERENCE = "2026-09-27"
|
|
|
|
|
|
def _days(a: str, b: str) -> int:
|
|
return abs((datetime.strptime(a, "%Y-%m-%d") - datetime.strptime(b, "%Y-%m-%d")).days)
|
|
|
|
|
|
def _engine(today: str) -> dict:
|
|
args = SimpleNamespace(**KAHLO, ayanamsa="raman", node_mode="mean", today=today, moon_lon=None,
|
|
nakshatra=None, pada=None, birthdate=None)
|
|
out = cmd_dasha(args)
|
|
return out.get("result") or out
|
|
|
|
|
|
def _kahlo_moon() -> float:
|
|
chart = dcs.compute_chart({**KAHLO, "ayanamsa": "raman", "node_mode": "mean"})
|
|
return float(chart["planets"]["Moon"]["lon"])
|
|
|
|
|
|
def test_kahlo_current_mahadasha_and_antardasha_in_the_second_cycle():
|
|
result = _engine(REFERENCE)
|
|
current = result["current_dasha"]
|
|
assert current is not None
|
|
assert (current["lord"], current["start"], current["end"]) == ("Sun", "2022-02-20", "2028-02-21")
|
|
assert current["cycle"] == 2
|
|
ad = current["antardasha"]
|
|
assert (ad["lord"], ad["start"], ad["end"]) == ("Mercury", "2025-12-09", "2026-10-16")
|
|
assert any(pd["is_current"] for pd in ad["pratyantar_dasha_timeline"])
|
|
# Independent arithmetic (365.25-day years, from the birth date): Sun 2022-02-21 to
|
|
# 2028-02-21, Mercury 2025-12-10 to 2026-10-16. The engine starts from the birth clock.
|
|
for got, independent in ((current["start"], "2022-02-21"), (current["end"], "2028-02-21"),
|
|
(ad["start"], "2025-12-10"), (ad["end"], "2026-10-16")):
|
|
assert _days(got, independent) <= 1
|
|
# The second cycle is the first one shifted by exactly 120 years of 365.25 days.
|
|
first_sun = result["timeline"][0]
|
|
shift = datetime.fromisoformat(current["start_datetime"]) - datetime.fromisoformat(first_sun["start_datetime"])
|
|
assert shift.total_seconds() == 120 * 365.25 * 86400
|
|
|
|
|
|
def test_an_age_inside_the_first_cycle_is_unchanged():
|
|
inside = _engine("2010-01-01")
|
|
assert len(inside["timeline"]) == 9
|
|
assert all("cycle" not in row for row in inside["timeline"])
|
|
assert inside["current_dasha"]["lord"] == "Venus"
|
|
# Same nine periods as at the later date, which only appends.
|
|
later = _engine(REFERENCE)
|
|
keys = ("lord", "start_datetime", "end_datetime", "years", "is_balance")
|
|
assert [{k: r[k] for k in keys} for r in later["timeline"][:9]] == [{k: r[k] for k in keys} for r in inside["timeline"]]
|
|
|
|
moon = _kahlo_moon()
|
|
plain = birth_moment.compute_vimshottari_timeline(birth_dt=KAHLO_BIRTH, moon_lon=moon)
|
|
dated = birth_moment.compute_vimshottari_timeline(birth_dt=KAHLO_BIRTH, moon_lon=moon, current_date=datetime(2010, 1, 1))
|
|
assert dated == plain
|
|
assert len(plain["periods"]) == 9
|
|
|
|
|
|
def test_the_boundary_at_exactly_the_cycle_end():
|
|
moon = _kahlo_moon()
|
|
first, *_ = birth_moment.mahadashas(KAHLO_BIRTH, moon)
|
|
cycle_end = first[-1]["end"]
|
|
assert cycle_end.strftime("%Y-%m-%d") == "2022-02-20"
|
|
|
|
before = _engine("2022-02-19")
|
|
assert len(before["timeline"]) == 9
|
|
assert before["current_dasha"]["lord"] == "Venus"
|
|
|
|
# On the end day the next period is current, as at every other boundary (date granularity).
|
|
on = _engine("2022-02-20")
|
|
assert len(on["timeline"]) == 10
|
|
assert (on["current_dasha"]["lord"], on["current_dasha"]["start"]) == ("Sun", "2022-02-20")
|
|
assert on["timeline"][9]["start_datetime"] == on["timeline"][8]["end_datetime"]
|
|
|
|
rows, *_ = birth_moment.mahadashas(KAHLO_BIRTH, moon, through=cycle_end)
|
|
assert [row["lord"] for row in rows] == [row["lord"] for row in first] + ["Sun"]
|
|
assert rows[9]["start"] == cycle_end and rows[9]["cycle"] == 2
|
|
|
|
|
|
def test_next_cycle_keeps_lord_order_and_full_years():
|
|
moon = _kahlo_moon()
|
|
first, *_ = birth_moment.mahadashas(KAHLO_BIRTH, moon)
|
|
rows, *_ = birth_moment.mahadashas(KAHLO_BIRTH, moon, through=datetime(2142, 1, 1))
|
|
assert [r["lord"] for r in rows[9:18]] == [r["lord"] for r in first]
|
|
assert [r["years"] for r in rows[9:18]] == [r["years"] for r in first]
|
|
assert {r["cycle"] for r in rows[9:18]} == {2}
|
|
for a, b in zip(rows, rows[1:]):
|
|
assert a["end"] == b["start"]
|
|
|
|
|
|
def test_consultation_sub_periods_cover_the_reference_past_the_first_cycle():
|
|
moon = _kahlo_moon()
|
|
# A chart computed for a day inside the first cycle stops at nine periods.
|
|
periods = birth_moment.compute_vimshottari_timeline(birth_dt=KAHLO_BIRTH, moon_lon=moon)["periods"]
|
|
chart = {
|
|
"birth": {"date": "1907-07-06", "hour": 8, "minute": 30, "second": 0},
|
|
"planets": {"Moon": {"lon": moon}},
|
|
"dasha": {"periods": periods},
|
|
}
|
|
sub = api._consultation_dasha_sub_periods(chart, {"reference_date": REFERENCE})
|
|
assert sub is not None
|
|
assert sub["current"]["mahadasha"] == {"lord": "Sun", "start": "2022-02-20", "end": "2028-02-21"}
|
|
assert sub["current"]["antardasha"]["lord"] == "Mercury"
|
|
# The chart surface carries the period itself when computed for the reference day.
|
|
dated = birth_moment.compute_vimshottari_timeline(birth_dt=KAHLO_BIRTH, moon_lon=moon, current_date=datetime(2026, 9, 27))
|
|
assert dated["periods"][-1] == {**sub["current"]["mahadasha"], "years": 6, "cycle": 2}
|
|
# A rebuilt timeline that does not reproduce the chart's periods is not used.
|
|
chart["planets"]["Moon"]["lon"] = moon + 5
|
|
assert api._consultation_dasha_sub_periods(chart, {"reference_date": REFERENCE}) is None
|