14 KiB
הפרדת תחומים משפטיים — 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_createtool.
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).
- בויזרד יצירת תיק (step 1) הוספנו שני dropdowns: "תחום משפטי" ו"סוג ערר" — שניהם בתוך אותו
חוזי 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 החדש חייב לכלול
- טופס יצירת תיק — שדה
practice_area(היום ועדת ערר בלבד, אבל בנו את הקומפוננטה כ-dropdown מוכן להרחבה) + שדהappeal_subtypeעם auto-fill ממספר התיק. אפשרות override ידני. - תצוגת תיק / רשימת תיקים — badge גלוי שמראה את התחום + סוג הערר. קריטי כדי שמשתמשים יבינו באיזה הקשר הם עובדים, במיוחד כשנוסיף תחומים נוספים.
- פילטרים ברשימות חיפוש/תיקים — הוסיפו אופציה לסנן לפי תחום (אופציונלית בשלב זה, חובה ברגע שנוסיף תחום שני).
- קריאות לחיפוש (
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_permitcase_create('8999-99', appeal_subtype='building_permit')→ audit log רושםcase_subtype_overridesearch_similar(practice_area='national_insurance')→ 0 תוצאות (אין leakage לתחום ריק)search_similar(practice_area='appeals_committee')→ 5 תוצאות תקינות
שאלות פתוחות לצוות UI
- Backfill ל-Paperclip routing: היום
paperclip_client.pyממפה לארגון (CMP/CMPA) לפיcommittee_typeהמילולי. רוצים שנעביר את ה-routing להסתמך עלappeal_subtypeכדי שיהיה מקור-אמת אחד? - פילטר תחום ברשימות חיפוש גלובליות: האם ה-UI החדש יציג את התחום כ-segmented control בראש כל מסך חיפוש, או כ-filter chip?
- תחומים עתידיים: האם יש כבר תכנון לאיזה תחום שני נוסיף ראשון (ביטוח לאומי / דיני עבודה / אחר)? אם כן — נוכל להכין את ה-
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 |