#!/usr/bin/env python3 """Standard Ashtakavarga reduction chain for display (ruling 乙8, BUG-1209). BAV of each of the seven planets -> Trikona Shodhana -> Ekadhipatya Shodhana -> Sodhita BAV; Sodhita SAV is the sum of the seven reduced BAV rows. The two reductions are `ashtakavarga.calc_trikona_shodhana` (the minimum of each trine removed from its three signs, i.e. PVR rules 1-3) and `ashtakavarga.calc_ekadhipatya_shodhana` (the five dual-lordship sign pairs, PVR rules 1-4), applied to every BAV row as BPHS ch. 67 orders them. Occupied signs for Ekadhipatya: signs holding one of the seven grahas (Rahu, Ketu and the Lagna are not counted). PyJHora 4.8.7 counts any non-empty chart cell (nodes, Lagna marker) and reduces only each planet's own sign pair in its own row; both differences are recorded as a question for the astrologer. Display only: transit (gochara) scoring keeps the raw BAV bindus (decision record of TASK-astrologer-rulings-batch1-20261003). Raw values are kept beside the reduced ones. """ from __future__ import annotations from typing import Any, Dict, List try: # scripts/ on sys.path from ashtakavarga import SEVEN_PLANETS, SIGNS, calc_ekadhipatya_shodhana, calc_trikona_shodhana except ImportError: # package import from scripts.ashtakavarga import SEVEN_PLANETS, SIGNS, calc_ekadhipatya_shodhana, calc_trikona_shodhana METHOD = 'Trikona Shodhana -> Ekadhipatya Shodhana (BPHS ch. 67), per BAV row; Sodhita SAV = sum of reduced BAV' METHOD_ZH = '三方净化 → 单宫主净化(BPHS 第 67 章,逐行 BAV);净化后 SAV = 七星净化后 BAV 之和' def occupied_signs(planets: Dict[str, Any]) -> set[int]: signs = set() for name in SEVEN_PLANETS: sign = (planets.get(name) or {}).get('sign') if isinstance(planets.get(name), dict) else None if sign in SIGNS: signs.add(SIGNS.index(sign)) return signs def sodhita_chain(bav_results: Dict[str, Any], planets: Dict[str, Any]) -> Dict[str, Any]: """Reduced BAV rows and Sodhita SAV from `calc_ashtakavarga(...)['bav']`.""" raw: Dict[str, List[int]] = {} for name in SEVEN_PLANETS: bindus = (bav_results.get(name) or {}).get('bindus') if isinstance(bindus, list) and len(bindus) == 12: raw[name] = [int(value) for value in bindus] if len(raw) != len(SEVEN_PLANETS): return {'status': 'blocked', 'reason': 'bav_rows_missing', 'method': METHOD} trikona = {name: calc_trikona_shodhana(row)['reduced_scores'] for name, row in raw.items()} occupied = occupied_signs(planets) ekadhipatya = calc_ekadhipatya_shodhana(trikona, occupied) reduced = ekadhipatya['rows'] sav = [sum(reduced[name][index] for name in SEVEN_PLANETS) for index in range(12)] raw_sav = [sum(raw[name][index] for name in SEVEN_PLANETS) for index in range(12)] return { 'status': 'executed', 'method': METHOD, 'method_zh': METHOD_ZH, 'source': 'scripts/ashtakavarga_shodhana.py (ashtakavarga.calc_trikona_shodhana + calc_ekadhipatya_shodhana)', 'occupied_signs': [SIGNS[index] for index in sorted(occupied)], 'raw_bav': raw, 'trikona_bav': trikona, 'sodhita_bav': reduced, 'ekadhipatya_audit': ekadhipatya['audit'], 'sodhita_sav': { 'scores': {SIGNS[index]: sav[index] for index in range(12)}, 'assessment': [{'sign': SIGNS[index], 'score': sav[index]} for index in range(12)], 'total': sum(sav), }, 'raw_sav': {'scores': {SIGNS[index]: raw_sav[index] for index in range(12)}, 'total': sum(raw_sav)}, 'transit_scoring': 'raw BAV bindus (unchanged)', } def attach_house_values(house_scores: Dict[str, Any], chain: Dict[str, Any]) -> None: """Add `sav_sodhita` next to the raw `sav_score` of each house row.""" if chain.get('status') != 'executed' or not isinstance(house_scores, dict): return scores = chain['sodhita_sav']['scores'] for row in house_scores.values(): if isinstance(row, dict) and row.get('sign') in scores: row['sav_sodhita'] = scores[row['sign']]