Files
Jyotisha/scripts/shadbala_d3_mapping_audit.py
T
2026-06-29 10:38:17 +08:00

150 lines
5.2 KiB
Python

#!/usr/bin/env python3
"""Audit whether D3 drift is caused by D3 mapping itself or dignity use in Shadbala."""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
SCRIPT_DIR = ROOT / "scripts"
if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))
import jyotish_engine # type: ignore
from divisional_charts_extended import DivisionalChartsCalculator # type: ignore
from oracle_boundary_audit import _load_oracle, _namespace_from_template # type: ignore
from shadbala_sapta_dignity_whitelist import build_report as build_whitelist_report # type: ignore
from varga import calc_varga # type: ignore
def _normalize_dignity_bucket(score: float) -> str:
if score >= 50:
return "exalted"
if score >= 45:
return "own"
if score >= 35:
return "friend"
if score >= 25:
return "neutral"
if score >= 15:
return "enemy"
return "debilitated"
def _discover_cases(oracle: dict[str, Any]) -> dict[str, dict[str, Any]]:
cases: dict[str, dict[str, Any]] = {}
for key in ("template_cases", "shadbala_cases"):
for case in oracle.get(key, []):
cid = case.get("id") or case.get("case_id")
if isinstance(cid, str):
cases[cid] = case
return cases
def build_report(oracle_file: str) -> dict[str, Any]:
whitelist = build_whitelist_report(oracle_file)
oracle = _load_oracle(oracle_file)
cases = _discover_cases(oracle)
calc = DivisionalChartsCalculator()
rows = []
mapping_fault = 0
dignity_fault = 0
for row in whitelist.get("whitelist_rows", []):
if row.get("layer") != "D3":
continue
case = cases[row["case_id"]]
result = jyotish_engine.cmd_shadbala(_namespace_from_template(case))
chart, _asc_idx, _jd, _aya = jyotish_engine._compute_chart_from_args(_namespace_from_template(case))
planet = row["planet"]
planet_data = (chart or {}).get("planets", {}).get(planet, {})
lon = float(planet_data.get("degree", 0.0))
d3_simple = calc_varga(lon, 3)
d3_extended_abs = calc._calculate_d3(int(lon // 30), lon % 30)
d3_extended = {
"sign_idx": int(d3_extended_abs // 30) % 12,
"sign": calc.SIGNS[int(d3_extended_abs // 30) % 12],
}
sthana = ((result.get("planets") or {}).get(planet) or {}).get("sthana_bala") or {}
engine_d3_score = float(sthana.get("sapta_d3", 0.0))
dignity_bucket = _normalize_dignity_bucket(engine_d3_score)
mapping_matches = d3_simple.get("sign") == d3_extended["sign"]
suspected = "d3_dignity_path" if mapping_matches else "d3_mapping_path"
if mapping_matches:
dignity_fault += 1
else:
mapping_fault += 1
rows.append({
"case_id": row["case_id"],
"planet": planet,
"mapping_matches_engine_sign": mapping_matches,
"d3_simple_sign": d3_simple.get("sign"),
"d3_extended_sign": d3_extended["sign"],
"d3_dignity_bucket": dignity_bucket,
"sapta_d3_score": round(engine_d3_score, 2),
"suspected_path": suspected,
})
return {
"scope": "shadbala_d3_mapping_audit",
"schema_version": 1,
"summary": {
"case_count": len({row["case_id"] for row in rows}),
"row_count": len(rows),
"global_closure_blocked": True,
},
"suspected_fault_split": {
"d3_mapping_path": mapping_fault,
"d3_dignity_path": dignity_fault,
},
"rows": rows,
"boundary": (
"This audit compares two local D3 mapping paths against the D3 score consumed by Shadbala. "
"If both mappings agree, the remaining suspect is the dignity score path rather than the D3 sign mapping."
),
}
def main() -> None:
parser = argparse.ArgumentParser(description="Audit D3 mapping vs dignity path for Shadbala")
parser.add_argument("--oracle-file", default="references/oracle/dasha_shadbala_oracle_cases.json")
parser.add_argument("--format", choices=("json", "markdown"), default="json")
args = parser.parse_args()
report = build_report(args.oracle_file)
if args.format == "markdown":
lines = [
"# Shadbala D3 Mapping Audit",
"",
f"- case_count: `{report['summary']['case_count']}`",
f"- row_count: `{report['summary']['row_count']}`",
"",
f"- suspected_fault_split: `{report['suspected_fault_split']}`",
"",
"| Case | Planet | Simple D3 | Extended D3 | D3 Dignity | Suspected Path |",
"| --- | --- | --- | --- | --- | --- |",
]
for row in report["rows"]:
lines.append(
f"| {row['case_id']} | {row['planet']} | {row['d3_simple_sign']} | {row['d3_extended_sign']} | "
f"{row['d3_dignity_bucket']} | {row['suspected_path']} |"
)
print("\n".join(lines))
return
print(json.dumps(report, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()