Files
legal-ai/docs/spec/02-data-model.md
2026-05-30 14:50:06 +00:00

14 KiB
Raw Blame History

02 — מודל-הנתונים (Data Model & Completeness Contract)

קובץ-תחום זה כפוף ל-חוקת המערכת ומגדיר את מודל-הנתונים הקנוני (TARGET) של עוזר משפטי — הישויות, שדות-המפתח, והיכן יושב כל פריט מואנדקס. הוא אוכף את G1 (מזהה קנוני יחיד), G4 (חוזה-שלמות) ו- G6 (re-index בשינוי-תוכן).

TARGET, לא תיאור-מצב. המודל כאן הוא היעד הקנוני. כל מקום שבו ה-schema בפועל (mcp-server/src/legal_mcp/services/db.py) סוטה ממנו — מתועד כ-audit-finding (§4), תסמין לאיחוד, לא התנהגות תקינה. כל טענה על ה-schema הקיים מצוטטת file:line.


1. הישויות הקנוניות

הטבלה מונה את ישויות-הליבה. "מזהה-קנוני" = השדה היחיד המזהה רשומה (G1).

ישות תפקיד מזהה-קנוני שדות-מפתח (מאומתים db.py)
cases תיק ערר חי (1xxx/8xxx/9xxx) case_number + proceeding_type title, status, practice_area, appeal_subtype, proceeding_type, chair_name (db.py:74-91,182-189,747,912)
documents מסמך-מקור משויך לתיק id (UUID); FK→cases doc_type, title, file_path, extracted_text, extraction_status, page_count (db.py:93-104)
document_chunks chunk של מסמך-תיק + embedding id; FK→documents/cases chunk_index, content, section_type, embedding vector(1024), page_number (db.py:106-116)
case_law קורפוס פסיקה — חיצוני וגם החלטות-ועדה ראה §2 + INV-DM2 case_name, court, practice_area, source_kind, proceeding_type, source_type, headnote, summary, subject_tags, extraction_status, halacha_extraction_status (db.py:366-378,522-526,599-611,883,907)
precedent_chunks chunk של פסק-דין מואנדקס (source_kind='external_upload'/internal_committee) id; FK→case_law chunk_index, content, section_type, page_number, embedding vector(1024), content_tsv (db.py:624-634,776)
halachot הלכה מחולצת — כלל + ציטוט מילולי id; FK→case_law rule_statement, supporting_quote, rule_type, practice_areas, subject_tags, confidence, quote_verified, review_status, embedding, rule_tsv (db.py:644-666,780)
decisions החלטת-תיק מנוסחת (גרסה) id; UNIQUE(case_id, version) version, status, outcome, outcome_summary (db.py:299-314)
decision_blocks בלוק (12) של החלטה id; UNIQUE(decision_id, block_id) block_id, block_index, content, status (db.py:317-334)
claims טענת-צד (בלוק ז) id; FK→cases party_role, claim_text, source_document, claim_type, claim_handling (db.py:349-359,506-512)
chair_feedback הערת-יו"ר על טיוטה id; FK→cases block_id, feedback_text, category, lesson_extracted, resolved (db.py:452-462)
missing_precedents תקדים חסר שהתבקש ולא נמצא id (db.py:806) — backlog ל-quality-at-source
style_corpus קורפוס-סגנון של דפנה (אימון) id; FK→documents decision_number, full_text, practice_area, appeal_subtype (db.py:118-131)

שכבות-עזר נוספות (document_image_embeddings, precedent_image_embeddings — multimodal, db.py:707,726; case_law_relations — שרשרת-תיק, db.py:754; precedent_internal_citations — גרף-ציטוטים, db.py:937) הן נגזרות (G2): משוחזרות מהמקור, לא מקור-אמת עצמאי.


2. חוזה-שלמות לכל ישות (Completeness Contract)

G4 דורש: רשומה אינה "שמישה / ניתנת-לחיפוש" עד ששדות-החובה שלה מולאו ואומתו מול spec מפורש. כל ישות מגדירה שתי רמות — usable (קיימת ומזוהה) ו-searchable (חשופה לאחזור). רשומה שנכשלת בחוזה מסומנת ומדווחת — לא מתקבלת בשקט (חוקה §6, "אין בליעה שקטה").

2א. case_law — החוזה הקונקרטי

המזהה הקנוני אינו case_number לבדו: case_law נושאת שני unique partial indexes לפי source_kind (db.py:904-909) — חיצוני: UNIQUE(case_number); פנימי: UNIQUE(case_number, proceeding_type). לכן המזהה הקנוני הוא (case_number מנורמל, source_kind, proceeding_type).

רמת usable (רשומה לגיטימית):

  • case_number קנוני מנורמל-בכתיבה (INV-DM2לא ציטוט-מלא)
  • case_name לא-ריק (לא fallback לציטוט/למספר)
  • court לא-ריק
  • practice_area ∈ {rishuy_uvniya, betterment_levy, compensation_197} (אכוף ב-CHECK, db.py:614-617)
  • source_kind מהמילון (external_upload / cited_only / internal_committee / nevo_seed) (db.py:599-601, internal_decisions.py:4)
  • proceeding_type ∈ {ערר, בל"מ} כשפנימי (אכוף ב-CHECK, db.py:897-899)

רמת searchable (חשוף לאחזור — מעבר ל-usable):

  • ≥1 precedent_chunk עם embedding לא-NULL (אחרת אין מה לאחזר סמנטית)
  • metadata לא-ריק: לפחות אחד מ-headnote / summary / subject_tags מלא — אלו השדות ש-search מציג ומסנן לפיהם
  • extraction_status = completed (מטא-דאטה הושלם, db.py:603)

אכיפה מפורשת: רשומה שעוברת usable אך נכשלת ב-searchable — מסומנת searchable=false ולא מוחזרת מ-search, ומופיעה ב-health-check כ-backlog. היא אינה מתקבלת בשקט כ"זמינה".

2ב. חוזה תמציתי לישויות נוספות

  • documents → usable: file_path+doc_type; searchable: extraction_status=completed ו-extracted_text לא-ריק ו-≥1 document_chunk עם embedding.
  • halachot → usable: rule_statement+supporting_quote; searchable: review_status ∈ {approved, published} בלבדpending_review/rejected מוסתרות מ-search_precedent_library (שער-הלכה ידני, db.py:644-660, G10).
  • decision_blocks → usable: block_id∈12-הבלוקים; "מוכן": status=final ו-content לא-ריק.
  • chair_feedback → usable: feedback_text+category מהמילון; "פתוח" עד resolved=true.

3. Invariants של התחום

INV-DM1: searchable רק כשחוזה-השלמות מתקיים

כלל: רשומת case_law נחשבת searchable אך ורק כשחוזה-השלמות של §2א מתקיים במלואו (מזהה קנוני · case_name/court/practice_area/source_kind · ≥1 chunk עם embedding · metadata לא-ריק). רשומה שנכשלת מסומנת searchable=false ומדווחת ל-health-check — לא מוחזרת מ-search ולא מתקבלת בשקט. מקורות: ISO 8000 (completeness) · DAMA-UK Six Primary Dimensions for Data Quality (2013, completeness) · ISO 15489-1:2016 (records reliability/usability) | סטטוס: verified אכיפה: ולידציית-כתיבה בנקודת-הקליטה (01-ingest.md צעד 8) + בדיקת-בריאות תקופתית שמסמנת backlog; הסינון נאכף בשכבת-החיפוש (03-retrieval.md). אוכף את G4. הפרה ידועה: ערן סופר 8046/24 אונדקס כ-searchable עם headnote/summary/subject_tags ריקים — המסלול הפנימי לא תיזמן חילוץ-מטא-דאטה (01-ingest INV-ING3, internal_decisions.py:208) → ממצא ל-audit.

INV-DM2: מזהה קנוני יחיד לכל ישות

כלל: לכל ישות מזהה קנוני אחד, מנורמל בכתיבה. אסור ששדה-המזהה יאחסן ציטוט-מלא — case_number הוא מספר-תיק מנורמל (8126-03-25), לא מחרוזת-ציטוט (ערר 8126/24 פלוני נ' הוועדה (נבו...)). הציטוט המלא חי בשדה ייעודי נפרד (citation_formatted, db.py:1070), לא במזהה. מקורות: SSOT (Single Source of Truth — normalization) · E.F. Codd, First Normal Form (CACM 13(6), 1970) · Martin Kleppmann, Designing Data-Intensive Applications (O'Reilly, 2017) | סטטוס: verified אכיפה: unique partial indexes על המזהה הקנוני (db.py:904-909) + נרמול-בכתיבה (X1-identifiers.md); ציטוט-מלא ב-citation_formatted בלבד. אוכף את G1. הפרה ידועה: החלטות "סופר" נקלטו עם ציטוט-מלא כ-case_number (שדה-המזהה של רשומה מכיל את מחרוזת-הציטוט במקום מספר-תיק מנורמל) — חיפוש מול 8126-03-25 נכשל, ו-_normalize_case_number (db.py:1196-1211) רק מטליא בקריאה (סלחני, לא קנוני), בניגוד ל-G1 → ממצא ל-audit.

INV-DM3: שינוי-תוכן ⇒ re-index

כלל: כל שינוי בתוכן-המקור של ישות מואנדקסת (content של chunk, rule_statement/supporting_quote של הלכה, full_text/extracted_text של מסמך) מפעיל re-index של ה-embedding ושל ה-tsvector הנגזרים. אין embedding או content_tsv/rule_tsv/meta_tsv מיושנים מול התוכן. מקורות: Pinecone (index freshness / data sync) · Weaviate (re-vectorization on update) · RAG freshness (Lewis et al., 2020, NeurIPS) | סטטוס: verified אכיפה: טריגר re-embed בנקודת-העדכון + בדיקת-בריאות לגילוי drift; ה-tsvectors GENERATED ALWAYS … STORED (db.py:776-788,1083-1090) מתעדכנים אוטומטית, אך ה-embedding אינו generated — הוא תלוי-טריגר. מפורט ב-03-retrieval.md. אוכף את G6. הפרה ידועה:


4. מצב קיים מול יעד — audit-findings

ההבדלים בין ה-schema בפועל ל-TARGET. אלו תסמינים, לא התנהגויות תקינות. כל פריט אומת מול db.py.

  • case_law כפולת-תפקיד ללא מזהה מודע-סוג בכתיבה. טבלה אחת משרתת פסיקה חיצונית וגם החלטות-ועדה, מובדלות ב-source_kind (db.py:599). המזהה הקנוני האמיתי הוא טריפלט (case_number, source_kind, proceeding_type, db.py:904-909), אך השדה case_number TEXT UNIQUE NOT NULL המקורי (db.py:368) הוסר רק ב-V15 (db.py:902-903) — מורשת שאפשרה את הפרת INV-DM2. יעד: נרמול-בכתיבה אכוף + ציטוט-מלא רק ב-citation_formatted.
  • summary קיים על case_law אך לא בחוזה-הקליטה הפנימי. העמודה קיימת (db.py:373) אך המסלול הפנימי אינו ממלא אותה (כפועל-יוצא מהיעדר חילוץ-מטא-דאטה, INV-ING3). יעד: searchable מותנה ב-metadata לא-ריק (INV-DM1).
  • שני שדות-סטטוס-חילוץ נפרדים, ללא דגל-searchable מפורש. extraction_status + halacha_extraction_status (db.py:603-605) מתארים תהליך, אך אין שדה יחיד שמסמן "עבר חוזה-שלמות → searchable". יעד: דגל/view נגזר ש-search מסנן לפיו, מגובה health-check.
  • embedding אינו GENERATED (בניגוד ל-tsvector). ה-tsvectors מסונכרנים אוטומטית (db.py:776,780,1083), אך ה-embedding vector(1024) תלוי-טריגר חיצוני — נקודת-drift אפשרית ל-INV-DM3. יעד: טריגר re-embed מובטח + health-check ל-drift.
  • halachot.review_status כשער-searchable ללא נראות-backlog. הסינון תקין (pending_review מוסתר, db.py:659), אך אין נראות כמה ממתינות — תואם את ההפרה הידועה ב-G10 (10/19 מאושרות, התגלה במקרה). יעד: health-check חושף backlog-הלכות.

5. הפניות-אחיות

  • 00-constitution.md — invariants גלובליים (G1, G4, G6) + כללי-הנדסה.
  • 01-ingest.md — חוזה-הקליטה שמייצר את הרשומות; חוזה-השלמות כאן אוכף את תוצריו.
  • 03-retrieval.md — שכבת-האחזור שאוכפת את הסינון searchable + re-index.
  • X1-identifiers.md — נרמול המזהה הקנוני בכתיבה (בסיס ל-INV-DM2).
  • X5-audit-provenance.md — שלמות-רשומה + עקיבוּת-מקור.