Files
legal-ai/web-ui/src/lib/api/citation-verification.ts
Chaim 88ca96bf19
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
perf(citation-verification): cache view + parallelize backend retrieval
The "אימות פסיקה" tab reloaded its data on every tab revisit and the first
load took 1.5–3s. Two independent causes, two fixes:

Frontend (web-ui/src/lib/api/citation-verification.ts):
- staleTime 10s → 5min and refetchOnMount: false. Radix Tabs unmounts inactive
  TabsContent, so switching back used to trigger a full rebuild once the 10s
  window elapsed. Verify mutations still invalidate the query, so edits pull
  fresh data.

Backend (mcp-server/.../case_citation_verification.py):
- The per-argument loop ran a Voyage embed + vector search + batched authority
  query sequentially (N args × ~300ms waterfall). Extracted to _fetch() and
  fanned out with asyncio.gather — order preserved, n_verified counter and
  per-argument seen-sets unchanged. First load ~5× faster on multi-argument cases.

Pure read/assembly — no new parallel path (G2), no silent error swallowing
(search/radar still log warnings). Invariants: INV-DIG1/INV-AH unaffected
(still read-only, never cites).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 17:35:52 +00:00

110 lines
3.1 KiB
TypeScript

/**
* Citation-verification domain (X11 Phase 2 / #154) — the compose "אימות פסיקה" tab.
*
* Per legal argument: in-corpus supporting precedents with the cumulative authority
* signal (cited_by — followed/distinguished), their verify state + chair note, and
* per-issue radar (unlinked digests). The chair verifies before the writer cites
* (INV-AH). Backed by GET/POST /api/cases/{n}/citation-verification[/verify].
*/
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "./client";
export type CitedBy = {
total: number;
positive: number;
negative: number;
unclassified: number;
by_treatment: Record<string, number>;
};
export type SupportingPrecedent = {
case_law_id: string;
case_number: string;
case_name: string;
quote: string;
score: number;
cited_by: CitedBy;
attached_id: string | null;
verified: boolean;
chair_note: string;
};
export type RadarLead = {
digest_id: string;
yomon_number?: string | number | null;
headline: string;
underlying_citation: string;
underlying_court: string;
score: number;
missing_precedent_id: string | null;
missing_precedent_status: string | null;
action: "new_lead" | "gap_open" | "fetched" | "available_link" | string;
matched_issues?: string[];
};
export type ArgumentBlock = {
argument_id: string;
title: string;
legal_topic: string;
priority: string;
party: string;
supporting: SupportingPrecedent[];
radar: RadarLead[];
};
export type CitationVerificationView = {
status: string;
case_number: string;
arguments: ArgumentBlock[];
summary: {
arguments_total: number;
arguments_with_support: number;
verified: number;
radar_leads: number;
};
};
export type VerifyCitationBody = {
argument_id: string;
case_law_id?: string;
quote?: string;
citation?: string;
chair_note?: string | null;
verified: boolean;
attached_id?: string;
};
export function useCitationVerification(caseNumber: string | undefined) {
return useQuery({
queryKey: ["citation-verification", caseNumber ?? ""],
queryFn: ({ signal }) =>
apiRequest<CitationVerificationView>(
`/api/cases/${caseNumber}/citation-verification`,
{ signal },
),
enabled: Boolean(caseNumber),
// 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,
});
}
export function useVerifyCitation(caseNumber: string | undefined) {
const qc = useQueryClient();
return useMutation({
mutationFn: (body: VerifyCitationBody) =>
apiRequest(`/api/cases/${caseNumber}/citation-verification/verify`, {
method: "POST",
body,
}),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ["citation-verification", caseNumber ?? ""] });
},
});
}