# 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-.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-.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-.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. > צעדים 1–3 לא דורשים את דפנה (אני מכין הכל). צעד 4 הוא שער-האישור. צעדים 5–6 אחרי אישורה.