Frontend half of the citation-verification panel (backend in #323), as the agreed third tab in /compose — matching the approved X17 mockup 24-citation-verification: - lib/api/citation-verification.ts: hand-written types + useCitationVerification query + useVerifyCitation mutation (POST verify, invalidates the view). - components/compose/citation-verification-panel.tsx: per legal argument → supporting corpus precedents with the cited_by authority chips (אומץ ×N / אובחן ×N), the exact supporting_quote, ✓ מאמת / ✗ לא רלוונטי verify actions, a per-citation chair-note field, and the per-issue 📡 radar (unlinked digests, pointer-only). Summary strip + INV-AH/INV-DIG1 reminders. - compose page: third tab "אימות פסיקה" alongside עורך הבלוקים / עמדות וטענות. No api:types regen needed (endpoints return assembled dicts; types hand-written per the lib/api convention). tsc + eslint clean. Invariants: INV-AH (writer cites only verified), INV-DIG1 (digest never cited), G2 (consumes the one backend view). Visual matches the gate-approved mockup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
104 lines
2.7 KiB
TypeScript
104 lines
2.7 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),
|
|
staleTime: 10_000,
|
|
});
|
|
}
|
|
|
|
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 ?? ""] });
|
|
},
|
|
});
|
|
}
|