/** * Legal Arguments domain — aggregated propositions (claim de-dup). * * Each raw "claim" is an extracted proposition from a litigation brief; * the LLM-driven aggregator groups them by party into 6-12 distinct * legal arguments. These hooks expose the read + trigger endpoints. */ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "./client"; export type LegalArgumentParty = | "appellant" | "respondent" | "committee" | "permit_applicant" | "unknown"; export type LegalArgumentPriority = | "threshold" | "substantive" | "procedural" | "relief"; export type SupportingProposition = { id: string; text: string; source_document: string | null; }; export type LegalArgument = { id: string; case_id: string; party: LegalArgumentParty; /** * The specific pleading this argument belongs to, for sides that may comprise * several litigants with opposing positions (respondent / permit_applicant * briefs). Empty for single-voice sides (appellant / committee). (#224) */ party_name?: string; argument_index: number; argument_title: string; argument_body: string; legal_topic: string | null; priority: LegalArgumentPriority; cited_precedents?: string[] | null; created_at?: string; updated_at?: string; supporting_claims: string[]; /** Raw extracted propositions (id + full text) backing this argument. */ supporting_propositions?: SupportingProposition[]; }; export type LegalArgumentsResponse = { case_number: string; total: number; by_party: Partial>; arguments: LegalArgument[]; }; export const legalArgumentsKeys = { all: ["legal-arguments"] as const, byCase: (caseNumber: string) => [...legalArgumentsKeys.all, caseNumber] as const, }; export function useLegalArguments(caseNumber: string | undefined) { return useQuery({ queryKey: legalArgumentsKeys.byCase(caseNumber ?? ""), queryFn: ({ signal }) => apiRequest( `/api/cases/${caseNumber}/legal-arguments`, { signal }, ), enabled: Boolean(caseNumber), staleTime: 10_000, }); } /** * The aggregation runs on the legal-analyst agent (host-side, where the * `claude` CLI lives) — NOT inline in the FastAPI container. The endpoint * either delegates via a Paperclip wakeup (`queued`) or short-circuits on a * cheap in-container DB pre-check (`no_claims` / `exists`). `skipped` means * no analyst route was available; the chair can run the MCP tool manually. */ export type AggregateArgumentsResult = | { status: "queued"; sub_issue_id: string; analyst_id: string; main_issue_id: string; } | { status: "no_claims" | "exists"; total: number; message: string; } | { status: "skipped"; reason: "no_api_key" | "no_analyst" | "no_issue" | string; company_id?: string; }; export function useAggregateArguments(caseNumber: string | undefined) { const qc = useQueryClient(); return useMutation({ mutationFn: (force: boolean = false) => apiRequest( `/api/cases/${caseNumber}/aggregate-arguments${force ? "?force=true" : ""}`, { method: "POST" }, ), onSuccess: () => { if (caseNumber) { qc.invalidateQueries({ queryKey: legalArgumentsKeys.byCase(caseNumber), }); } }, }); } export const PARTY_LABELS_HE: Record = { appellant: "עוררים", respondent: "משיבים", committee: "ועדה מקומית", permit_applicant: "מבקשי היתר", unknown: "צד לא מזוהה", }; export const PRIORITY_LABELS_HE: Record = { threshold: "סף", substantive: "מהותי", procedural: "פגם הליך", relief: "סעד", }; export const PRIORITY_ORDER: LegalArgumentPriority[] = [ "threshold", "substantive", "procedural", "relief", ];