Merge pull request 'feat(halacha): panel safety-net audit (selective-prediction monitoring)' (#136) from worktree-halacha-audit into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 8s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 8s
This commit was merged in pull request #136.
This commit is contained in:
@@ -47,6 +47,7 @@
|
|||||||
| `goldset_ai_recommend.py` | python | **#81.7 QA** — מייצר **חוות-דעת-AI שנייה** (claude מקומי, אפס עלות) לכל פריט ב-`halacha_goldset`: `is_holding`+`type`+נימוק, נשמר ב-`ai_*` ומוצג בדף לצד התיוג האנושי לזיהוי אי-הסכמות. **עצמאי** מהוולידטורים שנמדדים (אין מעגליות) ו**לא** מוחל אוטומטית. `--force` (חידוש)/`--limit N`. **חובה מקומי** (claude_session). | ידני — לאחר יצירת/הרחבת batch |
|
| `goldset_ai_recommend.py` | python | **#81.7 QA** — מייצר **חוות-דעת-AI שנייה** (claude מקומי, אפס עלות) לכל פריט ב-`halacha_goldset`: `is_holding`+`type`+נימוק, נשמר ב-`ai_*` ומוצג בדף לצד התיוג האנושי לזיהוי אי-הסכמות. **עצמאי** מהוולידטורים שנמדדים (אין מעגליות) ו**לא** מוחל אוטומטית. `--force` (חידוש)/`--limit N`. **חובה מקומי** (claude_session). | ידני — לאחר יצירת/הרחבת batch |
|
||||||
| `goldset_independent_judge.py` | python | **INV-DM7 ולידציה** — שופט-תפקיד **עצמאי שני** ממודל אחר (DeepSeek API ישיר, OpenAI-compatible) ששובר את עיגון-ה-AI: מסווג rule_role **בעיוור** (בלי לראות תיוג-אדם או המלצת-claude) ומחשב מטריצת-הסכמה (deepseek↔אדם מול ai↔אדם) + ציר-גס (כלל-בר-הכללה מול application/obiter). **ממצא (2026-06-07):** ai↔אדם=100% (מעוגן), deepseek↔אדם=50% מדויק אך **92% גס** → תת-הסוג holding/interpretive/procedural עמום-מטבעו (לא לשער עליו); הציר-הגס אמין חוצה-מודלים. read-only על הזהב. `--model`/`--limit`/`--concurrency`. מפתח מ-`~/.hermes/profiles/deepseek/.env`. raw→`/tmp/goldset_judge_raw.json`. | ידני — ולידציית אמינות-תוויות |
|
| `goldset_independent_judge.py` | python | **INV-DM7 ולידציה** — שופט-תפקיד **עצמאי שני** ממודל אחר (DeepSeek API ישיר, OpenAI-compatible) ששובר את עיגון-ה-AI: מסווג rule_role **בעיוור** (בלי לראות תיוג-אדם או המלצת-claude) ומחשב מטריצת-הסכמה (deepseek↔אדם מול ai↔אדם) + ציר-גס (כלל-בר-הכללה מול application/obiter). **ממצא (2026-06-07):** ai↔אדם=100% (מעוגן), deepseek↔אדם=50% מדויק אך **92% גס** → תת-הסוג holding/interpretive/procedural עמום-מטבעו (לא לשער עליו); הציר-הגס אמין חוצה-מודלים. read-only על הזהב. `--model`/`--limit`/`--concurrency`. מפתח מ-`~/.hermes/profiles/deepseek/.env`. raw→`/tmp/goldset_judge_raw.json`. | ידני — ולידציית אמינות-תוויות |
|
||||||
| `halacha_panel_approve.py` | python | **פאנל-אישור הלכות (Trust-or-Escalate, dry-run).** 3 שופטים בלתי-תלויי-לינאז' (Opus/claude_session · DeepSeek · Gemini-2.5-flash) מצביעים על ה**ציר-הגס האמין** (92% חוצה-מודלים): נקיות→"הלכה לשמירה?"; nli_unsupported→"הציטוט תומך בכלל?" (שיפוט-מחדש); פגומות→re-extraction. רק ורדיקט מוסכם פועל אוטומטית, **פיצול מסלים ליו"ר** (INV-G10). dry-run בלבד (אין `--apply` עדיין). מפתחות: DeepSeek מ-`~/.hermes/...`, Gemini מ-`~/.env`. **חובה מקומי**. dry-run 2026-06-07: 197→103 אוטו (פה-אחד) / ~15 (רוב). | ידני — טריאז' תור-אישור |
|
| `halacha_panel_approve.py` | python | **פאנל-אישור הלכות (Trust-or-Escalate, dry-run).** 3 שופטים בלתי-תלויי-לינאז' (Opus/claude_session · DeepSeek · Gemini-2.5-flash) מצביעים על ה**ציר-הגס האמין** (92% חוצה-מודלים): נקיות→"הלכה לשמירה?"; nli_unsupported→"הציטוט תומך בכלל?" (שיפוט-מחדש); פגומות→re-extraction. רק ורדיקט מוסכם פועל אוטומטית, **פיצול מסלים ליו"ר** (INV-G10). dry-run בלבד (אין `--apply` עדיין). מפתחות: DeepSeek מ-`~/.hermes/...`, Gemini מ-`~/.env`. **חובה מקומי**. dry-run 2026-06-07: 197→103 אוטו (פה-אחד) / ~15 (רוב). | ידני — טריאז' תור-אישור |
|
||||||
|
| `halacha_panel_audit.py` | python | **רשת-ביטחון לפאנל** (selective-prediction monitoring) — דוגם הלכות שאושרו ע"י הפאנל (`reviewer LIKE 'panel:%'`), מריץ עליהן **שוב** את הצבעת-ה-KEEP של 3 השופטים, ומציף כל מקרה שכעת נוטה DROP (false-keep פוטנציאלי). report-only כברירת-מחדל; `--flag` מחזיר את ה-flips ל-`pending_review` לסקירת-יו"ר. `--sample N`/`--seed`. בסיס 2026-06-07: 0/15. מיועד להרצה תקופתית (שבועי). מייבא שופטים מ-`halacha_panel_approve`. **חובה מקומי**. | תקופתי (שבועי) — ניטור |
|
||||||
| `halacha_panel_calibrate.py` | python | **כיול מדיניות-ההצבעה של הפאנל** (Trust-or-Escalate, ICLR 2025). מריץ את שאלת-ה-KEEP של `halacha_panel_approve` על מדגם-הזהב ומודד מול `is_holding` (הציר-הגס) precision+coverage לכל מדיניות (unanimous/majority) + ספירת false-keep/false-drop. נותן את **אחוז-הטעות בפועל** לבחירת סף-סיכון α. מייבא שופטים מ-`halacha_panel_approve` (מקור-אמת יחיד). read-only, **חובה מקומי**. | ידני — לפני חיווט `--apply` |
|
| `halacha_panel_calibrate.py` | python | **כיול מדיניות-ההצבעה של הפאנל** (Trust-or-Escalate, ICLR 2025). מריץ את שאלת-ה-KEEP של `halacha_panel_approve` על מדגם-הזהב ומודד מול `is_holding` (הציר-הגס) precision+coverage לכל מדיניות (unanimous/majority) + ספירת false-keep/false-drop. נותן את **אחוז-הטעות בפועל** לבחירת סף-סיכון α. מייבא שופטים מ-`halacha_panel_approve` (מקור-אמת יחיד). read-only, **חובה מקומי**. | ידני — לפני חיווט `--apply` |
|
||||||
| `halacha_rule_role_backfill.py` | python | **INV-DM7** — backfill חד-פעמי: מסווג-מחדש את ההלכות הישנות (`rule_type IN ('binding','persuasive')` — ערכי-סמכות שנשמרו במסווה תפקיד לפני פיצול הצירים) לאחד מחמשת **תפקידי-הכלל** (holding/interpretive/procedural/application/obiter) דרך claude_session המקומי (אפס עלות). **לא נוגע בסמכות** (נגזרת מ-`precedent_level`). `--apply` (ברירת-מחדל dry-run) / `--limit N` / `--concurrency`. כותב backup CSV ל-`data/audit/` תחילה. fail-safe (פריט שנכשל → נשמר ערך ישן). **חובה מקומי** (claude_session). | ידני חד-פעמי אחרי deploy של פיצול-הסמכות |
|
| `halacha_rule_role_backfill.py` | python | **INV-DM7** — backfill חד-פעמי: מסווג-מחדש את ההלכות הישנות (`rule_type IN ('binding','persuasive')` — ערכי-סמכות שנשמרו במסווה תפקיד לפני פיצול הצירים) לאחד מחמשת **תפקידי-הכלל** (holding/interpretive/procedural/application/obiter) דרך claude_session המקומי (אפס עלות). **לא נוגע בסמכות** (נגזרת מ-`precedent_level`). `--apply` (ברירת-מחדל dry-run) / `--limit N` / `--concurrency`. כותב backup CSV ל-`data/audit/` תחילה. fail-safe (פריט שנכשל → נשמר ערך ישן). **חובה מקומי** (claude_session). | ידני חד-פעמי אחרי deploy של פיצול-הסמכות |
|
||||||
| `halacha_batch_reconcile.py` | python | **#82.7** — dedup חוצה-פסקים offline (שמרני, **dry-run בלבד**). dedup-on-insert משווה רק תוך-פסק; כאן סף מחמיר (cosine ≥0.95, `--cosine`) ולא-הרסני: מאתר זוגות הלכות near-duplicate בין פסקים שונים (pgvector `<=>` exact) עם איתות לקסיקלי (Jaccard/Levenshtein) ומדווח ל-CSV ב-`data/audit/` לסקירת היו"ר. לא מדלג/ממזג/מוחק. `--include-pending`. **`--link`** רושם את הזוגות שנמצאו כ-`equivalent_halachot` (parallel authority, #84.2 — קישור-מקביל ברמת-הלכה, **לא** ציטוט; idempotent, לא-הרסני). רץ עם venv של mcp-server. אומת: 800 הלכות → 5 זוגות (קושרו). | ידני — דוח-סקירה / `--link` לקישור |
|
| `halacha_batch_reconcile.py` | python | **#82.7** — dedup חוצה-פסקים offline (שמרני, **dry-run בלבד**). dedup-on-insert משווה רק תוך-פסק; כאן סף מחמיר (cosine ≥0.95, `--cosine`) ולא-הרסני: מאתר זוגות הלכות near-duplicate בין פסקים שונים (pgvector `<=>` exact) עם איתות לקסיקלי (Jaccard/Levenshtein) ומדווח ל-CSV ב-`data/audit/` לסקירת היו"ר. לא מדלג/ממזג/מוחק. `--include-pending`. **`--link`** רושם את הזוגות שנמצאו כ-`equivalent_halachot` (parallel authority, #84.2 — קישור-מקביל ברמת-הלכה, **לא** ציטוט; idempotent, לא-הרסני). רץ עם venv של mcp-server. אומת: 800 הלכות → 5 זוגות (קושרו). | ידני — דוח-סקירה / `--link` לקישור |
|
||||||
|
|||||||
93
scripts/halacha_panel_audit.py
Normal file
93
scripts/halacha_panel_audit.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Safety-net audit for panel-approved halachot (selective-prediction monitoring).
|
||||||
|
|
||||||
|
A panel auto-approval is reversible and low-harm, but not infallible. The
|
||||||
|
literature (Trust-or-Escalate; selective prediction) prescribes MONITORING the
|
||||||
|
auto-decision error rate over time rather than trusting it blindly. This samples
|
||||||
|
panel-approved halachot, RE-RUNS the same 3-judge KEEP vote, and surfaces any
|
||||||
|
where the panel now leans DROP — the candidate false-keeps a human should glance
|
||||||
|
at. Zero standing load on the chair: it just produces a short weekly list.
|
||||||
|
|
||||||
|
Report-only by default. ``--flag`` sends the flips back to ``pending_review``
|
||||||
|
(with an audit reviewer note) so they re-enter the chair queue.
|
||||||
|
|
||||||
|
cd ~/legal-ai/mcp-server
|
||||||
|
.venv/bin/python ../scripts/halacha_panel_audit.py --sample 15
|
||||||
|
.venv/bin/python ../scripts/halacha_panel_audit.py --sample 15 --flag
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from legal_mcp.services import db
|
||||||
|
from halacha_panel_approve import ( # noqa: E402 — single source of truth for judges
|
||||||
|
KEEP_SYSTEM, _bool, _keep_user, judge_claude, judge_deepseek, judge_gemini,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _majority(votes: list[bool]) -> bool | None:
|
||||||
|
vs = [v for v in votes if v is not None]
|
||||||
|
if len(vs) < 2:
|
||||||
|
return None
|
||||||
|
y, n = sum(vs), len(vs) - sum(vs)
|
||||||
|
return True if y > n else (False if n > y else None)
|
||||||
|
|
||||||
|
|
||||||
|
async def main(args: argparse.Namespace) -> int:
|
||||||
|
pool = await db.get_pool()
|
||||||
|
# sample panel-approved halachot (ORDER BY random is fine for a small audit)
|
||||||
|
rows = await pool.fetch(
|
||||||
|
"SELECT h.id, h.rule_statement, h.reasoning_summary, h.supporting_quote, "
|
||||||
|
" cl.case_number "
|
||||||
|
"FROM halachot h LEFT JOIN case_law cl ON cl.id = h.case_law_id "
|
||||||
|
"WHERE h.review_status='approved' AND h.reviewer LIKE 'panel:%' "
|
||||||
|
"ORDER BY md5(h.id::text || $1) LIMIT $2",
|
||||||
|
args.seed, args.sample,
|
||||||
|
)
|
||||||
|
print(f"auditing {len(rows)} panel-approved halachot (re-running the KEEP vote)\n", flush=True)
|
||||||
|
|
||||||
|
flips = []
|
||||||
|
sem = asyncio.Semaphore(args.concurrency)
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
async def one(r):
|
||||||
|
async with sem:
|
||||||
|
user = _keep_user(dict(r))
|
||||||
|
c, ds, gm = await asyncio.gather(
|
||||||
|
judge_claude(KEEP_SYSTEM, user),
|
||||||
|
judge_deepseek(client, KEEP_SYSTEM, user),
|
||||||
|
judge_gemini(client, KEEP_SYSTEM, user),
|
||||||
|
)
|
||||||
|
votes = [_bool(c, "keep"), _bool(ds, "keep"), _bool(gm, "keep")]
|
||||||
|
if _majority(votes) is False: # panel now leans DROP → candidate false-keep
|
||||||
|
flips.append((r, votes))
|
||||||
|
tasks = [one(r) for r in rows]
|
||||||
|
for i in range(0, len(tasks), args.concurrency):
|
||||||
|
await asyncio.gather(*tasks[i : i + args.concurrency])
|
||||||
|
|
||||||
|
rate = len(flips) / len(rows) if rows else 0.0
|
||||||
|
print(f"=== AUDIT: {len(flips)}/{len(rows)} now lean DROP ({rate:.0%} candidate false-keeps) ===")
|
||||||
|
for r, votes in flips:
|
||||||
|
print(f"\n {r['case_number']} votes(c/ds/gm)={votes}")
|
||||||
|
print(f" {r['rule_statement'][:140]}")
|
||||||
|
|
||||||
|
if flips and args.flag:
|
||||||
|
for r, _ in flips:
|
||||||
|
await pool.execute(
|
||||||
|
"UPDATE halachot SET review_status='pending_review', "
|
||||||
|
"reviewer='panel-audit:reopened', updated_at=now() WHERE id=$1", r["id"])
|
||||||
|
print(f"\n→ flagged {len(flips)} back to pending_review for chair review.")
|
||||||
|
elif flips:
|
||||||
|
print("\n(report-only — pass --flag to reopen these for the chair)")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--sample", type=int, default=15)
|
||||||
|
ap.add_argument("--seed", default="audit", help="vary to draw a different sample")
|
||||||
|
ap.add_argument("--flag", action="store_true", help="reopen flips to pending_review")
|
||||||
|
ap.add_argument("--concurrency", type=int, default=6)
|
||||||
|
raise SystemExit(asyncio.run(main(ap.parse_args())))
|
||||||
Reference in New Issue
Block a user