diff --git a/scripts/ashtakavarga.py b/scripts/ashtakavarga.py index 8d71a019..95694eae 100644 --- a/scripts/ashtakavarga.py +++ b/scripts/ashtakavarga.py @@ -586,3 +586,37 @@ def calc_sodhita_av(bav_results: Dict, planets: Dict, asc_sign_idx: int) -> Dict 'reduction_summary': total_reduction, 'validation_note': 'Sodhita AV ≤ 原始BAV(逐宫位)', } + + +TRIKONA_SIGN_GROUPS = ( + (0, 4, 8), # Aries, Leo, Sagittarius + (1, 5, 9), # Taurus, Virgo, Capricorn + (2, 6, 10), # Gemini, Libra, Aquarius + (3, 7, 11), # Cancer, Scorpio, Pisces +) + + +def calc_trikona_shodhana(sav_scores: List[int]) -> Dict: + """Apply the classical Trikona Shodhana reduction to a 12-sign SAV row. + + The minimum value in each elemental trine is removed from all three signs; + the original row remains in the result for auditability. + """ + if len(sav_scores) != 12: + raise ValueError('SAV scores must contain exactly 12 signs') + original = [int(value) for value in sav_scores] + reduced = original[:] + groups = [] + for indexes in TRIKONA_SIGN_GROUPS: + baseline = min(original[index] for index in indexes) + for index in indexes: + reduced[index] -= baseline + groups.append({'sign_indexes': list(indexes), 'baseline_removed': baseline}) + return { + 'method': 'Trikona Shodhana', + 'original_scores': original, + 'reduced_scores': reduced, + 'trikona_groups': groups, + 'status': 'executed', + 'claim_boundary': 'Standalone classical reduction; Ekadhipatya Shodhana remains a separate layer.', + } diff --git a/tests/test_trikona_shodhana.py b/tests/test_trikona_shodhana.py new file mode 100644 index 00000000..9dc50b0d --- /dev/null +++ b/tests/test_trikona_shodhana.py @@ -0,0 +1,14 @@ +import sys +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 + + +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