Files
legal-ai/mcp-server/tests/test_court_citation.py
Chaim c27987ba72
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 14s
fix(precedents): חילוץ מספר-תיק קנוני מהציטוט — לא ציטוט-מלא כמזהה (#137)
בהעלאה דרך "פסיקה-חסרה" (ענף ועדת-ערר), כשטופס case_number ריק המסלול נפל-לאחור
לציטוט המלא (committee_case_number = case_number.strip() or citation), כך שמחרוזת-
תצוגה עם שמות-צדדים הושתלה בשדה-המזהה — הפרת INV-ID2/INV-ID1 (X1). נצפה על
precedent 1bf0bae0 (ערר 85074-04-25 רפאל לוי/חולון): case_number=85074/0425,
case_name=ציטוט שלם.

תיקון (G1 — נרמול-במקור, G2 — שימוש-חוזר בפרסר הקנוני):
- court_citation.case_number_from_citation(citation) — מחזיר את אסימון-המספר
  המנורמל בלבד (classify; '' כשאין מספר). חולץ נכון 85074-04-25 גם מתוך
  "ערר (ת\"א 85074-04-25) ...". reuse של הפרסר היחיד, בלי regex מקביל.
- web/app.py (ענף ועדת-ערר): fallback דרך case_number_from_citation; אם אין
  מספר — HTTPException 400 "נא להזין מספר-תיק ידנית" במקום השתלת ציטוט-מלא.
- db._canonical_case_number: מוקשח לחלץ את אסימון-המספר (זורק זנב שמות-צדדים),
  כך ששדה-המזהה לעולם לא נשמר מזוהם — גם בקריאה ישירה (committee + active cases).
  מספר נקי חוזר ללא שינוי; חודש לא מומצא (X1 §1).
- תיקון-נתון: scripts/fix_137_committee_case_number.py (בוצע) — 1bf0bae0:
  case_number→85074-04-25, case_name→צדדים, token ב-citation_formatted.
  אומת היחיד עם canon(num)≠num ב-internal_committee. אידמפוטנטי.

מחוץ-לתחום (תועד כ-follow-up): מסלול external (precedent_library) משתמש בציטוט-
מלא כמזהה-מורשת — זהו פריט-המיגרציה X1 §5 (138 רשומות external/cited_only),
לא הבאג הזה. prefill ב-UI של /missing-precedents — דורש שער Claude Design.

בדיקות: test_court_citation (case_number_from_citation: party-strip/forms/empty),
test_canonical_case_number (harden). כל 339 בדיקות mcp עוברות. guards נקיים.

Invariants: G1 (נרמול-במקור), INV-ID1/ID2 (מזהה מנורמל, אין ציטוט-מלא כמזהה),
G2 (פרסר יחיד), G12 (leak-guard נקי).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 03:21:10 +00:00

118 lines
4.4 KiB
Python

"""Unit tests for the X13 court-citation classifier."""
from __future__ import annotations
from legal_mcp.services.court_citation import (
case_number_from_citation,
classify,
normalize_case_number,
)
def test_admin_filed_format_the_example():
"""The plan's example: עת"מ 46111-12-22 → admin, parsed into (46111,12,22)."""
c = classify('עת"מ 46111-12-22 יכין-אפק בע"מ נ\' הוועדה המחוזית')
assert c.tier == "admin"
assert c.court_prefix in ('עת"מ', "עת״מ")
assert c.case_number_raw == "46111-12-22"
assert c.case_number_norm == "46111-12-22"
assert (c.file_number, c.month, c.year) == ("46111", "12", "22")
assert c.fetchable is True
def test_bare_filed_number_defaults_admin():
c = classify("46111-12-22")
assert c.tier == "admin"
assert (c.file_number, c.month, c.year) == ("46111", "12", "22")
def test_supreme_prefixes():
for cit, pref in [
('עע"מ 1234/22', "supreme"),
('בג"ץ 5678/21', "supreme"),
('ע"א 999/20', "supreme"),
('רע"א 4/19', "supreme"),
('בר"מ 8126/24', "supreme"),
]:
c = classify(cit)
assert c.tier == pref, f"{cit} -> {c.tier}"
assert c.fetchable is True
def test_appeals_committee_is_skip():
"""ערר / בל"מ must never be auto-fetched (needs Nevo) — INV-CF6."""
for cit in ['ערר 1110/20', 'בל"מ 8048/24', "ערר 1015-01-24 ירושלים שקופה"]:
c = classify(cit)
assert c.tier == "skip", f"{cit} -> {c.tier}"
assert c.fetchable is False
def test_skip_wins_over_court_match():
"""An 'ערר' citation that also contains court-like digits stays skip."""
c = classify("ראה החלטתי בערר 1041/24 ובהמשך")
assert c.tier == "skip"
def test_admin_amn_prefix():
c = classify('עמ"נ 12345-06-23')
assert c.tier == "admin"
assert (c.file_number, c.month, c.year) == ("12345", "06", "23")
def test_two_group_serial_has_no_filed_triple():
"""Supreme serial 1234/22 normalizes but yields no (file,month,year)."""
c = classify('עע"מ 1234/22')
assert c.case_number_norm == "1234-22"
assert c.file_number is None
def test_implausible_month_not_parsed_as_filed():
# 1234-22-05 has month=22 → not a valid filed triple.
assert classify("1234-22-05").tier in ("unknown", "admin")
c = classify("1234-22-05")
if c.tier == "admin":
assert c.month is None
def test_empty_and_garbage():
assert classify("").tier == "unknown"
assert classify("שלום עולם בלי ציטוט").tier == "unknown"
def test_normalize_case_number():
assert normalize_case_number('עת"מ 46111/12/22') == "46111-12-22"
assert normalize_case_number("1110/20") == "1110-20"
def test_case_number_from_citation_strips_party_names():
"""#137 — a full ועדת-ערר citation yields ONLY the number, never the
display string with party names (INV-ID2). This is the exact precedent
1bf0bae0 that planted ``85074-04-25) רפאל לוי …`` into case_number."""
cit = 'ערר (ת"א 85074-04-25) רפאל לוי ואח\' נ\' הוועדה המקומית - חולון'
assert case_number_from_citation(cit) == "85074-04-25"
def test_case_number_from_citation_various_forms():
assert case_number_from_citation('ערר (ת"א 1198-12-25) זאטוס') == "1198-12-25"
assert case_number_from_citation("85074-04-25") == "85074-04-25"
assert case_number_from_citation('בל"מ 85074-09-24') == "85074-09-24"
assert case_number_from_citation("ערר 8137/24") == "8137-24"
def test_case_number_from_citation_empty_when_unparseable():
"""No number → '' so the caller demands a manual number, never the raw
citation (the #137 fallback that caused the bug)."""
assert case_number_from_citation("") == ""
assert case_number_from_citation("פסק דין בלי מספר") == ""
def test_supreme_with_net_format_triple():
"""A Supreme prefix carrying a נט-format number exposes the triple so the
orchestrator can route it to Tier-1 (נט המשפט serves Supreme too)."""
c = classify('בר"מ 72182-06-25 הימנותא נ\' הוועדה המקומית')
assert c.tier == "supreme"
assert (c.file_number, c.month, c.year) == ("72182", "06", "25")
# serial-format Supreme has no triple → stays Tier-0-only
s = classify('עע"מ 5886/24')
assert s.tier == "supreme" and s.file_number is None