#!/usr/bin/env python3 """One-shot LOCAL pipeline for the 'run-halacha' button (halacha validation). The /api/cases/{case}/final/run-halacha endpoint wakes the Hermes curator, which runs THIS single deterministic command (the 3-judge panel uses local DeepSeek+Gemini keys + the local claude CLI, so it can't run inside the container). Steps: [1] extract_internal_citations(chair) → links the citation graph for the chair's decisions (idempotent; ON CONFLICT DO NOTHING). [2] corroboration_rebuild → builds the citation-treatment signal and applies the corroborated→approved / overruled→pending policy (X11 Phase 2). [3] halacha_panel_approve --apply → 3 judges (Opus+DeepSeek+Gemini); agreement auto-approves/rejects (reversible, CSV-backed); splits/defects → chair (INV-G10). NB: per-precedent halacha extraction for newly-cited precedents is NOT automated here (it needs each cited precedent to be in the library with a known case_law_id) — the chair drives that from /precedents when a missing precedent is added. Local-only. Idempotent. The panel pass over the full pending queue can take minutes. cd ~/legal-ai/mcp-server .venv/bin/python ../scripts/final_halacha_pipeline.py --case 8126-03-25 """ from __future__ import annotations import argparse import asyncio import json import sys from argparse import Namespace from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) from legal_mcp.services import corroboration, db # noqa: E402 from legal_mcp.tools.citations import extract_internal_citations # noqa: E402 async def main(args: argparse.Namespace) -> int: case_number = args.case case = await db.get_case_by_number(case_number) if not case: print(f"✗ תיק {case_number} לא נמצא") return 1 chair = case.get("chair_name") or "דפנה תמיר" # [1] citation graph print(f"[1/3] extract_internal_citations (chair={chair})…", flush=True) raw = await extract_internal_citations(chair_name=chair, limit=0) try: d = json.loads(raw).get("data", {}) print(f" ✓ extracted {d.get('extracted')} · linked {d.get('linked')} " f"· new {d.get('new')}") except Exception: print(f" (citations returned: {str(raw)[:160]})") # [2] corroboration signal + policy (whole corpus backfill) — skipped on dry-run if args.dry_run: print("[2/3] corroboration_rebuild — מדולג (dry-run)") else: print("[2/3] corroboration_rebuild (backfill)…", flush=True) try: cr = await corroboration.build_all() print(f" ✓ {cr}") except Exception as e: print(f" ⚠ corroboration failed (non-fatal): {e}") # [3] three-judge halacha panel apply = not args.dry_run print(f"[3/3] halacha_panel_approve {'--apply' if apply else '(dry-run)'} " f"(Opus+DeepSeek+Gemini)…", flush=True) import halacha_panel_approve as hpa rc = await hpa.main(Namespace(limit=args.limit, concurrency=6, apply=apply)) print("\n✓ pipeline-אימות-הלכות הושלם" + (" (dry-run)" if args.dry_run else "")) return rc or 0 if __name__ == "__main__": ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("--case", required=True, help="case_number, e.g. 8126-03-25") ap.add_argument("--limit", type=int, default=0, help="cap pending halachot judged (0 = full queue)") ap.add_argument("--dry-run", dest="dry_run", action="store_true", help="citations only; skip corroboration writes; panel in dry-run") raise SystemExit(asyncio.run(main(ap.parse_args())))