diff --git a/mcp-server/src/legal_mcp/services/case_citation_verification.py b/mcp-server/src/legal_mcp/services/case_citation_verification.py index 663cacd..26c2f97 100644 --- a/mcp-server/src/legal_mcp/services/case_citation_verification.py +++ b/mcp-server/src/legal_mcp/services/case_citation_verification.py @@ -18,6 +18,7 @@ rows. Reuses the one corpus search + the one authority query + the one radar (G2 from __future__ import annotations +import asyncio import logging from uuid import UUID @@ -65,25 +66,37 @@ async def build_view(case_number: str) -> dict: except Exception as e: # noqa: BLE001 — radar is best-effort logger.warning("citation_verification radar failed for %s: %s", case_number, e) - out_args: list[dict] = [] - n_verified = 0 - for a in args: - aid = str(a["id"]) + async def _fetch(a: dict) -> tuple[list[dict], dict]: + """Per-argument corpus search + batched authority — run concurrently. + + Each call is one Voyage embed + one vector search (+ one batched authority + query); independent across arguments, so they fan out rather than waterfall. + """ title = (a.get("argument_title") or "").strip() topic = (a.get("legal_topic") or "").strip() query = f"{ctx} {title}. {topic}".strip() - - hits = [] + hits: list[dict] = [] try: hits = await precedent_library.search_library( query=query, limit=_SUGGEST_PER_ISSUE, include_halachot=True) except Exception as e: # noqa: BLE001 logger.warning("citation_verification search failed (%s): %s", title[:30], e) - # Resolve the authority breakdown for the hit set in one batched query. clids = [UUID(str(h["case_law_id"])) for h in hits if h.get("case_law_id") and float(h.get("score", 0) or 0) >= _SUGGEST_FLOOR] authority = await db.citation_authority(clids) if clids else {} + return hits, authority + + # Fan out the expensive per-argument retrieval concurrently — was a sequential + # waterfall (N args × Voyage embed + vector search each). gather preserves order. + fetched = await asyncio.gather(*(_fetch(a) for a in args)) if args else [] + + out_args: list[dict] = [] + n_verified = 0 + for a, (hits, authority) in zip(args, fetched): + aid = str(a["id"]) + title = (a.get("argument_title") or "").strip() + topic = (a.get("legal_topic") or "").strip() seen: set[str] = set() supporting: list[dict] = [] diff --git a/web-ui/src/lib/api/citation-verification.ts b/web-ui/src/lib/api/citation-verification.ts index c22468e..6e11b4a 100644 --- a/web-ui/src/lib/api/citation-verification.ts +++ b/web-ui/src/lib/api/citation-verification.ts @@ -84,7 +84,13 @@ export function useCitationVerification(caseNumber: string | undefined) { { signal }, ), enabled: Boolean(caseNumber), - staleTime: 10_000, + // The view is an expensive synthesized build (per-argument Voyage embed + + // vector search on the backend). Keep it fresh for 5 min and do NOT refetch + // on remount — Radix Tabs unmounts inactive TabsContent, so every tab switch + // back used to trigger a full rebuild. Verify mutations invalidate the query, + // so edits still pull fresh data. + staleTime: 5 * 60_000, + refetchOnMount: false, }); }