untracked files on main: 3541238 Update CLAUDE.md: add corpus-analysis.md to reference table

This commit is contained in:
2026-04-13 12:42:00 +00:00
commit ee83b6b345
10 changed files with 978 additions and 0 deletions

View File

@@ -0,0 +1,214 @@
# הפרדת תחומים משפטיים — `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` |