feat(hearing): פאנל "מה קרה בדיון" — חשיפת ניתוח-הפרוטוקול ב-UI (#226)
תוצר analyze_protocol (איך הטענות הכתובות התפתחו בדיון בעל-פה) היה שכבת ידע-תיק ללא endpoint ותצוגה — נצרך רק downstream ע"י הכותב בבלוק-י. פאנל חדש בתחתית טאב "טיעונים ועמדות", מתחת לכרטיס-הטיעונים (INV-IA1: לא טאב-מקביל). Backend: - SCHEMA_V51: cases.hearing_attendees JSONB — בית קנוני לנוכחות-בדיון (panel_members/appellants_present/respondents_present). נבדל סמנטית מ-decisions.panel_members (מותב חותם-ההחלטה) → אין מסלול-מקביל (G2). - protocol_analyzer._extract_header: מאכלס hearing_attendees; refresh על re-run (מתקן #223), לא דורס עם חילוץ ריק. אישור-משתמש: "אפשרות א" (persist+הצגה). - GET /api/cases/{n}/protocol-analysis — header (תאריך+נוכחים קנוניים) + verdicts מקובצים לפי צד. קריאה-בלבד, container-safe, ריק≠שגיאה. - POST /api/cases/{n}/analyze-protocol — מעיר את המנתח (analyze_protocol מריץ claude מקומי, נעדר בקונטיינר) כתבנית aggregate-arguments. - מגע-Paperclip רק דרך agent_platform_port (G12): wake_analyst_for_protocol_analysis. Frontend: - lib/api/protocol-analysis.ts (hooks read+trigger) + HearingChangesPanel (סרגל-כותרת, פסי-סינון התחזק/נטען-לראשונה, כרטיסים לפי צד, ציטוט-ראיה). - חיווט לטאב arguments מתחת ל-LegalArgumentsPanel. עיצוב: מוקאפ 27-case-hearing-changes-panel אושר ע"י חיים דרך שער Claude Design (X17). Invariants: מקיים G2 (בית קנוני יחיד, לא מסלול-מקביל), G1 (נרמול-במקור), G12 (שער-הפלטפורמה), INV-IA1 (לא טאב-מקביל), INV-AH (evidence_quote כבר נאכף במקור). אימות: py_compile + tsc --noEmit + eslint ירוקים; runtime של ה-endpoint = post-deploy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
134
web-ui/src/lib/api/protocol-analysis.ts
Normal file
134
web-ui/src/lib/api/protocol-analysis.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Protocol comparative analysis — the "מה קרה בדיון" panel (#226).
|
||||
*
|
||||
* `analyze_protocol` compares the ערר hearing protocol against the written
|
||||
* pleadings and records, per argument, whether it was strengthened, newly
|
||||
* raised, or dropped at the hearing — plus the sharpened legal question and a
|
||||
* verbatim evidence quote. This module exposes the read + trigger endpoints.
|
||||
*/
|
||||
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { apiRequest } from "./client";
|
||||
import type { LegalArgumentParty } from "./legal-arguments";
|
||||
|
||||
export type ProtocolChangeType = "strengthened" | "newly_raised" | "dropped";
|
||||
|
||||
/** Empty string = an unassigned party_role on a verdict row. */
|
||||
export type ProtocolPartyRole = LegalArgumentParty | "";
|
||||
|
||||
export type ProtocolAnalysisRow = {
|
||||
id: string;
|
||||
case_id: string;
|
||||
document_id: string;
|
||||
party_role: ProtocolPartyRole;
|
||||
change_type: ProtocolChangeType;
|
||||
/** The pleaded argument this verdict matched (strengthened/dropped); null for newly_raised. */
|
||||
argument_id: string | null;
|
||||
argument_title: string;
|
||||
summary: string;
|
||||
sharpened_question: string;
|
||||
evidence_quote: string;
|
||||
page_number: number | null;
|
||||
created_at?: string;
|
||||
};
|
||||
|
||||
export type ProtocolHeader = {
|
||||
hearing_date: string | null;
|
||||
protocol_title: string;
|
||||
protocol_document_id: string;
|
||||
panel_members: string[];
|
||||
appellants_present: string[];
|
||||
respondents_present: string[];
|
||||
};
|
||||
|
||||
export type ProtocolAnalysisResponse = {
|
||||
case_number: string;
|
||||
total: number;
|
||||
header: ProtocolHeader;
|
||||
by_change: Partial<Record<ProtocolChangeType, number>>;
|
||||
by_party: Partial<Record<ProtocolPartyRole, ProtocolAnalysisRow[]>>;
|
||||
analysis: ProtocolAnalysisRow[];
|
||||
};
|
||||
|
||||
export const protocolAnalysisKeys = {
|
||||
all: ["protocol-analysis"] as const,
|
||||
byCase: (caseNumber: string) =>
|
||||
[...protocolAnalysisKeys.all, caseNumber] as const,
|
||||
};
|
||||
|
||||
export function useProtocolAnalysis(caseNumber: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: protocolAnalysisKeys.byCase(caseNumber ?? ""),
|
||||
queryFn: ({ signal }) =>
|
||||
apiRequest<ProtocolAnalysisResponse>(
|
||||
`/api/cases/${caseNumber}/protocol-analysis`,
|
||||
{ signal },
|
||||
),
|
||||
enabled: Boolean(caseNumber),
|
||||
staleTime: 10_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The analysis runs on the legal-analyst agent (host-side, where the `claude`
|
||||
* CLI lives) — NOT inline in the FastAPI container. The endpoint delegates via
|
||||
* a Paperclip wakeup (`queued`), or reports `skipped` when no analyst route is
|
||||
* available (the chair can then run the MCP tool manually).
|
||||
*/
|
||||
export type AnalyzeProtocolResult =
|
||||
| {
|
||||
status: "queued";
|
||||
sub_issue_id: string;
|
||||
analyst_id: string;
|
||||
main_issue_id: string;
|
||||
}
|
||||
| {
|
||||
status: "skipped";
|
||||
reason: "no_api_key" | "no_analyst" | "no_issue" | string;
|
||||
company_id?: string;
|
||||
};
|
||||
|
||||
export function useAnalyzeProtocol(caseNumber: string | undefined) {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (documentId?: string) =>
|
||||
apiRequest<AnalyzeProtocolResult>(
|
||||
`/api/cases/${caseNumber}/analyze-protocol${
|
||||
documentId ? `?document_id=${encodeURIComponent(documentId)}` : ""
|
||||
}`,
|
||||
{ method: "POST" },
|
||||
),
|
||||
onSuccess: () => {
|
||||
if (caseNumber) {
|
||||
qc.invalidateQueries({
|
||||
queryKey: protocolAnalysisKeys.byCase(caseNumber),
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const CHANGE_LABELS_HE: Record<ProtocolChangeType, string> = {
|
||||
strengthened: "התחזק בדיון",
|
||||
newly_raised: "נטען לראשונה",
|
||||
dropped: "נזנח בדיון",
|
||||
};
|
||||
|
||||
export const PROTOCOL_PARTY_LABELS_HE: Record<ProtocolPartyRole, string> = {
|
||||
appellant: "עוררים",
|
||||
respondent: "משיבים",
|
||||
committee: "ועדה מקומית",
|
||||
permit_applicant: "מבקשי היתר",
|
||||
unknown: "צד לא מזוהה",
|
||||
"": "צד לא מסווג",
|
||||
};
|
||||
|
||||
/** Display order for the per-party sections. */
|
||||
export const PROTOCOL_PARTY_ORDER: ProtocolPartyRole[] = [
|
||||
"appellant",
|
||||
"committee",
|
||||
"respondent",
|
||||
"permit_applicant",
|
||||
"unknown",
|
||||
"",
|
||||
];
|
||||
Reference in New Issue
Block a user