# הפרדת תחומים משפטיים — `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.py` — `case_create` קיבל `practice_area` + `appeal_subtype`. אם המשתמש סיפק subtype שונה ממה שההסקה האוטומטית אומרת, נכתב `audit_log` עם `action='case_subtype_override'`. - `mcp-server/src/legal_mcp/tools/search.py` — `search_decisions` ו-`find_similar_cases` קיבלו פרמטרים `practice_area`, `appeal_subtype`, `case_number`. אם רק `case_number` סופק — ה-`practice_area` נשלף אוטומטית מה-DB. - `mcp-server/src/legal_mcp/tools/documents.py` — `document_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`** הפך ל-`` כדי לא לשבור backwards compatibility (Paperclip integration עדיין משתמש בו לבחירת ארגון CMP/CMPA — ראו `web/paperclip_client.py`). --- ## חוזי API ל-UI חדש ### `POST /api/cases/create` — request body ```jsonc { "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 ```jsonc { "id": "...", "case_number": "1130-25", // ... כל השדות הקיימים "practice_area": "appeals_committee", // ← חדש "appeal_subtype": "building_permit" // ← חדש } ``` `list_cases` ו-`case_get` מחזירים את אותם שדות. ### Enums לסנכרון בקליינט ```typescript type PracticeArea = 'appeals_committee' | 'national_insurance' | 'labor_law'; type AppealSubtype = 'building_permit' | 'betterment_levy' | 'compensation_197' | 'unknown'; const PRACTICE_AREA_LABELS: Record = { appeals_committee: 'ועדת ערר', national_insurance: 'ביטוח לאומי', labor_law: 'דיני עבודה', }; const SUBTYPE_LABELS: Record = { 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` |