From eb67962d700397d3531bb9945d94d65ea38a1ff2 Mon Sep 17 00:00:00 2001 From: jesse-ux Date: Thu, 22 Oct 2026 00:54:02 +0800 Subject: [PATCH] test: port Goel Jaimini strength checks onto our Niryaana selector Issue #94 T2. Keep the upstream test and retarget select_niryaana_shoola_start_sign to scripts.rashi_dashas_native. Add test_full_reading_exposes_goel_jaimini_strength using the existing fictional BASE_BIRTH_ARGS. Co-Authored-By: Grok (agent loop) Co-authored-by: Cursor --- tests/test_bphs_validation_runtime.py | 9 ++ tests/test_goel_jaimini_strength.py | 134 ++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 tests/test_goel_jaimini_strength.py diff --git a/tests/test_bphs_validation_runtime.py b/tests/test_bphs_validation_runtime.py index cb1e405e..0550f91f 100644 --- a/tests/test_bphs_validation_runtime.py +++ b/tests/test_bphs_validation_runtime.py @@ -63,3 +63,12 @@ def test_varga_cli_d9_uses_same_navamsa_mapping_as_varga_full() -> None: assert short["ascendant"] == full["Ascendant"]["sign"] for planet in ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu"]: assert short[planet]["sign"] == full[planet]["sign"] + + +def test_full_reading_exposes_goel_jaimini_strength() -> None: + report = _run_engine("full-reading", *BASE_BIRTH_ARGS) + goel = report["modules"]["jaimini"] + assert goel["goel_full_strength"]["status"] == "executed" + assert len(goel["goel_full_strength"]["rashi_strengths"]) == 12 + assert set(goel["goel_special_points"]) == {"Brahma", "Maheshwara", "Rudra"} + assert goel["niryaana_shoola_strength"]["deprecated"] is True diff --git a/tests/test_goel_jaimini_strength.py b/tests/test_goel_jaimini_strength.py new file mode 100644 index 00000000..db8d46a1 --- /dev/null +++ b/tests/test_goel_jaimini_strength.py @@ -0,0 +1,134 @@ +"""Hand-checked synthetic positions, plus Goel's printed p15 worked table. + +No private birth data; these fixtures exercise supplied-position arithmetic. +""" +from copy import deepcopy +import importlib +import importlib.util +import math + +import pytest + + +SYNTHETIC = dict(Sun=29., Moon=58., Mars=87., Mercury=116., + Jupiter=145., Venus=174., Saturn=203., Rahu=245., Ketu=65.) + + +def module(): + assert importlib.util.find_spec('scripts.goel_jaimini_strength'), 'Goel producer must be published' + return importlib.import_module('scripts.goel_jaimini_strength') + + +def test_synthetic_five_component_totals_and_special_points(): + original = deepcopy(SYNTHETIC) + result = module().build_goel_chart_evidence(SYNTHETIC, 0.) + strength = result['strength'] + assert strength['atmakaraka'] == 'Sun' + assert {p: row['total'] for p, row in strength['planets'].items()} == dict( + Sun=180, Moon=135, Mars=172.5, Mercury=90, Jupiter=187.5, + Venus=86.25, Saturn=123.75) + assert {p: row['value'] for p, row in result['special_points'].items()} == dict( + Brahma='Jupiter', Maheshwara='Mars', Rudra='Mars') + assert strength['rashi_strengths'][0]['total'] == 367.5 + assert strength['rashi_strengths'][6]['total'] == 225 + assert SYNTHETIC == original + + +def test_node_policy_only_changes_sthana_and_sign_total(): + m = module() + text = m.calculate_goel_strength(SYNTHETIC) + example = m.calculate_goel_strength(SYNTHETIC, node_occupancy_policy='exclude_nodes_worked_example') + assert text['planets'] == example['planets'] + # Gemini has Mars+Ketu; Sagittarius has only Rahu. + assert (text['rashi_strengths'][2]['sthana'], example['rashi_strengths'][2]['sthana']) == (75, 60) + assert (text['rashi_strengths'][8]['sthana'], example['rashi_strengths'][8]['sthana']) == (60, 0) + assert text['rashi_strengths'][2]['total'] - example['rashi_strengths'][2]['total'] == 15 + assert text['rashi_strengths'][8]['total'] - example['rashi_strengths'][8]['total'] == 60 + + +def test_goel_printed_p15_numeric_example_without_birth_identity(): + positions = dict(Sun=20+18/60, Moon=210+20+15/60, Mars=210+23+2/60, + Mercury=30+11+12/60, Jupiter=150+3+13/60, + Venus=330+17+28/60, Saturn=7+7/60, + Rahu=330+5+58/60, Ketu=150+5+58/60) + result = module().calculate_goel_strength(positions, node_occupancy_policy='exclude_nodes_worked_example') + assert {p: r['total'] for p, r in result['planets'].items()} == dict( + Sun=240, Moon=93.75, Mars=210, Mercury=97.5, Jupiter=101.25, + Venus=172.5, Saturn=146.25) + assert [r['total'] for r in result['rashi_strengths']] == [ + 412.5, 337.5, 217.5, 228.75, 330, 281.25, 247.5, 465, + 281.25, 221.25, 236.25, 363.75] + + +def test_equal_karaka_degrees_block_all_special_point_choices(): + result = module().build_goel_chart_evidence(dict(SYNTHETIC, Moon=59.), 0.) + assert result['strength']['status'] == 'blocked' + assert all(result['special_points'][p]['status'] == 'blocked' + for p in ('Brahma', 'Maheshwara', 'Rudra')) + + +@pytest.mark.parametrize('bad', [math.nan, math.inf, -1., 360., True, '0']) +def test_invalid_ascendant_rejected(bad): + with pytest.raises(ValueError): + module().build_goel_chart_evidence(SYNTHETIC, bad) + + +def test_legacy_lagna_seventh_alias_does_not_replace_pvr_second_eighth(): + result = module().build_goel_chart_evidence(SYNTHETIC, 0.) + legacy = result['niryaana_shoola_strength'] + assert (legacy['lagna'], legacy['seventh']) == (367.5, 225) + assert legacy['deprecated'] is True + assert legacy['comparison'] == 'lagna_vs_seventh' + from scripts.rashi_dashas_native import select_niryaana_shoola_start_sign + pvr = select_niryaana_shoola_start_sign(ascendant_longitude=0., planet_longitudes=SYNTHETIC) + assert pvr['selected_sign_idx'] == 1 + assert pvr['profile'] == 'pvr_niryaana_shoola_ayur_rasi_strength_v1' + + +def test_brahma_ak_eighth_occupancy_overrides_lagna_selector(): + result = module().build_goel_chart_evidence(dict(SYNTHETIC, Sun=239., Mercury=86.), 0.) + assert result['special_points']['Brahma']['value'] == 'Mercury' + assert result['special_points']['Brahma']['rule'] == 'eighth_lord_from_AK_occupies_eighth_from_AK' + + +def test_exalted_ak_eighth_lord_compares_twelfth_for_maheshwara(): + result = module().build_goel_chart_evidence(dict(SYNTHETIC, Mars=297.), 0.) + assert result['special_points']['Maheshwara']['value'] == 'Jupiter' + assert result['special_points']['Maheshwara']['candidates'] == {'Mars': 150, 'Jupiter': 187.5} + + +def test_rudra_weaker_malefic_contact_exception(): + result = module().build_goel_chart_evidence(dict(SYNTHETIC, Sun=329., Rahu=125., Ketu=305.), 0.) + planets = result['strength']['planets'] + assert planets['Mars']['total'] == 127.5 + assert planets['Mars']['malefic_contacts'] == [] + assert planets['Venus']['total'] == 101.25 + assert planets['Venus']['malefic_contacts'] == ['Mars'] + assert result['special_points']['Rudra']['value'] == 'Venus' + + +def test_incomplete_chart_entry_preserves_blocked_points(): + from scripts.jyotish_engine import _goel_jaimini_evidence + result = _goel_jaimini_evidence({}, 0.) + assert result['goel_full_strength']['status'] == 'blocked' + assert all(r['status'] == 'blocked' for r in result['goel_special_points'].values()) + assert 'niryaana_shoola_strength' not in result + + +@pytest.mark.parametrize('bad', [dict(SYNTHETIC, Sun=math.nan), + dict(SYNTHETIC, Ketu=66.), {p: v for p, v in SYNTHETIC.items() if p != 'Saturn'}]) +def test_incomplete_or_inconsistent_positions_rejected(bad): + with pytest.raises(ValueError): + module().calculate_goel_strength(bad) + + +def test_jaimini_special_entry_exposes_real_goel_results(monkeypatch): + from types import SimpleNamespace + from scripts import jyotish_engine as engine + chart = {'planets': {p: {'degree': v} for p, v in SYNTHETIC.items()}, + 'ascendant': {'degree': 0.}} + monkeypatch.setattr(engine, '_compute_chart_from_args', lambda args: (chart, 0, 2451545., 24.)) + result = engine.cmd_jaimini(SimpleNamespace(mode='special', hour=12, minute=0)) + assert result.get('goel_full_strength', {}).get('atmakaraka') == 'Sun' + assert result['goel_special_points']['Brahma']['value'] == 'Jupiter' + assert result['niryaana_shoola_strength']['deprecated'] is True