/** * 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; }; 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( `/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 ?? ""] }); }, }); }