feat(halacha): NLI entailment validator via claude_session (#81.3) + task #86
#81.3 — a post-extraction validator that flags halachot whose rule_statement is NOT entailed by its supporting_quote (the model over-reaching beyond its source). - Engine: claude_session-as-judge (local CLI, zero API cost) per chaim's standing preference — one batched judge call per chunk, NOT a hosted NLI model. - Pure, unit-tested helpers in halacha_quality: NLI_SYSTEM, build_nli_prompt, parse_nli_verdicts (fails OPEN — any shape/label ambiguity → 'entailed'). - halacha_extractor._nli_check wraps the call; fails OPEN on any error (e.g. no CLI in the container) so a flaky judge never blocks a genuine halacha. - Non-entailed (neutral/contradiction) → quality_flag 'nli_unsupported' which blocks auto-approve (routes to pending_review) via the existing store gate. - config: HALACHA_NLI_ENABLED/MODEL/EFFORT (effort 'low' — entailment is simple). Verified: suite 166 passed (10 new); LIVE smoke test against the real claude CLI returned ['entailed','neutral'] for a supported vs unsupported rule. Also commits TaskMaster #86 (Nevo preamble/ratio: anti-contamination strip fix + gold-set benchmark) capturing today's strip_nevo_preamble findings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2739,7 +2739,7 @@
|
||||
"description": "התגלו שני באגים: (1) halacha_index מוקצה per-chunk ולכן אינו ייחודי לפסק — שני עקרונות שונים מקבלים אותו מספר (לא כפילות, אך שובר dedup/מיון מבוסס-אינדקס); (2) חילוץ רץ פי-2/3 על אותו פסק (למשל 85026-17 שלוש ריצות תוך דקתיים) ומוסיף append במקום להחליף — ה-advisory lock לא מנע. המשימה: אינדוקס ייחודי לפסק, force=True שמוחק לפני re-extract, וחיזוק ה-lock/אידמפוטנטיות. מחקר קצר: דפוסי idempotency/exactly-once ב-pipelines.",
|
||||
"details": "קוד: halacha_extractor.py (global advisory lock, per-chunk checkpoints ב-precedent_chunks.halacha_extracted_at, force flag), db.store_halachot_for_chunk (הקצאת halacha_index). לשקול unique constraint (case_law_id, halacha_index) אחרי תיקון ההקצאה.",
|
||||
"testStrategy": "הרצת חילוץ פעמיים ברצף על אותו פסק → ספירה זהה, אפס אינדקסים כפולים. הרצת force=True → המאגר מוחלף ולא מצטבר. בדיקת מרוץ: שתי הרצות במקביל → רק אחת מבצעת (lock).",
|
||||
"status": "pending",
|
||||
"status": "done",
|
||||
"dependencies": [],
|
||||
"priority": "medium",
|
||||
"subtasks": [
|
||||
@@ -2748,70 +2748,76 @@
|
||||
"title": "נעילה גלובלית אמינה (advisory lock על חיבור ייעודי)",
|
||||
"description": "העברת ה-pg_advisory_lock מחיבור pooled לחיבור ייעודי שאינו ממוחזר לאורך כל ה-job (או pg_advisory_xact_lock בדפוס מתאים), עם finally שמשחרר תמיד; תיעוד איסור transaction-pooler לפני החיבור.",
|
||||
"details": "מקור: PG docs — session advisory lock לא בטוח תחת transaction-pooling (PgBouncer ממליץ נגדו). השורש האמיתי של 3 ריצות תוך 2 דקות: הנעילה לא החזיקה. קוד: halacha_extractor.py:372-394.",
|
||||
"status": "pending",
|
||||
"status": "done",
|
||||
"dependencies": [],
|
||||
"testStrategy": "שתי הרצות extract במקביל על אותו precedent — השנייה מחזירה busy ולא רצה (נבדק עם 2 תהליכים נפרדים).",
|
||||
"parentId": "83"
|
||||
"parentId": "83",
|
||||
"updatedAt": "2026-06-03T13:08:00.800Z"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "אילוץ UNIQUE (case_law_id, halacha_index)",
|
||||
"description": "migration ל-halachot עם UNIQUE (case_law_id, halacha_index) כרשת ביטחון נגד התנגשויות מספור.",
|
||||
"details": "מקור: FireHydrant/OneUptime — per-scope ordinal דורש UNIQUE(scope,number) כערובת התקינות; הנעילה היא אופטימיזציה. כיום ההקצאה MAX+1 מוגנת רק ב-asyncio.Lock תוך-תהליכי. db.py:645-669.",
|
||||
"status": "pending",
|
||||
"status": "done",
|
||||
"dependencies": [],
|
||||
"testStrategy": "INSERT ידני של index כפול לאותו precedent נכשל ב-DB; query GROUP BY case_law_id,halacha_index HAVING count>1 מחזיר 0 בקורפוס.",
|
||||
"parentId": "83"
|
||||
"parentId": "83",
|
||||
"updatedAt": "2026-06-03T13:08:00.811Z"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "דה-דופ לפי תוכן (content_hash + ON CONFLICT DO NOTHING)",
|
||||
"description": "עמודת content_hash (md5 של rule_statement+supporting_quote) + UNIQUE(case_law_id, content_hash); שינוי ה-INSERT ל-ON CONFLICT DO NOTHING.",
|
||||
"details": "מקור: upsert/replace הם פרימיטיב האידמפוטנטיות. משלים את שער ה-fuzzy של #82 במפתח-זהות מדויק. db.py:3325-3374.",
|
||||
"status": "pending",
|
||||
"status": "cancelled",
|
||||
"dependencies": [],
|
||||
"testStrategy": "הרצת extract(force=False) פעמיים ברצף על precedent שהושלם → מספר ה-halachot לא גדל.",
|
||||
"parentId": "83"
|
||||
"parentId": "83",
|
||||
"updatedAt": "2026-06-03T13:08:06.185Z"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "מספור עמיד למרוץ ב-store_halachot_for_chunk",
|
||||
"description": "retry-on-unique-violation סביב read-MAX→insert (או חישוב index מ-sequence עם RETURNING) — ללא הסתמכות על asyncio.Lock בלבד לבטיחות חוצת-תהליכים.",
|
||||
"details": "מקור: race condition handling ב-PG. כיום MAX+1 מוגן רק תוך-תהליכית. db.py:3341-3344.",
|
||||
"status": "pending",
|
||||
"status": "done",
|
||||
"dependencies": [
|
||||
2
|
||||
],
|
||||
"testStrategy": "בדיקה שמדמה שני קוראי-MAX מקבילים — שניהם נשמרים עם indexים שונים רצופים, אפס DuplicateKey לא-מטופל.",
|
||||
"parentId": "83"
|
||||
"parentId": "83",
|
||||
"updatedAt": "2026-06-03T13:08:00.826Z"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "semantics של force = replace אטומי + עמידות לקריסה",
|
||||
"description": "ודאות ש-force=True מבצע delete+checkpoint-clear בטרנזקציה אחת (קיים ב-reset_halacha_extraction), וש-resume אחרי קריסה אמצע re-extract לא מכפיל (הודות לאילוצי 83.2/83.3).",
|
||||
"details": "מקור: delete-before-insert atomic / replace-partition. per-chunk commits נשמרים ל-resumability. db.py:3299-3304.",
|
||||
"status": "pending",
|
||||
"status": "done",
|
||||
"dependencies": [
|
||||
2,
|
||||
3
|
||||
],
|
||||
"testStrategy": "הזרקת קריסה אחרי חלק מהצ'אנקים → resume משלים ללא כפילויות; count(halachot) תואם הרצה נקייה.",
|
||||
"parentId": "83"
|
||||
"parentId": "83",
|
||||
"updatedAt": "2026-06-03T13:08:00.834Z"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"title": "ניקוי נתונים היסטוריים לפני החלת אילוצים",
|
||||
"description": "סקריפט חד-פעמי (scripts/ + SCRIPTS.md) שמזהה precedents עם indexים מתנגשים, ממספר מחדש רציף, ומכין את הקורפוס להחלת UNIQUE של 83.2/83.3.",
|
||||
"details": "הניקוי של 2026-06-03 טיפל בכפילויות תוכן אך לא במספור; אילוצי UNIQUE ייכשלו אם יש index כפול שריר. גיבוי קיים: data/audit/halacha-cleanup-backup-*.sql.",
|
||||
"status": "pending",
|
||||
"status": "done",
|
||||
"dependencies": [
|
||||
2
|
||||
],
|
||||
"testStrategy": "אחרי הרצה, אילוצי 83.2/83.3 נוצרים ללא שגיאה; דוח CSV ב-data/audit/ מפרט כמה תוקנו לכל precedent.",
|
||||
"parentId": "83"
|
||||
"parentId": "83",
|
||||
"updatedAt": "2026-06-03T13:08:00.841Z"
|
||||
}
|
||||
],
|
||||
"updatedAt": "2026-06-03T00:00:00.000Z"
|
||||
"updatedAt": "2026-06-03T13:08:10.793Z"
|
||||
},
|
||||
{
|
||||
"id": "84",
|
||||
@@ -2819,7 +2825,7 @@
|
||||
"description": "אישור ההלכות ידני ומתיש: קריאת עקרונות כמעט-זהים שוב ושוב, ללא תיעדוף או קיבוץ. המשימה: לייעל את חוויית האישור — מיון לפי ביטחון/corroboration, קיבוץ near-duplicates יחד, auto-defer/הסתרה של פריטים באיכות נמוכה, ופעולות batch (אישור/דחייה מרובים). מבוססת מחקר (human-in-the-loop review UX, active-learning prioritization, triage queues). תתי-המשימות לאחר המחקר.",
|
||||
"details": "הקשר: הדחייה כמעט לא בשימוש (1/1650) — התור הוא 'אשר-או-השאר-תלוי'. כלים קיימים: halachot_pending, halacha_review (MCP), דף ביקורת ב-UI. לשלב עם פלט #81 (איכות) ו-#82 (dedup) כדי שהתור יציג רק מועמדים אמיתיים ומקובצים.",
|
||||
"testStrategy": "מדידת זמן/קליקים לאישור N הלכות לפני/אחרי. בדיקה: פריטים כמעט-זהים מוצגים כקבוצה אחת; פריטי איכות-נמוכה אינם מופיעים כברירת-מחדל בתור.",
|
||||
"status": "pending",
|
||||
"status": "in-progress",
|
||||
"dependencies": [
|
||||
"81",
|
||||
"82"
|
||||
@@ -2831,10 +2837,11 @@
|
||||
"title": "סינון מועמדים אמיתיים בלבד בתור (quality gating מ-#81)",
|
||||
"description": "הסתרת פריטים שסומנו low-quality (quote_verified=false, rule_type=application, truncated) מתצוגת ברירת-המחדל של halachot_pending; ניתובם ל-bucket 'דורש תיקון-חילוץ'.",
|
||||
"details": "מקור: default-defer/auto-archive של איכות-נמוכה (Prodigy/content-moderation). צורך פלט #81. כלי: halachot_pending.",
|
||||
"status": "pending",
|
||||
"status": "in-progress",
|
||||
"dependencies": [],
|
||||
"testStrategy": "תור ברירת-מחדל מחזיר 0 פריטים עם דגלי low-quality; פרמטר include_low_quality=true עדיין חושף אותם.",
|
||||
"parentId": "84"
|
||||
"parentId": "84",
|
||||
"updatedAt": "2026-06-03T13:43:18.478Z"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
@@ -2851,43 +2858,47 @@
|
||||
"title": "תיעדוף התור לפי ציון משוקלל (uncertainty + impact)",
|
||||
"description": "החלפת FIFO בציון עדיפות משולב: ביטחון באזור-אפור קודם, מוגבר ע\"י corroboration של ציטוט-בודד ותחומי-עיסוק בכיסוי דליל; דיכוי כפילויות של פריט-הראש.",
|
||||
"details": "מקור: active learning — least-confidence first + diversity/impact weighting (Encord/Label Studio/greip). uncertainty לבד מדגים יתר-על-המידה near-dups.",
|
||||
"status": "pending",
|
||||
"status": "in-progress",
|
||||
"dependencies": [],
|
||||
"testStrategy": "בהינתן set מתוכנן, ראש התור הוא הפריט בעל הציון המשולב הגבוה; שתי כפילויות לא מופיעות יחד ב-top-5.",
|
||||
"parentId": "84"
|
||||
"parentId": "84",
|
||||
"updatedAt": "2026-06-03T13:43:18.488Z"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "פעולות batch: אישור/דחייה לכל הקבוצה",
|
||||
"description": "הרחבת halacha_review לקבלת מזהה-קבוצה והחלת approve/reject על כל הוריאנטים בקריאה אחת, עם אפשרות override ידני של וריאנט לפני commit.",
|
||||
"details": "מקור: propagate-one-decision-to-group + checkpoint אנושי על bulk (Labelbox). סיכון: bulk-apply עיוור מפיץ שגיאה — חובה הצגת וריאנטים לפני אישור.",
|
||||
"status": "pending",
|
||||
"status": "done",
|
||||
"dependencies": [],
|
||||
"testStrategy": "אישור קבוצת 5 וריאנטים מסמן את כולם approved בפעולה אחת; דחייה מסמנת את כולם rejected; override של וריאנט בודד אפשרי.",
|
||||
"parentId": "84"
|
||||
"parentId": "84",
|
||||
"updatedAt": "2026-06-03T13:43:13.227Z"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "דחייה/השהיה זולה + סמנטיקת reject נכונה",
|
||||
"description": "הוספת outcomes מפורשים reject ו-defer ל-halacha_review; reject שומר אות שלילי מתמשך (מזין חזרה ל-#81), defer משאיר pending ומחזיר לסוף התור.",
|
||||
"details": "מקור: Prodigy accept/reject/ignore — הבחנה סמנטית היא הפתרון ל-1/1650. כיום אין פועל 'זבל' זול ולכן זבל מצטבר כ-pending.",
|
||||
"status": "pending",
|
||||
"status": "done",
|
||||
"dependencies": [],
|
||||
"testStrategy": "reject קובע status rejected מתמשך + סיבה (queryable למשוב מחלץ); defer משאיר pending אך מוריד עדיפות.",
|
||||
"parentId": "84"
|
||||
"parentId": "84",
|
||||
"updatedAt": "2026-06-03T13:43:13.239Z"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"title": "UI: ביקורת keyboard-first עם 4 מקשים (a/r/space/e)",
|
||||
"description": "עיצוב-מחדש של דף הביקורת ב-Next.js לכרטיס-בכל-פעם, מונע-מקלדת (Approve a / Reject r / Defer space / Edit e), עם הקשר-קבוצה וציטוט-מקור inline.",
|
||||
"details": "מקור: keyboard-first מעלה throughput 20-30% בלי פגיעה באיכות (CleverX); one-card-at-a-time מפחית עומס קוגניטיבי (Hick's law). דף: web-ui /feedback או דף ביקורת ייעודי.",
|
||||
"status": "pending",
|
||||
"status": "done",
|
||||
"dependencies": [
|
||||
4,
|
||||
5
|
||||
],
|
||||
"testStrategy": "מבקר יכול approve/reject/defer/edit-then-approve כולו במקלדת בלי עכבר; הכרטיס מציג מונה-וריאנטים וציטוט-מקור.",
|
||||
"parentId": "84"
|
||||
"parentId": "84",
|
||||
"updatedAt": "2026-06-03T13:43:13.253Z"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
@@ -2902,7 +2913,7 @@
|
||||
"parentId": "84"
|
||||
}
|
||||
],
|
||||
"updatedAt": "2026-06-03T00:00:00.000Z"
|
||||
"updatedAt": "2026-06-03T13:43:18.488Z"
|
||||
},
|
||||
{
|
||||
"id": "85",
|
||||
@@ -2914,13 +2925,60 @@
|
||||
"dependencies": [],
|
||||
"priority": "high",
|
||||
"subtasks": []
|
||||
},
|
||||
{
|
||||
"id": "86",
|
||||
"title": "טיפול ב-preamble/רציו של נבו — anti-contamination + gold-set מהרציו",
|
||||
"description": "התגלה (2026-06-03) ש-`strip_nevo_preamble` קיים ומחווט ל-ingest, אבל ה-regex `_DECISION_START` מזהה רק פתיחות של ועדת ערר (בפנינו/הערר שבנדון/ועדת הערר לתכנון/רקע עובדתי/עסקינן) — ולא פסקי-דין שנפתחים ב'פסק-דין' (כמו בג\"ץ 1764/05). לכן בפסקי-דין מנבו — בדיוק אלה שיש להם מיני-רציו — ה-preamble/רציו **אינו נחתך**, דולף לצ'אנקים, ועלול לזהם את חילוץ ההלכות (המחלץ קורא את התשובון של נבו) ואת הקורפוס. במקביל — הרציו של נבו הוא gold-set אנושי-מקצועי חינמי לאמידת איכות החילוץ.",
|
||||
"details": "קוד: mcp-server/src/legal_mcp/services/extractor.py — `strip_nevo_preamble` (~367), `_NEVO_MARKERS` (ספרות:/חקיקה שאוזכרה:/מיני-רציו:/...), `_DECISION_START` (~361). מחווט ב-ingest.py:161 ו-documents.py:152. הוכחה: ב-1764/05 המיני-רציו שרד כ-chunk מסוג intro (לא נחתך) ורק במזל לא חולץ (intro לא ב-EXTRACTABLE_SECTIONS). השוואת benchmark שבוצעה ידנית על 1764/05: 14 הלכות שלנו כיסו 100% מ-4 הלכות-הרציו של נבו + 2 נוספות, בגרנולריות פי ~3.5 (קשור ל-#81.5).",
|
||||
"testStrategy": "strip_nevo_preamble על טקסט 1764/05 מסיר את בלוק המיני-רציו ומתחיל מ'פסק-דין'; regression: פתיחות ועדת-ערר ממשיכות להיחתך נכון. benchmark מפיק recall/precision/granularity.",
|
||||
"status": "pending",
|
||||
"dependencies": [],
|
||||
"priority": "high",
|
||||
"subtasks": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "הרחבת _DECISION_START לפסקי-דין (anti-contamination)",
|
||||
"description": "הוספת פתיחות פסק-דין ל-`_DECISION_START` (פסק-דין / פסק דין / 'השופט'/'כב' השופט'/'לפני:') כך ש-strip_nevo_preamble חותך את ה-preamble/רציו גם בפסקי-דין מנבו.",
|
||||
"details": "קוד: extractor.py `_DECISION_START`. שמירה על תאימות לאחור לפתיחות ועדת-ערר הקיימות.",
|
||||
"status": "pending",
|
||||
"dependencies": [],
|
||||
"testStrategy": "unit: strip_nevo_preamble(טקסט 1764/05) מסיר את המיני-רציו ומתחיל מ'פסק-דין'; טקסט ועדת-ערר עם בפנינו עדיין נחתך נכון; טקסט ללא preamble חוזר ללא שינוי.",
|
||||
"parentId": "86"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "backfill — זיהוי וטיהור פסקי-דין שהרציו דלף אליהם",
|
||||
"description": "סקריפט לזיהוי פסקי-דין בקורפוס שה-preamble/רציו של נבו דלף לצ'אנקים (intro/legal_analysis) או להלכות שחולצו; re-ingest/strip + בדיקת זיהום בהלכות הקיימות.",
|
||||
"details": "להריץ אחרי 85.1. גיבוי לפני re-ingest. לבדוק האם הלכות קיימות הן העתק של רציו.",
|
||||
"status": "pending",
|
||||
"dependencies": [
|
||||
1
|
||||
],
|
||||
"testStrategy": "דוח (CSV ב-data/audit/) של פסקים מושפעים; אחרי טיהור — אף chunk לא מכיל בלוק מיני-רציו; re-extraction נקי.",
|
||||
"parentId": "86"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Nevo-ratio gold-set benchmark (מזין #81.7)",
|
||||
"description": "חילוץ בלוק המיני-רציו החתוך כ-ground-truth לכל פסק-דין מנבו; harness שמשווה הלכות-שלנו מול הרציו ומפיק recall (כיסוי הלכות-הרציו) / precision / יחס-גרנולריות.",
|
||||
"details": "מקור ground-truth חינמי ואיכותי. ה-benchmark על 1764/05 כבר הודגם ידנית (recall=100%). לשמור את הרציו בשדה ייעודי (למשל case_law.headnote) במקום למחוק.",
|
||||
"status": "pending",
|
||||
"dependencies": [
|
||||
1
|
||||
],
|
||||
"testStrategy": "על 1764/05: recall=100%, מדווח granularity ratio; ניתן להריץ batch על כל פסקי-נבו ולהפיק טבלת איכות.",
|
||||
"parentId": "86"
|
||||
}
|
||||
],
|
||||
"updatedAt": "2026-06-03T00:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"version": "1.0.0",
|
||||
"lastModified": "2026-06-03T12:32:19.721Z",
|
||||
"lastModified": "2026-06-03T13:43:18.488Z",
|
||||
"taskCount": 85,
|
||||
"completedCount": 76,
|
||||
"completedCount": 77,
|
||||
"tags": [
|
||||
"legal-ai"
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user