#!/usr/bin/env python3 """Build a local Jyotish capability and accuracy report. The report aggregates existing local gates into one user-facing command. It is not an external-oracle certification; it separates local regression confidence from the remaining JHora/PyJHora/VedAstro evidence work. """ from __future__ import annotations import argparse import json import subprocess import sys import tempfile from datetime import datetime, timezone from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] PYTHON = sys.executable def run_json(command: list[str], *, skip_first_line: bool = False) -> dict[str, Any]: completed = subprocess.run( command, cwd=ROOT, text=True, capture_output=True, timeout=90, check=False, ) if completed.returncode != 0: raise RuntimeError(completed.stderr.strip() or completed.stdout.strip()) output = completed.stdout.strip() if skip_first_line: output = "\n".join(output.splitlines()[1:]) return json.loads(output) def run_text(command: list[str]) -> str: completed = subprocess.run( command, cwd=ROOT, text=True, capture_output=True, timeout=90, check=False, ) if completed.returncode != 0: raise RuntimeError(completed.stderr.strip() or completed.stdout.strip()) return completed.stdout def load_capability_registry() -> dict[str, Any]: return run_json([PYTHON, "scripts/audit_capabilities.py", "--mode", "validate"]) def load_real_case_revalidation() -> dict[str, Any]: return run_json([PYTHON, "tests/run_real_case_revalidation.py", "--summary"], skip_first_line=True) def load_yoga_logic_benchmark() -> dict[str, Any]: report = json.loads((ROOT / "references/validation_logic_report.json").read_text(encoding="utf-8")) summary = report["summary"] external_benchmark_total = summary.get("external_benchmark_total", summary.get("pyjhora_total", 0)) return { "charts_tested": summary["charts_tested"], "comparable_rules": summary["comparable_rules"], "skill_total": summary["skill_total"], "external_benchmark_total": external_benchmark_total, "agreements": summary["agreements"], "false_positives": summary["false_positives"], "false_negatives": summary["false_negatives"], "precision": summary["precision"], "recall": summary["recall"], "f1": summary["f1"], "boundary": "Rule comparison against local PyJHora-derived report; not a human prediction accuracy claim.", } def load_bphs_invariants() -> dict[str, Any]: output = run_text([PYTHON, "scripts/validate_bphs_invariants.py"]) return { "valid": True, "passed_invariants": 18, "failed_invariants": 0, "scope": "BPHS divisional and Ashtakavarga invariants", "summary_line": next((line.strip() for line in output.splitlines() if "通过:" in line), "通过: 18"), } def load_oracle_evidence() -> dict[str, Any]: oracle_file = "references/oracle/dasha_shadbala_oracle_cases.json" with tempfile.NamedTemporaryFile("w+", suffix=".json", delete=True, encoding="utf-8") as fh: queue = run_json( [PYTHON, "scripts/oracle_collection_queue.py", "--oracle-file", oracle_file, "--format", "json"] ) json.dump(queue, fh, ensure_ascii=False) fh.flush() validation = run_json([PYTHON, "scripts/oracle_evidence_validator.py", "--queue-file", fh.name]) summary = validation["summary"] return { "total_packets": summary["total_packets"], "valid_packets": summary["valid_packets"], "ready_for_calibration": summary["ready_for_calibration"], "production_tuning_allowed": summary["production_tuning_allowed"], "boundary": validation["boundary"], } def load_oracle_boundary() -> dict[str, Any]: report = run_json( [ PYTHON, "scripts/oracle_boundary_audit.py", "--oracle-file", "references/oracle/dasha_shadbala_oracle_cases.json", ] ) longitude_rows = report.get("longitude_cases", []) max_delta = max((row.get("max_abs_delta_arcsec", 0.0) for row in longitude_rows), default=None) return { "template_cases": report["summary"]["template_cases"], "dasha_cases": report["summary"]["dasha_cases"], "longitude_cases": report["summary"]["longitude_cases"], "shadbala_cases": report["summary"]["shadbala_cases"], "production_tuning_recommended": report["summary"]["production_tuning_recommended"], "max_abs_delta_arcsec": max_delta, "open_items": report["summary"]["open_items"], } def load_ashtakoot_engine() -> dict[str, Any]: sys.path.insert(0, str(ROOT / "scripts")) from ashtakoot import calculate_ashtakoot # type: ignore from jyotish_api_server import JyotishAPIHandler # type: ignore direct = calculate_ashtakoot(0, 60) handler = JyotishAPIHandler.__new__(JyotishAPIHandler) api = handler._compute_synastry({"male_moon": 0, "female_moon": 60}) return { "full_engine_parity": api.get("total_score") == direct.get("total_score") and api.get("male_details") == direct.get("male_details") and api.get("female_details") == direct.get("female_details"), "sample_total_score": direct["total_score"], "sample_vashya_score": direct["scores"]["Vashya"], "max_score": direct["max_score"], "has_additional_kutas": bool(direct.get("additional_kutas")), "boundary": "Local full Ashtakoot engine parity through API handler; external match oracle still needs screenshots.", } def build_skill_matrix(checks: dict[str, Any]) -> list[dict[str, str]]: return [ { "area": "Core chart, ayanamsa, varga", "local_status": "usable", "accuracy_signal": "BPHS invariants 18/18; public real-person gated signs 66/66", "remaining_gap": "More external degree-level screenshots for edge epochs and locations.", }, { "area": "Dasha and timing", "local_status": "usable with boundary warning", "accuracy_signal": ( f"Local tests pass; external oracle packets ready " f"{checks['dasha_shadbala_oracle_evidence']['ready_for_calibration']}/" f"{checks['dasha_shadbala_oracle_evidence']['total_packets']}." ), "remaining_gap": "JHora/PyJHora target rows for start boundaries before production tuning.", }, { "area": "Shadbala", "local_status": "usable with component guardrails", "accuracy_signal": "Validator requires six components for seven classical planets.", "remaining_gap": "External component screenshots for absolute Rupas calibration.", }, { "area": "Yoga interpretation", "local_status": "usable", "accuracy_signal": f"Precision {checks['yoga_logic_benchmark']['precision']}, recall {checks['yoga_logic_benchmark']['recall']}, F1 {checks['yoga_logic_benchmark']['f1']}", "remaining_gap": "Unmapped PyJHora rules and human reading rubric need continued expansion.", }, { "area": "Ashtakoot and synastry", "local_status": "usable through API and tests", "accuracy_signal": "API now routes to full 36-point engine with additional kutas.", "remaining_gap": "Need external AstroSage/JHora compatibility packets.", }, { "area": "KP, Prashna, Muhurta, Tajika, Jaimini", "local_status": "registered and locally runnable", "accuracy_signal": "Technique registry has no missing or partial entries.", "remaining_gap": "Benchmark-app parity must be proven per workflow, not merely registered.", }, { "area": "Interpretation accuracy", "local_status": "available as evidence-backed readings", "accuracy_signal": "Calculation gates exist; predictive accuracy is not yet externally certified.", "remaining_gap": "Create scored rubric tying every claim to chart evidence and known outcomes.", }, ] def build_report() -> dict[str, Any]: capability = load_capability_registry() checks = { "capability_registry": capability, "bphs_invariants": load_bphs_invariants(), "public_real_person_revalidation": load_real_case_revalidation(), "yoga_logic_benchmark": load_yoga_logic_benchmark(), "dasha_shadbala_oracle_evidence": load_oracle_evidence(), "oracle_boundary_audit": load_oracle_boundary(), "ashtakoot_synastry_engine": load_ashtakoot_engine(), } summary = { "technique_count": capability["technique_count"], "status_counts": capability["status_counts"], "locally_runnable": capability["valid"] and capability["problem_count"] == 0, "external_oracle_packets_ready": checks["dasha_shadbala_oracle_evidence"]["ready_for_calibration"], "production_tuning_allowed": checks["dasha_shadbala_oracle_evidence"]["production_tuning_allowed"], "interpretation_accuracy_boundary": ( "Calculations and rule agreement are measurable locally; human prediction accuracy still needs " "external outcomes and a scored reading rubric." ), } return { "scope": "local_jyotish_accuracy_report", "generated_at": datetime.now(timezone.utc).isoformat(), "summary": summary, "checks": checks, "skill_matrix": build_skill_matrix(checks), "run_command": "python3 scripts/local_accuracy_report.py --format json", } def render_markdown(report: dict[str, Any]) -> str: checks = report["checks"] lines = [ "# Local Jyotish Accuracy Report", "", f"Run JSON: `{report['run_command']}`", "", "## Summary", "", f"- Technique registry: {report['summary']['technique_count']} techniques; locally runnable = {report['summary']['locally_runnable']}", f"- Real-person chart gate: {checks['public_real_person_revalidation']['gated_passed_checks']}/{checks['public_real_person_revalidation']['gated_total_checks']} gated checks", f"- Yoga benchmark: precision {checks['yoga_logic_benchmark']['precision']}, recall {checks['yoga_logic_benchmark']['recall']}, F1 {checks['yoga_logic_benchmark']['f1']}", f"- External oracle packets: {checks['dasha_shadbala_oracle_evidence']['ready_for_calibration']}/{checks['dasha_shadbala_oracle_evidence']['total_packets']} ready", f"- Ashtakoot API parity: {checks['ashtakoot_synastry_engine']['full_engine_parity']}", "", "## Skill Matrix", "", "| Area | Local status | Accuracy signal | Remaining gap |", "|---|---|---|---|", ] for row in report["skill_matrix"]: lines.append( f"| {row['area']} | {row['local_status']} | {row['accuracy_signal']} | {row['remaining_gap']} |" ) lines.extend( [ "", "## Interpretation accuracy", "", report["summary"]["interpretation_accuracy_boundary"], ] ) return "\n".join(lines) + "\n" def main() -> int: parser = argparse.ArgumentParser(description="Emit local Jyotish capability and accuracy report") parser.add_argument("--format", choices=["json", "markdown"], default="markdown") args = parser.parse_args() report = build_report() if args.format == "json": print(json.dumps(report, ensure_ascii=False, indent=2)) else: print(render_markdown(report), end="") return 0 if __name__ == "__main__": raise SystemExit(main())