Files
Jyotisha/tests/test_kp_system.py
2026-06-24 14:59:57 +08:00

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'