Merge codex/goel-jaimini-strength-20261022 (#94) into staging
# Conflicts: # CHANGELOG.md # docs/tasks/README.md
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
"""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
|
||||
|
||||
|
||||
def test_wired_special_entry_keeps_both_node_policies(monkeypatch):
|
||||
from types import SimpleNamespace
|
||||
from scripts import jyotish_engine as engine
|
||||
chart = {'planets': {p: {'degree': round(v, 4), 'degree_raw': v} for p, v in SYNTHETIC.items()},
|
||||
'ascendant': {'degree': 0., 'lon': 0.}}
|
||||
chart['planets']['Sun']['degree'] = 1.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))
|
||||
strength = result['goel_full_strength']
|
||||
assert strength['status'] == 'executed'
|
||||
assert strength['node_occupancy_policy'] == 'include_nodes_text'
|
||||
exclusion = strength['worked_example_node_exclusion']
|
||||
assert exclusion['node_occupancy_policy'] == 'exclude_nodes_worked_example'
|
||||
assert strength['atmakaraka'] == 'Sun'
|
||||
assert abs(strength['planets']['Sun']['degree_in_sign'] - (SYNTHETIC['Sun'] % 30)) <= 1e-6
|
||||
|
||||
|
||||
def test_wired_equal_degrees_block_without_atmakaraka_or_rank(monkeypatch):
|
||||
from types import SimpleNamespace
|
||||
from scripts import jyotish_engine as engine
|
||||
positions = dict(SYNTHETIC, Moon=59.)
|
||||
chart = {'planets': {p: {'degree': v} for p, v in positions.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))
|
||||
strength = result['goel_full_strength']
|
||||
assert strength['status'] == 'blocked'
|
||||
assert strength['reason'] == 'equal_degree_seven_karaka_rank_requires_finer_positions'
|
||||
assert 'atmakaraka' not in strength
|
||||
assert 'planets' not in strength
|
||||
for name in ('Brahma', 'Maheshwara', 'Rudra'):
|
||||
point = result['goel_special_points'][name]
|
||||
assert point['status'] == 'blocked'
|
||||
assert 'value' not in point
|
||||
assert 'candidates' not in point
|
||||
|
||||
|
||||
def test_full_reading_feeds_goel_degree_raw(monkeypatch):
|
||||
import scripts.goel_jaimini_strength as goel
|
||||
from scripts.jyotish_engine import cmd_full_reading
|
||||
captured = []
|
||||
real = goel.build_goel_chart_evidence
|
||||
|
||||
def wrapped(longitudes, ascendant):
|
||||
captured.append(dict(longitudes))
|
||||
return real(longitudes, ascendant)
|
||||
|
||||
monkeypatch.setattr(goel, 'build_goel_chart_evidence', wrapped)
|
||||
report = cmd_full_reading(type('Args', (), {
|
||||
'name': 'fictional',
|
||||
'year': 1990, 'month': 1, 'day': 1, 'hour': 12, 'minute': 0, 'second': 0,
|
||||
'lat': 39.9, 'lon': 116.4, 'tz': 8.0,
|
||||
'node_mode': 'mean', 'ayanamsa': 'lahiri',
|
||||
'today': '2026-10-22', 'transit_date': '2026-10-22',
|
||||
'target_year': 2026, 'age': 36,
|
||||
'birth_time_accuracy': 'confirmed',
|
||||
})())
|
||||
assert captured, 'full-reading did not call Goel'
|
||||
planets = report['modules']['chart']['planets']
|
||||
expected = {
|
||||
name: pdata['degree_raw']
|
||||
for name, pdata in planets.items()
|
||||
if isinstance(pdata, dict) and 'degree_raw' in pdata
|
||||
}
|
||||
assert expected
|
||||
for longitudes in captured:
|
||||
assert set(longitudes) == set(expected)
|
||||
for name, raw in expected.items():
|
||||
assert abs(float(longitudes[name]) - float(raw)) <= 1e-6
|
||||
strength = report['modules']['jaimini']['goel_full_strength']
|
||||
assert strength['status'] == 'executed'
|
||||
assert strength['node_occupancy_policy'] == 'include_nodes_text'
|
||||
assert strength['worked_example_node_exclusion']['node_occupancy_policy'] == 'exclude_nodes_worked_example'
|
||||
@@ -18,11 +18,12 @@ SCRIPTS = ROOT / "scripts"
|
||||
# 本地新写的静默处理仍不得出现;允许的命中只有这一处。
|
||||
SILENT_EXCEPT_BUDGET = 1
|
||||
UPSTREAM_SILENT_EXCEPT = "scripts/classical_astrology.py:12558"
|
||||
# 原值:446
|
||||
# 新值:448
|
||||
# 原因:产品 2026-10-18(issue #81 选 A):上游原样文件按上游路径放在 scripts/ 根目录,
|
||||
# classical_astrology.py、unified_western_reading.py 两个;本地新写的文件仍不得增加。
|
||||
ROOT_PY_BUDGET = 448
|
||||
# 原值:448
|
||||
# 新值:452
|
||||
# 原因:产品 2026-10-18 #81 决策 A 先例,issue #94:上游原样文件 goel_jaimini_strength、
|
||||
# trikona_raman、sthira_brahma_goel_short_method、trikona_dasha 按上游路径放根目录;
|
||||
# 本地新写的文件仍不得增加。
|
||||
ROOT_PY_BUDGET = 452
|
||||
|
||||
|
||||
def _silent_handlers() -> list[str]:
|
||||
|
||||
Reference in New Issue
Block a user