From 67aa1b33d7d3c22d009f8767ebf505ab3af08ab8 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Fri, 2 Oct 2026 14:35:31 +0800 Subject: [PATCH] feat: add Ekadhipatya Shodhana (cherry picked from commit 868f1dadb69e870fbf20223e4fcd0550286bc220) --- scripts/ashtakavarga.py | 42 ++++++++++++++++++++++++++++++++++ tests/test_trikona_shodhana.py | 11 ++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/scripts/ashtakavarga.py b/scripts/ashtakavarga.py index 95694eae..32ec5182 100644 --- a/scripts/ashtakavarga.py +++ b/scripts/ashtakavarga.py @@ -620,3 +620,45 @@ def calc_trikona_shodhana(sav_scores: List[int]) -> Dict: 'status': 'executed', 'claim_boundary': 'Standalone classical reduction; Ekadhipatya Shodhana remains a separate layer.', } + + +EKADHIPATYA_SIGN_PAIRS = ((0, 7), (1, 6), (2, 5), (8, 11), (9, 10)) + + +def calc_ekadhipatya_shodhana( + rows: Dict[str, List[int]], occupied_signs: set[int] +) -> Dict: + """Apply PyJHora/PVR Ekadhipatya Shodhana to post-Trikona rows.""" + reduced = {planet: list(values) for planet, values in rows.items()} + audit = [] + for planet, values in reduced.items(): + if len(values) != 12: + raise ValueError('each BAV row must contain exactly 12 signs') + for first, second in EKADHIPATYA_SIGN_PAIRS: + first_value, second_value = values[first], values[second] + if first_value == 0 or second_value == 0: + continue + first_occupied = first in occupied_signs + second_occupied = second in occupied_signs + before = [first_value, second_value] + if first_occupied and second_occupied: + continue + if first_occupied != second_occupied: + occupied, vacant = (first, second) if first_occupied else (second, first) + values[vacant] = values[occupied] if values[occupied] < values[vacant] else 0 + elif first_value == second_value: + values[first] = values[second] = 0 + elif first_value > second_value: + values[first] = second_value + else: + values[second] = first_value + audit.append({'planet': planet, 'pair': [first, second], 'before': before, + 'after': [values[first], values[second]]}) + return { + 'method': 'Ekadhipatya Shodhana', + 'rows': reduced, + 'occupied_signs': sorted(occupied_signs), + 'audit': audit, + 'status': 'executed', + 'claim_boundary': 'Applied after Trikona Shodhana; source profile follows PyJHora/PVR rule ordering.', + } diff --git a/tests/test_trikona_shodhana.py b/tests/test_trikona_shodhana.py index 9dc50b0d..9da268bd 100644 --- a/tests/test_trikona_shodhana.py +++ b/tests/test_trikona_shodhana.py @@ -5,10 +5,19 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / 'scripts')) -from ashtakavarga import calc_trikona_shodhana # noqa: E402 +from ashtakavarga import calc_ekadhipatya_shodhana, calc_trikona_shodhana # noqa: E402 def test_trikona_shodhana_removes_each_trine_baseline() -> None: result = calc_trikona_shodhana(list(range(1, 13))) assert result['reduced_scores'] == [0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8] assert result['trikona_groups'][0]['baseline_removed'] == 1 + + +def test_ekadhipatya_shodhana_records_occupied_vacant_pair() -> None: + result = calc_ekadhipatya_shodhana( + {'Sun': [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 0]}, + {9}, + ) + assert result['rows']['Sun'][9:11] == [6, 0] + assert result['audit'][0]['pair'] == [9, 10]