fix(cases): tolerant case_number lookup so agents see case documents
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m39s

Reported: an agent claimed the case had no documents because document_list
returned empty — but the documents exist. Root cause: get_case_by_number did
an exact `WHERE case_number = $1`, so any formatting variant of the number
silently failed to resolve. Verified on 8137-24 (9 docs): "8137/24",
"ערר 8137-24", leading/trailing space, and "בל\"מ 8126/03/25" all returned
"תיק לא נמצא", which the agent read as "no documents" and went blind.

Add _normalize_case_number (strip leading proceeding-type prefix to the first
digit, trim, unify '/'→'-') and a normalized fallback in the lookup query
(exact match preferred via ORDER BY). One fix covers every case_number-scoped
tool (document_list, extract_references, search_case_documents, get_claims,
drafting, ...). Bogus numbers still correctly resolve to "not found". (#58)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 11:54:52 +00:00
parent 58ab003206
commit 7826ff4910
2 changed files with 44 additions and 4 deletions

View File

@@ -5,6 +5,7 @@ from __future__ import annotations
import asyncio
import json
import logging
import re
from datetime import date
from uuid import UUID, uuid4
@@ -1192,11 +1193,38 @@ async def get_active_draft_path(case_id: UUID) -> str | None:
return row["active_draft_path"] if row else None
def _normalize_case_number(s: str) -> str:
"""Canonicalise a case number for tolerant lookup.
Agents receive the number in many shapes — from a Paperclip issue
title ("ערר 8137/24"), with a slash instead of a dash, padded, or with
surrounding whitespace. Stored values are bare ("8137-24"). Without
this, get_case_by_number's exact match silently fails and the agent
concludes the case has no documents (see #58). Strategy: drop any
leading proceeding-type prefix (everything before the first digit),
trim, and unify '/''-'.
"""
s = (s or "").strip()
m = re.search(r"\d", s)
if m:
s = s[m.start():]
return s.strip().replace("/", "-")
async def get_case_by_number(case_number: str) -> dict | None:
pool = await get_pool()
norm = _normalize_case_number(case_number)
async with pool.acquire() as conn:
# Exact match first (fast path + preferred); fall back to a
# separator/prefix-normalised comparison so common formatting
# variants still resolve to the right case. See #58.
row = await conn.fetchrow(
"SELECT * FROM cases WHERE case_number = $1", case_number
"""SELECT * FROM cases
WHERE case_number = $1
OR replace(btrim(case_number), '/', '-') = $2
ORDER BY (case_number = $1) DESC, created_at
LIMIT 1""",
case_number, norm,
)
if row is None:
return None