From da80bcf0fe24f6486b5e95a31c25974e3827fa12 Mon Sep 17 00:00:00 2001 From: Chaim Date: Sat, 30 May 2026 14:42:26 +0000 Subject: [PATCH] docs(spec): 01-ingest unified intake contract Co-Authored-By: Claude Opus 4.8 --- docs/spec/01-ingest.md | 154 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 docs/spec/01-ingest.md diff --git a/docs/spec/01-ingest.md b/docs/spec/01-ingest.md new file mode 100644 index 0000000..11358ea --- /dev/null +++ b/docs/spec/01-ingest.md @@ -0,0 +1,154 @@ +# 01 — קליטה מאוחדת (Unified Ingest Contract) + +קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) ומפרט את **חוזה הקליטה** של כל סוגי +ה-intake. הוא אוכף את [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) +(מקור-אמת יחיד, אין מסלולים מקבילים) ואת [G3](00-constitution.md#inv-g3-ingest-אחיד-ו-idempotent) +(ingest אחיד ו-idempotent), ונשען על [G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש) +ו-[G6](00-constitution.md#inv-g6-re-index-בכל-שינוי-תוכן). + +כשל-השורש שהקובץ מייבש: **שני מסלולי ingest לישויות-אחיות שמתפצלים** — `ingest_precedent` +(פסיקה חיצונית) מול `ingest_internal_decision` (החלטות-ועדה). מסלולים מקבילים גוררים drift: +פריט שנקלט במסלול אחד מקבל טיפול שונה מפריט במסלול האחר, והפער מתגלה רק כשרשומה חסרה +metadata או לא נמצאת בחיפוש. החוזה כאן מגדיר **מסלול קנוני אחד** ש-3 סוגי ה-intake עוברים בו. + +--- + +## 1. שלושת סוגי ה-intake + +| סוג-intake | מזהה-קנוני | קורפוס-יעד | מאפיין ייחודי | +|------------|------------|------------|----------------| +| מסמכי-תיק (case documents) | `case_number` + מזהה-מסמך | תיק ערר פעיל | משויך לתיק, מסווג לפי סוג-מסמך | +| פסיקה חיצונית (external precedent) | `citation` (קנוני) | `case_law` (external) | staging לפי `source_type`, ולידציית-enums, citation guard, multimodal | +| החלטות-ועדה (internal-committee) | `case_number` (קנוני) | `case_law` (internal_committee) | staging לפי district, `chair_name` חובה, גזירת district/proceeding_type | + +שלושתם הם **ישויות-אחיות**: אותו טיפוס-עיבוד (קובץ → טקסט → chunks → embeddings → metadata +→ הלכות), נבדלים בפרמטרים בלבד — לא במסלול-קוד. זוהי משמעות "סימטריה" (חוקה §6). + +--- + +## 2. המסלול הקנוני (Canonical Pipeline) + +צעדי-העיבוד, **בסדר מחייב**. כל סוג-intake עובר את אותם צעדים; ההבדל הוא אילו פרמטרים +מוזרקים בקלט, לא אילו צעדים מורצים. + +1. **Stage file** — העתקה דטרמיניסטית לאחסון המתמיד. נתיב-ה-staging הוא פרמטר + (`source_type` לפסיקה חיצונית, district להחלטות-ועדה), לא ענף-קוד נפרד. +2. **Extract text** — `extractor.extract_text` → `(text, page_count, page_offsets)`. + טקסט ריק = כשל מדווח (לא בליעה שקטה; חוקה §6). +3. **Strip Nevo preamble** — `extractor.strip_nevo_preamble` להסרת עטיפת-Nevo. **אחיד לכל סוג.** +4. **Chunk** — היררכי (`chunk_document_hierarchical`) אם `PARENT_DOC_RETRIEVAL_ENABLED`, + אחרת שטוח (`chunk_document`). **אותו ענף-flag בדיוק לכל סוג** — בורר הצ'אנקינג נגזר + מ-config, לא מסוג-ה-intake. +5. **Embed** — `embeddings.embed_texts(..., input_type="document")` ל-children (היררכי) + או לכל ה-chunks (שטוח). +6. **Store chunks** — `store_precedent_chunks_hierarchical` או `store_precedent_chunks`. +7. **Page-image embed (multimodal)** — אם `MULTIMODAL_ENABLED` **וגם** הקובץ PDF + **וגם** `page_count>0`: הטמעת עמודי-תמונה (`_embed_precedent_pages`). non-fatal: + מסלול-הטקסט כבר הצליח. **התנאי אחיד** — הפעלה תלויה ב-flag+סוג-קובץ, לא בסוג-ה-intake. +8. **Queue metadata extraction** — `request_metadata_extraction(case_law_id)`. נדרש לכל + סוג שתומך במטא-דאטה (ראה [INV-ING3](#inv-ing3-תור-חילוץ-מטא-דאטה--הלכות-לכל-סוג)). +9. **Queue halacha extraction** — `request_halacha_extraction(case_law_id)`. +10. **Set statuses** — `extraction_status=completed`, `halacha_status=pending`. + החילוץ ה-LLM-י (metadata + הלכות) רץ בנפרד מ-Claude Code המקומי + (`precedent_process_pending`), כי `claude` CLI אינו זמין בקונטיינר. + +> **צעדים שחייבים להיות אחידים בכל סוג (תיקון האסימטריה):** 2 (extract), 3 (strip-Nevo), +> 4 (בורר-chunk לפי flag), 5–6 (embed+store), **7 (multimodal — לפי flag+PDF, לא לפי +> סוג)**, **8–9 (תיזמון שני החילוצים)**, 10 (statuses). מה ש**רשאי** להשתנות לפי סוג: +> נתיב-ה-staging (צעד 1), ולידציות-קלט ספציפיות, וגזירת-שדות (district/proceeding_type) +> — אלו פרמטרים של אותו מסלול, לא מסלול נפרד. + +--- + +## 3. Invariants של התחום + +### INV-ING1: מסלול-קליטה קנוני יחיד +**כלל:** כל סוגי ה-intake (מסמכי-תיק / פסיקה חיצונית / החלטות-ועדה) זורמים דרך **פונקציית- +קליטה קנונית אחת**. סוג-intake חדש מורחב דרך **פרמטרים** של אותה פונקציה — לעולם לא דרך +פונקציה מקבילה. נתון-נגזר (district, proceeding_type) מחושב בתוך המסלול, לא בענף נפרד. +**מקורות:** Martin Kleppmann, *DDIA* (O'Reilly, 2017 — system of record יחיד) · Martin +Fowler (*Canonical Data Model*) · SSOT (Single Source of Truth) | סטטוס: verified +**אכיפה:** ביקורת-ארכיטקטורה + כלל-הנדסה "סימטריה" (חוקה §6); הקליטה מתנקזת לפונקציה אחת +שמקבלת פרמטרי-סוג. אוכף את [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים). +**הפרה ידועה:** היום קיימים **שני** מסלולים — `ingest_precedent` +(`precedent_library.py:88`) ו-`ingest_internal_decision` (`internal_decisions.py:73`) — +שמשכפלים את צעדי 2–10 ומתפצלים בפרטים → ממצא ל-[audit](../audit-report.md). + +### INV-ING2: קליטה idempotent על המזהה הקנוני +**כלל:** הקליטה היא **idempotent על המזהה הקנוני** (`citation` לפסיקה חיצונית, +`case_number` להחלטות-ועדה ולמסמכי-תיק). קליטה חוזרת של אותו פריט = **upsert** — +אין רשומה כפולה ואין chunks כפולים; התוצאה זהה. +**מקורות:** Martin Kleppmann, *DDIA* (idempotence & exactly-once) · Stripe / CDC +idempotency-key pattern · ISO 8000 (Data quality) | סטטוס: verified +**אכיפה:** מפתח-upsert דטרמיניסטי על המזהה הקנוני בנקודת-הקליטה (`create_external_case_law` +/ `create_internal_committee_decision`) + ולידציית-כתיבה; קשור ל- +[X1-identifiers.md](X1-identifiers.md) (נרמול בכתיבה). אוכף את +[G3](00-constitution.md#inv-g3-ingest-אחיד-ו-idempotent). +**הפרה ידועה:** 3 החלטות "סופר" נקלטו ב-3 פורמטים (`8126/24`, ציטוט-מלא כ-`case_number`) +— היעדר מפתח-upsert דטרמיניסטי גרר רשומות-כפל במקום עדכון → ממצא ל-[audit](../audit-report.md). + +### INV-ING3: תור חילוץ מטא-דאטה + הלכות לכל סוג +**כלל:** חילוץ-מטא-דאטה **וגם** חילוץ-הלכות מתוזמנים (queue) עבור **כל** סוג-intake שתומך +בהם — תיזמון אחיד, **לא** מותנה במסלול. שני התורים נפתחים יחד בסיום העיבוד הלא-LLM-י. +**מקורות:** ISO 8000 (completeness) · DAMA-UK *Six Primary Dimensions for Data Quality* +(2013, completeness) · Martin Fowler (quality-at-source) | סטטוס: verified +**אכיפה:** קריאה ל-`request_metadata_extraction` **ו**-`request_halacha_extraction` +בנקודת-סיום-הקליטה, לכל סוג; חוזה-שלמות יסמן רשומה ללא מטא-דאטה כלא-שמישה +([G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש), מפורט ב- +[02-data-model.md](02-data-model.md)). +**הפרה ידועה:** המסלול הפנימי (`internal_decisions.py:208`) מתזמן **רק** +`request_halacha_extraction` ואינו קורא ל-`request_metadata_extraction` (בניגוד +ל-`precedent_library.py:292-293` שקורא לשניהם) → ערן סופר 8046/24 נקלטה **בלי +metadata** (headnote/summary/tags ריקים) → ממצא ל-[audit](../audit-report.md). + +### INV-ING4: re-index בקליטה-חוזרת (upsert ⇒ re-embed) +**כלל:** קליטה-חוזרת ששינתה את תוכן-הפריט מפעילה **re-index** — chunks ו-embeddings +ישנים נמחקים ונבנים מחדש מהתוכן החדש. אין embeddings מיושנים אחרי upsert. +**מקורות:** Pinecone (index freshness / data sync) · Weaviate (re-vectorization on update) +· RAG freshness (Lewis et al., 2020, NeurIPS) | סטטוס: verified +**אכיפה:** טריגר re-embed בנתיב ה-upsert של הקליטה + בדיקת-בריאות לגילוי drift; מפורט +ב-[02-data-model.md](02-data-model.md) ו-[03-retrieval.md](03-retrieval.md). אוכף את +[G6](00-constitution.md#inv-g6-re-index-בכל-שינוי-תוכן). +**הפרה ידועה:** — + +--- + +## 4. מצב קיים מול יעד — audit-findings + +הסעיף מתעד את ההבדלים בין שני המסלולים הקיימים. **אלו תסמינים לאיחוד תחת המסלול הקנוני, +לא התנהגויות תקינות.** כל פריט אומת מול הקוד בפועל. + +- **חילוץ מטא-דאטה חסר במסלול הפנימי.** `ingest_precedent` קורא ל- + `request_metadata_extraction` **ו**-`request_halacha_extraction` + (`precedent_library.py:292-293`); `ingest_internal_decision` קורא **רק** ל- + `request_halacha_extraction` (`internal_decisions.py:208`) — אין `request_metadata_extraction`. + זו ההפרה הקונקרטית של [INV-ING3](#inv-ing3-תור-חילוץ-מטא-דאטה--הלכות-לכל-סוג) (ערן סופר 8046/24). + **יעד:** צעד 8 אחיד לשני הסוגים. +- **ולידציית-enums א-סימטרית.** המסלול החיצוני מוודא `practice_area`/`source_type` מול + רשימות חוקיות (`precedent_library.py:131-134`); המסלול הפנימי **אינו** מוודא enums. + **יעד:** ולידציה אחידה בנקודת-הקליטה (חוזה-שלמות, [G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש)). +- **staging מפוצל.** החיצוני עושה stage לפי `source_type` (`precedent_library.py:138`); + הפנימי עושה stage לפי district (`internal_decisions.py:113-115`). **יעד:** נתיב-staging + כפרמטר של המסלול הקנוני (צעד 1), לא ענף-קוד. +- **גזירת-שדות רק במסלול הפנימי.** הפנימי גוזר district מ-court (`:104`) ו-proceeding_type + מ-appeal_subtype/case_name (`:105`), ודורש `chair_name` (`:134`). החיצוני אינו גוזר אלו. + **יעד:** גזירה כפרמטר אופציונלי של המסלול הקנוני (שדות-סוג, לא מסלול-סוג). +- **citation guard רק במסלול החיצוני.** החיצוני חוסם ציטוט שמתחיל ב-`ערר`/`בל"מ` + ומפנה למסלול הפנימי (`precedent_library.py:124-130`). היעד שומר על השער הזה כניתוב-סוג + בתוך המסלול הקנוני, לא כהפרדת-פונקציות. +- **multimodal page-image embed רק במסלול החיצוני.** החיצוני מטמיע עמודי-תמונה כש- + `MULTIMODAL_ENABLED` + PDF (`precedent_library.py:272-278`); הפנימי **אינו** מטמיע + עמודי-תמונה. **יעד:** צעד 7 אחיד — מותנה ב-flag+סוג-קובץ בלבד. +- **fallback `case_name→citation` רק במסלול החיצוני.** החיצוני נופל ל-`citation` כשם + כשחסר `case_name` (`precedent_library.py:158`); הפנימי נופל ל-`case_number` + (`internal_decisions.py:130`). **יעד:** מדיניות-fallback אחת לשם-תצוגה במסלול הקנוני. + +--- + +## 5. הפניות-אחיות + +- [00-constitution.md](00-constitution.md) — invariants גלובליים + כללי-הנדסה. +- [02-data-model.md](02-data-model.md) — סכמת-האחסון + חוזה-שלמות שאוכף את תוצרי הקליטה. +- [03-retrieval.md](03-retrieval.md) — אחזור, re-index, eval — היעד של ה-chunks הנקלטים. +- [X1-identifiers.md](X1-identifiers.md) — נרמול המזהה הקנוני בכתיבה (בסיס ל-INV-ING2). +- [X5-audit-provenance.md](X5-audit-provenance.md) — שלמות-רשומה + עקיבוּת-מקור של פריט נקלט.