feat(learning): מטא-דאטה מלא להחלטות-פנימיות בקליטה + חילוץ-הלכות אוטומטי

סוגר את הפער שעלה על בל"מ 8126: החלטה שנכנסה לספריית-הפסיקה הופיעה ללא
מטא-דאטה (summary/citation/date ריקים, proceeding_type שגוי) כי מחלץ-ה-Gemini
מיועד לפסיקה חיצונית ומחזיר no_metadata לפנימיות, והחילוץ-הלכות נשאר pending.

web/app.py — `_enroll_final_in_library` עכשיו ממלא **דטרמיניסטית** מהתיק (בלי LLM):
- proceeding_type (מהתיק — בל"מ/ערר, גם idempotency key נכון מהקליטה הראשונה),
  decision_date (fallback ל-hearing_date), subject_tags, summary (=subject).
- `citation_formatted` נבנה דטרמיניסטית (`_build_internal_citation`):
  'ועדת ערר ... בל"מ <num> <עורר> נ' <משיב> (יו"ר עו"ד <chair>)'.

scripts/final_halacha_pipeline.py — שלב [0] חדש: `precedent_extract_halachot`
על ההחלטה עצמה (idempotent — מדלג כש-completed/dry-run), כך שהלכות-ההחלטה
לא נשארות pending.

אומת: py_compile ✓ · ה-pipeline רץ dry-run נקי (4 שלבים). 8126 כבר תוקן ידנית;
מכאן זה אוטומטי לכל החלטה. Invariants: INV-LRN4/X11 · G1 (נרמול-במקור) ·
DM7 · feedback_silent_swallow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 15:19:14 +00:00
parent de777c2b13
commit 2f43960353
2 changed files with 93 additions and 7 deletions

View File

@@ -6,6 +6,8 @@ runs THIS single deterministic command (the 3-judge panel uses local DeepSeek+Ge
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
@@ -35,6 +37,19 @@ 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:
@@ -45,8 +60,26 @@ async def main(args: argparse.Namespace) -> int:
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/3] extract_internal_citations (chair={chair})…", flush=True)
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", {})
@@ -57,9 +90,9 @@ async def main(args: argparse.Namespace) -> int:
# [2] corroboration signal + policy (whole corpus backfill) — skipped on dry-run
if args.dry_run:
print("[2/3] corroboration_rebuild — מדולג (dry-run)")
print("[2/4] corroboration_rebuild — מדולג (dry-run)")
else:
print("[2/3] corroboration_rebuild (backfill)…", flush=True)
print("[2/4] corroboration_rebuild (backfill)…", flush=True)
try:
cr = await corroboration.build_all()
print(f"{cr}")
@@ -68,7 +101,7 @@ async def main(args: argparse.Namespace) -> int:
# [3] three-judge halacha panel
apply = not args.dry_run
print(f"[3/3] halacha_panel_approve {'--apply' if apply else '(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))