#!/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: [0] precedent_extract_halachot → extract the halachot the DECISION ITSELF states (its own case_law row), so they aren't left pending. Idempotent. [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 from legal_mcp.tools.precedent_library import precedent_extract_halachot # noqa: E402 async def _decision_law_row(case_number: str) -> dict | None: """The case's own decision row in case_law (internal_committee), if enrolled.""" pool = await db.get_pool() async with pool.acquire() as conn: r = await conn.fetchrow( "SELECT id, halacha_extraction_status FROM case_law WHERE case_number = $1 " "AND source_kind = 'internal_committee' ORDER BY created_at DESC LIMIT 1", case_number, ) return dict(r) if r else None 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 "דפנה תמיר" # [0] extract the halachot the decision ITSELF states (its own row in case_law) — # so they are not left pending. Idempotent: skip when already completed or on dry-run. row = await _decision_law_row(case_number) if not row: print(f"[0/4] ההחלטה {case_number} אינה ב-case_law עדיין — דילוג על חילוץ-הלכות") elif row.get("halacha_extraction_status") == "completed": print(f"[0/4] חילוץ-הלכות מההחלטה — דולג (כבר completed)") elif args.dry_run: print(f"[0/4] חילוץ-הלכות מההחלטה — מדולג (dry-run)") else: print(f"[0/4] precedent_extract_halachot (החלטה {case_number})…", flush=True) try: raw0 = await precedent_extract_halachot(str(row["id"])) d0 = json.loads(raw0).get("data", {}) print(f" ✓ status={d0.get('status')} stored={d0.get('stored', d0.get('extracted'))}") except Exception as e: print(f" ⚠ halacha extraction failed (non-fatal): {e}") # [1] citation graph print(f"[1/4] 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/4] corroboration_rebuild — מדולג (dry-run)") else: print("[2/4] 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/4] 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())))