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>
This commit is contained in:
@@ -117,6 +117,23 @@ def normalize_case_number(raw: str) -> str:
|
||||
return cleaned.replace("/", "-").strip("-")
|
||||
|
||||
|
||||
def case_number_from_citation(citation: str) -> str:
|
||||
"""Canonical ``case_number`` extracted from a full citation, or ``''``.
|
||||
|
||||
Returns the normalized number token only (e.g. ``85074-04-25``) — NEVER the
|
||||
full citation string with party names / court / date. This is the
|
||||
identifier-field rule from X1 (INV-ID2): a citation like
|
||||
``ערר (ת"א 85074-04-25) רפאל לוי ואח' נ' הוועדה … - חולון`` yields
|
||||
``85074-04-25``, not the whole display string.
|
||||
|
||||
Reuses ``classify`` (the one canonical citation parser) so callers that need
|
||||
a case_number out of an arbitrary citation never roll their own regex (#137,
|
||||
G2). Returns ``''`` when no number can be parsed — the caller MUST treat that
|
||||
as "needs a manual case_number" and never fall back to the raw citation.
|
||||
"""
|
||||
return classify(citation).case_number_norm
|
||||
|
||||
|
||||
def _split_filed(num_norm: str) -> tuple[str, str, str] | None:
|
||||
"""Split a normalized NNNNN-MM-YY number into (file, month, year).
|
||||
|
||||
|
||||
@@ -1757,12 +1757,23 @@ def _canonical_case_number(s: str) -> str:
|
||||
Used at the write boundary for identifier-keyed corpora (internal
|
||||
committee decisions, active cases). NOT for external precedents, whose
|
||||
canonical identifier is the full citation.
|
||||
|
||||
Extracts the case-number TOKEN — the leading run of digits and internal
|
||||
separators after the proceeding-type prefix — and drops any trailing display
|
||||
text. Without this, a full citation mis-passed as the identifier (e.g.
|
||||
``ערר (ת"א 85074-04-25) רפאל לוי נ' …``) left party names glued to the number
|
||||
(``85074-04-25) רפאל לוי …``), breaking equality lookups (#137, INV-ID2). A
|
||||
clean number is returned unchanged.
|
||||
"""
|
||||
s = (s or "").strip()
|
||||
m = re.search(r"\d", s)
|
||||
if m:
|
||||
s = s[m.start():]
|
||||
return s.strip().replace("/", "-")
|
||||
if not m:
|
||||
return ""
|
||||
s = s[m.start():]
|
||||
token = re.match(r"\d[\d/\-]*", s)
|
||||
if token:
|
||||
s = token.group(0)
|
||||
return s.strip().rstrip("/-").replace("/", "-")
|
||||
|
||||
|
||||
def _content_hash(text: str) -> str:
|
||||
|
||||
Reference in New Issue
Block a user