feat(citation-verify): frontend "אימות פסיקה" tab in the decision editor (#154)
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>
This commit is contained in:
103
web-ui/src/lib/api/citation-verification.ts
Normal file
103
web-ui/src/lib/api/citation-verification.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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 ?? ""] });
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user