feat: migrate precise Shadbala parity
This commit is contained in:
@@ -6281,12 +6281,25 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
birth_minute = self._get_float(body, 'birth_minute', body.get('minute', 0), 0, 59)
|
||||
birth_second = self._get_birth_second(body)
|
||||
birth_hour_decimal = self._birth_hour_decimal(birth_hour, birth_minute, birth_second)
|
||||
result = _load_local_module('shadbala').calc_shadbala(
|
||||
shadbala_module = _load_local_module('shadbala')
|
||||
context = None
|
||||
if all(key in body for key in ('year', 'month', 'day', 'lat', 'lon')):
|
||||
year = self._get_int(body, 'year', 1990, 1800, 2400)
|
||||
month = self._get_int(body, 'month', 6, 1, 12)
|
||||
day = self._get_int(body, 'day', 15, 1, 31)
|
||||
lat = self._get_float(body, 'lat', 0, -90, 90)
|
||||
lon = self._get_float(body, 'lon', 0, -180, 180)
|
||||
tz = self._parse_timezone(body, lat, lon, year, month, day, birth_hour, birth_minute, birth_second)
|
||||
jd = swe.julday(year, month, day, birth_hour_decimal - tz)
|
||||
context = shadbala_module.build_shadbala_context(jd, lat, lon, body.get('ayanamsa', 'lahiri'))
|
||||
result = shadbala_module.calc_shadbala(
|
||||
planets,
|
||||
SIGNS[asc_sign_idx],
|
||||
birth_hour_decimal,
|
||||
planet_lons['Sun'],
|
||||
planet_lons['Moon'],
|
||||
birth_minute,
|
||||
context=context,
|
||||
)
|
||||
advanced = self._compute_shadbala_advanced_layer(body, planets, result)
|
||||
return {
|
||||
|
||||
@@ -3356,7 +3356,7 @@ def cmd_shadbala(args):
|
||||
return {"error": "swisseph未安装"}
|
||||
try:
|
||||
sys.path.insert(0, SCRIPT_DIR)
|
||||
from shadbala import calc_shadbala
|
||||
from shadbala import build_shadbala_context, calc_shadbala
|
||||
except ImportError as e:
|
||||
return {"error": f"shadbala模块导入失败: {e}"}
|
||||
planets = chart.get("planets", {})
|
||||
@@ -3364,7 +3364,8 @@ def cmd_shadbala(args):
|
||||
birth_hour = _birth_hour_decimal(args.hour, args.minute, _arg_second(args))
|
||||
sun_lon = planets.get("Sun", {}).get("degree", 0)
|
||||
moon_lon = planets.get("Moon", {}).get("degree", 0)
|
||||
return calc_shadbala(planets, asc_sign, birth_hour, sun_lon, moon_lon)
|
||||
context = build_shadbala_context(jd, args.lat, args.lon, _current_ayanamsa_name(args))
|
||||
return calc_shadbala(planets, asc_sign, birth_hour, sun_lon, moon_lon, context=context)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -3752,11 +3753,12 @@ def cmd_audit(args):
|
||||
# P9 Shadbala(六重力量)
|
||||
try:
|
||||
sys.path.insert(0, SCRIPT_DIR)
|
||||
from shadbala import calc_shadbala
|
||||
from shadbala import build_shadbala_context, calc_shadbala
|
||||
birth_hour = _birth_hour_decimal(args.hour, args.minute, _arg_second(args))
|
||||
sun_lon = planets.get('Sun', {}).get('degree', 0)
|
||||
moon_lon = planets.get('Moon', {}).get('degree', 0)
|
||||
shadbala = calc_shadbala(planets, asc_sign, birth_hour, sun_lon, moon_lon)
|
||||
context = build_shadbala_context(jd, args.lat, args.lon, _current_ayanamsa_name(args))
|
||||
shadbala = calc_shadbala(planets, asc_sign, birth_hour, sun_lon, moon_lon, context=context)
|
||||
report['audit']['P9_shadbala'] = {
|
||||
'summary': shadbala.get('summary', {}),
|
||||
'ishta_bala_ranking': shadbala.get('ishta_bala_ranking', []),
|
||||
@@ -5524,7 +5526,7 @@ def cmd_full_reading(args):
|
||||
birth_hour = _birth_hour_decimal(args.hour, args.minute, _arg_second(args))
|
||||
sun_lon = planet_lons.get('Sun', 0)
|
||||
moon_lon = planet_lons.get('Moon', 0)
|
||||
shadbala_result = calc_shadbala(planets, asc_sign, birth_hour, sun_lon, moon_lon)
|
||||
shadbala_result = calc_shadbala(planets, asc_sign, birth_hour, sun_lon, moon_lon, context=context)
|
||||
# 添加顶层汇总
|
||||
if isinstance(shadbala_result, dict):
|
||||
sb_planets = shadbala_result.get('planets', {})
|
||||
|
||||
+186
-51
@@ -20,6 +20,7 @@ v6.9.12 修复:
|
||||
"""
|
||||
|
||||
import math
|
||||
import swisseph as swe
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
@@ -118,11 +119,189 @@ SPECIAL_ASPECTS = {
|
||||
|
||||
# Virupas → Rupas 转换(60 Virupas = 1 Rupa)
|
||||
VIRUPAS_PER_RUPA = 60.0
|
||||
_DIG_POWERLESS_HOUSE = {"Sun": 3, "Moon": 9, "Mars": 3, "Mercury": 6, "Jupiter": 6, "Venus": 9, "Saturn": 0}
|
||||
_MOOLATRIKONA = {
|
||||
"Sun": (4, 0.0, 20.0), "Moon": (1, 4.0, 30.0), "Mars": (0, 0.0, 12.0),
|
||||
"Mercury": (5, 16.0, 20.0), "Jupiter": (8, 0.0, 10.0),
|
||||
"Venus": (6, 0.0, 15.0), "Saturn": (10, 0.0, 20.0),
|
||||
}
|
||||
|
||||
|
||||
def build_shadbala_context(jd_ut: float, lat: float, lon: float, ayanamsa: str = "lahiri") -> Dict:
|
||||
"""Build precise sidereal house context from standard birth inputs."""
|
||||
sid_mode = swe.SIDM_LAHIRI
|
||||
if str(ayanamsa).lower() in {"raman"}:
|
||||
sid_mode = swe.SIDM_RAMAN
|
||||
elif str(ayanamsa).lower() in {"kp", "krishnamurti"}:
|
||||
sid_mode = swe.SIDM_KRISHNAMURTI
|
||||
swe.set_sid_mode(sid_mode)
|
||||
cusps, _ = swe.houses_ex(float(jd_ut), float(lat), float(lon), b"P", swe.FLG_SIDEREAL)
|
||||
return {"jd_ut": float(jd_ut), "lat": float(lat), "lon": float(lon), "ayanamsa": str(ayanamsa), "house_midpoints": [float(value) % 360 for value in cusps]}
|
||||
|
||||
|
||||
def calc_dig_bala_precise(pname: str, planet_lon: float, house_midpoints: list[float]) -> float:
|
||||
"""Classical directional strength from the powerless bhava midpoint."""
|
||||
powerless = house_midpoints[_DIG_POWERLESS_HOUSE[pname]]
|
||||
return round(abs(float(powerless) - (float(planet_lon) % 360)) / 3.0, 2)
|
||||
|
||||
|
||||
def _planet_longitude(name: str, planets: Dict) -> float:
|
||||
data = planets[name]
|
||||
degree = float(data.get("degree", 0.0))
|
||||
if degree >= 30.0 or data.get("sign") not in SIGNS:
|
||||
return degree % 360.0
|
||||
return (SIGNS.index(data["sign"]) * 30.0 + degree) % 360.0
|
||||
|
||||
|
||||
def classify_drik_planets(planets: Dict) -> tuple[set[str], set[str]]:
|
||||
"""Classify the seven planets for Drik Bala from lunar phase and Mercury's company."""
|
||||
benefics = {name for name in ("Jupiter", "Venus") if name in planets}
|
||||
malefics = {name for name in ("Sun", "Mars", "Saturn") if name in planets}
|
||||
|
||||
if "Moon" in planets and "Sun" in planets:
|
||||
target = benefics if (_planet_longitude("Moon", planets) - _planet_longitude("Sun", planets)) % 360.0 <= 180.0 else malefics
|
||||
target.add("Moon")
|
||||
|
||||
if "Mercury" in planets:
|
||||
mercury_lon = _planet_longitude("Mercury", planets)
|
||||
mercury_sign = int(mercury_lon // 30)
|
||||
companions = [
|
||||
name for name in benefics | malefics
|
||||
if name != "Mercury" and int(_planet_longitude(name, planets) // 30) == mercury_sign
|
||||
]
|
||||
benefic_count = sum(name in benefics for name in companions)
|
||||
malefic_count = sum(name in malefics for name in companions)
|
||||
if benefic_count >= malefic_count and (benefic_count != malefic_count or not companions):
|
||||
benefics.add("Mercury")
|
||||
elif malefic_count > benefic_count:
|
||||
malefics.add("Mercury")
|
||||
else:
|
||||
nearest = min(companions, key=lambda name: abs(_planet_longitude(name, planets) - mercury_lon))
|
||||
(benefics if nearest in benefics else malefics).add("Mercury")
|
||||
|
||||
return benefics, malefics
|
||||
|
||||
|
||||
def _sphuta_drishti_virupas(angle: float, aspecting_planet: str) -> float:
|
||||
angle = round(float(angle) % 360.0, 2)
|
||||
if angle < 30.0:
|
||||
strength = 0.0
|
||||
elif angle < 60.0:
|
||||
strength = (angle - 30.0) / 2.0
|
||||
elif angle < 90.0:
|
||||
strength = angle - 45.0 + (45.0 if aspecting_planet == "Saturn" else 0.0)
|
||||
elif angle < 120.0:
|
||||
strength = (120.0 - angle) / 2.0 + 30.0 + (15.0 if aspecting_planet == "Mars" else 0.0)
|
||||
elif angle < 150.0:
|
||||
strength = 150.0 - angle + (30.0 if aspecting_planet == "Jupiter" else 0.0)
|
||||
elif angle < 180.0:
|
||||
strength = 2.0 * (angle - 150.0)
|
||||
elif angle < 300.0:
|
||||
strength = (300.0 - angle) / 2.0
|
||||
if aspecting_planet == "Mars" and 210.0 <= angle < 240.0:
|
||||
strength += 15.0
|
||||
elif aspecting_planet == "Jupiter" and 240.0 <= angle < 270.0:
|
||||
strength += 30.0
|
||||
elif aspecting_planet == "Saturn" and 270.0 <= angle < 300.0:
|
||||
strength += 45.0
|
||||
else:
|
||||
strength = 0.0
|
||||
return round(strength, 2)
|
||||
|
||||
|
||||
def calc_drik_bala_precise(pname: str, all_planets: Dict) -> float:
|
||||
"""Continuous Sphuta Drishti, quarter-weighted by natural benefic/malefic status."""
|
||||
if pname not in all_planets:
|
||||
return 0.0
|
||||
benefics, malefics = classify_drik_planets(all_planets)
|
||||
target_lon = _planet_longitude(pname, all_planets)
|
||||
total = 0.0
|
||||
for other_name in benefics | malefics:
|
||||
if other_name == pname:
|
||||
continue
|
||||
strength = _sphuta_drishti_virupas(target_lon - _planet_longitude(other_name, all_planets), other_name)
|
||||
total += strength if other_name in benefics else -strength
|
||||
return round(total / 4.0, 2)
|
||||
|
||||
|
||||
def _d30_sign(sign_idx: int, degree: float) -> int:
|
||||
ranges = (
|
||||
((5, 0), (10, 10), (18, 8), (25, 2), (30, 6))
|
||||
if sign_idx % 2 == 0 else
|
||||
((5, 1), (12, 5), (20, 11), (25, 9), (30, 7))
|
||||
)
|
||||
return next(sign for upper, sign in ranges if degree <= upper)
|
||||
|
||||
|
||||
def _saptavarga_signs(planets: Dict) -> Dict[int, Dict[str, int]]:
|
||||
result = {division: {} for division in (1, 2, 3, 7, 9, 12, 30)}
|
||||
for name in set(planets) & set(_MOOLATRIKONA):
|
||||
longitude = _planet_longitude(name, planets)
|
||||
sign_idx, degree = int(longitude // 30), longitude % 30.0
|
||||
result[1][name] = sign_idx
|
||||
result[2][name] = (4 if sign_idx % 2 == 0 else 3) if degree < 15.0 else (3 if sign_idx % 2 == 0 else 4)
|
||||
for division in (3, 7, 9, 12):
|
||||
result[division][name] = varga_map(sign_idx, min(division - 1, int(degree / (30.0 / division))), division)
|
||||
result[30][name] = _d30_sign(sign_idx, degree)
|
||||
return result
|
||||
|
||||
|
||||
def _compound_relation_score(planet: str, owner: str, d1_signs: Dict[str, int]) -> float:
|
||||
natural = "friend" if owner in FRIENDSHIP[planet]["friend"] else "enemy" if owner in FRIENDSHIP[planet]["enemy"] else "neutral"
|
||||
separation = (d1_signs[owner] - d1_signs[planet]) % 12
|
||||
temporary = "friend" if separation in {1, 2, 3, 9, 10, 11} else "enemy"
|
||||
return {
|
||||
("friend", "friend"): 22.5,
|
||||
("neutral", "friend"): 15.0,
|
||||
("enemy", "friend"): 7.5,
|
||||
("friend", "enemy"): 7.5,
|
||||
("neutral", "enemy"): 3.75,
|
||||
("enemy", "enemy"): 1.875,
|
||||
}[(natural, temporary)]
|
||||
|
||||
|
||||
def calc_sthana_bala_precise(pname: str, all_planets: Dict, house: int) -> Dict:
|
||||
longitude = _planet_longitude(pname, all_planets)
|
||||
degree = longitude % 30.0
|
||||
vargas = _saptavarga_signs(all_planets)
|
||||
scores = {}
|
||||
for division, positions in vargas.items():
|
||||
sign_idx = positions[pname]
|
||||
owner = SIGN_LORDS[SIGNS[sign_idx]]
|
||||
mt_sign, mt_start, mt_end = _MOOLATRIKONA[pname]
|
||||
if division == 1 and sign_idx == mt_sign and mt_start <= degree < mt_end:
|
||||
score = 45.0
|
||||
elif owner == pname:
|
||||
score = 30.0
|
||||
else:
|
||||
score = _compound_relation_score(pname, owner, vargas[1])
|
||||
scores[f"sapta_d{division}"] = score
|
||||
|
||||
offset = (longitude - DEBILITATION_DEG[pname]) % 360.0
|
||||
ucha_bala = round(min(offset, 360.0 - offset) / 3.0, 2)
|
||||
wants_even = pname in {"Moon", "Venus"}
|
||||
ojayugma = 15.0 * sum((vargas[division][pname] % 2 == 1) == wants_even for division in (1, 9))
|
||||
kendra_bala = 60.0 if house in (1, 4, 7, 10) else 30.0 if house in (2, 5, 8, 11) else 15.0
|
||||
drekkana_bala = 15.0 if (
|
||||
(pname in {"Sun", "Mars", "Jupiter"} and degree < 10.0)
|
||||
or (pname in {"Mercury", "Saturn"} and 10.0 <= degree < 20.0)
|
||||
or (pname in {"Moon", "Venus"} and degree >= 20.0)
|
||||
) else 0.0
|
||||
sapta_score = round(sum(scores.values()), 2)
|
||||
return {
|
||||
"ucha_bala": ucha_bala,
|
||||
**scores,
|
||||
"sapta_score": sapta_score,
|
||||
"ojayugma_bala": ojayugma,
|
||||
"kendra_bala": kendra_bala,
|
||||
"drekkana_bala": drekkana_bala,
|
||||
"total": round(ucha_bala + sapta_score + ojayugma + kendra_bala + drekkana_bala, 2),
|
||||
}
|
||||
|
||||
|
||||
def calc_shadbala(planets: Dict, asc_sign: str, birth_hour: float,
|
||||
sun_lon: float, moon_lon: float,
|
||||
birth_minute: float = 0.0) -> Dict:
|
||||
birth_minute: float = 0.0, context: Dict | None = None) -> Dict:
|
||||
"""
|
||||
计算 Shadbala 相对强弱参考(covered;外部绝对值校准前保留置信度上限)
|
||||
|
||||
@@ -152,8 +331,8 @@ def calc_shadbala(planets: Dict, asc_sign: str, birth_hour: float,
|
||||
retro = p.get('retrograde', False)
|
||||
speed = p.get('speed', 1.0)
|
||||
|
||||
sthana = calc_sthana_bala(pname, lon, sign, house)
|
||||
dig = calc_dig_bala(pname, house)
|
||||
sthana = calc_sthana_bala_precise(pname, planets, house)
|
||||
dig = calc_dig_bala_precise(pname, lon, context["house_midpoints"]) if context and context.get("house_midpoints") else calc_dig_bala(pname, house)
|
||||
kala = calc_kala_bala(pname, is_night, sun_northern, sun_lon, moon_lon, birth_hour, birth_minute)
|
||||
chesta = calc_chesta_bala(pname, retro, speed, sun_lon, moon_lon)
|
||||
naisargika = NAISARGIKA_BALA.get(pname, 30.0)
|
||||
@@ -176,8 +355,8 @@ def calc_shadbala(planets: Dict, asc_sign: str, birth_hour: float,
|
||||
retro = p.get('retrograde', False)
|
||||
speed = p.get('speed', 1.0)
|
||||
|
||||
sthana = calc_sthana_bala(pname, lon, sign, house)
|
||||
dig = calc_dig_bala(pname, house)
|
||||
sthana = calc_sthana_bala_precise(pname, planets, house)
|
||||
dig = calc_dig_bala_precise(pname, lon, context["house_midpoints"]) if context and context.get("house_midpoints") else calc_dig_bala(pname, house)
|
||||
kala = calc_kala_bala(pname, is_night, sun_northern, sun_lon, moon_lon, birth_hour, birth_minute)
|
||||
chesta = calc_chesta_bala(pname, retro, speed, sun_lon, moon_lon)
|
||||
naisargika = NAISARGIKA_BALA.get(pname, 30.0)
|
||||
@@ -737,52 +916,8 @@ def calc_yuddha_bala(planets: Dict) -> Dict:
|
||||
|
||||
def calc_drik_bala(pname: str, sign: str, house: int,
|
||||
all_planets: Dict) -> float:
|
||||
"""Drik Bala(相位力量),基于精确度数差(Sputa Drishti)计算。
|
||||
v6.1.13: 升级为基于精确角距离的Sputa Drishti(替代简化宫位差)"""
|
||||
drik = 0.0
|
||||
p_sign_idx = SIGNS.index(sign) if sign in SIGNS else 0
|
||||
p_degree_in_sign = (all_planets[pname].get('degree', 0) if pname in all_planets else 0) % 30
|
||||
p_lon = p_sign_idx * 30 + p_degree_in_sign
|
||||
|
||||
for other_name, other_data in all_planets.items():
|
||||
if other_name == pname or other_name == 'Rahu' or other_name == 'Ketu':
|
||||
continue
|
||||
|
||||
other_sign = other_data.get('sign', '')
|
||||
if other_sign not in SIGNS:
|
||||
continue
|
||||
other_sign_idx = SIGNS.index(other_sign)
|
||||
other_deg_in_sign = other_data.get('degree', 0) % 30
|
||||
other_lon = other_sign_idx * 30 + other_deg_in_sign
|
||||
|
||||
# 计算精确角度差
|
||||
diff = abs(p_lon - other_lon)
|
||||
if diff > 180:
|
||||
diff = 360 - diff
|
||||
|
||||
# 检查传统相位规则(7宫=180°冲,特殊相位=Mars4/8, Jupiter5/9, Saturn3/10)
|
||||
has_aspect = False
|
||||
house_diff = (p_sign_idx - other_sign_idx) % 12 + 1
|
||||
if house_diff == 7 or house_diff == 1:
|
||||
has_aspect = True
|
||||
if other_name in SPECIAL_ASPECTS:
|
||||
if house_diff in SPECIAL_ASPECTS[other_name]:
|
||||
has_aspect = True
|
||||
|
||||
if not has_aspect:
|
||||
continue
|
||||
|
||||
# 使用Sputa Drishti计算相位力量 (0-1) → 缩放为0-60 Virupas
|
||||
sputa_value = _get_sputa_drishti_value(diff, other_name)
|
||||
|
||||
if other_name in BENEFICS:
|
||||
drik += sputa_value * 60.0
|
||||
elif other_name in MALEFICS:
|
||||
drik -= sputa_value * 60.0
|
||||
else:
|
||||
drik += sputa_value * 30.0 # 中性行星
|
||||
|
||||
return max(-60.0, min(60.0, drik))
|
||||
"""Compatibility wrapper for the continuous Sphuta Drishti calculation."""
|
||||
return calc_drik_bala_precise(pname, all_planets)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build same-case D2/D4/D9/D10/AV/Shadbala parity artifacts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
SCRIPTS = ROOT / "scripts"
|
||||
if str(SCRIPTS) not in sys.path:
|
||||
sys.path.insert(0, str(SCRIPTS))
|
||||
|
||||
from benchmarks.jyotish.scripts.run_pyjhora_compare import build_pyjhora_sample
|
||||
from benchmarks.jyotish.scripts.run_skill_baseline import run_sample
|
||||
from scripts.three_engine_parity_runner import _capture_jyotishganit_raw
|
||||
|
||||
ORACLE = ROOT / "references" / "oracle"
|
||||
ARTIFACTS = ORACLE / "artifacts"
|
||||
PLANETS = ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"]
|
||||
SIGNS = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"]
|
||||
VED_VARGA_FIELDS = {"D2": "PlanetHoraD2Signs", "D4": "PlanetChaturthamshaD4Sign", "D9": "PlanetNavamshaD9Sign", "D10": "PlanetDashamamshaD10Sign"}
|
||||
VED_COMPONENT_FIELDS = {
|
||||
"sthana": "PlanetSthanaBala", "kala": "PlanetKalaBala", "dig": "PlanetDigBala",
|
||||
"chesta": "PlanetChestaBala", "naisargika": "PlanetNaisargikaBala", "drik": "PlanetDrikBala",
|
||||
}
|
||||
SAMPLE = {
|
||||
"id": "steve_jobs_public_aa", "label": "Steve Jobs public AA", "category": "public", "privacy": "public",
|
||||
"birth": {"year": 1955, "month": 2, "day": 24, "hour": 19, "minute": 15, "lat": 37.7749, "lon": -122.4194, "tz": -8.0},
|
||||
"today": "2026-07-17",
|
||||
}
|
||||
|
||||
|
||||
def _write(path: Path, value: Any) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8")
|
||||
|
||||
|
||||
def _sha(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def _jyotish_planet_signs(chart: dict[str, Any]) -> dict[str, str]:
|
||||
return {str(p["celestialBody"]): str(p["sign"]) for h in chart.get("houses", []) for p in h.get("occupants", []) if p.get("celestialBody") in PLANETS}
|
||||
|
||||
|
||||
def _jyotish_shadbala(raw: dict[str, Any]) -> tuple[dict[str, float], dict[str, dict[str, float]]]:
|
||||
totals: dict[str, float] = {}; components: dict[str, dict[str, float]] = {}
|
||||
for house in raw["d1Chart"]["houses"]:
|
||||
for p in house.get("occupants", []):
|
||||
name = p.get("celestialBody")
|
||||
if name not in PLANETS or "shadbala" not in p:
|
||||
continue
|
||||
s = p["shadbala"]
|
||||
totals[name] = float(s["Shadbala"]["Total"])
|
||||
components[name] = {"sthana": float(s["Sthanabala"]["Total"]), "kala": float(s["Kaalabala"]["Total"]), "dig": float(s["Digbala"]), "chesta": float(s["Cheshtabala"]), "naisargika": float(s["Naisargikabala"]), "drik": float(s["Drikbala"])}
|
||||
return totals, components
|
||||
|
||||
|
||||
def _ved_payload(raw: dict[str, Any]) -> dict[str, Any]:
|
||||
return raw["Payload"]["AllPlanetData"]
|
||||
|
||||
|
||||
def _ved_components(raw: dict[str, Any], chesta: Any | None = None) -> dict[str, float]:
|
||||
payload = _ved_payload(raw)
|
||||
values = {name: float(payload[field]) for name, field in VED_COMPONENT_FIELDS.items() if field in payload}
|
||||
if chesta is not None:
|
||||
values["chesta"] = float(chesta)
|
||||
return values
|
||||
|
||||
|
||||
def _status(values: dict[str, Any], tolerance: float | None = None) -> str:
|
||||
data = list(values.values())
|
||||
if tolerance is not None and all(isinstance(v, (int, float)) for v in data):
|
||||
return "match" if max(data) - min(data) <= tolerance else "mismatch"
|
||||
return "match" if len({json.dumps(v, sort_keys=True) for v in data}) == 1 else "mismatch"
|
||||
|
||||
|
||||
def build() -> dict[str, Any]:
|
||||
local_result = run_sample(SAMPLE)
|
||||
if not local_result.get("ok"):
|
||||
raise RuntimeError(local_result.get("error"))
|
||||
local = local_result["canonical"]
|
||||
local["shadbala_method_variants"] = {
|
||||
"kala": "bphs_local_solar_events_ahargana_declination",
|
||||
"chesta": "bphs_bounded_surya_mean_motion_seeghrochcha",
|
||||
"chesta_source": "MIT jyotishganit structure plus public Surya Siddhanta revolution constants",
|
||||
}
|
||||
pyjhora = build_pyjhora_sample(SAMPLE)
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
jyotish, _ = _capture_jyotishganit_raw(Path(tmp))
|
||||
ved_path = ARTIFACTS / "vedastro_steve_jobs_public_aa_divisional_raw.json"
|
||||
ved = json.loads(ved_path.read_text(encoding="utf-8"))
|
||||
|
||||
local_path = ARTIFACTS / "local_steve_jobs_high_rigor_raw.json"
|
||||
py_path = ARTIFACTS / "pyjhora_steve_jobs_high_rigor_raw.json"
|
||||
jy_path = ARTIFACTS / "jyotishganit_steve_jobs_high_rigor_raw.json"
|
||||
_write(local_path, local); _write(py_path, pyjhora); _write(jy_path, jyotish)
|
||||
|
||||
rows: list[dict[str, Any]] = []
|
||||
jy_d1 = _jyotish_planet_signs(jyotish["d1Chart"])
|
||||
for planet in PLANETS:
|
||||
values = {
|
||||
"local": local["planets"][planet]["sign"],
|
||||
"PyJHora_JHora": pyjhora["planets"][planet]["sign"],
|
||||
"VedAstro": _ved_payload(ved["raw_responses"][planet])["PlanetRasiD1Sign"]["Name"],
|
||||
"jyotishganit": jy_d1[planet],
|
||||
}
|
||||
rows.append({"section": "D1", "field": f"{planet}.sign", "local_value": values.pop("local"), "oracle_values": values, "status": _status({"local": local["planets"][planet]["sign"], **values})})
|
||||
for section, ved_field in VED_VARGA_FIELDS.items():
|
||||
jy_signs = _jyotish_planet_signs(jyotish["divisionalCharts"][section.lower()])
|
||||
for planet in PLANETS:
|
||||
values = {
|
||||
"local": local["varga"][section][planet]["sign"],
|
||||
"PyJHora_JHora": pyjhora["varga"][section][planet]["sign"],
|
||||
"VedAstro": _ved_payload(ved["raw_responses"][planet])[ved_field]["Name"],
|
||||
"jyotishganit": jy_signs[planet],
|
||||
}
|
||||
rows.append({"section": section, "field": f"{planet}.sign", "local_value": values.pop("local"), "oracle_values": values, "status": _status({"local": local["varga"][section][planet]["sign"], **values})})
|
||||
|
||||
ved_bav = ved["scalar_responses"]["ashtakavarga_bav"]["Payload"]["BhinnashtakavargaChart"]
|
||||
ved_sav = ved["scalar_responses"]["ashtakavarga_sav_chart"]["Payload"]["SarvashtakavargaChart"]["Sarvashtakavarga"]["Rows"]
|
||||
jy_av = jyotish["ashtakavarga"]
|
||||
for planet in PLANETS:
|
||||
values = {"local": local["ashtakavarga"]["bav"][planet], "PyJHora_JHora": pyjhora["ashtakavarga"]["bav"][planet], "VedAstro": ved_bav[planet]["Rows"], "jyotishganit": [jy_av[f"{planet.lower()}Bhav"][s] for s in SIGNS]}
|
||||
rows.append({"section": "ashtakavarga_bav", "field": planet, "local_value": values.pop("local"), "oracle_values": values, "status": _status({"local": local["ashtakavarga"]["bav"][planet], **values})})
|
||||
sav_values = {"local": local["ashtakavarga"]["sav"], "PyJHora_JHora": pyjhora["ashtakavarga"]["sav"], "VedAstro": ved_sav, "jyotishganit": [jy_av["sav"][s] for s in SIGNS]}
|
||||
rows.append({"section": "ashtakavarga_sav", "field": "12_sign_scores", "local_value": sav_values.pop("local"), "oracle_values": sav_values, "status": _status({"local": local["ashtakavarga"]["sav"], **sav_values})})
|
||||
|
||||
jy_totals, jy_components = _jyotish_shadbala(jyotish)
|
||||
for planet in PLANETS:
|
||||
total_values = {"local": local["shadbala"][planet], "PyJHora_JHora": pyjhora["shadbala"][planet], "VedAstro": float(_ved_payload(ved["raw_responses"][planet])["PlanetShadbalaPinda"]), "jyotishganit": jy_totals[planet]}
|
||||
rows.append({"section": "shadbala_total", "field": planet, "local_value": total_values.pop("local"), "oracle_values": total_values, "status": _status({"local": local["shadbala"][planet], **total_values}, 0.5)})
|
||||
ved_chesta = ved["component_responses"][f"{planet}.chesta"]["Payload"]["PlanetChestaBala"]
|
||||
ved_components = _ved_components(ved["raw_responses"][planet], ved_chesta)
|
||||
for component in VED_COMPONENT_FIELDS:
|
||||
values = {"local": local["shadbala_components"][planet][component], "PyJHora_JHora": pyjhora["shadbala_components"][planet][component], "VedAstro": ved_components[component], "jyotishganit": jy_components[planet][component]}
|
||||
row = {"section": "shadbala_components", "field": f"{planet}.{component}", "local_value": values.pop("local"), "oracle_values": values, "status": _status({"local": local["shadbala_components"][planet][component], **values}, 0.5)}
|
||||
if component == "chesta":
|
||||
row["method_conflict"] = "PyJHora unbounded; VedAstro/jyotishganit bounded; Sun/Moon treatment differs across engines."
|
||||
rows.append(row)
|
||||
|
||||
manifest = {
|
||||
"case_id": "steve_jobs_public_1955_lahiri", "birth_data_policy": "public_case_only", "blocked_reason": "none",
|
||||
"engines": {
|
||||
"VedAstro": {"status": "official_verified", "official_raw_response_path": "artifacts/" + ved_path.name, "artifact_hash": _sha(ved_path), "settings": ved["settings"]},
|
||||
"PyJHora_JHora": {"status": "imported", "raw_output_path": "artifacts/" + py_path.name, "artifact_hash": _sha(py_path), "settings": pyjhora["settings"]},
|
||||
"jyotishganit": {"status": "imported", "raw_output_path": "artifacts/" + jy_path.name, "artifact_hash": _sha(jy_path), "settings": {"ayanamsa": jyotish["ayanamsa"]}},
|
||||
},
|
||||
"comparison_rows": rows,
|
||||
"method_arbitration": {
|
||||
"kala": "local_vs_PyJHora_7_of_7_within_0.05_virupa",
|
||||
"chesta": "blocked_cross_engine_method_conflict",
|
||||
"chesta_local_variant": "bphs_bounded_surya_mean_motion_seeghrochcha",
|
||||
},
|
||||
"runtime_boundary": "Full same-case raw coverage; mismatches remain formula/method differences, not missing artifacts.",
|
||||
}
|
||||
_write(ORACLE / "three_engine_parity_replay_manifest.json", manifest)
|
||||
return manifest
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = build()
|
||||
print(json.dumps({"status": "built", "rows": len(result["comparison_rows"])}, indent=2))
|
||||
Reference in New Issue
Block a user