diff --git a/mcp-server/src/legal_mcp/services/db.py b/mcp-server/src/legal_mcp/services/db.py index 278d1c1..8cb9275 100644 --- a/mcp-server/src/legal_mcp/services/db.py +++ b/mcp-server/src/legal_mcp/services/db.py @@ -6405,10 +6405,16 @@ async def list_missing_precedents( status: str | None = None, case_id: UUID | None = None, legal_topic: str | None = None, + q: str | None = None, limit: int = 200, offset: int = 0, ) -> list[dict]: - """List missing precedents, joining the cited-in case_number for display.""" + """List missing precedents, joining the cited-in case_number for display. + + ``q`` is a free-text term matched (ILIKE) across the gap's own מראה-מקום + (``mp.citation``), its case name, and the cited-in appeal case number — so a + chair can find a gap by the missing decision's number (e.g. ``85074``), not + only by the appeal it was cited in.""" pool = await get_pool() conditions: list[str] = [] params: list = [] @@ -6425,6 +6431,13 @@ async def list_missing_precedents( conditions.append(f"mp.legal_topic ILIKE ${idx}") params.append(f"%{legal_topic}%") idx += 1 + if q: + conditions.append( + f"(mp.citation ILIKE ${idx} OR mp.case_name ILIKE ${idx} " + f"OR c.case_number ILIKE ${idx})" + ) + params.append(f"%{q}%") + idx += 1 where = f"WHERE {' AND '.join(conditions)}" if conditions else "" params.append(limit) params.append(offset) diff --git a/web-ui/src/app/missing-precedents/page.tsx b/web-ui/src/app/missing-precedents/page.tsx index 6099b21..5188f8b 100644 --- a/web-ui/src/app/missing-precedents/page.tsx +++ b/web-ui/src/app/missing-precedents/page.tsx @@ -31,19 +31,19 @@ const STATUS_CHIPS: { value: StatusFilter; label: string }[] = [ ]; export default function MissingPrecedentsPage() { - const [caseNumber, setCaseNumber] = useState(""); + const [search, setSearch] = useState(""); const [legalTopic, setLegalTopic] = useState(""); const [filter, setFilter] = useState("open"); /* Debounce the filters so the table fires one query after the user stops - * typing — not one per keystroke. Each intermediate value used to - * round-trip to the API (and a non-existent case number errored mid-typing). */ - const [caseNumberQ, setCaseNumberQ] = useState(""); + * typing — not one per keystroke. The search term matches the gap's own + * מראה-מקום, case name, and cited-in appeal number (server-side ILIKE). */ + const [searchQ, setSearchQ] = useState(""); const [legalTopicQ, setLegalTopicQ] = useState(""); useEffect(() => { - const t = setTimeout(() => setCaseNumberQ(caseNumber.trim()), 350); + const t = setTimeout(() => setSearchQ(search.trim()), 350); return () => clearTimeout(t); - }, [caseNumber]); + }, [search]); useEffect(() => { const t = setTimeout(() => setLegalTopicQ(legalTopic.trim()), 350); return () => clearTimeout(t); @@ -87,11 +87,11 @@ export default function MissingPrecedentsPage() { {/* shared filters */}
- + setCaseNumber(e.target.value)} - placeholder="1017-03-26" + value={search} + onChange={(e) => setSearch(e.target.value)} + placeholder="85074 או 1017-03-26" dir="rtl" />
@@ -137,7 +137,7 @@ export default function MissingPrecedentsPage() { diff --git a/web-ui/src/components/missing-precedents/missing-precedents-table.tsx b/web-ui/src/components/missing-precedents/missing-precedents-table.tsx index b68424a..d1dc157 100644 --- a/web-ui/src/components/missing-precedents/missing-precedents-table.tsx +++ b/web-ui/src/components/missing-precedents/missing-precedents-table.tsx @@ -81,15 +81,15 @@ function TableSkeleton({ cols }: { cols: number }) { type Props = { status?: MissingPrecedentStatus | ""; - caseNumber?: string; + q?: string; legalTopic?: string; }; -export function MissingPrecedentsTable({ status, caseNumber, legalTopic }: Props) { +export function MissingPrecedentsTable({ status, q, legalTopic }: Props) { const [openId, setOpenId] = useState(null); const { data, isPending, error } = useMissingPrecedents({ status: status === "" ? undefined : status, - caseNumber, + q, legalTopic, limit: 200, }); diff --git a/web-ui/src/lib/api/missing-precedents.ts b/web-ui/src/lib/api/missing-precedents.ts index 7b58ab7..b1f42f0 100644 --- a/web-ui/src/lib/api/missing-precedents.ts +++ b/web-ui/src/lib/api/missing-precedents.ts @@ -91,6 +91,9 @@ export type MissingPrecedentFilters = { caseNumber?: string; caseId?: string; legalTopic?: string; + /** Free-text term — matched across the gap's מראה-מקום, case name, and + * cited-in appeal number (find a gap by the missing decision's number). */ + q?: string; limit?: number; }; @@ -110,6 +113,7 @@ export function useMissingPrecedents(filters: MissingPrecedentFilters = {}) { if (filters.caseNumber) p.set("case_number", filters.caseNumber); if (filters.caseId) p.set("case_id", filters.caseId); if (filters.legalTopic) p.set("legal_topic", filters.legalTopic); + if (filters.q) p.set("q", filters.q); if (filters.limit) p.set("limit", String(filters.limit)); const qs = p.toString(); return apiRequest( diff --git a/web/app.py b/web/app.py index 9e209b7..7277aa7 100644 --- a/web/app.py +++ b/web/app.py @@ -7406,10 +7406,16 @@ async def missing_precedents_list( case_id: str = "", case_number: str = "", legal_topic: str = "", + q: str = "", limit: int = 200, offset: int = 0, ): - """List missing precedents, optionally filtered by status / case.""" + """List missing precedents, optionally filtered by status / case / text. + + ``q`` is a free-text term matched across the gap's own מראה-מקום, case name, + and cited-in appeal number — so the chair can find a gap by the missing + decision's number (e.g. 85074), not only by the appeal it was cited in. + ``case_id``/``case_number`` remain exact filters for programmatic callers.""" s = status.strip() or None if s and s not in _ALLOWED_MP_STATUS: raise HTTPException(400, f"status לא תקין: {status}") @@ -7444,6 +7450,7 @@ async def missing_precedents_list( status=s, case_id=case_uuid, legal_topic=legal_topic.strip() or None, + q=q.strip() or None, limit=max(1, min(int(limit), 500)), offset=max(0, int(offset)), )