feat: link related precedents across court instances (SCHEMA_V11)

Add ability to mark case_law records as related (e.g. same appeal
through ועדת ערר → מנהלי → עליון):
- DB: case_law_relations join table (bidirectional, V11 migration)
- DB CRUD: add/remove/get_case_law_relations
- Service: get_precedent() now returns related_cases[]
- MCP: precedent_link_cases + precedent_unlink_cases tools
- REST: POST/DELETE /api/precedent-library/{id}/relations
- UI: RelatedCasesSection on detail page with search dialog and unlink

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 07:52:29 +00:00
parent 13a8d9e58f
commit 3e14cd6798
8 changed files with 443 additions and 2 deletions

View File

@@ -84,9 +84,20 @@ export type Halacha = {
precedent_level?: string;
};
export type RelatedCase = {
id: string;
case_number: string;
case_name: string;
court: string;
precedent_level: string;
date: string | null;
relation_type: string;
};
export type PrecedentDetail = Precedent & {
full_text: string;
halachot: Halacha[];
related_cases: RelatedCase[];
};
export type SearchHit =
@@ -357,6 +368,40 @@ export function useDeletePrecedent() {
});
}
export function useLinkRelatedCase(caseId: string) {
const qc = useQueryClient();
return useMutation({
mutationFn: (vars: { relatedId: string; relationType?: string }) =>
apiRequest<{ linked: boolean }>(
`/api/precedent-library/${encodeURIComponent(caseId)}/relations`,
{
method: "POST",
body: {
related_id: vars.relatedId,
relation_type: vars.relationType ?? "same_case_chain",
},
},
),
onSuccess: () => {
qc.invalidateQueries({ queryKey: libraryKeys.detail(caseId) });
},
});
}
export function useUnlinkRelatedCase(caseId: string) {
const qc = useQueryClient();
return useMutation({
mutationFn: (relatedId: string) =>
apiRequest<{ unlinked: boolean }>(
`/api/precedent-library/${encodeURIComponent(caseId)}/relations/${encodeURIComponent(relatedId)}`,
{ method: "DELETE" },
),
onSuccess: () => {
qc.invalidateQueries({ queryKey: libraryKeys.detail(caseId) });
},
});
}
export type PrecedentPatch = Partial<{
case_name: string;
court: string;