93 lines
3.1 KiB
Python
93 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""KP SubLord oracle tests based on VedicAstro KP_SL_Divisions.csv.
|
|
|
|
These tests protect the most precision-sensitive part of KP timing: the
|
|
Nakshatra/SubLord degree partitions. A wrong boundary changes event houses and
|
|
therefore changes concrete predictions.
|
|
"""
|
|
import csv
|
|
import os
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'scripts'))
|
|
|
|
from kp_system import get_kp_lords
|
|
|
|
SIGNS = [
|
|
'Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
|
|
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces'
|
|
]
|
|
|
|
CSV_PATH = os.path.join(
|
|
os.path.dirname(__file__),
|
|
'..',
|
|
'references',
|
|
'open_source_sources',
|
|
'VedicAstro',
|
|
'vedicastro',
|
|
'data',
|
|
'KP_SL_Divisions.csv',
|
|
)
|
|
|
|
|
|
def _dms_to_degree(value: str) -> float:
|
|
"""Convert DMS string like 03:40:00 into decimal degrees."""
|
|
parts = [p for p in value.strip().split(':') if p != '']
|
|
deg, minute, second = [float(x) for x in parts[:3]]
|
|
return deg + minute / 60.0 + second / 3600.0
|
|
|
|
|
|
def _load_rows(limit=None):
|
|
if not os.path.exists(CSV_PATH):
|
|
pytest.skip(f"VedicAstro KP oracle fixture not available: {CSV_PATH}")
|
|
with open(CSV_PATH, newline='', encoding='utf-8') as f:
|
|
rows = list(csv.DictReader(f))
|
|
return rows if limit is None else rows[:limit]
|
|
|
|
|
|
def _absolute_midpoint(row):
|
|
sign_offset = SIGNS.index(row['Sign']) * 30.0
|
|
start = _dms_to_degree(row['From_DMS'])
|
|
end = _dms_to_degree(row['To_DMS'])
|
|
return sign_offset + (start + end) / 2.0
|
|
|
|
|
|
def test_kp_sublord_matches_vedicastro_csv_first_36_segments():
|
|
"""First 36 segments cover four complete Nakshatras across Aries/Taurus."""
|
|
for row in _load_rows(limit=36):
|
|
degree = _absolute_midpoint(row)
|
|
actual = get_kp_lords(degree)
|
|
assert actual['sign'] == row['Sign']
|
|
assert actual['rasi_lord'] == row['RasiLord']
|
|
assert actual['nakshatra_lord'] == row['NakshatraLord']
|
|
assert actual['sub_lord'] == row['SubLord'], (
|
|
f"degree={degree:.6f} expected SL={row['SubLord']} got {actual['sub_lord']} row={row}"
|
|
)
|
|
|
|
|
|
def test_kp_sublord_near_internal_boundaries():
|
|
"""Boundary +/- epsilon should fall into adjacent CSV rows."""
|
|
rows = _load_rows(limit=12)
|
|
epsilon = 1e-6
|
|
for i in range(1, len(rows)):
|
|
prev_row = rows[i - 1]
|
|
row = rows[i]
|
|
if prev_row['Sign'] != row['Sign']:
|
|
continue
|
|
boundary = SIGNS.index(row['Sign']) * 30.0 + _dms_to_degree(row['From_DMS'])
|
|
before = get_kp_lords(boundary - epsilon)
|
|
after = get_kp_lords(boundary + epsilon)
|
|
assert before['sub_lord'] == prev_row['SubLord']
|
|
assert after['sub_lord'] == row['SubLord']
|
|
|
|
|
|
def test_kp_lords_wrap_at_360_degrees():
|
|
"""360° and 0° should both resolve to Aries/Ashvini/Ketu segment."""
|
|
zero = get_kp_lords(0.0)
|
|
wrapped = get_kp_lords(360.0)
|
|
assert zero['sign'] == wrapped['sign'] == 'Aries'
|
|
assert zero['nakshatra_lord'] == wrapped['nakshatra_lord'] == 'Ketu'
|
|
assert zero['sub_lord'] == wrapped['sub_lord'] == 'Ketu'
|