Files
Jyotisha/scripts/ashtakavarga_shodhana.py
T
Jesse_ChenandClaude Opus 5.5 ea78804b78 feat(ashtakavarga): standard Sodhita chain on reports and cards, raw values beside (T7, BUG-1209)
Astrologer ruling 乙8. New scripts/ashtakavarga_shodhana.py: per BAV row Trikona then
Ekadhipatya (the two existing ashtakavarga functions, previously only called by
tests), Sodhita SAV = sum of reduced rows; occupied signs = seven grahas. Removed
calc_sodhita_av (Sun/Mars/Saturn contribution removal labelled 'BPHS标准').
Wired into /api/ashtakavarga (result.sodhita, house_scores[*].sav_sodhita) and the
full reading. Reader: SAV table 'Sodhita SAV | Raw SAV', house table gains
Sodhita column, Sodhita BAV table before raw BAV; reference edition section
rewritten. Card: sav_sodhita before sav_score plus sav_columns label. Personal
report facts: score = Sodhita, rawScore/savRawTotal beside (optional schema
fields). Fact tables: Sodhita column and Sodhita BAV subtable. Transit scoring
unchanged (raw bindus).

Golden: report-density-fictional-engine.json leaf worksheets.strengths_and_scores
.ashtakavarga recaptured from the real engine (scripts/research/refresh_density_
fixture_ashtakavarga.py); bav/sav identical, every other leaf byte-equal.
Changed assertions (three columns in PROGRESS): BUG-1200 house-table tests,
report-fact-tables ashtakavarga columns/subtables, run_all t1/t57.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
2026-10-03 12:41:26 +08:00

86 lines
4.0 KiB
Python

#!/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']]