feat: migrate precise Shadbala parity
This commit is contained in:
@@ -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