Files
legal-ai/mcp-server/src/legal_mcp/tools/internal_decisions.py
Chaim d359ab9884
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m40s
feat(proceeding-type): explicit ערר/בל"מ field for cases + corpus
Same case_number can exist as both a regular appeal (ערר) and an
extension-of-time request (בל"מ), and we were inferring the difference
from appeal_subtype prefixes — fragile, and case-number lookups
weren't disambiguated. Now stored as a first-class field on both
case_law (corpus) and cases (live cases), with partial unique indexes
on (case_number, proceeding_type).

- SCHEMA_V15: column + CHECK constraints + backfill from
  appeal_subtype LIKE 'extension_request_%' + partial unique indexes
  replace the old global UNIQUE(case_number).
- derive_proceeding_type() centralizes the inference rule
  (extension_request_* → בל"מ; subject regex fallback; default ערר).
- Metadata extractor prompt asks Claude to populate the new field
  explicitly; apply_to_record writes it for internal_committee rows.
- internal_decision_upload, case_create, case_update accept an
  optional proceeding_type; FastAPI request models expose it.
- Wizard + edit dialog get a sided Select; case header renders the
  resolved label (ערר / בל"מ).
- Uploaded the 2 staged בל"מ decisions on betterment levy:
  8126/24 (סופר נוח, 13 chunks), 8047/23 (הרנון, 48 chunks).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 09:17:33 +00:00

117 lines
4.6 KiB
Python
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.
"""MCP tools for the Internal Decisions corpus.
Decisions of appeals committees (ועדות ערר) live in the same physical
``case_law`` table as court rulings but are distinguished by
``source_kind='internal_committee'`` and must carry ``chair_name`` +
``district``.
The existing ``precedent_library_upload`` MCP tool always stores
``source_kind='external_upload'`` and does not accept chair/district —
which is why **44+ existing appeals-committee decisions were tagged
wrong**. This wrapper is the authoritative ingestion path for committee
decisions and enforces the required metadata at the tool boundary.
"""
from __future__ import annotations
import json
from legal_mcp.services import internal_decisions as int_svc
# Valid Hebrew district names (matches _COURT_TO_DISTRICT in service)
VALID_DISTRICTS = {"ירושלים", "מרכז", "תל אביב", "תל-אביב", "צפון", "דרום", "חיפה", "ארצי"}
# proceeding_type — ערר vs בל"מ. The service can derive it from
# appeal_subtype/subject if left empty, so this stays optional at the API.
VALID_PROCEEDING_TYPES = {"ערר", 'בל"מ'}
def _ok(payload) -> str:
return json.dumps(payload, ensure_ascii=False, indent=2, default=str)
def _err(msg: str) -> str:
return json.dumps({"error": msg}, ensure_ascii=False)
async def internal_decision_upload(
file_path: str,
case_number: str,
chair_name: str,
district: str,
case_name: str = "",
court: str = "",
decision_date: str = "",
practice_area: str = "",
appeal_subtype: str = "",
subject_tags: list[str] | None = None,
summary: str = "",
is_binding: bool = False,
proceeding_type: str = "",
) -> str:
"""העלאת החלטה של ועדת ערר (internal_committee) לקורפוס הסמכותי.
Required: file_path, case_number, chair_name, district.
The tool enforces chair_name+district so the record cannot be saved
in the broken legacy mode (external_upload with empty chair/district).
Args:
file_path: נתיב מלא לקובץ PDF/DOCX/RTF/TXT/MD.
case_number: מספר הערר ("ערר (ועדות ערר - תכנון ובנייה ירושלים) 1110/20 ...").
chair_name: שם יו"ר הוועדה (חובה).
district: מחוז (ירושלים/מרכז/תל אביב/צפון/דרום/חיפה/ארצי) — חובה.
case_name: שם קצר.
court: ערכאה ("ועדת הערר לתכנון ובנייה — מחוז ירושלים").
decision_date: ISO date (YYYY-MM-DD), אופציונלי.
practice_area: rishuy_uvniya / betterment_levy / compensation_197.
appeal_subtype: building_permit / וכו'.
subject_tags: תגיות נושא.
is_binding: בד"כ False (ועדת ערר לא מחייבת ועדה אחרת — שכנוע אופקי).
proceeding_type: 'ערר' או 'בל"מ'. אם ריק — נגזר מ-appeal_subtype/case_name.
Returns: JSON עם case_law_id, מספר chunks, halachot_pending.
"""
if not file_path.strip():
return _err("file_path חובה")
if not case_number.strip():
return _err("case_number חובה")
if not chair_name.strip():
return _err(
"chair_name חובה. החלטות ועדת ערר חייבות שם יו\"ר — "
"בלעדיו ההחלטה לא ניתנת לחיפוש סלקטיבי לפי הרכב."
)
if not district.strip():
return _err(
"district חובה. ערכים תקפים: " + ", ".join(sorted(VALID_DISTRICTS))
)
if district.strip() not in VALID_DISTRICTS:
return _err(
f"district לא תקין: {district!r}. ערכים תקפים: "
+ ", ".join(sorted(VALID_DISTRICTS))
)
if proceeding_type.strip() and proceeding_type.strip() not in VALID_PROCEEDING_TYPES:
return _err(
f"proceeding_type לא תקין: {proceeding_type!r}. ערכים תקפים: "
+ ", ".join(sorted(VALID_PROCEEDING_TYPES))
)
try:
result = await int_svc.ingest_internal_decision(
case_number=case_number,
case_name=case_name,
court=court,
decision_date=decision_date or None,
chair_name=chair_name,
district=district,
practice_area=practice_area,
appeal_subtype=appeal_subtype,
subject_tags=subject_tags or [],
summary=summary,
is_binding=is_binding,
file_path=file_path,
proceeding_type=proceeding_type,
)
except Exception as e:
return _err(str(e))
return _ok(result)