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>
8.5 KiB
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+ flagNEEDS_CHAIR. - consistency guard: אם
proposed_bareלא מופיע ב-citation_formatted→ flagMISMATCH(לא יוחל אוטומטית; ליו"ר). (כיום 0 כאלה, אך הסקריפט בודק בזמן-ריצה.) - גיבוי: לפני apply, כתיבת
data/audit/fu2b-backup-<ts>.csv=(id, old_case_number)לכל רשומה שתשונה → revert-script טריוויאלי. - edge 8047/23: הסקריפט לא ממזג; מסמן את הזוג ב-flag
DUP_CHECKבטבלה. ההכרעה (מובחנות מול כפילות) היא של היו"ר; אם כפילות — מחיקה ידנית נפרדת (לא חלק מה-apply הדטרמיניסטי).
4. שער-אישור-היו"ר (chair gate)
- הרצת
--dry-run→ טבלת-תיאום (.md) + סיכום (כמה ישתנו, אילו flags). - הצגה לדפנה: הטבלה (52 שורות: ציטוט-נוכחי → bare מוצע) + ה-edge של 8047/23. היא מסמנת שורות שגויות (אם יש) ומכריעה על 8047/23.
- תיקון flags לפי הערותיה (אם יש), ואז
--apply --approved data/audit/fu2b-approved-<ts>.csv(קובץ-האישור = הטבלה לאחר סקירתה; הסקריפט מחיל רק שורות שאושרו). - אימות אחרי 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. סדר-ביצוע
- בדיקות אדומות ל-
_extract_bare+ consistency guard. _extract_bare+ הסקריפט (--dry-runבלבד תחילה) + הפקת טבלת-תיאום + גיבוי.- בדיקות ירוקות + dry-run מול DB → הפקת הטבלה.
- עצירה: הצגת הטבלה + 8047/23 ליו"ר (דפנה) — שער-אישור.
- (אחרי אישור) מימוש
--apply --approved+ אימות + revert-script. - הרצת apply בייצור (מבוקר) + אימות-אחרי + TaskMaster #67.
צעדים 1–3 לא דורשים את דפנה (אני מכין הכל). צעד 4 הוא שער-האישור. צעדים 5–6 אחרי אישורה.