Files
Jyotisha/scripts/jyotishganit_vs_local_field_comparison.py

172 lines
5.5 KiB
Python

#!/usr/bin/env python3
"""Compare local varga signs with jyotishganit observation raw.
Only compares fields with shared schema: D2/D4/D9/D10 sign labels plus
coverage for Panchanga/BAV/SAV/Shadbala. Observation-only.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import subprocess
import sys
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
PUBLIC_CASE_LATITUDE = 37.7749
PUBLIC_CASE_LONGITUDE = -122.4194
TARGETS = {
"D2": ["D2_Hora"],
"D4": ["D4_Chaturthamsa", "D4_Turyamsa"],
"D9": ["D9_Navamsa"],
"D10": ["D10_Dasamsa"],
}
def stable(data: Any) -> str:
return json.dumps(data, ensure_ascii=False, sort_keys=True, separators=(",", ":"), default=str)
def local_varga(args: argparse.Namespace) -> dict[str, Any]:
cmd = [
sys.executable,
"scripts/jyotish_engine.py",
"varga-full",
"--year",
str(args.year),
"--month",
str(args.month),
"--day",
str(args.day),
"--hour",
str(args.hour),
"--minute",
str(args.minute),
"--lat",
str(args.latitude),
"--lon",
str(args.longitude),
"--tz",
str(args.timezone),
"--divisions",
"D2,D4,D9,D10",
]
return json.loads(subprocess.check_output(cmd, cwd=ROOT, text=True))
def jyotishganit_raw(args: argparse.Namespace) -> dict[str, Any]:
sys.path.insert(0, str(ROOT / "references/open_source_sources/jyotishganit"))
from jyotishganit.main import calculate_birth_chart # type: ignore
from datetime import datetime
chart = calculate_birth_chart(
datetime(args.year, args.month, args.day, args.hour, args.minute),
args.latitude,
args.longitude,
args.timezone,
args.location,
args.name,
)
return chart.to_dict()
def jyotishganit_signs(raw: dict[str, Any], code: str) -> dict[str, str]:
section = raw.get("divisionalCharts", {}).get(code.lower(), {})
rows: dict[str, str] = {}
asc = section.get("ascendant", {}).get("sign")
if asc:
rows["Ascendant"] = asc
for house in section.get("houses", []):
for occ in house.get("occupants", []):
planet = occ.get("celestialBody")
sign = occ.get("sign")
if planet and sign:
rows[planet] = sign
return rows
def local_signs(raw: dict[str, Any], code: str) -> dict[str, str]:
section = {}
for key in TARGETS[code]:
if key in raw:
section = raw[key]
break
return {
body: value.get("sign")
for body, value in section.items()
if isinstance(value, dict) and body != "_meta" and value.get("sign")
}
def build(args: argparse.Namespace) -> dict[str, Any]:
local = local_varga(args)
jyo = jyotishganit_raw(args)
rows = []
for code in TARGETS:
lrows = local_signs(local, code)
jrows = jyotishganit_signs(jyo, code)
bodies = sorted(set(lrows) | set(jrows))
for body in bodies:
rows.append(
{
"section": code,
"body": body,
"local_sign": lrows.get(body),
"jyotishganit_sign": jrows.get(body),
"status": "match" if lrows.get(body) == jrows.get(body) else "mismatch",
}
)
summary = {
"row_count": len(rows),
"match_count": sum(1 for r in rows if r["status"] == "match"),
"mismatch_count": sum(1 for r in rows if r["status"] == "mismatch"),
}
return {
"scope": "jyotishganit_vs_local_field_comparison",
"created_at": "2026-07-19",
"status": "mismatch" if summary["mismatch_count"] else "match",
"claim_status": "observation_only",
"production_tuning_allowed": False,
"truth_matrix_allowed": False,
"request": vars(args),
"summary": summary,
"coverage": {
"panchanga_jyotishganit": jyo.get("panchanga") is not None,
"BAV_SAV_jyotishganit": isinstance(jyo.get("ashtakavarga"), dict)
and "sav" in jyo.get("ashtakavarga", {}),
"Shadbala_jyotishganit": jyo.get("shadbala") is not None or jyo.get("strengths") is not None,
},
"comparison_hash": hashlib.sha256(stable(rows).encode("utf-8")).hexdigest(),
"rows": rows,
"boundary": "Sign-level comparison only. Mismatch rows require field-level formula/ayanamsa/node/rounding attribution; do not majority-vote truth.",
}
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--year", type=int, default=1955)
ap.add_argument("--month", type=int, default=2)
ap.add_argument("--day", type=int, default=24)
ap.add_argument("--hour", type=int, default=19)
ap.add_argument("--minute", type=int, default=15)
ap.add_argument("--latitude", type=float, default=PUBLIC_CASE_LATITUDE)
ap.add_argument("--longitude", type=float, default=PUBLIC_CASE_LONGITUDE)
ap.add_argument("--timezone", type=float, default=-8.0)
ap.add_argument("--location", default="San Francisco, CA")
ap.add_argument("--name", default="Steve Jobs public")
ap.add_argument("--output")
args = ap.parse_args()
payload = build(args)
text = json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True)
if args.output:
Path(args.output).write_text(text + "\n", encoding="utf-8")
print(text)
return 0
if __name__ == "__main__":
raise SystemExit(main())