ui(precedents): upload sheet routes ערר/בל"מ to internal-decisions endpoint
Some checks failed
Build & Deploy / build-and-deploy (push) Has been cancelled

Citations starting with ערר/בל"מ/ARAR are committee decisions and must
carry chair_name + district. The /precedents upload form previously
errored out for these (precedent_library service rejects them) with no
in-UI path forward — internal_decision_upload was only reachable via
the /missing-precedents flow.

The form now auto-detects committee citations, reveals chair_name +
district fields, hides the irrelevant source_type/precedent_level
(derived server-side), and posts to /api/internal-decisions/upload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 10:22:03 +00:00
parent a3454bcb57
commit 5ad541e54c
2 changed files with 194 additions and 37 deletions

View File

@@ -355,6 +355,85 @@ export function useUploadPrecedent() {
});
}
// Valid Hebrew districts for appeals-committee decisions. Mirrors
// VALID_DISTRICTS in mcp-server/src/legal_mcp/tools/internal_decisions.py —
// keep in sync with the service-side guard.
export const COMMITTEE_DISTRICTS = [
"ירושלים",
"תל אביב",
"מרכז",
"חיפה",
"צפון",
"דרום",
"ארצי",
] as const;
export type CommitteeDistrict = (typeof COMMITTEE_DISTRICTS)[number];
// A citation that targets internal_decision_upload, not the external library.
// Matches the prefix list in precedent_library service (ערר/בל"מ/ARAR).
const COMMITTEE_PREFIXES = ["ערר ", "ערר(", "בל\"מ ", "בל\"מ(", "ARAR "];
export function isCommitteeCitation(citation: string): boolean {
const c = citation.trimStart();
return COMMITTEE_PREFIXES.some((p) => c.startsWith(p));
}
export type InternalDecisionUploadInput = {
file: File;
case_number: string;
chair_name: string;
district: CommitteeDistrict | string;
case_name?: string;
court?: string;
decision_date?: string;
practice_area?: PracticeArea;
appeal_subtype?: string;
subject_tags?: string[];
is_binding?: boolean;
summary?: string;
};
export function useUploadInternalDecision() {
const qc = useQueryClient();
return useMutation({
mutationFn: async (input: InternalDecisionUploadInput) => {
const fd = new FormData();
fd.append("file", input.file);
fd.append("case_number", input.case_number);
fd.append("chair_name", input.chair_name);
fd.append("district", input.district);
if (input.case_name) fd.append("case_name", input.case_name);
if (input.court) fd.append("court", input.court);
if (input.decision_date) fd.append("decision_date", input.decision_date);
if (input.practice_area) fd.append("practice_area", input.practice_area);
if (input.appeal_subtype)
fd.append("appeal_subtype", input.appeal_subtype);
if (input.subject_tags && input.subject_tags.length)
fd.append("subject_tags", JSON.stringify(input.subject_tags));
fd.append("is_binding", String(input.is_binding ?? false));
if (input.summary) fd.append("summary", input.summary);
const res = await fetch("/api/internal-decisions/upload", {
method: "POST",
body: fd,
});
const parsed = await res.json().catch(() => null);
if (!res.ok) {
throw new ApiError(
`Upload failed with ${res.status}`,
res.status,
parsed,
);
}
return parsed as { task_id: string };
},
onSuccess: () => {
qc.invalidateQueries({ queryKey: libraryKeys.all });
},
});
}
export function useDeletePrecedent() {
const qc = useQueryClient();
return useMutation({