Files
legal-ai/docs/superpowers/specs/2026-05-31-fu2b-identifier-reconciliation-design.md
Chaim 105d9626ca docs(spec): FU-2b internal identifier reconciliation design (GAP-07/08) + split external to #68
Deterministic migration of ~52 internal_committee rows whose case_number holds
a full citation → normalized bare number (citation_formatted already correct).
DB analysis (2026-05-31): clean 1-token extraction, 0 key-collisions, 0
citation↔case_number mismatches, no month-padding dups. Chair-gated reversible
migration (backup→dry-run→approve→apply). One edge for chair: 8047/23 ערר vs בל"מ.
External (#68/FU-2c) split out — its citation_formatted is inconsistent.
Verified all 11 case_law FKs use id(UUID), not case_number → rename is FK-safe.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 06:12:43 +00:00

8.5 KiB
Raw Blame History

FU-2b — תיאום מזהי case_number (Identifier Reconciliation) — עיצוב

סטטוס: מאושר-לעיצוב · תאריך: 2026-05-31 · ענף: TBD מכסה: GAP-07, GAP-08 (scope: internal_committee בלבד) · מספק: INV-ID1, INV-ID2, INV-DM2 משימה: TaskMaster #67 · תלוי ב: FU-2a (#60, פונקציית הנרמול) · סוג: data-migration + chair-gate מחוץ-להיקף: external_upload → #68 / FU-2c (נתונים סותרים, ראה §1).


1. הבעיה והיקף (מאומת מול DB, 2026-05-31)

internal_committee הוא הקורפוס שבו case_number חייב להיות מספר-ועדה מנורמל (X1 §1), אך ~52/56 רשומות מחזיקות ציטוט-מלא בשדה-המזהה (GAP-08 — "החלטות סופר"), בניגוד ל-INV-ID2 (ציטוט = שדה-תצוגה נגזר, לעולם לא מזהה).

ממצאי-נתונים שמעצבים את המיגרציה:

  • חילוץ דטרמיניסטי ונקי: כל 56 הרשומות → בדיוק token-מספר אחד (regex [0-9]{2,6}(?:[-/][0-9]{1,2}){1,2}). 0 רב-משמעיים, 0 בלתי-פתירים.
  • עקביות מושלמת: ב-55/56 המספר המחולץ מופיע ב-citation_formatted; 0 סתירות. (1 רשומה בלי citation_formatted — כבר bare.)
  • 0 התנגשויות-מפתח על (bare, proceeding_type) → אין dedup.
  • אין בעיית with/without-month: ה"צורות הכפולות" (1024-24 מול 1024-25 וכו') הן שנים שונות = תיקים שונים, לא padding.
  • edge יחיד ליו"ר: 8047/23 קיים פעמיים — אחת proceeding_type=ערר, אחת בל"מ (48 chunks כל אחת). לפי X1 אלו שתי רשומות מובחנות (ערר מול בל"מ), אך זהות chunk-count מצדיקה אימות-יו"ר שאינן כפילות מתויגת-שגוי.

external מופרד (#68): ב-external נמצאה סתירה (case_number=25226-04-25 מול citation_formatted=1975/24) — ה-citation_formatted נוצר בנפרד ואינו ground-truth אמין; דורש טיפול נפרד. בנוסף, זהות פסיקה-חיצונית היא טבעית הציטוט (אין מספר-ועדה). מחוץ ל-FU-2b.

2. ההכרעה (מבוססת X1 + ממצאי-נתונים)

הצורה הקנונית של case_number ל-internal = trim · prefix-strip · /- על המספר הרשמי, בלי להמציא/להסיר חודש (X1 §1; מקורות: Codd 1NF · Kleppmann DDIA · SSOT — verified ב-X1). המיגרציה דטרמיניסטית (לא LLM): מחלצת את ה-token המספרי היחיד ומנרמלת. הציטוט כבר חי ב-citation_formatted — אין מה לנגוע בו.

דפוס-בטיחות (chair-gated reversible migration): גיבוי-לפני-שינוי → dry-run שמפיק טבלת-תיאום → שער-אישור-יו → apply מפורש → אימות. זהו דפוס סטנדרטי למיגרציה בלתי-הפיכה על נתוני-ייצור.

3. הרכיבים

  • סקריפט scripts/fu2b_reconcile_internal_case_numbers.py (לא MCP tool — מיגרציה חד-פעמית מבוקרת):
    • --dry-run (ברירת-מחדל): מפיק טבלת-תיאום data/audit/fu2b-reconciliation-<ts>.csv + .md קריא ליו"ר. עמודות: id, current_case_number, proposed_bare, proceeding_type, citation_formatted, consistency_ok, flag.
    • --apply: דורש קובץ-אישור (ראה §4); מגבה ואז מבצע.
    • מעבד רק source_kind='internal_committee' ורק רשומות שבהן proposed_bare != case_number (idempotent — already-bare לא נוגעים).
  • חילוץ: _extract_bare(case_number) -> str|None — regex token יחיד + _canonical_case_number (מ-FU-2a, db.py) לנרמול הסופי. אם 0 או >1 tokens → None + flag NEEDS_CHAIR.
  • consistency guard: אם proposed_bare לא מופיע ב-citation_formatted → flag MISMATCH (לא יוחל אוטומטית; ליו"ר). (כיום 0 כאלה, אך הסקריפט בודק בזמן-ריצה.)
  • גיבוי: לפני apply, כתיבת data/audit/fu2b-backup-<ts>.csv = (id, old_case_number) לכל רשומה שתשונה → revert-script טריוויאלי.
  • edge 8047/23: הסקריפט לא ממזג; מסמן את הזוג ב-flag DUP_CHECK בטבלה. ההכרעה (מובחנות מול כפילות) היא של היו"ר; אם כפילות — מחיקה ידנית נפרדת (לא חלק מה-apply הדטרמיניסטי).

4. שער-אישור-היו"ר (chair gate)

  1. הרצת --dry-run → טבלת-תיאום (.md) + סיכום (כמה ישתנו, אילו flags).
  2. הצגה לדפנה: הטבלה (52 שורות: ציטוט-נוכחי → bare מוצע) + ה-edge של 8047/23. היא מסמנת שורות שגויות (אם יש) ומכריעה על 8047/23.
  3. תיקון flags לפי הערותיה (אם יש), ואז --apply --approved data/audit/fu2b-approved-<ts>.csv (קובץ-האישור = הטבלה לאחר סקירתה; הסקריפט מחיל רק שורות שאושרו).
  4. אימות אחרי apply: כל internal case_number תואם regex bare; 0 ציטוטים בשדה-המזהה; search/get_case_by_number עדיין פותרים (FU-2a tolerant-read + הנרמול).

5. אינטראקציה עם FU-2a (forward-consistency)

FU-2a _canonical_case_number מנרמל prefix+separator אך אינו מחלץ מספר מתוך ציטוט-מלא. לכן אם קליטה עתידית תעביר ציטוט-מלא כ-case_number, ייווצר שוב מזהה מלוכלך. הערכת-סיכון: נמוכה — טופס-ההעלאה וה-MCP tool מעבירים שדה-case_number נפרד (בד"כ נקי). החלטה: FU-2b הוא ניקוי-נתונים בלבד; הקשחת-כתיבה (חילוץ-token גם ב-create) לא בהיקף — תיפתח רק אם יתגלה caller שמעביר ציטוט. (מתועד; לא לשנות התנהגות-כתיבה בלי ראיה.)

6. שינויי-התנהגות וסיכון

שינוי השפעה סיכון
case_number של ~52 internal → bare חיפוש exact-match על המספר עובד; (case_number,proceeding_type) נקי נמוך — דטרמיניסטי, גיבוי, שער-יו"ר, 0 collisions
8047/23 edge אולי מחיקת רשומה כפולה בינוני — רק בהחלטת-יו"ר, מחיקה ידנית נפרדת, לא ב-apply האוטומטי
citation_formatted לא משתנה (כבר תקין) אין
FK/relations case_law_relations/precedent_internal_citations מפנים ל-id (UUID), לא ל-case_number אין — שינוי case_number לא שובר קשרים
chunks/embeddings מפתח-זר case_law_id (UUID) — לא תלוי ב-case_number אין — re-index לא נדרש

7. אסטרטגיית בדיקה

  • בדיקות-יחידה offline (tests/test_fu2b_reconcile.py): _extract_bare — token יחיד→bare מנורמל; ציטוט מלא→המספר הנכון (דוגמאות אמיתיות: "ערר (...) 403/17 אהרון ברק..."403-17, "...8136-10-24 שחר..."8136-10-24 חודש נשמר); 0/רב-token→None+flag; consistency guard.
  • dry-run מול DB מקומי: הטבלה מופקת, מספר-השורות-לשינוי = ~52, 0 MISMATCH, 1 DUP_CHECK (8047).
  • apply בסביבת-בדיקה: על עותק/תיק-בדיקה — אימות idempotency (הרצה שנייה = 0 שינויים) + revert מהגיבוי.
  • ה-apply בייצור רץ רק אחרי אישור-יו (לא חלק מה-CI/PR; ידני ומבוקר).

8. סדר-ביצוע

  1. בדיקות אדומות ל-_extract_bare + consistency guard.
  2. _extract_bare + הסקריפט (--dry-run בלבד תחילה) + הפקת טבלת-תיאום + גיבוי.
  3. בדיקות ירוקות + dry-run מול DB → הפקת הטבלה.
  4. עצירה: הצגת הטבלה + 8047/23 ליו"ר (דפנה) — שער-אישור.
  5. (אחרי אישור) מימוש --apply --approved + אימות + revert-script.
  6. הרצת apply בייצור (מבוקר) + אימות-אחרי + TaskMaster #67.

צעדים 13 לא דורשים את דפנה (אני מכין הכל). צעד 4 הוא שער-האישור. צעדים 56 אחרי אישורה.