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
86 lines
4.0 KiB
Python
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']]
|