Files
Jyotisha/tests/test_shadbala_complete.py
T
linmeng f4e35974c6
Deploy staging to test server / deploy (push) Failing after 14m49s
Revert "merge: sync GitHub staging to Gitea"
This reverts commit a55c69115d, reversing
changes made to 02c9c9f3d6.
2026-07-30 14:38:17 +08:00

317 lines
13 KiB
Python

#!/usr/bin/env python3
"""Tests for shadbala.py — 6-fold strength, Kendra 3-tier, Bhava Bala, Hora Bala."""
from __future__ import annotations
import json
from pathlib import Path
import sys, os, pytest
SCRIPTS = os.path.join(os.path.dirname(__file__), '..', 'scripts')
if SCRIPTS not in sys.path:
sys.path.insert(0, SCRIPTS)
from shadbala import (
calc_shadbala, calc_sthana_bala, calc_dig_bala, calc_kala_bala,
calc_chesta_bala, calc_drik_bala, calc_bhava_bala,
NAISARGIKA_BALA, MIN_REQUIRED, DIG_BALA_HOUSE,
EXALTATION_DEG, DEBILITATION_DEG, FRIENDSHIP, SHADBALA_CONSTANTS_PATH,
)
from scripts.shadbala_oracle_comparison import compare_case
# Standard test chart data
def _sample_planets():
return {
'Sun': {'sign': 'Gemini', 'degree': 70.0, 'house': 3, 'retrograde': False, 'speed': 1.0},
'Moon': {'sign': 'Taurus', 'degree': 45.0, 'house': 2, 'retrograde': False, 'speed': 13.0},
'Mars': {'sign': 'Capricorn', 'degree': 280.0, 'house': 10, 'retrograde': False, 'speed': 0.5},
'Mercury': {'sign': 'Gemini', 'degree': 80.0, 'house': 3, 'retrograde': False, 'speed': 1.2},
'Jupiter': {'sign': 'Cancer', 'degree': 105.0, 'house': 5, 'retrograde': False, 'speed': 0.08},
'Venus': {'sign': 'Leo', 'degree': 130.0, 'house': 4, 'retrograde': False, 'speed': 1.2},
'Saturn': {'sign': 'Aquarius', 'degree': 320.0, 'house': 12, 'retrograde': True, 'speed': -0.03},
}
def _shadbala_constants():
with open(os.path.join(os.path.dirname(__file__), '..', 'references', 'shat_bala_constants.json'), 'r', encoding='utf-8') as handle:
return json.load(handle)
# ── Sthana Bala Tests ───────────────────────────────────────────────
class TestSthanaBala:
def test_ucha_bala_range(self):
result = calc_sthana_bala('Sun', 70.0, 'Gemini', 3)
assert 0 <= result['ucha_bala'] <= 60
def test_exalted_planet_high_ucha(self):
# Sun exalted at 10° Aries
result = calc_sthana_bala('Sun', 10.0, 'Aries', 1)
assert result['ucha_bala'] > 55 # Near max
def test_debilitated_planet_low_ucha(self):
# Sun debilitated in Libra
result = calc_sthana_bala('Sun', 190.0, 'Libra', 7)
assert result['ucha_bala'] < 5
def test_sapta_score_has_7_components(self):
result = calc_sthana_bala('Jupiter', 105.0, 'Cancer', 5)
for key in ['sapta_d1', 'sapta_d2', 'sapta_d3', 'sapta_d4',
'sapta_d7', 'sapta_d9', 'sapta_d12']:
assert key in result
def test_own_sign_d1_score(self):
# Sun in Leo = own sign
result = calc_sthana_bala('Sun', 125.0, 'Leo', 5)
assert result['sapta_d1'] == 45.0
def test_ojayugma_bala_range(self):
result = calc_sthana_bala('Mercury', 80.0, 'Gemini', 3)
assert result['ojayugma_bala'] in (0, 15)
def test_drekkana_bala_range(self):
result = calc_sthana_bala('Sun', 5.0, 'Aries', 1)
assert result['drekkana_bala'] in (0, 15)
def test_sapta_d3_exaltation_branch_is_capped_below_d1_exaltation(self):
result = calc_sthana_bala('Sun', 0.0, 'Aries', 1)
assert result['sapta_d1'] == 50.0
assert result['sapta_d3'] == 45.0
def test_sapta_d3_own_sign_branch_is_capped_below_d1_own_sign(self):
result = calc_sthana_bala('Venus', 30.0, 'Taurus', 2)
assert result['sapta_d1'] == 45.0
assert result['sapta_d3'] == 30.0
def test_total_positive(self):
result = calc_sthana_bala('Jupiter', 105.0, 'Cancer', 5)
assert result['total'] > 0
# ── Kendra Bala 3-Tier Tests ───────────────────────────────────────
class TestKendraBala:
@pytest.mark.precision
def test_kendra_house_60(self):
result = calc_sthana_bala('Sun', 70.0, 'Gemini', 1) # House 1 = Kendra
assert result['kendra_bala'] == 60.0
@pytest.mark.precision
def test_panapara_house_30(self):
result = calc_sthana_bala('Sun', 70.0, 'Gemini', 2) # House 2 = Panapara
assert result['kendra_bala'] == 30.0
@pytest.mark.precision
def test_apoklima_house_15(self):
result = calc_sthana_bala('Sun', 70.0, 'Gemini', 3) # House 3 = Apoklima
assert result['kendra_bala'] == 15.0
@pytest.mark.parametrize("house,expected", [
(1, 60.0), (4, 60.0), (7, 60.0), (10, 60.0),
(2, 30.0), (5, 30.0), (8, 30.0), (11, 30.0),
(3, 15.0), (6, 15.0), (9, 15.0), (12, 15.0),
])
def test_all_houses_kendra_bala(self, house, expected):
result = calc_sthana_bala('Sun', 70.0, 'Gemini', house)
assert result['kendra_bala'] == expected
# ── Dig Bala Tests ──────────────────────────────────────────────────
class TestDigBala:
def test_sun_in_10th_max(self):
assert calc_dig_bala('Sun', 10) == 60.0
def test_sun_in_4th_min(self):
assert calc_dig_bala('Sun', 4) == 0.0
def test_jupiter_in_1st_max(self):
assert calc_dig_bala('Jupiter', 1) == 60.0
def test_saturn_in_7th_max(self):
assert calc_dig_bala('Saturn', 7) == 60.0
def test_moon_in_4th_max(self):
assert calc_dig_bala('Moon', 4) == 60.0
def test_midrange_positive(self):
assert 0 < calc_dig_bala('Sun', 7) < 60
def test_dig_bala_jupiter_synthetic_north_china_case_needs_better_than_house_only_linear_model(self):
try:
comparison = compare_case("references/oracle/dasha_shadbala_oracle_cases.json", "template_synthetic_north_china_shadbala_raman")
except KeyError:
pytest.skip("synthetic North China oracle case is not distributed in the public release")
jupiter_dig_gap = comparison["comparison"]["Jupiter"]["components"]["dig"]["abs_diff_rupa"]
assert jupiter_dig_gap < 3.6348
# ── Kala Bala Tests ─────────────────────────────────────────────────
class TestKalaBala:
def test_mercury_always_60_nathonnata(self):
result = calc_kala_bala('Mercury', False, True, 45.0, 120.0, 12.0)
assert result['nathonnata'] == 60.0
def test_nathonnata_day_birth_sun(self):
result = calc_kala_bala('Sun', False, True, 45.0, 120.0, 12.0)
assert result['nathonnata'] == 60.0 # Noon
def test_nathonnata_night_birth_moon(self):
result = calc_kala_bala('Moon', True, True, 45.0, 120.0, 0.0)
assert result['nathonnata'] == 60.0 # Midnight
def test_paksha_bala_range(self):
result = calc_kala_bala('Jupiter', False, True, 45.0, 120.0, 12.0)
assert 0 <= result['paksha'] <= 30
def test_hora_bala_present(self):
result = calc_kala_bala('Sun', False, True, 45.0, 120.0, 12.0)
assert 'hora' in result
def test_hora_lord_gets_60(self):
# Test that some planet gets hora = 60
results = []
for pname in ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn']:
r = calc_kala_bala(pname, False, True, 45.0, 120.0, 12.0)
results.append(r['hora'])
assert 60.0 in results # At least one planet is hora lord
def test_total_positive(self):
result = calc_kala_bala('Jupiter', False, True, 45.0, 120.0, 12.0)
assert result['total'] > 0
# ── Chesta Bala Tests ───────────────────────────────────────────────
class TestChestaBala:
def test_retrograde_max(self):
assert calc_chesta_bala('Mars', True, -0.5, 45.0, 120.0) == 60.0
def test_moon_full_max(self):
# Moon opposite Sun → full moon → 60
assert calc_chesta_bala('Moon', False, 13.0, 0.0, 180.0) == 60.0
def test_moon_new_min(self):
assert calc_chesta_bala('Moon', False, 13.0, 0.0, 0.0) == 0.0
def test_sun_speed_based(self):
result = calc_chesta_bala('Sun', False, 0.985, 45.0, 120.0)
assert 0 <= result <= 60
def test_direct_fast_moderate(self):
result = calc_chesta_bala('Mars', False, 1.5, 45.0, 120.0)
assert 0 <= result <= 60
# ── Drik Bala Tests ─────────────────────────────────────────────────
class TestDrikBala:
def test_drik_bala_range(self):
planets = _sample_planets()
result = calc_drik_bala('Jupiter', 'Cancer', 5, planets)
assert -60 <= result <= 60
def test_benefic_aspect_positive(self):
planets = _sample_planets()
result = calc_drik_bala('Mars', 'Capricorn', 10, planets)
assert isinstance(result, float)
# ── Full Shadbala Tests ─────────────────────────────────────────────
class TestShadbalaFull:
def test_shadbala_module_exposes_reference_constants_path(self):
constants_path = Path(SHADBALA_CONSTANTS_PATH).resolve()
assert constants_path.parts[-2:] == ('references', 'shat_bala_constants.json')
assert constants_path.exists()
def test_static_shadbala_constants_are_loaded_from_reference_json(self):
constants = _shadbala_constants()
sun_exalt = constants['exaltation_degrees']['Sun']
saturn_debil = constants['debilitation_degrees']['Saturn']
mercury_rel = constants['natural_relationships']['Mercury']
assert EXALTATION_DEG['Sun'] == pytest.approx(sun_exalt['sign'] * 30 + sun_exalt['degree'])
assert DEBILITATION_DEG['Saturn'] == pytest.approx(saturn_debil['sign'] * 30 + saturn_debil['degree'])
assert FRIENDSHIP['Mercury']['friend'] == mercury_rel['friends']
assert FRIENDSHIP['Mercury']['enemy'] == mercury_rel['enemies']
assert FRIENDSHIP['Mercury']['neutral'] == mercury_rel['neutrals']
def test_all_7_planets_calculated(self):
result = calc_shadbala(_sample_planets(), 'Leo', 10.0, 70.0, 45.0)
for pname in ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn']:
assert pname in result['planets']
def test_total_rupas_positive(self):
result = calc_shadbala(_sample_planets(), 'Leo', 10.0, 70.0, 45.0)
for pname, data in result['planets'].items():
assert data['total_rupas'] > 0
def test_total_virupas_preserves_component_sum_without_global_normalization(self):
result = calc_shadbala(_sample_planets(), 'Leo', 10.0, 70.0, 45.0)
for pname, data in result['planets'].items():
component_sum = (
data['sthana_bala']['total']
+ data['dig_bala']
+ data['kala_bala']['total']
+ data['chesta_bala']
+ data['naisargika_bala']
+ data['drik_bala']
)
assert data['total_virupas'] == pytest.approx(component_sum, abs=0.08), pname
def test_total_required_strength_is_not_halved_by_global_1200_invariant(self):
result = calc_shadbala(_sample_planets(), 'Leo', 10.0, 70.0, 45.0)
total_rupas = sum(data['total_rupas'] for data in result['planets'].values())
assert total_rupas >= sum(MIN_REQUIRED.values())
def test_ishta_bala_calculated(self):
result = calc_shadbala(_sample_planets(), 'Leo', 10.0, 70.0, 45.0)
for pname, data in result['planets'].items():
assert data['ishta_bala_pct'] >= 0
def test_strength_level_present(self):
result = calc_shadbala(_sample_planets(), 'Leo', 10.0, 70.0, 45.0)
for pname, data in result['planets'].items():
assert data['strength_level'] in ('极强', '', '充足', '略弱', '', '极弱')
def test_ranking_present(self):
result = calc_shadbala(_sample_planets(), 'Leo', 10.0, 70.0, 45.0)
assert len(result['ranking']) == 7
assert result['strongest'] is not None
def test_naisargika_bala_standard(self):
assert NAISARGIKA_BALA['Sun'] == 60.0
assert NAISARGIKA_BALA['Saturn'] == 8.57
# ── Bhava Bala Tests ────────────────────────────────────────────────
class TestBhavaBala:
def test_12_houses_calculated(self):
result = calc_bhava_bala(_sample_planets(), 'Leo')
for house in range(1, 13):
assert house in result
def test_benefic_in_house_positive(self):
# Jupiter in house 5 = benefic
result = calc_bhava_bala(_sample_planets(), 'Leo')
assert result[5]['score'] > 0
def test_strength_classification(self):
result = calc_bhava_bala(_sample_planets(), 'Leo')
for house, data in result.items():
assert data['strength'] in ('Strong', 'Moderate', 'Weak')
def test_lord_info_present(self):
result = calc_bhava_bala(_sample_planets(), 'Leo')
for house, data in result.items():
assert 'lord' in data
assert 'sign' in data
def test_factors_list(self):
result = calc_bhava_bala(_sample_planets(), 'Leo')
for house, data in result.items():
assert isinstance(data['factors'], list)