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

102 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 אחרי אישורה.