feat(halacha): UNIQUE(case_law_id, halacha_index) backstop + task tracking (#83)

#83 pipeline robustness — the index-numbering correctness guarantee:
- Add CREATE UNIQUE INDEX idx_halachot_unique_index ON halachot(case_law_id,
  halacha_index). The extractor assigns the index as MAX+1 under an in-process
  store-lock + a cross-process pg advisory lock, so collisions shouldn't occur
  in normal operation — but per the research (FireHydrant/OneUptime) the
  constraint is the actual correctness guarantee while the lock is the
  optimization. A racing/double run now fails LOUDLY (UniqueViolation, chunk
  left un-checkpointed → clean resume) instead of silently appending the
  duplicates that were the 2026-05/06 over-extraction root cause.

Data prep (run against the live DB before the constraint, backed up to
data/audit/halacha-reindex-backup-*.sql): the 6 precedents that still carried
colliding halacha_index values (9 groups, distinct principles that shared a
number — NOT content dups) were renumbered to unique sequential indices.

Verified: advisory lock holds cross-process and the DB path is direct asyncpg
(no transaction-pooler), so the session lock is safe (83.1); force=True does
delete+checkpoint-clear in one transaction (83.5); constraint rejects a
duplicate-index insert (integration-checked). Full suite 156 passed.

Also commits the TaskMaster tracking for the whole halacha-quality initiative
(#81-#84 + research-backed subtasks, statuses).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 13:06:58 +00:00
parent 8e3d14abee
commit 0f64b4c062
2 changed files with 380 additions and 3 deletions

View File

@@ -2546,13 +2546,381 @@
"priority": "low", "priority": "low",
"subtasks": [], "subtasks": [],
"updatedAt": "2026-06-03T00:00:00.000Z" "updatedAt": "2026-06-03T00:00:00.000Z"
},
{
"id": "81",
"title": "איכות חילוץ הלכות — לוודא שמה שמחולץ הוא הלכה אמיתית ולא ציטוט/אמרת-אגב",
"description": "מנוע חילוץ ההלכות מפיק כיום פריטים שאינם 'הלכות' במובן המהותי: אמרות-אגב שהערכאה לא הכריעה בהן, יישומים ספציפיים-לתיק (rule_type=application), ציטוטים חתוכים, ופירוק-יתר (עד 351 'הלכות' מפסק אחד). המשימה: לחדד את ה-prompt ולהוסיף ולידטורים אוטומטיים כך שרק עיקרון משפטי בר-הכללה ובר-הסתמכות ייכנס למאגר. מבוססת מחקר מקצועי (ratio decidendi מול obiter dictum, holding-extraction בספרות legal-NLP, קריטריונים לאיכות rule_statement). תתי-המשימות יוגדרו לאחר המחקר.",
"details": "רקע מבצעי (ניקוי 2026-06-03): נמחקו 196 רשומות מתוך 1650 (165 כפילויות תוכן + 31 Tier B/C). גיבוי: data/audit/halacha-cleanup-backup-20260603T101747Z.sql ; מניפסט: data/audit/halacha-cleanup-manifest-20260603T101747Z.csv . קוד רלוונטי: mcp-server/src/legal_mcp/services/halacha_extractor.py (prompts BINDING/PERSUASIVE, _coerce_halacha, אימות ציטוט). תחומי בדיקה ידועים: (א) חסימת quote_verified=false מהתור; (ב) הוצאת rule_type=application מהגדרת 'הלכה'; (ג) שמירה על דחיית dicta שלא הוכרעו; (ד) תקרת כמות/גרנולריות לפסק; (ה) הגנה מפני ציטוט חתוך (truncation guard). מפרט מאומת: docs/halacha-strict-rubric.md (הרובריקה האגרסיבית שהנחיתה ניקוי קורפוס 1454→534 ב-2026-06-03, שפר 51→22). להטמיע אותה במחלץ + dedup-on-insert (#82).",
"testStrategy": "Gold-set ידני של ~30 פריטים מתויגים הלכה/לא-הלכה ע\"י דפנה/חיים; מדידת precision/recall של המחלץ המעודכן מולו. בדיקת רגרסיה: הרצת המחלץ החדש על 3-5 פסקים שכבר נוקו והשוואת הפריטים.",
"status": "in-progress",
"dependencies": [],
"priority": "high",
"subtasks": [
{
"id": 1,
"title": "מבחן ההיפוך (Wambaugh) + גלאי אי-הכרעה בפרומפט",
"description": "הטמעת מבחן ההיפוך של Wambaugh ולקסיקון ביטויי אי-הכרעה עבריים בשני וריאנטי הפרומפט; ניסוח עיקרון, שלילתו, ובדיקה אם תוצאת הוועדה הייתה משתנה — אם לא → obiter.",
"details": "מקור: Wambaugh inversion test; Goodhart material-facts. לקסיקון: 'אין צורך להכריע','מבלי לקבוע מסמרות','ניתן להניח','לכאורה','למעלה מן הצורך','אגב אורחא' → מאלצים rule_type=obiter ו-confidence<0.80. קוד: halacha_extractor.py prompts BINDING/PERSUASIVE.",
"status": "done",
"dependencies": [],
"testStrategy": "על מדגם 30 רשומות obiter שזוהו ידנית באודיט, ≥90% מסווגות obiter ולא מאושרות אוטומטית.",
"parentId": "81",
"updatedAt": "2026-06-03T12:32:01.304Z"
},
{
"id": 2,
"title": "ולידטור שלמות ציטוט (truncation guard, V1)",
"description": "בדיקה ש-supporting_quote מתחיל בגבול משפט ומסתיים בפיסוק סופי (./?/:/מרכאה סוגרת) ואינו קטוע באמצע משפט; דגלון לציטוט קטוע.",
"details": "מקור: span-completeness / grounding-score. כיום הבדיקה מאמתת נוכחות verbatim אך לא שלמות. דוגמת אודיט: ציטוט '...ראוי כי תהיה השפעה על ה' (חתוך).",
"status": "done",
"dependencies": [],
"testStrategy": "על 50 ציטוטים קטועים ידועים ≥95% מסומנים; false-positive <5% על ציטוטים תקינים.",
"parentId": "81",
"updatedAt": "2026-06-03T12:32:01.318Z"
},
{
"id": 3,
"title": "ולידטור entailment (NLI) בין rule ל-quote (V2)",
"description": "מודל NLI מאמת ש-rule_statement נובע (entailed) מ-supporting_quote; neutral/contradiction → מתחת לסף אישור אוטומטי.",
"details": "מקור: NLI-faithfulness (DeBERTa-NLI / atomic-fact verification). בודק שהכלל אכן נתמך בציטוט ולא הוזה/הורחב מעבר למקור.",
"status": "pending",
"dependencies": [],
"testStrategy": "על gold-set מתויג, NLI-reject מתאם עם פסילה אנושית ב-AUC ≥0.80.",
"parentId": "81"
},
{
"id": 4,
"title": "ולידטור הפשטה + סיווג application (V3)",
"description": "פסילת restatement דק (token-overlap גבוה מול הציטוט) וסיווג כללים תלויי-עובדות/סכומים/צדדים-ספציפיים כ-application (מוחרג מאישור binding).",
"details": "מקור: rule-synthesis (Mangan) — broad holding מפשיט עובדות ספציפיות. כיום application סותם את התור (16/55 pending נמחקו באודיט).",
"status": "in-progress",
"dependencies": [],
"testStrategy": "על מדגם 40 פריטי application מהאודיט, ≥85% מזוהים ולא מאושרים כ-binding/interpretive.",
"parentId": "81",
"updatedAt": "2026-06-03T12:32:19.701Z"
},
{
"id": 5,
"title": "בקרת over-extraction: clustering + תקרה (V5)",
"description": "embedding+clustering של ה-rule_statements בתוך פסק-דין, נציג אחד לאשכול, ותקרה לכמות לפי מספר מקטעי reasoning/decision (לא לפי מספר משפטים).",
"details": "מקור: SemDeDup + claim-clustering (MultiClaimNet). דוגמה: פסק 403-17 ייצר 351 'הלכות' — over-decomposition.",
"status": "pending",
"dependencies": [],
"testStrategy": "פסק עם 351 פריטים יורד ל-≤ מספר מקטעי ההנמקה; ירידה כוללת ≥40% בכפילויות.",
"parentId": "81"
},
{
"id": 6,
"title": "סינון לפי תפקיד רטורי (rhetorical-role pre-filter)",
"description": "רק מקטעי Reasoning/Decision מועמדים לחילוץ; Facts/Arguments מוחרגים מראש.",
"details": "מקור: LegalSeg / rhetorical-role labeling (Bhattacharya). בלבול Facts↔Reasoning הוא מחלקת השגיאה הדומיננטית — סינון מקדים מעלה precision.",
"status": "pending",
"dependencies": [],
"testStrategy": "precision של מועמדים שעוברים סינון עולה ≥10 נק' מול baseline על gold-set.",
"parentId": "81"
},
{
"id": 7,
"title": "בניית gold-set + מבחן אנונימיזציית ציטוטים",
"description": "תיוג ידני של ~150 רשומות (binding/interpretive/obiter/application + שלמות-ציטוט) ע\"י חיים/דפנה, ומבחן אנונימיזציה (שמות-תיק בדויים) לזיהוי שינון מול הנמקה אמיתית.",
"details": "מקור: CaseHOLD/RegLab (macro-F1≈0.72-0.74 לזיהוי holdings — לא 'פתור') + מבחן anonymization (arXiv:2505.02172). בסיס למדידת כל הוולידטורים.",
"status": "pending",
"dependencies": [],
"testStrategy": "gold-set מתויג עם κ≥0.6 בין שני מתייגים; pipeline מודד P/R/F1 מולו.",
"parentId": "81"
},
{
"id": 8,
"title": "כיול מחדש של סף האישור האוטומטי",
"description": "אחרי V1-V5, כיול סף ה-0.80; שקילת החלפת confidence עצמי בציון משולב (confidence × validators).",
"details": "מקור: CaseHOLD מראה ש-self-confidence 0.80 אופטימי מדי בלי אימות חיצוני. תלוי ב-gold-set (81.7).",
"status": "pending",
"dependencies": [
7
],
"testStrategy": "precision של פריטים מאושרים-אוטומטית ≥0.90 על gold-set, עם recall מתועד ל-trade-off.",
"parentId": "81"
}
],
"updatedAt": "2026-06-03T12:32:19.701Z"
},
{
"id": "82",
"title": "Dedup בזמן הכנסה — מניעת הלכות כמעט-זהות בכתיבה (semantic dedup on insert)",
"description": "store_halachot_for_chunk מבצע INSERT עיוור ללא בדיקת כפילות, ולכן נצברו עשרות הלכות 'אותו עיקרון במילים אחרות'. המשימה: לפני שמירה, לבדוק דמיון סמנטי (embedding) מול הלכות קיימות באותו פסק ולדלג/למזג near-duplicates, וכן לזהות ציטוט-תומך זהה. מבוססת מחקר (ספי near-duplicate ב-embedding-IR, MinHash/LSH מול cosine, בחירת צורה קנונית, merge מול skip). תתי-המשימות יוגדרו לאחר המחקר.",
"details": "ממצא מהניקוי: סף cosine ≥0.90 תוך-פסק זיהה כפילויות אמיתיות בוודאות גבוהה (הרצועה 0.90-0.95 הייתה כמעט כולה 'אותו עיקרון בניסוח שונה'); ≥0.95 = ודאי. ציטוט-תומך זהה = איתות ודאי. להחליט: סף מבצעי, התנהגות (skip/merge/flag-for-review), ובחירת השורד (approved>pending, ביטחון גבוה, quote_verified). קוד: db.store_halachot_for_chunk; קיים idx_halachot_vec (pgvector).",
"testStrategy": "הרצה חוזרת של חילוץ על פסק שכבר חולץ — אפס כפילויות חדשות. בדיקת יחידה: הזנת שתי הלכות מנוסחות-שונה של אותו עיקרון → רק אחת נשמרת. בדיקה שלילית: שתי הלכות שונות ≥0.85 אך מובחנות → שתיהן נשמרות.",
"status": "in-progress",
"dependencies": [],
"priority": "high",
"subtasks": [
{
"id": 1,
"title": "כיול ספים מתוך ה-audit (calibration harness)",
"description": "בניית set מתויג מזוגות האודיט שכבר נבדקו ידנית; sweep של cosine × (Jaccard/Levenshtein) ודיווח precision/recall לבחירת נקודת עבודה.",
"details": "מקור: SemDeDup tunes ε ע\"י דגימת ~10% ואינטרפולציה ליעד — לכייל, לא לנחש. הנתונים קיימים במניפסט הניקוי data/audit/halacha-cleanup-manifest-*.csv.",
"status": "pending",
"dependencies": [],
"testStrategy": "הסקריפט מפיק (cosine_hi, band_lo, lexical_lo, jaccard_τ, levenshtein_δ) עם ≥0.95 precision נגד מיזוג-שווא של כללים מובחנים.",
"parentId": "82"
},
{
"id": 2,
"title": "שכבת בדיקה אקזקטית בתוך precedent (exact-cosine probe)",
"description": "בהכנסה: שאילתת halachot קיימות מסוננת ב-precedent_id עם cosine מדויק (לא ANN) + התאמת supporting_quote מנורמל.",
"details": "מקור: pgvector ANN (HNSW/IVFFlat) מאבד recall → false-negative בדדופ. מועמדים בתוך פסק בודד = set קטן → exact scan בטוח. שמירה על HNSW לנתיב הקריאה.",
"status": "done",
"dependencies": [],
"testStrategy": "בדיקת יחידה: dup מנוסח-שונה (cosine 0.92) + dup ציטוט-verbatim שניהם מזוהים; כלל מובחן (0.7) לא; EXPLAIN מראה filtered scan ולא ANN.",
"parentId": "82",
"updatedAt": "2026-06-03T12:32:01.334Z"
},
{
"id": 3,
"title": "זנב מתחת ל-0.90 — אות לקסיקלי משני",
"description": "הוספת Jaccard-on-shingles + Levenshtein מנורמל על rule_statement; שער לרצועה 0.83-0.90 (cosine נמוך אך חפיפה לקסיקלית גבוהה → flag).",
"details": "מקור: hybrid lexical+semantic עדיף על כל אחד לבד (arXiv:1805.11611, WED arXiv:1810.10752). תופס את הזנב שדמיון-וקטורי לבד מפספס בלי להוריד סף גלובלי.",
"status": "pending",
"dependencies": [],
"testStrategy": "זוג מנוסח-שונה ב-cosine~0.87 עם חפיפה גבוהה → flag; זוג בחפיפה נמוכה ב-0.87 → נשמר כחדש.",
"parentId": "82"
},
{
"id": 4,
"title": "התנהגות בהתאמה: skip/merge/flag + מיזוג provenance",
"description": "מימוש החלטה מדורגת (ציטוט-זהה/≥0.95→skip+merge; 0.90-0.95→merge; 0.83-0.90+לקסיקלי→flag). במיזוג: איחוד citations/corroboration/source-refs; בחירת קנוני לפי provenance עשיר ביותר, שובר-שוויון ציטוט verbatim ארוך.",
"details": "מקור: entity-resolution — לכללים יקרי-ערך flag+merge-with-provenance, לא drop עיוור (ThingSolver/ScrapingAnt). בחירת נציג לפי כלל-תוכן (SemDeDup: גאומטריה שולית).",
"status": "in-progress",
"dependencies": [],
"testStrategy": "מיזוג שתי שורות → שורה אחת עם איחוד קישורי citation/corroboration; שורות flag נוחתות בטבלת תור-ביקורת, לא נמחקות ולא ממוזגות אוטומטית.",
"parentId": "82",
"updatedAt": "2026-06-03T12:32:19.710Z"
},
{
"id": 5,
"title": "אידמפוטנטיות ומפתח ייחוד",
"description": "אילוץ UNIQUE על (precedent_id, normalized supporting_quote) + נתיב ON CONFLICT כך שהרצת חילוץ חוזרת אינרטית.",
"details": "מקור: 'dedup=suppression, idempotency=identity' — שער fuzzy חייב מפתח-זהות מדויק לצדו. מתואם עם #83.",
"status": "in-progress",
"dependencies": [],
"testStrategy": "הרצת store_halachot_for_chunk פעמיים על אותו chunk → אפס שורות נטו חדשות.",
"parentId": "82",
"updatedAt": "2026-06-03T12:32:19.721Z"
},
{
"id": 6,
"title": "הגנה מפני over-merge טרנזיטיבי",
"description": "מיזוג בהכנסה רק pairwise מול שורות קנוניות קיימות; ללא connected-components closure בהכנסה — קריסות רב-שורתיות נדחות לתור-ביקורת.",
"details": "מקור: over-merge דרך CC הוא הסיכון המרכזי (A~B~C קורס גם כש-A,C מובחנים) — בכלל משפטי = אובדן תקדים.",
"status": "pending",
"dependencies": [],
"testStrategy": "שרשרת סינתטית A~B~C כש-A,C מתחת לסף זה-לזה אינה קורסת לשורה אחת בהכנסה; לכל היותר מיזוג pairwise + flag.",
"parentId": "82"
},
{
"id": 7,
"title": "(אופציונלי) batch reconciliation חוצה-פסקים",
"description": "job offline שמרני (סף ≥0.95) לדדופ חוצה-פסקים, נפרד מנתיב ההכנסה, כותב לאותו תור-ביקורת.",
"details": "מקור: cross-precedent שמרני יותר מ-within-precedent (האודיט הראה ש-≥0.90 אמין רק תוך-פסק). תחת scripts/ + עדכון SCRIPTS.md.",
"status": "pending",
"dependencies": [
1
],
"testStrategy": "dry-run מפיק דוח מועמדים חוצי-פסקים עם cosine+lexical; כלום לא מוחל בלי flag.",
"parentId": "82"
}
],
"updatedAt": "2026-06-03T12:32:19.721Z"
},
{
"id": "83",
"title": "חוסן pipeline החילוץ — re-run אידמפוטנטי + אינדוקס תקין (תיקון באגים)",
"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",
"dependencies": [],
"priority": "medium",
"subtasks": [
{
"id": 1,
"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",
"dependencies": [],
"testStrategy": "שתי הרצות extract במקביל על אותו precedent — השנייה מחזירה busy ולא רצה (נבדק עם 2 תהליכים נפרדים).",
"parentId": "83"
},
{
"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",
"dependencies": [],
"testStrategy": "INSERT ידני של index כפול לאותו precedent נכשל ב-DB; query GROUP BY case_law_id,halacha_index HAVING count>1 מחזיר 0 בקורפוס.",
"parentId": "83"
},
{
"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",
"dependencies": [],
"testStrategy": "הרצת extract(force=False) פעמיים ברצף על precedent שהושלם → מספר ה-halachot לא גדל.",
"parentId": "83"
},
{
"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",
"dependencies": [
2
],
"testStrategy": "בדיקה שמדמה שני קוראי-MAX מקבילים — שניהם נשמרים עם indexים שונים רצופים, אפס DuplicateKey לא-מטופל.",
"parentId": "83"
},
{
"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",
"dependencies": [
2,
3
],
"testStrategy": "הזרקת קריסה אחרי חלק מהצ'אנקים → resume משלים ללא כפילויות; count(halachot) תואם הרצה נקייה.",
"parentId": "83"
},
{
"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",
"dependencies": [
2
],
"testStrategy": "אחרי הרצה, אילוצי 83.2/83.3 נוצרים ללא שגיאה; דוח CSV ב-data/audit/ מפרט כמה תוקנו לכל precedent.",
"parentId": "83"
}
],
"updatedAt": "2026-06-03T00:00:00.000Z"
},
{
"id": "84",
"title": "טריאז' תור אישור ההלכות — אישור יעיל ולא מתיש",
"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",
"dependencies": [
"81",
"82"
],
"priority": "medium",
"subtasks": [
{
"id": 1,
"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",
"dependencies": [],
"testStrategy": "תור ברירת-מחדל מחזיר 0 פריטים עם דגלי low-quality; פרמטר include_low_quality=true עדיין חושף אותם.",
"parentId": "84"
},
{
"id": 2,
"title": "קיבוץ near-duplicates לכרטיס ביקורת אחד (מ-#82)",
"description": "halachot_pending מחזיר near-duplicates (cosine ≥0.90) מקובצים בכרטיס אחד: נציג קנוני + מונה וריאנטים + רשימת וריאנטים.",
"details": "מקור: similarity-clustering + review-by-cluster (Labelbox/Label Studio). צורך פלט #82.",
"status": "pending",
"dependencies": [],
"testStrategy": "קבוצת כפילויות ידועה (מהניקוי 2026-06-03) חוזרת ככרטיס מקובץ אחד ולא N שורות; הוריאנטים ניתנים למנייה.",
"parentId": "84"
},
{
"id": 3,
"title": "תיעדוף התור לפי ציון משוקלל (uncertainty + impact)",
"description": "החלפת FIFO בציון עדיפות משולב: ביטחון באזור-אפור קודם, מוגבר ע\"י corroboration של ציטוט-בודד ותחומי-עיסוק בכיסוי דליל; דיכוי כפילויות של פריט-הראש.",
"details": "מקור: active learning — least-confidence first + diversity/impact weighting (Encord/Label Studio/greip). uncertainty לבד מדגים יתר-על-המידה near-dups.",
"status": "pending",
"dependencies": [],
"testStrategy": "בהינתן set מתוכנן, ראש התור הוא הפריט בעל הציון המשולב הגבוה; שתי כפילויות לא מופיעות יחד ב-top-5.",
"parentId": "84"
},
{
"id": 4,
"title": "פעולות batch: אישור/דחייה לכל הקבוצה",
"description": "הרחבת halacha_review לקבלת מזהה-קבוצה והחלת approve/reject על כל הוריאנטים בקריאה אחת, עם אפשרות override ידני של וריאנט לפני commit.",
"details": "מקור: propagate-one-decision-to-group + checkpoint אנושי על bulk (Labelbox). סיכון: bulk-apply עיוור מפיץ שגיאה — חובה הצגת וריאנטים לפני אישור.",
"status": "pending",
"dependencies": [],
"testStrategy": "אישור קבוצת 5 וריאנטים מסמן את כולם approved בפעולה אחת; דחייה מסמנת את כולם rejected; override של וריאנט בודד אפשרי.",
"parentId": "84"
},
{
"id": 5,
"title": "דחייה/השהיה זולה + סמנטיקת reject נכונה",
"description": "הוספת outcomes מפורשים reject ו-defer ל-halacha_review; reject שומר אות שלילי מתמשך (מזין חזרה ל-#81), defer משאיר pending ומחזיר לסוף התור.",
"details": "מקור: Prodigy accept/reject/ignore — הבחנה סמנטית היא הפתרון ל-1/1650. כיום אין פועל 'זבל' זול ולכן זבל מצטבר כ-pending.",
"status": "pending",
"dependencies": [],
"testStrategy": "reject קובע status rejected מתמשך + סיבה (queryable למשוב מחלץ); defer משאיר pending אך מוריד עדיפות.",
"parentId": "84"
},
{
"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",
"dependencies": [
4,
5
],
"testStrategy": "מבקר יכול approve/reject/defer/edit-then-approve כולו במקלדת בלי עכבר; הכרטיס מציג מונה-וריאנטים וציטוט-מקור.",
"parentId": "84"
},
{
"id": 7,
"title": "מדדי תור: throughput + איכות",
"description": "אינסטרומנטציה וחשיפה: time-per-item, decisions-per-session, יחס approve/reject/defer/edit, pending-count + גיל-הישן-ביותר, ומדגם spot-check post-hoc של פריטים מאושרים.",
"details": "מקור: Prodigy metrics + IAA (Krippendorff α / Gwet AC2). מבקר-יחיד לא נמדד ב-IAA → spot-check error rate חיוני.",
"status": "pending",
"dependencies": [
5
],
"testStrategy": "endpoint/דף מדדים מדווח את הנ\"ל; נמדד before/after של קליקים-וזמן לביקורת N=20 הלכות.",
"parentId": "84"
}
],
"updatedAt": "2026-06-03T00:00:00.000Z"
},
{
"id": "85",
"title": "CEO MCP instance: nested claude -p exits 1 in write_interim_draft",
"description": "write_interim_draft נכשל לכל 5 הבלוקים מתוך session ה-CEO עם 'Claude CLI failed (exit 1): unknown error'. אומת: claude CLI תקין מ-bash (exit 0), PATH+HOME של תהליך ה-MCP תקינים (/home/chaim/.local/bin/claude), אין ANTHROPIC_API_KEY ב-.env, הבלוקים נכתבים סדרתית (לא concurrency). סוכני משנה (proofreader/analyst CMPA-73..76) הריצו claude -p בהצלחה באותו יום. ⇒ כשל ספציפי ל-MCP server instance של ה-CEO. עוקף: האצלה ל-writer agent. דרוש: לבדוק מדוע nested claude -p נכשל מ-instance זה (אולי session lock / env stale); שקול restart ל-CEO agent session.",
"details": "",
"testStrategy": "",
"status": "pending",
"dependencies": [],
"priority": "high",
"subtasks": []
} }
], ],
"metadata": { "metadata": {
"version": "1.0.0", "version": "1.0.0",
"lastModified": "2026-06-03T08:10:57.844Z", "lastModified": "2026-06-03T12:32:19.721Z",
"taskCount": 79, "taskCount": 85,
"completedCount": 72, "completedCount": 76,
"tags": [ "tags": [
"legal-ai" "legal-ai"
] ]

View File

@@ -676,6 +676,15 @@ CREATE INDEX IF NOT EXISTS idx_halachot_practice ON halachot USING gin(practice_
CREATE INDEX IF NOT EXISTS idx_halachot_tags ON halachot USING gin(subject_tags); CREATE INDEX IF NOT EXISTS idx_halachot_tags ON halachot USING gin(subject_tags);
CREATE INDEX IF NOT EXISTS idx_halachot_vec CREATE INDEX IF NOT EXISTS idx_halachot_vec
ON halachot USING ivfflat (embedding vector_cosine_ops) WITH (lists = 50); ON halachot USING ivfflat (embedding vector_cosine_ops) WITH (lists = 50);
-- #83: halacha_index must be unique per precedent. The extractor assigns it as
-- MAX(halacha_index)+1 under an in-process store-lock + a cross-process advisory
-- lock, so collisions shouldn't occur — but per FireHydrant/OneUptime the
-- constraint is the actual correctness guarantee (the lock is the optimization).
-- A racing/double run now fails LOUDLY instead of silently appending duplicates
-- (the 2026-05/06 over-extraction root cause). Requires clean data first (see
-- scripts: the 6 colliding precedents were renumbered 2026-06-03).
CREATE UNIQUE INDEX IF NOT EXISTS idx_halachot_unique_index
ON halachot(case_law_id, halacha_index);
""" """