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:
@@ -17,6 +17,7 @@ import { DraftsPanel } from "@/components/cases/drafts-panel";
|
||||
import { DecisionBlocksPanel } from "@/components/cases/decision-blocks-panel";
|
||||
import { LegalArgumentsPanel } from "@/components/cases/legal-arguments-panel";
|
||||
import { PositionsPanel } from "@/components/cases/positions-panel";
|
||||
import { HearingChangesPanel } from "@/components/cases/hearing-changes-panel";
|
||||
import { CitationVerificationPanel } from "@/components/compose/citation-verification-panel";
|
||||
import { AgentActivityFeed } from "@/components/cases/agent-activity-feed";
|
||||
import { AgentActivityPreview } from "@/components/cases/agent-activity-preview";
|
||||
@@ -181,6 +182,14 @@ export default function CaseDetailPage({
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
{/* מה קרה בדיון — comparative analysis of the hearing protocol vs.
|
||||
the written pleadings (#226). Sits below the aggregated arguments
|
||||
because it speaks to those same arguments in their oral gloss. */}
|
||||
<Card className="bg-surface border-rule shadow-sm">
|
||||
<CardContent className="px-6 py-5">
|
||||
<HearingChangesPanel caseNumber={caseNumber} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
{/* אימות פסיקה — per-argument supporting-precedent verify gate (#154),
|
||||
|
||||
374
web-ui/src/components/cases/hearing-changes-panel.tsx
Normal file
374
web-ui/src/components/cases/hearing-changes-panel.tsx
Normal file
@@ -0,0 +1,374 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import {
|
||||
ArrowDown,
|
||||
ArrowUp,
|
||||
CalendarDays,
|
||||
HelpCircle,
|
||||
Link2,
|
||||
Loader2,
|
||||
MessageSquareText,
|
||||
Plus,
|
||||
RotateCw,
|
||||
Sparkles,
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
CHANGE_LABELS_HE,
|
||||
PROTOCOL_PARTY_LABELS_HE,
|
||||
PROTOCOL_PARTY_ORDER,
|
||||
useAnalyzeProtocol,
|
||||
useProtocolAnalysis,
|
||||
type ProtocolAnalysisRow,
|
||||
type ProtocolChangeType,
|
||||
type ProtocolPartyRole,
|
||||
} from "@/lib/api/protocol-analysis";
|
||||
|
||||
const PARTY_MARK_TONE: Record<ProtocolPartyRole, string> = {
|
||||
appellant: "bg-info",
|
||||
respondent: "bg-gold-deep",
|
||||
committee: "bg-success",
|
||||
permit_applicant: "bg-warn",
|
||||
unknown: "bg-ink-muted",
|
||||
"": "bg-ink-muted",
|
||||
};
|
||||
|
||||
const CHANGE_BADGE_TONE: Record<ProtocolChangeType, string> = {
|
||||
strengthened: "bg-gold-wash text-gold-deep border-gold/40",
|
||||
newly_raised: "bg-emerald-50 text-emerald-900 border-emerald-200",
|
||||
dropped: "bg-rule-soft text-ink-soft border-rule",
|
||||
};
|
||||
|
||||
const CHANGE_ACCENT: Record<ProtocolChangeType, string> = {
|
||||
strengthened: "border-s-gold",
|
||||
newly_raised: "border-s-emerald-500",
|
||||
dropped: "border-s-rule",
|
||||
};
|
||||
|
||||
function ChangeIcon({ type }: { type: ProtocolChangeType }) {
|
||||
if (type === "strengthened") return <ArrowUp className="size-3.5" aria-hidden />;
|
||||
if (type === "newly_raised") return <Plus className="size-3.5" aria-hidden />;
|
||||
return <ArrowDown className="size-3.5" aria-hidden />;
|
||||
}
|
||||
|
||||
type FilterKey = "all" | ProtocolChangeType;
|
||||
|
||||
function AttendeeColumn({
|
||||
label,
|
||||
dotTone,
|
||||
names,
|
||||
}: {
|
||||
label: string;
|
||||
dotTone: string;
|
||||
names: string[];
|
||||
}) {
|
||||
if (!names.length) return null;
|
||||
return (
|
||||
<div className="min-w-[210px] flex-1">
|
||||
<div className="text-gold-deep mb-1.5 flex items-center gap-1.5 text-[0.7rem] font-bold">
|
||||
<span className={`size-1.5 flex-none rounded-full ${dotTone}`} aria-hidden />
|
||||
{label}
|
||||
</div>
|
||||
<div className="text-ink-soft text-xs leading-relaxed">
|
||||
{names.join(" · ")}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DevelopmentCard({ row }: { row: ProtocolAnalysisRow }) {
|
||||
return (
|
||||
<div
|
||||
className={`bg-surface border-rule overflow-hidden rounded-lg border border-s-4 shadow-sm ${CHANGE_ACCENT[row.change_type]}`}
|
||||
>
|
||||
<div className="space-y-2.5 px-4 py-3.5">
|
||||
<div className="flex flex-wrap items-start gap-2.5">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`${CHANGE_BADGE_TONE[row.change_type]} flex-none gap-1 text-[0.72rem] font-bold`}
|
||||
>
|
||||
<ChangeIcon type={row.change_type} />
|
||||
{CHANGE_LABELS_HE[row.change_type]}
|
||||
</Badge>
|
||||
<span className="text-navy flex-1 text-sm font-bold leading-snug">
|
||||
{row.argument_title}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{row.summary && (
|
||||
<p className="text-ink-soft text-[0.82rem] leading-relaxed">
|
||||
{row.summary}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{row.sharpened_question && (
|
||||
<div className="bg-parchment border-rule-soft rounded-md border px-3 py-2">
|
||||
<div className="text-gold-deep mb-1 flex items-center gap-1.5 text-[0.66rem] font-bold">
|
||||
<HelpCircle className="size-3" aria-hidden />
|
||||
השאלה שהתחדדה
|
||||
</div>
|
||||
<div className="text-ink text-xs leading-relaxed">
|
||||
{row.sharpened_question}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{row.evidence_quote && (
|
||||
<blockquote className="border-rule text-ink-muted border-s-[3px] px-3 text-xs italic leading-relaxed">
|
||||
“{row.evidence_quote}”
|
||||
<span className="text-ink-muted mt-1 block text-[0.68rem] not-italic font-semibold">
|
||||
— מתוך פרוטוקול הדיון
|
||||
</span>
|
||||
</blockquote>
|
||||
)}
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2 pt-0.5">
|
||||
{row.change_type === "newly_raised" ? (
|
||||
<span className="text-emerald-800 inline-flex items-center gap-1.5 text-[0.72rem] font-semibold">
|
||||
<Sparkles className="size-3.5" aria-hidden />
|
||||
אין מקבילה בכתב — עלה לראשונה בדיון
|
||||
</span>
|
||||
) : row.argument_id ? (
|
||||
<span className="text-ink-muted inline-flex items-center gap-1.5 text-[0.72rem] font-semibold">
|
||||
<Link2 className="size-3.5" aria-hidden />
|
||||
מבוסס על טיעון כתוב קיים
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type HearingChangesPanelProps = {
|
||||
caseNumber: string;
|
||||
};
|
||||
|
||||
export function HearingChangesPanel({ caseNumber }: HearingChangesPanelProps) {
|
||||
const { data, isPending, isError, error } = useProtocolAnalysis(caseNumber);
|
||||
const analyze = useAnalyzeProtocol(caseNumber);
|
||||
const [filter, setFilter] = useState<FilterKey>("all");
|
||||
|
||||
const handleAnalyze = () => {
|
||||
analyze.mutate(undefined, {
|
||||
onSuccess: (res) => {
|
||||
if (res.status === "queued") {
|
||||
toast.success("נשלח למנתח המשפטי — הניתוח ירוץ ברקע; רענן בעוד כמה דקות.");
|
||||
} else {
|
||||
toast.warning(
|
||||
`לא ניתן להריץ אוטומטית (${res.reason}). ניתן להריץ ידנית מ-Claude Code.`,
|
||||
);
|
||||
}
|
||||
},
|
||||
onError: (e) => toast.error(`שגיאה: ${(e as Error).message}`),
|
||||
});
|
||||
};
|
||||
|
||||
const header = data?.header;
|
||||
const counts = data?.by_change ?? {};
|
||||
const strengthened = counts.strengthened ?? 0;
|
||||
const newlyRaised = counts.newly_raised ?? 0;
|
||||
const dropped = counts.dropped ?? 0;
|
||||
|
||||
const sections = useMemo(() => {
|
||||
if (!data) return [];
|
||||
return PROTOCOL_PARTY_ORDER.map((role) => {
|
||||
const rows = (data.by_party[role] ?? []).filter(
|
||||
(r) => filter === "all" || r.change_type === filter,
|
||||
);
|
||||
return { role, rows };
|
||||
}).filter((s) => s.rows.length > 0);
|
||||
}, [data, filter]);
|
||||
|
||||
const runButton = (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={analyze.isPending}
|
||||
onClick={handleAnalyze}
|
||||
>
|
||||
{analyze.isPending ? (
|
||||
<Loader2 className="me-1.5 size-3.5 animate-spin" />
|
||||
) : (
|
||||
<RotateCw className="me-1.5 size-3.5" />
|
||||
)}
|
||||
{data?.total ? "נתח מחדש" : "נתח את פרוטוקול הדיון"}
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||
<div>
|
||||
<h2 className="text-navy flex items-center gap-2 text-base font-semibold">
|
||||
<MessageSquareText className="text-gold-deep size-5" aria-hidden />
|
||||
מה קרה בדיון
|
||||
</h2>
|
||||
<p className="text-ink-muted mt-0.5 max-w-2xl text-xs leading-relaxed">
|
||||
השוואה בין הטענות בכתב לבין מה שנטען בדיון בעל-פה — אילו טענות התחזקו
|
||||
ואילו נטענו לראשונה, כדי שהדיון (בלוק י) יתייחס לגלגול העדכני.
|
||||
</p>
|
||||
</div>
|
||||
{runButton}
|
||||
</div>
|
||||
|
||||
{isPending ? (
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-20 w-full" />
|
||||
<Skeleton className="h-24 w-full" />
|
||||
</div>
|
||||
) : isError ? (
|
||||
<p className="text-danger text-sm">
|
||||
שגיאה בטעינת ניתוח-הפרוטוקול: {(error as Error).message}
|
||||
</p>
|
||||
) : !data?.total ? (
|
||||
<div className="bg-surface border-rule rounded-lg border border-dashed px-6 py-8 text-center">
|
||||
<h3 className="text-navy text-sm font-bold">טרם נותח פרוטוקול</h3>
|
||||
<p className="text-ink-muted mx-auto mt-1.5 max-w-md text-xs leading-relaxed">
|
||||
כשיש בתיק פרוטוקול של ועדת-הערר וטיעונים מאוגדים, הניתוח מזהה מה
|
||||
התחזק ומה נטען לראשונה בדיון. ההרצה נשלחת למנתח המשפטי ורצה ברקע.
|
||||
</p>
|
||||
<div className="mt-4 flex justify-center">{runButton}</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* hearing header strip */}
|
||||
{header && (
|
||||
<div className="bg-parchment border-rule rounded-lg border px-5 py-4 shadow-sm">
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<span className="bg-gold-wash border-rule flex size-9 flex-none items-center justify-center rounded-lg border">
|
||||
<CalendarDays className="text-gold-deep size-5" aria-hidden />
|
||||
</span>
|
||||
<div className="flex-1">
|
||||
<div className="text-navy text-[0.95rem] font-bold">
|
||||
{header.protocol_title || "פרוטוקול דיון"}
|
||||
</div>
|
||||
<div className="text-ink-muted mt-0.5 text-xs">
|
||||
{header.hearing_date
|
||||
? `תאריך דיון: ${header.hearing_date}`
|
||||
: "תאריך דיון לא זוהה"}
|
||||
{header.panel_members.length > 0 &&
|
||||
` · מותב: ${header.panel_members.join(", ")}`}
|
||||
</div>
|
||||
</div>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="bg-info-bg text-info border-info/30 flex-none rounded-full text-[0.72rem] font-semibold"
|
||||
>
|
||||
פרוטוקול ועדת-הערר
|
||||
</Badge>
|
||||
</div>
|
||||
{(header.appellants_present.length > 0 ||
|
||||
header.respondents_present.length > 0) && (
|
||||
<div className="border-rule-soft mt-3 flex flex-wrap gap-6 border-t pt-3">
|
||||
<AttendeeColumn
|
||||
label="נכחו מטעם העוררים"
|
||||
dotTone="bg-info"
|
||||
names={header.appellants_present}
|
||||
/>
|
||||
<AttendeeColumn
|
||||
label="נכחו מטעם המשיבים"
|
||||
dotTone="bg-gold-deep"
|
||||
names={header.respondents_present}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* filter pills */}
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<FilterPill
|
||||
active={filter === "all"}
|
||||
onClick={() => setFilter("all")}
|
||||
label="הכל"
|
||||
count={data.total}
|
||||
/>
|
||||
{strengthened > 0 && (
|
||||
<FilterPill
|
||||
active={filter === "strengthened"}
|
||||
onClick={() => setFilter("strengthened")}
|
||||
label="התחזקו"
|
||||
count={strengthened}
|
||||
icon={<ArrowUp className="size-3.5" aria-hidden />}
|
||||
/>
|
||||
)}
|
||||
{newlyRaised > 0 && (
|
||||
<FilterPill
|
||||
active={filter === "newly_raised"}
|
||||
onClick={() => setFilter("newly_raised")}
|
||||
label="נטענו לראשונה"
|
||||
count={newlyRaised}
|
||||
icon={<Plus className="size-3.5" aria-hidden />}
|
||||
/>
|
||||
)}
|
||||
{dropped > 0 && (
|
||||
<FilterPill
|
||||
active={filter === "dropped"}
|
||||
onClick={() => setFilter("dropped")}
|
||||
label="נזנחו"
|
||||
count={dropped}
|
||||
icon={<ArrowDown className="size-3.5" aria-hidden />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* per-party sections */}
|
||||
{sections.map(({ role, rows }) => (
|
||||
<div key={role || "unassigned"} className="space-y-2.5">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span
|
||||
className={`h-5 w-1.5 flex-none rounded ${PARTY_MARK_TONE[role]}`}
|
||||
aria-hidden
|
||||
/>
|
||||
<span className="text-navy text-[0.95rem] font-bold">
|
||||
{PROTOCOL_PARTY_LABELS_HE[role]}
|
||||
</span>
|
||||
<span className="text-ink-muted text-xs">{rows.length}</span>
|
||||
</div>
|
||||
<div className="space-y-2.5">
|
||||
{rows.map((r) => (
|
||||
<DevelopmentCard key={r.id} row={r} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FilterPill({
|
||||
active,
|
||||
onClick,
|
||||
label,
|
||||
count,
|
||||
icon,
|
||||
}: {
|
||||
active: boolean;
|
||||
onClick: () => void;
|
||||
label: string;
|
||||
count: number;
|
||||
icon?: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={`inline-flex items-center gap-1.5 rounded-full border px-3.5 py-1.5 text-xs font-semibold transition-colors ${
|
||||
active
|
||||
? "bg-navy border-navy text-white"
|
||||
: "bg-surface border-rule text-ink-soft hover:bg-parchment"
|
||||
}`}
|
||||
>
|
||||
{icon}
|
||||
{label}
|
||||
<span className="font-bold">{count}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
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