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

215 lines
14 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.
# הפרדת תחומים משפטיים — `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`** הפך ל-`<input type="hidden">` כדי לא לשבור 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<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` |