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>
110 lines
3.1 KiB
TypeScript
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 ?? ""] });
|
|
},
|
|
});
|
|
}
|