/** * Training / style corpus hooks. * * Endpoints touched (all under /api/training/): * - GET /style-report → the dashboard payload (corpus stats + anatomy * + signature phrases + per-decision contribution) * - GET /corpus → flat list of decisions for the corpus tab / compare tool * - GET /compare?a=UUID&b=UUID → side-by-side comparison * - DELETE /corpus/{id} → remove a decision from the corpus */ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "./client"; export type StyleReport = { corpus: { decision_count: number; total_chars: number; avg_chars: number; date_range: [string | null, string | null]; decisions: Array<{ number: string; date: string; chars: number; subjects: string[]; }>; subject_distribution: Array<{ label: string; count: number }>; headline: string; }; anatomy: { sections: Array<{ type: string; label: string; avg_chars: number; pct: number; coverage: number; }>; total_coverage: number; headline: string; }; signature_phrases: { items: Array<{ type: string; text: string; context: string; frequency: number; examples: string[]; }>; total_decisions: number; top_display: string; headline: string; }; contribution: { growth_curve: Array<{ decision_number: string; date: string; cumulative: number; }>; decision_contributions: unknown[]; total_patterns: number; headline: string; }; }; export type CorpusDecision = { id: string; decision_number: string; decision_date: string; subject_categories: string[]; chars: number; created_at: string; }; export type CompareResult = { a: CompareSide; b: CompareSide; shared: PatternEntry[]; only_a: PatternEntry[]; only_b: PatternEntry[]; }; export type CompareSide = { id: string; decision_number: string; decision_date: string; chars: number; subjects: string[]; sections: Array<{ type: string; chars: number }>; patterns_count: number; }; export type PatternEntry = { id: string; type: string; text: string; context: string; }; export const trainingKeys = { all: ["training"] as const, report: () => [...trainingKeys.all, "style-report"] as const, corpus: () => [...trainingKeys.all, "corpus"] as const, compare: (a: string, b: string) => [...trainingKeys.all, "compare", a, b] as const, }; export function useStyleReport() { return useQuery({ queryKey: trainingKeys.report(), queryFn: ({ signal }) => apiRequest("/api/training/style-report", { signal }), staleTime: 60_000, }); } export function useCorpus() { return useQuery({ queryKey: trainingKeys.corpus(), queryFn: ({ signal }) => apiRequest("/api/training/corpus", { signal }), staleTime: 60_000, }); } export function useCompare(a: string | null, b: string | null) { return useQuery({ queryKey: trainingKeys.compare(a ?? "", b ?? ""), queryFn: ({ signal }) => apiRequest( `/api/training/compare?a=${encodeURIComponent(a!)}&b=${encodeURIComponent(b!)}`, { signal }, ), enabled: Boolean(a && b && a !== b), staleTime: Infinity, }); } export function useDeleteCorpusEntry() { const qc = useQueryClient(); return useMutation({ mutationFn: (id: string) => apiRequest<{ deleted: boolean }>( `/api/training/corpus/${encodeURIComponent(id)}`, { method: "DELETE" }, ), onSuccess: () => { qc.invalidateQueries({ queryKey: trainingKeys.corpus() }); qc.invalidateQueries({ queryKey: trainingKeys.report() }); }, }); }