Add content checklists for block-yod and chair feedback system

Addresses Dafna's observation that licensing decisions lack comprehensive
planning discussion. Systematic corpus analysis of all 24 training decisions
revealed the system learned writing style but not substantive content.

Changes:
- Corpus analysis of all 24 decisions (docs/corpus-analysis.md)
- 5 content checklists by appeal subtype injected into block-yod prompt
- chair_feedback DB table + API endpoints + MCP tools
- Feedback management page in Next.js UI (/feedback)
- Navigation updated with "הערות יו״ר" link

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-12 20:58:28 +00:00
parent ca6ec48580
commit 0fef20e272
10 changed files with 1214 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
/**
* Chair feedback hooks — recording and managing Dafna's feedback on drafts.
*/
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "./client";
export type FeedbackCategory =
| "missing_content"
| "wrong_tone"
| "wrong_structure"
| "factual_error"
| "style"
| "other";
export type ChairFeedback = {
id: string;
case_id: string | null;
case_number: string;
block_id: string;
category: FeedbackCategory;
feedback_text: string;
lesson_extracted: string;
resolved: boolean;
applied_to: string[];
created_at: string | null;
};
export type CreateFeedbackInput = {
case_number?: string;
block_id?: string;
feedback_text: string;
category?: FeedbackCategory;
lesson_extracted?: string;
};
const feedbackKeys = {
all: ["feedback"] as const,
list: (filters: { category?: string; unresolved_only?: boolean }) =>
[...feedbackKeys.all, "list", filters] as const,
};
export function useFeedbackList(filters: {
category?: string;
unresolved_only?: boolean;
} = {}) {
const params = new URLSearchParams();
if (filters.category) params.set("category", filters.category);
if (filters.unresolved_only) params.set("unresolved_only", "true");
const qs = params.toString();
return useQuery({
queryKey: feedbackKeys.list(filters),
queryFn: ({ signal }) =>
apiRequest<ChairFeedback[]>(`/api/feedback${qs ? `?${qs}` : ""}`, { signal }),
});
}
export function useCreateFeedback() {
const qc = useQueryClient();
return useMutation({
mutationFn: (data: CreateFeedbackInput) =>
apiRequest<{ id: string; status: string }>("/api/feedback/json", {
method: "POST",
body: data,
}),
onSuccess: () => {
qc.invalidateQueries({ queryKey: feedbackKeys.all });
},
});
}
export function useResolveFeedback() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
feedbackId,
applied_to,
}: {
feedbackId: string;
applied_to: string[];
}) =>
apiRequest<{ status: string }>(
`/api/feedback/${feedbackId}/resolve`,
{ method: "PATCH", body: { applied_to } },
),
onSuccess: () => {
qc.invalidateQueries({ queryKey: feedbackKeys.all });
},
});
}
/** Hebrew labels for feedback categories */
export const CATEGORY_LABELS: Record<FeedbackCategory, string> = {
missing_content: "תוכן חסר",
wrong_tone: "טון שגוי",
wrong_structure: "מבנה שגוי",
factual_error: "שגיאה עובדתית",
style: "סגנון",
other: "אחר",
};
/** Block ID labels */
export const BLOCK_LABELS: Record<string, string> = {
"block-he": "ה — פתיחה",
"block-vav": "ו — רקע עובדתי",
"block-zayin": "ז — טענות הצדדים",
"block-chet": "ח — הליכים",
"block-tet": "ט — תכניות חלות",
"block-yod": "י — דיון והכרעה",
"block-yod-alef": "יא — סיכום",
};