f83db2fac1
- 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
408 lines
14 KiB
Python
408 lines
14 KiB
Python
"""
|
|
Test suite for Vimshottari Dasha calculations in jyotishganit.
|
|
|
|
Tests dasha period calculations, Mahadasha start dates, sub-period divisions,
|
|
and consistency with classical references.
|
|
"""
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
from jyotishganit.core.constants import (
|
|
HUMAN_LIFE_SPAN_FOR_VIMSHOTTARI,
|
|
VIMSHOTTARI_DASHA_DURATIONS,
|
|
)
|
|
from jyotishganit.dasha.vimshottari import (
|
|
calculate_dasha_start_date,
|
|
calculate_vimshottari_dashas,
|
|
get_next_adhipati,
|
|
)
|
|
from jyotishganit.main import calculate_birth_chart
|
|
|
|
|
|
class TestDashaConstants:
|
|
"""Test Vimshottari constants and fundamental values."""
|
|
|
|
def test_dasha_durations_sum_correct(self):
|
|
"""Verify total dasha durations sum to human lifespan."""
|
|
total_years = sum(VIMSHOTTARI_DASHA_DURATIONS.values())
|
|
expected_total = HUMAN_LIFE_SPAN_FOR_VIMSHOTTARI
|
|
|
|
assert total_years == expected_total, (
|
|
f"Total dasha years {total_years} != {expected_total}"
|
|
)
|
|
|
|
def test_all_planets_have_durations(self):
|
|
"""Verify all 9 Vimshottari planets have duration assignments."""
|
|
expected_planets = [
|
|
"Sun",
|
|
"Moon",
|
|
"Mars",
|
|
"Rahu",
|
|
"Jupiter",
|
|
"Saturn",
|
|
"Mercury",
|
|
"Ketu",
|
|
"Venus",
|
|
]
|
|
actual_planets = list(VIMSHOTTARI_DASHA_DURATIONS.keys())
|
|
|
|
assert set(actual_planets) == set(expected_planets)
|
|
|
|
def test_dasha_durations_positive(self):
|
|
"""Verify all dasha durations are positive."""
|
|
for planet, years in VIMSHOTTARI_DASHA_DURATIONS.items():
|
|
assert years > 0, f"{planet} has non-positive duration: {years}"
|
|
|
|
|
|
class TestMoonNakshatraCalculation:
|
|
"""Test Moon nakshatra calculation for dasha determination."""
|
|
|
|
def test_nakshatra_index_range(self):
|
|
"""Test nakshatra calculation returns valid index."""
|
|
birth = datetime(2020, 5, 15, 12, 0, 0)
|
|
chart = calculate_birth_chart(birth, 28.6139, 77.2090, 5.5)
|
|
|
|
# Extract Moon position for nakshatra calc
|
|
moon = next(
|
|
(p for p in chart.d1_chart.planets if p.celestial_body == "Moon"), None
|
|
)
|
|
assert moon is not None
|
|
|
|
# Mock the calculation (would use Skyfield in real implementation)
|
|
moon_longitude = [
|
|
"Aries",
|
|
"Taurus",
|
|
"Gemini",
|
|
"Cancer",
|
|
"Leo",
|
|
"Virgo",
|
|
"Libra",
|
|
"Scorpio",
|
|
"Sagittarius",
|
|
"Capricorn",
|
|
"Aquarius",
|
|
"Pisces",
|
|
].index(moon.sign) * 30 + moon.sign_degrees
|
|
|
|
nakshatra_span = 360.0 / 27.0
|
|
nak_index = int(moon_longitude / nakshatra_span)
|
|
remainder_degs = moon_longitude % nakshatra_span
|
|
|
|
# Valid range checks
|
|
assert 0 <= nak_index <= 26, f"Nakshatra index {nak_index} out of range 0-26"
|
|
assert 0 <= remainder_degs <= nakshatra_span, (
|
|
f"Remainder {remainder_degs} out of range"
|
|
)
|
|
|
|
|
|
class TestDashaStartCalculation:
|
|
"""Test calculation of dasha start dates from birth."""
|
|
|
|
def test_start_date_before_birth(self):
|
|
"""Verify dasha start date is always before birth date."""
|
|
birth = datetime(1994, 10, 23, 10, 20, 0)
|
|
chart = calculate_birth_chart(birth, 19.0760, 72.8777, 5.5)
|
|
|
|
_, dasha_start = calculate_dasha_start_date(birth, 5.5, chart.ayanamsa.value)
|
|
|
|
assert dasha_start < birth, (
|
|
f"Dasha start {dasha_start} should be before birth {birth}"
|
|
)
|
|
|
|
def test_birth_within_dasha_period(self):
|
|
"""Verify birth date falls within calculated dasha period."""
|
|
birth = datetime(1994, 10, 23, 10, 20, 0)
|
|
chart = calculate_birth_chart(birth, 19.0760, 72.8777, 5.5)
|
|
|
|
lord, start_date = calculate_dasha_start_date(birth, 5.5, chart.ayanamsa.value)
|
|
|
|
# Calculate period end
|
|
duration_years = VIMSHOTTARI_DASHA_DURATIONS[lord]
|
|
end_date = start_date + timedelta(days=duration_years * 365.25)
|
|
|
|
# Birth should be within this period
|
|
assert start_date <= birth <= end_date, (
|
|
f"Birth {birth} not within period {start_date} to {end_date}"
|
|
)
|
|
|
|
def test_valid_planet_returned(self):
|
|
"""Verify returned lord is a valid Vimshottari planet."""
|
|
birth = datetime(1994, 10, 23, 10, 20, 0)
|
|
chart = calculate_birth_chart(birth, 19.0760, 72.8777, 5.5)
|
|
|
|
lord, _ = calculate_dasha_start_date(birth, 5.5, chart.ayanamsa.value)
|
|
|
|
assert lord in VIMSHOTTARI_DASHA_DURATIONS, f"Invalid lord returned: {lord}"
|
|
|
|
|
|
class TestSequenceLogic:
|
|
"""Test the Vimshottari sequence progression."""
|
|
|
|
def test_next_adhipati_cyclic(self):
|
|
"""Test that next lord cycles through complete sequence."""
|
|
# Start with Sun and get full cycle
|
|
current = "Sun"
|
|
sequence = [current]
|
|
|
|
for _ in range(8): # Should get all planets except starting one
|
|
current = get_next_adhipati(current)
|
|
sequence.append(current)
|
|
|
|
# Should return to Sun
|
|
next_after_last = get_next_adhipati(sequence[-1])
|
|
assert next_after_last == "Sun"
|
|
|
|
def test_sequence_order_consistent(self):
|
|
"""Test Vimshottari sequence matches classical order."""
|
|
# Classical Vimshottari sequence
|
|
expected_sequence = [
|
|
"Sun",
|
|
"Moon",
|
|
"Mars",
|
|
"Rahu",
|
|
"Jupiter",
|
|
"Saturn",
|
|
"Mercury",
|
|
"Ketu",
|
|
"Venus",
|
|
]
|
|
|
|
current = "Sun"
|
|
actual_sequence = [current]
|
|
|
|
for i in range(8):
|
|
current = get_next_adhipati(current)
|
|
actual_sequence.append(current)
|
|
|
|
# Check each step matches expected
|
|
assert current == expected_sequence[i + 1], (
|
|
f"Sequence mismatch at position {i + 1}: expected {expected_sequence[i + 1]}, got {current}"
|
|
)
|
|
|
|
assert actual_sequence == expected_sequence
|
|
|
|
|
|
class TestCompleteDashaCalculation:
|
|
"""Test full Vimshottari dasha calculations."""
|
|
|
|
def test_dashas_all_periods_structure(self, sample_vedic_chart_delhi):
|
|
"""Test structure of complete dasha periods."""
|
|
chart = sample_vedic_chart_delhi
|
|
|
|
dashas = calculate_vimshottari_dashas(
|
|
chart.person.birth_datetime,
|
|
chart.person.timezone_offset,
|
|
chart.person.latitude,
|
|
chart.person.longitude,
|
|
chart.ayanamsa.value,
|
|
)
|
|
|
|
# Should have all major sections
|
|
assert hasattr(dashas, "all")
|
|
assert hasattr(dashas, "current")
|
|
assert hasattr(dashas, "upcoming")
|
|
assert hasattr(dashas, "balance")
|
|
|
|
# Should have mahadashas
|
|
assert "mahadashas" in dashas.all
|
|
|
|
mahadashas = dashas.all["mahadashas"]
|
|
assert len(mahadashas) == 9, f"Expected 9 mahadashas, got {len(mahadashas)}"
|
|
|
|
def test_mahadasha_coverage(self):
|
|
"""Test that mahadashas cover human lifespan."""
|
|
birth = datetime(1994, 10, 23, 10, 20, 0)
|
|
chart = calculate_birth_chart(birth, 19.0760, 72.8777, 5.5)
|
|
|
|
dashas = calculate_vimshottari_dashas(
|
|
birth, 5.5, 19.0760, 72.8777, chart.ayanamsa.value
|
|
)
|
|
|
|
total_span = timedelta(0)
|
|
for _lord, data in dashas.all["mahadashas"].items():
|
|
duration = data["end"] - data["start"]
|
|
total_span += duration
|
|
|
|
# Should cover expected lifespan (with some tolerance for calculation)
|
|
expected_days = HUMAN_LIFE_SPAN_FOR_VIMSHOTTARI * 365.25
|
|
actual_days = total_span.total_seconds() / (24 * 3600)
|
|
|
|
tolerance_days = 10 # Allow small calculation differences
|
|
assert abs(actual_days - expected_days) < tolerance_days
|
|
|
|
def test_subperiods_exist(self, sample_vedic_chart_delhi):
|
|
"""Test that antardashas and pratyantardashas are calculated."""
|
|
chart = sample_vedic_chart_delhi
|
|
|
|
dashas = calculate_vimshottari_dashas(
|
|
chart.person.birth_datetime,
|
|
chart.person.timezone_offset,
|
|
chart.person.latitude,
|
|
chart.person.longitude,
|
|
chart.ayanamsa.value,
|
|
max_depth=3, # Include pratyantardashas
|
|
)
|
|
|
|
# Check first mahadasha
|
|
first_md = list(dashas.all["mahadashas"].keys())[0]
|
|
ma_data = dashas.all["mahadashas"][first_md]
|
|
|
|
# Should have antardashas
|
|
assert "antardashas" in ma_data
|
|
antardashas = ma_data["antardashas"]
|
|
|
|
# Should have 9 antardashas
|
|
assert len(antardashas) == 9
|
|
|
|
# Check first antardasha has pratyantardashas
|
|
first_ad = list(antardashas.keys())[0]
|
|
ad_data = antardashas[first_ad]
|
|
|
|
assert "pratyantardashas" in ad_data
|
|
assert len(ad_data["pratyantardashas"]) == 9
|
|
|
|
def test_balance_calculation(self):
|
|
"""Test dasha balance at birth calculation."""
|
|
birth = datetime(1994, 10, 23, 10, 20, 0)
|
|
chart = calculate_birth_chart(birth, 19.0760, 72.8777, 5.5)
|
|
|
|
dashas = calculate_vimshottari_dashas(
|
|
birth, 5.5, 19.0760, 72.8777, chart.ayanamsa.value
|
|
)
|
|
|
|
# Should have balance for one planet
|
|
assert len(dashas.balance) == 1
|
|
|
|
balance_planet = list(dashas.balance.keys())[0]
|
|
balance_years = dashas.balance[balance_planet]
|
|
|
|
# Balance should be positive (remaining years in current dasha)
|
|
assert balance_years > 0, f"Balance years should be positive: {balance_years}"
|
|
|
|
# Should be less than or equal to total duration of that planet's dasha
|
|
total_duration = VIMSHOTTARI_DASHA_DURATIONS[balance_planet]
|
|
assert balance_years <= total_duration, (
|
|
f"Balance {balance_years} exceeds total duration {total_duration}"
|
|
)
|
|
|
|
def test_current_dasha_identification(self, sample_vedic_chart_delhi):
|
|
"""Test identification of current running dasha."""
|
|
chart = sample_vedic_chart_delhi
|
|
|
|
dashas = calculate_vimshottari_dashas(
|
|
chart.person.birth_datetime,
|
|
chart.person.timezone_offset,
|
|
chart.person.latitude,
|
|
chart.person.longitude,
|
|
chart.ayanamsa.value,
|
|
)
|
|
|
|
# Current should exist and have proper structure
|
|
assert "mahadashas" in dashas.current
|
|
assert len(dashas.current["mahadashas"]) == 1 # Only one current mahadasha
|
|
|
|
current_md = list(dashas.current["mahadashas"].keys())[0]
|
|
current_data = dashas.current["mahadashas"][current_md]
|
|
|
|
# Should have start/end dates and antardashas
|
|
assert "start" in current_data
|
|
assert "end" in current_data
|
|
assert "antardashas" in current_data
|
|
|
|
# Current time should be within current mahadasha period
|
|
now = datetime.now()
|
|
assert current_data["start"] <= now <= current_data["end"]
|
|
|
|
def test_upcoming_dashas(self, sample_vedic_chart_delhi):
|
|
"""Test upcoming dasha identification."""
|
|
chart = sample_vedic_chart_delhi
|
|
|
|
dashas = calculate_vimshottari_dashas(
|
|
chart.person.birth_datetime,
|
|
chart.person.timezone_offset,
|
|
chart.person.latitude,
|
|
chart.person.longitude,
|
|
chart.ayanamsa.value,
|
|
)
|
|
|
|
# Should have upcoming periods
|
|
assert "mahadashas" in dashas.upcoming
|
|
upcoming_count = sum(
|
|
len(md_data.get("antardashas", {}))
|
|
for md_data in dashas.upcoming["mahadashas"].values()
|
|
)
|
|
|
|
# Should have 3 upcoming antardashas as mentioned in implementation
|
|
assert upcoming_count <= 3, f"Too many upcoming periods: {upcoming_count}"
|
|
|
|
|
|
class TestConsistency:
|
|
"""Test calculation consistency and edge cases."""
|
|
|
|
def test_deterministic_calculations(self):
|
|
"""Test that repeated calculations give same results."""
|
|
birth = datetime(1994, 10, 23, 10, 20, 0)
|
|
chart = calculate_birth_chart(birth, 19.0760, 72.8777, 5.5)
|
|
|
|
dashas1 = calculate_vimshottari_dashas(
|
|
birth, 5.5, 19.0760, 72.8777, chart.ayanamsa.value
|
|
)
|
|
dashas2 = calculate_vimshottari_dashas(
|
|
birth, 5.5, 19.0760, 72.8777, chart.ayanamsa.value
|
|
)
|
|
|
|
# Balance should be identical
|
|
assert dashas1.balance == dashas2.balance
|
|
|
|
# Check few dates are same (avoid full nested comparison)
|
|
first_md_1 = list(dashas1.all["mahadashas"].keys())[0]
|
|
first_md_2 = list(dashas2.all["mahadashas"].keys())[0]
|
|
|
|
assert first_md_1 == first_md_2
|
|
|
|
date1 = dashas1.all["mahadashas"][first_md_1]["start"]
|
|
date2 = dashas2.all["mahadashas"][first_md_2]["start"]
|
|
|
|
assert date1 == date2
|
|
|
|
def test_different_ayanamsa_effect(self):
|
|
"""Test that different ayanamsa values affect moon nakshatra."""
|
|
birth = datetime(2020, 5, 15, 12, 0, 0)
|
|
|
|
# Calculate with two different ayanamsa values
|
|
dashas_lahiri = calculate_vimshottari_dashas(
|
|
birth, 5.5, 19.0760, 72.8777, 23.85
|
|
) # Lahiri
|
|
dashas_pushya = calculate_vimshottari_dashas(
|
|
birth, 5.5, 19.0760, 72.8777, 21.21
|
|
) # Pushya
|
|
|
|
# Starting dasha lord might differ (moon nakshatra depends on ayanamsa)
|
|
lord_lahiri = list(dashas_lahiri.balance.keys())[0]
|
|
lord_pushya = list(dashas_pushya.balance.keys())[0]
|
|
|
|
# Might be different due to ayanamsa difference
|
|
# Just verify both return valid lords
|
|
assert lord_lahiri in VIMSHOTTARI_DASHA_DURATIONS
|
|
assert lord_pushya in VIMSHOTTARI_DASHA_DURATIONS
|
|
|
|
def test_edge_dates(self):
|
|
"""Test dasha calculation for edge dates."""
|
|
edge_dates = [
|
|
datetime(1900, 1, 1, 0, 0, 0), # Century start
|
|
datetime(2000, 1, 1, 0, 0, 0), # Millennium
|
|
datetime(2020, 2, 29, 12, 0, 0), # Leap year leap day
|
|
datetime(2023, 12, 31, 23, 59, 59), # Year end
|
|
]
|
|
|
|
for birth_date in edge_dates:
|
|
chart = calculate_birth_chart(birth_date, 28.6139, 77.2090, 0.0)
|
|
|
|
dashas = calculate_vimshottari_dashas(
|
|
birth_date, 0.0, 28.6139, 77.2090, chart.ayanamsa.value
|
|
)
|
|
|
|
# Should not raise exceptions and have valid structure
|
|
assert len(dashas.all["mahadashas"]) == 9
|
|
assert len(dashas.balance) == 1
|