fix(retrieval): make decisions findable by name + unhide committee uploads
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 3m57s

Root cause of "agent can't find the Agasi decision in the corpus" (CMPA-55):
the decision was fully ingested, but the retrieval layer failed on the
realistic agent query — searching by case name.

- RC-A (#52): lexical tsvector covered only chunk content + halacha text,
  so a bare-name query ("אגסי") matched decisions that *cite* the case, not
  the case itself. Add meta_tsv on case_law(case_name, case_number) (SCHEMA
  V20) and OR it into the lexical halacha/chunk SQL with a match boost, so a
  name/number hit surfaces the case's own rows. Agasi: rank 4 → rank 1.
- RC-B (#53): precedent_library_list hard-defaulted source_kind=external_upload
  and never exposed the param, hiding uploaded ערר/בל"מ (internal_committee)
  decisions. Thread source_kind through service → tool → MCP tool (supports
  'internal_committee' / 'all_committees').
- #54: agent instructions (researcher/analyst/writer) — search-by-name
  protocol: add content/case-number, search both corpora, use all_committees
  before declaring "not in corpus".
- #55: chunker produced tiny fragment chunks ("דיון", "החלטה") from header
  keywords matched mid-sentence. Anchor SECTION_PATTERNS to line start +
  merge sub-min sections; exclude <50-char fragments at query time (484
  existing fragments hidden; full re-chunk tracked as #57).

Tests: scripts/test_retrieval_by_name.py (name ranks case above citer +
substantive regressions); chunker unit checks (0 tiny chunks). New findings
filed as tasks #56 (halacha source_kind leak) and #57 (re-chunk migration).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 11:26:19 +00:00
parent 165efc62b0
commit 58ab003206
11 changed files with 355 additions and 57 deletions

View File

@@ -1388,50 +1388,59 @@
{
"id": 1,
"title": "Audit + migration practice_area (1xxx→rishuy_uvniya, 8xxx→betterment_levy, 9xxx→compensation_197)",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 2,
"title": "Audit + reclassify case_law source_kind external_upload → internal_committee עבור 'ערר' prefix",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 3,
"title": "Delete + re-extract halachot עבור רשומות שעברו reclassification",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 4,
"title": "תיקון נתיב יצירת תיק לתיוג practice_area נכון מההתחלה",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 5,
"title": "תיקון /api/precedent-library/upload לניתוב לפי תחילית הציטוט",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 6,
"title": "מבחני רגרסיה לכל 3 הbaגים",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 7,
"title": "תיקון MCP `case_update` + API `PUT /api/cases/{case_number}` לתמוך בעדכון practice_area + appeal_subtype",
"status": "done",
"details": "התגלה ב-26/05/2026: MCP tool case_update והAPI לא מקבלים את השדה practice_area, ולכן אי-אפשר לתקן תיוג שגוי דרך הממשק. נאלצתי לעדכן ידנית ב-SQL. צריך להוסיף את השדות ל-CaseUpdateRequest ב-web/app.py וב-cases_tools.case_update בmcp-server."
"details": "התגלה ב-26/05/2026: MCP tool case_update והAPI לא מקבלים את השדה practice_area, ולכן אי-אפשר לתקן תיוג שגוי דרך הממשק. נאלצתי לעדכן ידנית ב-SQL. צריך להוסיף את השדות ל-CaseUpdateRequest ב-web/app.py וב-cases_tools.case_update בmcp-server.",
"parentId": "undefined"
},
{
"id": 8,
"title": "[prevention] DB CHECK constraints: source_kind='internal_committee' ⇒ chair_name NOT NULL; cases.practice_area enum",
"status": "done",
"description": "מיגרציה: ALTER TABLE case_law ADD CONSTRAINT chair_required_for_internal CHECK (source_kind <> 'internal_committee' OR (chair_name IS NOT NULL AND chair_name <> '')); וכן CHECK על cases.practice_area לערכים תקינים. חייב לרוץ אחרי subtask #2 (backfill) אחרת constraint creation ייכשל."
"description": "מיגרציה: ALTER TABLE case_law ADD CONSTRAINT chair_required_for_internal CHECK (source_kind <> 'internal_committee' OR (chair_name IS NOT NULL AND chair_name <> '')); וכן CHECK על cases.practice_area לערכים תקינים. חייב לרוץ אחרי subtask #2 (backfill) אחרת constraint creation ייכשל.",
"parentId": "undefined"
},
{
"id": 9,
"title": "[prevention] Unify practice_area taxonomy — מיפוי או מחיקה של appeals_committee מ-practice_area.py",
"status": "done",
"description": "ב-mcp-server/src/legal_mcp/services/practice_area.py:21 יש PRACTICE_AREAS={appeals_committee,national_insurance,labor_law} שסותר את ה-DB constraint של case_law (rishuy_uvniya/betterment_levy/compensation_197). grep מקיף לכל caller של 'appeals_committee'; להחליף במיפוי מפורש או למחוק."
"description": "ב-mcp-server/src/legal_mcp/services/practice_area.py:21 יש PRACTICE_AREAS={appeals_committee,national_insurance,labor_law} שסותר את ה-DB constraint של case_law (rishuy_uvniya/betterment_levy/compensation_197). grep מקיף לכל caller של 'appeals_committee'; להחליף במיפוי מפורש או למחוק.",
"parentId": "undefined"
}
],
"updatedAt": "2026-05-26T08:35:22.762800Z"
@@ -1452,19 +1461,22 @@
"id": 1,
"title": "Backfill chair_name + district לכל 7 הרשומות החסרות (LLM extraction)",
"status": "done",
"description": "psql query: SELECT id, case_number FROM case_law WHERE source_kind='internal_committee' AND (chair_name IS NULL OR chair_name=''); לכל אחת — חילוץ ע\"י precedent_metadata_extractor.extract_and_apply."
"description": "psql query: SELECT id, case_number FROM case_law WHERE source_kind='internal_committee' AND (chair_name IS NULL OR chair_name=''); לכל אחת — חילוץ ע\"י precedent_metadata_extractor.extract_and_apply.",
"parentId": "undefined"
},
{
"id": 2,
"title": "[prevention] Validation: chair_name+district required ב-internal_decisions_upload (API+MCP)",
"status": "done",
"description": "ב-web/app.py:4607-4680 כיום chair_name/district = Form(\"\") (default ריק). שנה ל-required עם validation שדוחה ריק כשsource_kind='internal_committee'. הוסף enum של 6 ערכי district (ירושלים/מרכז/תל אביב/צפון/דרום/ארצי)."
"description": "ב-web/app.py:4607-4680 כיום chair_name/district = Form(\"\") (default ריק). שנה ל-required עם validation שדוחה ריק כשsource_kind='internal_committee'. הוסף enum של 6 ערכי district (ירושלים/מרכז/תל אביב/צפון/דרום/ארצי).",
"parentId": "undefined"
},
{
"id": 3,
"title": "[prevention] UI dropdown ל-district בטופס העלאת החלטות ועדה (web-ui)",
"status": "done",
"description": "במקום free-text — Select של 6 הערכים. גם בטופס חיפוש (search_internal_decisions)."
"description": "במקום free-text — Select של 6 הערכים. גם בטופס חיפוש (search_internal_decisions).",
"parentId": "undefined"
}
],
"updatedAt": "2026-05-26T08:35:22.762800Z"
@@ -1521,32 +1533,38 @@
{
"id": 1,
"title": "Migration + model missing_precedents",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 2,
"title": "API endpoints POST/GET/upload/PATCH /api/missing-precedents",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 3,
"title": "MCP tools missing_precedent_create/list/close",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 4,
"title": "Next.js page /missing-precedents עם list + detail + upload form",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 5,
"title": "Auto-creation hook במחקר (legal-researcher יוצר רשומה כשמזהה ציטוט חסר)",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 6,
"title": "Webhook עדכון לפלאגין Paperclip + Comment לחיים",
"status": "done"
"status": "done",
"parentId": "undefined"
}
],
"updatedAt": "2026-05-26T08:35:22.762800Z"
@@ -1564,27 +1582,32 @@
{
"id": 1,
"title": "Migration + models legal_arguments + legal_argument_propositions",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 2,
"title": "LLM aggregation job (Hermes/DeepSeek profile)",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 3,
"title": "API + MCP tool aggregate_claims_to_arguments",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 4,
"title": "UI display update — case detail page מציג טיעונים אמיתיים",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 5,
"title": "Backfill לכל התיקים הקיימים",
"status": "done"
"status": "done",
"parentId": "undefined"
}
],
"updatedAt": "2026-05-26T08:35:22.762800Z"
@@ -1602,27 +1625,32 @@
{
"id": 1,
"title": "הוספת 3 ערכי enum ל-practice_area.py APPEALS_COMMITTEE_SUBTYPES",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 2,
"title": "כתיבת 3 templates מתודולוגיים ב-docs/methodology/extension-request-{type}.md",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 3,
"title": "אוטו-זיהוי בקוד יצירת תיק (subject='בקשה להארכת מועד' → קביעת subtype לפי practice_area)",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 4,
"title": "UI badge + filter ייעודי לבל\"מ",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 5,
"title": "עדכון web/paperclip_client.py mapping ל-company עבור 3 הערכים החדשים",
"status": "done"
"status": "done",
"parentId": "undefined"
}
],
"updatedAt": "2026-05-26T08:35:22.762800Z"
@@ -1647,55 +1675,65 @@
{
"id": 1,
"title": "עדכון .claude/agents/legal-ceo.md — routing + statuses + wake reasons",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 2,
"title": "עדכון .claude/agents/legal-analyst.md — practice_area, legal_arguments, בל\"מ detection",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 3,
"title": "עדכון .claude/agents/legal-researcher.md — 2 layers, missing_precedents, citations, בל\"מ templates",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 4,
"title": "עדכון .claude/agents/legal-writer.md — legal_arguments view, בל\"מ templates",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 5,
"title": "עדכון .claude/agents/legal-qa.md — בל\"מ-aware validation",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 6,
"title": "עדכון .claude/agents/HEARTBEAT.md — כללי routing משותפים + research_complete status",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 7,
"title": "סנכרון לכל החברות CMPA mirror — sync_agents_across_companies.py",
"status": "done"
"status": "done",
"parentId": "undefined"
},
{
"id": 8,
"title": "[alignment] researcher docs: דרישה מפורשת שכל 'ערר' → internal_decision_upload, לעולם לא precedent_library_upload",
"status": "done",
"description": "בלגל-researcher.md: דוגמת קוד מפורשת + flowchart החלטה: לפי תחילית הציטוט. הסבר על השלילה של precedent_library_upload כשמדובר ב-ערר. תלוי במשימה #39 (MCP tool חדש)."
"description": "בלגל-researcher.md: דוגמת קוד מפורשת + flowchart החלטה: לפי תחילית הציטוט. הסבר על השלילה של precedent_library_upload כשמדובר ב-ערר. תלוי במשימה #39 (MCP tool חדש).",
"parentId": "undefined"
},
{
"id": 9,
"title": "[alignment] analyst docs: הסבר על 2 taxonomies של practice_area + מתי משתמשים בכל אחת",
"status": "done",
"description": "בלגל-analyst.md: טבלה ברורה — practice_area (case_law) vs practice_area (cases). מתי להעביר rishuy_uvniya ומתי appeals_committee. אחרי משימה #30.9 (taxonomy unification) — סביר שזה ייפשט."
"description": "בלגל-analyst.md: טבלה ברורה — practice_area (case_law) vs practice_area (cases). מתי להעביר rishuy_uvniya ומתי appeals_committee. אחרי משימה #30.9 (taxonomy unification) — סביר שזה ייפשט.",
"parentId": "undefined"
},
{
"id": 10,
"title": "[alignment] writer docs: הבחנה בין source_kind בציטוט (binding vs persuasive)",
"status": "done",
"description": "בלגל-writer.md: 'החלטת ועדת ערר אחרת ⇒ עקביות אופקית, לא הלכה מחייבת'. 'פס\"ד עליון/מנהלי ⇒ סמכותי בינדינג'. דוגמאות פרזיולוגיה מ-skills/decision/SKILL.md."
"description": "בלגל-writer.md: 'החלטת ועדת ערר אחרת ⇒ עקביות אופקית, לא הלכה מחייבת'. 'פס\"ד עליון/מנהלי ⇒ סמכותי בינדינג'. דוגמאות פרזיולוגיה מ-skills/decision/SKILL.md.",
"parentId": "undefined"
}
],
"updatedAt": "2026-05-26T07:41:47.880478Z"
@@ -1870,17 +1908,96 @@
"priority": "low",
"subtasks": [],
"updatedAt": "2026-05-26T11:27:09.039154Z"
},
{
"id": "52",
"title": "[Retrieval RC-A] הוספת case_name + case_number ל-tsvector הלקסיקלי",
"description": "השורש האמיתי לכך שסוכן לא מאתר החלטה לפי שם (אגסי). ה-tsvector הלקסיקלי (SCHEMA_V12_SQL ב-db.py) בנוי רק מ-precedent_chunks.content ומ-halachot rule/quote/reasoning — לא משם התיק/הצד או ממספר התיק. לכן שאילתת-שם מחזירה את מי שמצטט את ההחלטה, לא את ההחלטה עצמה. לשלב את case_law.case_name + case_number באינדקס הלקסיקלי (tsvector ייעודי על case_law או setweight) כך שחיפוש לפי שם יפגע ברשומה עצמה.",
"status": "done",
"priority": "high",
"dependencies": [],
"details": "קבצים: mcp-server/src/legal_mcp/services/db.py (SCHEMA_V12_SQL ~שורה 774, search_precedent_library_lexical), hybrid_search.py (_merge_sem_lex). דורש ALTER TABLE + migration על Postgres (localhost:5433) + restart MCP server. בדיקה: search_internal_decisions('אגסי') ו-search_precedent_library('אגסי') חייבים להחזיר את אגסי (1a87efe5) בעמוד הראשון.",
"testStrategy": "reproduction test: query='אגסי' → expect case_law_id 1a87efe5 in top-3. regression: substantive query עדיין מחזיר 0.6+ score.",
"subtasks": [],
"updatedAt": "2026-05-30T11:05:36.307Z"
},
{
"id": "53",
"title": "[Retrieval RC-B] חיפוש/רשימה מאוחדים — לא לחתוך internal_committee",
"description": "החלטות ערר/בל\"מ שמועלות נשמרות source_kind='internal_committee'. precedent_library_list ברירת מחדל external_upload ומסתיר אותן; כלי ה-MCP precedent_library_list אפילו לא חושף פרמטר source_kind, כך שסוכן לעולם לא יכול לדפדף בהן. לחשוף source_kind/all_committees בכלי ה-MCP ובמידת הצורך לאחד את שכבת ה-list/search.",
"status": "done",
"priority": "high",
"dependencies": [
"52"
],
"details": "קבצים: web/app.py (precedent_library_list ~5194, all_committees expansion ב-db.list_external_case_law ~2708), mcp-server tool def ל-precedent_library_list. בדיקה: precedent_library_list יכול להחזיר את אגסי כשמבקשים committees; חיפוש סמנטי כבר מאוחד (אומת).",
"testStrategy": "precedent_library_list(source_kind='all_committees', practice_area='betterment_levy') כולל את אגסי+וינפלד. regression: ברירת מחדל external_upload עדיין מחזירה 14 ולא שוברת UI.",
"subtasks": [],
"updatedAt": "2026-05-30T11:09:44.511Z"
},
{
"id": "54",
"title": "[Retrieval RC-3] הנחיית סוכנים — איתור לפי שם + שני קורפוסים",
"description": "לעדכן הנחיות legal-analyst/researcher/writer: לאיתור החלטה ספציפית לפי שם להוסיף מונחי תוכן או מספר תיק, ולחפש בשני הקורפוסים (search_internal_decisions + search_precedent_library) לפני שמסיקים 'לא קיים בקורפוס'. כולל יצירת missing_precedent רק אחרי חיפוש כפול.",
"status": "done",
"priority": "medium",
"dependencies": [
"53"
],
"details": "קבצים: .claude/agents/legal-analyst.md, legal-researcher.md, legal-writer.md. אחרי שינוי skills/agent config — להריץ sync_agents_across_companies.py.",
"testStrategy": "קריאת ההנחיות מאשרת fallback ברור; (אם אפשר) הרצת סוכן על שאילתת-שם מחזירה את ההחלטה.",
"subtasks": [],
"updatedAt": "2026-05-30T11:12:44.727Z"
},
{
"id": "55",
"title": "[Retrieval RC-4] תיקון chunking — פרגמנטים זעירים",
"description": "בתוצאות החיפוש מופיעים chunks של מילה-שתיים ('דיון','דיון וב','סיכום ו') כתוצאות מובילות. מציפים תוצאות ומורידים דירוג תוכן אמיתי. לחקור את chunker.py (פיצול לפי כותרת-סעיף שיוצר chunks ריקים) ולתקן: מינימום אורך chunk / מיזוג כותרת לגוף.",
"status": "done",
"priority": "medium",
"dependencies": [
"54"
],
"details": "קבצים: mcp-server/src/legal_mcp/services/chunker.py (SECTION_PATTERNS). דורש שיקול re-chunk לרשומות קיימות — לבדוק עלות מול feedback_no_reocr_retrofit (להשתמש בטקסט שמור, לא re-OCR).",
"testStrategy": "אין chunks < N תווים בקורפוס אחרי תיקון; search_internal_decisions('אגסי') לא מציג פרגמנטי 'דיון'.",
"subtasks": [],
"updatedAt": "2026-05-30T11:19:23.923Z"
},
{
"id": "56",
"title": "[Retrieval finding] halacha_filters לא מסננים source_kind — דליפה חוצת-קורפוסים",
"description": "התגלה תוך כדי משימה 53. ב-search_precedent_library_semantic וב-search_precedent_library_lexical (db.py): chunk_filters כוללים cl.source_kind=$sk אבל halacha_filters כוללים רק review_status. תוצאה: search_precedent_library(external) מחזיר גם הלכות internal_committee, ו-search_internal_decisions(internal) מחזיר גם הלכות external. אי-עקביות: chunks מסוננים, halachot לא. כרגע זה דווקא מסייע למציאוּת (לכן לא רגרסיה), אבל לא עקבי. דורש החלטת מדיניות: או לסנן halachot גם לפי source_kind (עקבי, אך 'מסתיר' שכבות), או להשאיר מאוחד במכוון + לתעד. אם משאירים מאוחד — לעדכן docstrings של שני הכלים שזה לא 'corpus נפרד'.",
"status": "pending",
"priority": "low",
"dependencies": [],
"details": "db.py: search_precedent_library_semantic (~שורה הקודמת ל-3311), search_precedent_library_lexical (3346). שתי הפונקציות: halacha_filters=['h.review_status IN ...'] — חסר cl.source_kind. נמצא בעת בדיקת רגרסיה למשימה 53.",
"testStrategy": "לאחר החלטה: אם מסננים — search_precedent_library('...substantive...', external) לא מחזיר case_law_id internal; אם משאירים — docstring מעודכן + טסט מאשר התנהגות מכוונת.",
"subtasks": [],
"updatedAt": "2026-05-30T11:09:30.257989+00:00"
},
{
"id": "57",
"title": "[Retrieval #55 follow-up] re-chunk+re-embed של פסיקה שהוטמעה לפני תיקון ה-chunker",
"description": "משימה 55 תיקנה את ה-chunker (עיגון כותרות + מיזוג) ומסננת את 484 הפרגמנטים בזמן query. הרמדיאציה המלאה: re-chunk מ-full_text השמור (ללא re-OCR — תואם feedback_no_reocr_retrofit) + re-embed, כדי שהתוכן יהיה נכון ולא רק מוסתר. נדחה כי זו מיגרציית-נתונים עם עלות Voyage API על ~13+ תיקים — דורש אישור עלות מ-chaim לפני הרצה. לבדוק כמה תיקים מושפעים (יש להם chunk<50) ולהריץ בקבוצות.",
"status": "pending",
"priority": "low",
"dependencies": [
"55"
],
"details": "מקור: case_law.full_text קיים. נתיב: chunker.chunk_document(_hierarchical) → embeddings → החלפת precedent_chunks לתיק. למחוק chunks ישנים של התיק לפני הוספה. אחרי הרצה — ניתן להסיר את פילטר ה->=50 query (אופציונלי). תיקים מושפעים: SELECT DISTINCT case_law_id WHERE length(trim(content))<50.",
"testStrategy": "אחרי re-chunk לתיק לדוגמה: 0 chunks<50 לאותו case_law_id; search_internal_decisions עדיין מחזיר את התיק; ספירת chunks סבירה.",
"subtasks": [],
"updatedAt": "2026-05-30T11:19:06.142606+00:00"
}
],
"metadata": {
"version": "1.0.0",
"lastModified": "2026-05-04T17:29:25.687Z",
"taskCount": 29,
"completedCount": 24,
"lastModified": "2026-05-30T11:19:23.923Z",
"taskCount": 57,
"completedCount": 52,
"tags": [
"legal-ai"
],
"updated": "2026-05-26T06:39:31.733370"
]
}
}
}