Files
legal-ai/docs/practice-area-separation.md

14 KiB
Raw Blame History

הפרדת תחומים משפטיים — practice_area

מסמך לצוות פיתוח ה-UI: סיכום השינוי הבק-אנדי שהוכנס ב-26d09d6 והשלכותיו על קליינטים.

Commit: 26d09d6 · main Repo: gitea.nautilus.marcusgroup.org/ezer-mishpati/legal-ai גודל: 8 קבצים, +467/-33, קובץ אחד חדש


הרעיון בשורה אחת

הוספנו ציר multi-tenant לדאטה בייס שמפריד תיקים, מסמכים, החלטות וקורפוס סגנון לפי תחום משפטי — כדי שחיפוש סמנטי ו-RAG לא יערבבו precedents בין תחומים שונים (היום: רישוי-ובנייה מול היטל-השבחה; בעתיד: גם ביטוח-לאומי, דיני-עבודה).

למה זה חשוב

הרכיב המרכזי שמטפל בכתיבת בלוק הדיון (block-writer) שולף "פסקאות תקדים" מקורפוס ההחלטות הקודמות של דפנה תמיר באמצעות חיפוש סמנטי. לפני השינוי, שום פילטר תחומי לא הופעל — היה אפשר לקבל פסקה מהחלטה בהיטל השבחה (8xxx) כדוגמה לבלוק דיון של תיק רישוי-ובנייה (1xxx). הכללים, הסעיפים והטון בכל תחום שונים מהותית, וערבוב כזה הוא הזיה משפטית שמסכנת את איכות הטיוטה.

באותו אופן: כשנוסיף בעתיד תחומים שונים לחלוטין (ביטוח לאומי, דיני עבודה), קורפוס הסגנון יהפוך מעורב — וללא הציר הזה לא נוכל להבטיח שדוגמאות סגנון נבחרות מהתחום הנכון.


שני שדות חדשים בכל הטבלאות הרלוונטיות

שדה טיפוס ערכים אפשריים תפקיד
practice_area TEXT appeals_committee (כיום היחיד) · national_insurance · labor_law (עתידיים) תחום-העל. ציר ה-multi-tenancy. כל חיפוש מסונן לפיו.
appeal_subtype TEXT building_permit · betterment_levy · compensation_197 · unknown מעדן בתוך תחום. רלוונטי כיום רק ל-appeals_committee.

הסקה אוטומטית ממספר התיק (קונבנציית ועדת ערר ירושלים):

מספר תיק appeal_subtype
1xxx-yy building_permit
8xxx-yy betterment_levy
9xxx-yy compensation_197
אחר unknown

המשתמש יכול לעקוף ידנית; אם הוא בחר subtype שלא תואם להסקה האוטומטית, נכתבת רשומת case_subtype_override ל-audit_log כדי שנוכל לאתר טעויות סיווג.


שינויים במסד הנתונים (כבר רץ בפרודקשן)

SCHEMA_V4_SQL ב-mcp-server/src/legal_mcp/services/db.py (idempotent — IF NOT EXISTS בכל מקום):

  • שתי עמודות חדשות בכל אחת מ-5 הטבלאות: cases, documents, document_chunks, decisions, style_corpus
  • 4 אינדקסים composite (practice_area תמיד ראשון): idx_cases_practice, idx_chunks_practice, idx_corpus_practice, idx_decisions_practice
  • Backfill אוטומטי של כל הרשומות הקיימות לפי regex על case_number, ופיזור ל-documents/chunks/decisions לפי FK. רשומות קורפוס היסטוריות (case_id IS NULL) מסומנות appeals_committee.

אומת אחרי הריצה (פרודקשן): 0 ערכי NULL ב-practice_area בכל 5 הטבלאות (2 cases · 51 documents · 2810 chunks · 1 decision · 24 style_corpus).


קבצים שהשתנו

חדש

קובץ תפקיד
mcp-server/src/legal_mcp/services/practice_area.py מודול מרכזי: derive_subtype(case_number), validate(area, subtype), is_override(...), וקבועי enum (PRACTICE_AREAS, APPEALS_COMMITTEE_SUBTYPES, SUBTYPES_BY_AREA). כל קוד שצריך להחליט על תחום צריך לעבור דרך המודול הזה — אל תכתבו regex משלכם.

Backend — מסד נתונים (mcp-server/src/legal_mcp/services/db.py)

  • SCHEMA_V4_SQL חדש + הקריאה אליו ב-init_schema().
  • create_case(...) קיבל פרמטרים practice_area, appeal_subtype.
  • create_document(...) ו-store_chunks(...) יורשים אוטומטית מה-case (או מקבלים override מפורש לקורפוס אימון שאין לו case_id).
  • create_decision(...) יורש מה-case.
  • חדש: get_case_practice_area(case_id) ו-get_case_practice_area_by_number(case_number) — שליפה מהירה.
  • search_similar(...) ו-search_similar_paragraphs(...) קיבלו פרמטרי practice_area ו-appeal_subtype אופציונליים. הסינון משתמש בעמודה ה-denormalized ב-document_chunks ו-decisions (ללא JOIN יקר).
  • add_to_style_corpus(...) קיבל practice_area (ברירת מחדל appeals_committee) ו-appeal_subtype.

Backend — MCP tools

  • mcp-server/src/legal_mcp/tools/cases.pycase_create קיבל practice_area + appeal_subtype. אם המשתמש סיפק subtype שונה ממה שההסקה האוטומטית אומרת, נכתב audit_log עם action='case_subtype_override'.
  • mcp-server/src/legal_mcp/tools/search.pysearch_decisions ו-find_similar_cases קיבלו פרמטרים practice_area, appeal_subtype, case_number. אם רק case_number סופק — ה-practice_area נשלף אוטומטית מה-DB.
  • mcp-server/src/legal_mcp/tools/documents.pydocument_upload_training קיבל practice_area ו-appeal_subtype, עם הסקה אוטומטית מ-decision_number.
  • mcp-server/src/legal_mcp/services/block_writer.pyתיקון קריטי ב-_build_precedents_context(): כשבונים את הקשר ה-precedent לבלוק י (דיון), השליפה מסוננת לפי תחום התיק הפעיל. זה סוגר את חור הזיהום שתואר ב"למה זה חשוב".

API ו-UI הקיימים

  • web/app.py:
    • CaseCreateRequest (Pydantic) קיבל שני שדות חדשים: practice_area: str = "appeals_committee" ו-appeal_subtype: str = "". שניהם אופציונליים עם defaults — קליינט קיים לא נשבר.
    • POST /api/cases/create מעביר אותם הלאה ל-case_create tool.
  • web/static/index.html:
    • בויזרד יצירת תיק (step 1) הוספנו שני dropdowns: "תחום משפטי" ו"סוג ערר" — שניהם בתוך אותו form-row.
    • JS auto-fill: wireSubtypeAutofill() מאזין ל-input על wiz-case-number ומעדכן את wiz-appeal-subtype עד שהמשתמש שינה ידנית את הסלקט.
    • getWizardData() מחזיר עכשיו גם practice_area ו-appeal_subtype.
    • buildSummary() ומסך פרטי תיק (loadCaseView) מציגים badge כחול עם practice_area · subtype.
    • השדה הישן committee_type הפך ל-<input type="hidden"> כדי לא לשבור backwards compatibility (Paperclip integration עדיין משתמש בו לבחירת ארגון CMP/CMPA — ראו web/paperclip_client.py).

חוזי API ל-UI חדש

POST /api/cases/create — request body

{
  "case_number": "1130-25",
  "title": "...",
  "appellants": ["..."],
  "respondents": ["..."],
  "subject": "",
  "property_address": "",
  "permit_number": "",
  "committee_type": "ועדה מקומית",
  "hearing_date": "",
  "notes": "",
  "expected_outcome": "",

  // ← חדש (אופציונלי, יש defaults)
  "practice_area": "appeals_committee",
  "appeal_subtype": "building_permit"   // ריק = יוסק אוטומטית מ-case_number
}

חשוב: אם תשלחו appeal_subtype ריק או לא תשלחו אותו בכלל — השרת יסיק לבד מ-case_number (1xxx/8xxx/9xxx). הסקה ידנית בצד הקליינט אופציונלית בלבד (משפרת UX, לא נדרשת לתקינות).

GET /api/cases/{case_number}/details — response

{
  "id": "...",
  "case_number": "1130-25",
  // ... כל השדות הקיימים
  "practice_area": "appeals_committee",   // ← חדש
  "appeal_subtype": "building_permit"     // ← חדש
}

list_cases ו-case_get מחזירים את אותם שדות.

Enums לסנכרון בקליינט

type PracticeArea = 'appeals_committee' | 'national_insurance' | 'labor_law';
type AppealSubtype = 'building_permit' | 'betterment_levy' | 'compensation_197' | 'unknown';

const PRACTICE_AREA_LABELS: Record<PracticeArea, string> = {
  appeals_committee: 'ועדת ערר',
  national_insurance: 'ביטוח לאומי',
  labor_law: 'דיני עבודה',
};

const SUBTYPE_LABELS: Record<AppealSubtype, string> = {
  building_permit: 'רישוי ובנייה',
  betterment_levy: 'היטל השבחה',
  compensation_197: "פיצויים (ס' 197)",
  unknown: 'לא ידוע',
};

// 1xxx → building_permit · 8xxx → betterment_levy · 9xxx → compensation_197
function deriveSubtypeFromCaseNumber(caseNumber: string): AppealSubtype {
  const m = caseNumber.trim().match(/^(\d)/);
  if (!m) return 'unknown';
  return ({ '1': 'building_permit', '8': 'betterment_levy', '9': 'compensation_197' } as const)[m[1]] ?? 'unknown';
}

מה ה-UI החדש חייב לכלול

  1. טופס יצירת תיק — שדה practice_area (היום ועדת ערר בלבד, אבל בנו את הקומפוננטה כ-dropdown מוכן להרחבה) + שדה appeal_subtype עם auto-fill ממספר התיק. אפשרות override ידני.
  2. תצוגת תיק / רשימת תיקים — badge גלוי שמראה את התחום + סוג הערר. קריטי כדי שמשתמשים יבינו באיזה הקשר הם עובדים, במיוחד כשנוסיף תחומים נוספים.
  3. פילטרים ברשימות חיפוש/תיקים — הוסיפו אופציה לסנן לפי תחום (אופציונלית בשלב זה, חובה ברגע שנוסיף תחום שני).
  4. קריאות לחיפוש (search_decisions, find_similar_cases) — אם אתם מבצעים חיפוש בהקשר של תיק, תמיד תעבירו case_number — השרת יוסיף את הסינון אוטומטית. אחרת תקבלו warning ב-log על חיפוש חוצה-תחומים.

תאימות לאחור

אין שבירות חוזה. כל השדות החדשים אופציונליים עם defaults. קליינט שלא מכיר את practice_area/appeal_subtype ממשיך לעבוד — פשוט יקבל ערכי ברירת המחדל (appeals_committee + מה שיוסק ממספר התיק).

שינוי התנהגות אחד שכדאי לדעת: committee_type נשאר בסכמה ובחוזה אבל ב-UI הקיים הוא הפך ל-hidden field. אם ה-UI החדש שלכם מאפשר עריכה ידנית של committee_type, ודאו שהעדכון לא דורס את הסמנטיקה החדשה. עדיף לחשוב על:

  • committee_type — "מצב הוועדה במקור" (legacy + Paperclip routing)
  • practice_area / appeal_subtype — "איך המערכת מסווגת את התיק לצורך RAG/חיפוש"

בדיקות שעברו

  • Unit tests של practice_area.py (derive_subtype, validate, is_override)
  • Migration רץ על פרודקשן: 0 NULLs ב-5 הטבלאות
  • case_create('1999-99') → אוטומטית building_permit
  • case_create('8999-99', appeal_subtype='building_permit') → audit log רושם case_subtype_override
  • search_similar(practice_area='national_insurance') → 0 תוצאות (אין leakage לתחום ריק)
  • search_similar(practice_area='appeals_committee') → 5 תוצאות תקינות

שאלות פתוחות לצוות UI

  1. Backfill ל-Paperclip routing: היום paperclip_client.py ממפה לארגון (CMP/CMPA) לפי committee_type המילולי. רוצים שנעביר את ה-routing להסתמך על appeal_subtype כדי שיהיה מקור-אמת אחד?
  2. פילטר תחום ברשימות חיפוש גלובליות: האם ה-UI החדש יציג את התחום כ-segmented control בראש כל מסך חיפוש, או כ-filter chip?
  3. תחומים עתידיים: האם יש כבר תכנון לאיזה תחום שני נוסיף ראשון (ביטוח לאומי / דיני עבודה / אחר)? אם כן — נוכל להכין את ה-SUBTYPES_BY_AREA מראש ב-practice_area.py.

נקודות מגע בקוד (להפניית קריאה מהירה)

תחום קובץ פונקציה / סימן
מודול דרייב/ולידציה mcp-server/src/legal_mcp/services/practice_area.py derive_subtype, validate, is_override
סכמה ומיגרציה mcp-server/src/legal_mcp/services/db.py SCHEMA_V4_SQL, init_schema
יצירת תיק mcp-server/src/legal_mcp/services/db.py · mcp-server/src/legal_mcp/tools/cases.py create_case, case_create
הורשה למסמכים/chunks mcp-server/src/legal_mcp/services/db.py create_document, store_chunks, create_decision
חיפוש מסונן mcp-server/src/legal_mcp/services/db.py · mcp-server/src/legal_mcp/tools/search.py search_similar, search_similar_paragraphs, search_decisions, find_similar_cases
תיקון contamination בבלוק דיון mcp-server/src/legal_mcp/services/block_writer.py _build_precedents_context
קורפוס אימון mcp-server/src/legal_mcp/tools/documents.py · mcp-server/src/legal_mcp/services/db.py document_upload_training, add_to_style_corpus
API חוזה web/app.py CaseCreateRequest, api_case_create
UI קיים web/static/index.html wireSubtypeAutofill, getWizardData, buildSummary, loadCaseView