Files
Jyotisha/scripts/validate_interpretation_templates.py
T

125 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""Validate high-granularity Jyotish interpretation templates."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_REGISTRY = ROOT / "references" / "interpretation_template_registry.json"
REQUIRED_TEMPLATE_FIELDS = [
"title",
"status",
"authority_level",
"domain",
"source_refs",
"trigger_patterns",
"required_cross_checks",
"confidence_ceiling",
"forbidden_claims",
"safe_output_patterns",
]
def _resolve(path: str | None) -> Path:
if not path:
return DEFAULT_REGISTRY
candidate = Path(path)
return candidate if candidate.is_absolute() else ROOT / candidate
def _load_json(path: Path) -> dict[str, Any]:
with path.open("r", encoding="utf-8") as fh:
return json.load(fh)
def validate_registry(path: Path) -> dict[str, Any]:
problems: list[str] = []
registry = _load_json(path)
if registry.get("scope") != "jyotish_interpretation_template_registry":
problems.append("invalid_scope")
templates = registry.get("templates")
if not isinstance(templates, dict) or not templates:
problems.append("missing_templates")
templates = {}
for template_id, template in templates.items():
if not isinstance(template, dict):
problems.append(f"{template_id}:not_object")
continue
for field in REQUIRED_TEMPLATE_FIELDS:
value = template.get(field)
if value in (None, "", [], {}):
problems.append(f"{template_id}:missing_{field}")
for ref in template.get("source_refs", []):
ref_path = ROOT / ref
if not ref_path.exists():
problems.append(f"{template_id}:missing_source_ref:{ref}")
if not template.get("forbidden_claims"):
problems.append(f"{template_id}:missing_forbidden_claims")
if "alone" not in " ".join(template.get("forbidden_claims", [])).lower():
problems.append(f"{template_id}:forbidden_claims_should_block_single-factor_use")
return {
"scope": "jyotish_interpretation_template_validation",
"schema_version": 1,
"registry": str(path.relative_to(ROOT) if path.is_relative_to(ROOT) else path),
"valid": not problems,
"summary": {
"template_count": len(templates),
"template_ids": sorted(templates.keys()),
"problem_count": len(problems),
},
"problems": problems,
"boundary": (
"These templates are interpretation guardrails. They do not replace chart calculation, "
"Dasha/transit convergence, external oracle validation or the strict workflow router."
),
}
def render_markdown(report: dict[str, Any]) -> str:
lines = [
"# Jyotish Interpretation Template Validation",
"",
f"- valid: `{str(report['valid']).lower()}`",
f"- template_count: `{report['summary']['template_count']}`",
f"- problem_count: `{report['summary']['problem_count']}`",
"",
"## Templates",
"",
]
lines.extend(f"- `{template_id}`" for template_id in report["summary"]["template_ids"])
lines.extend(["", "## Boundary", "", report["boundary"], ""])
if report["problems"]:
lines.extend(["## Problems", ""])
lines.extend(f"- {problem}" for problem in report["problems"])
lines.append("")
return "\n".join(lines)
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Validate Jyotish interpretation template registry")
parser.add_argument("--registry", help="Path to interpretation_template_registry.json")
parser.add_argument("--format", choices=["json", "markdown"], default="json")
return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> int:
args = parse_args(argv)
report = validate_registry(_resolve(args.registry))
if args.format == "markdown":
print(render_markdown(report))
else:
print(json.dumps(report, ensure_ascii=False, indent=2))
return 0 if report["valid"] else 1
if __name__ == "__main__":
raise SystemExit(main())