Files
legal-ai/web-ui/src/lib/api/feedback.ts
Chaim 36bae6c592
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 13s
fix(ia): IA גל-1 — סנכרון-cache + נתונים-שגויים + מחיקת-מתים (#130, X17)
גל-1 מבקלוג #127 (docs/ia-audit-redesign.md §4) — תיקון מקומי, ללא הגירת-IA.
מקיים G2 בשכבת-ה-UI דרך INV-IA1/IA2/IA5/IA6 (docs/spec/X17).

א) פערי-סנכרון (INV-IA2 — mutation מבטל כל קורא):
- CAS-1/2: העלאת-DOCX/export מבטלים ['decision-blocks'] (מחוון source_of_truth)
- APR-1/4: פתרון/יצירת-הערה מבטלים ['chair','pending'] (תיבה+תג-סרגל)
- APR-5/ADM-2: אישור/batch הלכות מבטלים ['chair','pending']+['operations']
- APR-6/ADM-3: create/update/delete/upload פסיקה-חסרה מבטלים שניהם
- LRN-6: ComparePanel גוזר בחירה מהקורפוס המרוענן (אין POST ל-id מחוק → 404)
- LRN-8: מחיקת-קורפוס מבטלת רשימת-צ'אטים (chat שהתייתם לא נשאר עם קישור-קורפוס תקוע)
- LRN-10/MET-1/MET-8: promote מבטל גם lessons וגם methodology (LessonsTab+/methodology)

ב) נתונים-שגויים (INV-IA5 — סטטוס מגובה-צרכן):
- LRN-4: KPI "דפוסי סגנון" — הוסר היחס-השקרי "מתוך total_patterns" (שאילתות עצמאיות)
- LRN-5: findings_applied (דגל אינפורמטיבי-בלבד) → findings_approved (שער INV-LRN1 האמיתי)
- ADM-1: halacha_backlog שהוחזר ונזרק → מרונדר ב-/diagnostics, מצביע ל-/approvals (INV-IA1)
- ADM-6: מוני-סוכנים מסמנים "חלקי+" כשחברת-Paperclip לא נטענה
- APR-3: מכוסה ע"י APR-1 (count+sample מאותה שאילתה; הבעיה היתה staleness-cache)
- MET-6: עורך-צ'קליסטים מציג איזה case בוחר כל צ'קליסט (explainer-תחולה)
- ADM-5: ערך-Container מסומן "ממתין ל-redeploy" כש-Coolify≠Container

ג) מתים/jargon:
- PRE-2: הוסר GET /api/precedent-library/queue/pending (אפס צרכני-frontend)
- PRE-3/5: AuthorityBadge (binding/persuasive) מרונדר גם בחיפוש, לא רק בתור-הביקורת
- MET-5: הוסר ז'רגון T7/T15 מטקסט-העזר ב-/methodology (INV-IA6)

Invariants: מקיים INV-IA1/IA2/IA5/IA6 (X17), G2 (מקור-אמת יחיד בשכבת-UI), G10
(לא הוסר שום שער-אנושי — רק סנכרון/נתון/קוד-מת). שומר INV-LRN1.

בדיקות: py_compile web/app.py ✓ · tsc --noEmit ✓ · eslint ✓ (לבד מ-learning-panel:109
unescaped-quote — קיים-מראש ב-main, מחוץ לסט-הממצאים). next build נכשל רק בגלל
symlink node_modules ב-worktree (Turbopack) — ה-build ב-Docker/CI תקין.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 20:47:32 +00:00

151 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 }),
});
}
/** Feedback filtered by case number */
export function useCaseFeedback(caseNumber: string | undefined) {
const params = caseNumber ? `?case_number=${caseNumber}` : "";
return useQuery({
queryKey: [...feedbackKeys.all, "case", caseNumber ?? ""] as const,
queryFn: ({ signal }) =>
apiRequest<ChairFeedback[]>(`/api/feedback${params}`, { signal }),
enabled: Boolean(caseNumber),
staleTime: 5_000,
refetchInterval: 5_000,
});
}
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 });
// APR-4 (INV-IA2): a new unresolved comment raises the chair-pending
// aggregate — keep /approvals + the nav badge in sync.
qc.invalidateQueries({ queryKey: ["chair", "pending"] });
},
});
}
export function useResolveFeedback() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
feedbackId,
applied_to,
fold,
}: {
feedbackId: string;
applied_to: string[];
/** When true (default server-side), wakes the CEO to fold the lesson
* into the right knowledge file. Pass false for pure bookkeeping. */
fold?: boolean;
}) =>
apiRequest<{ status: string; fold_queued: boolean }>(
`/api/feedback/${feedbackId}/resolve`,
{ method: "PATCH", body: { applied_to, ...(fold === undefined ? {} : { fold }) } },
),
onSuccess: () => {
qc.invalidateQueries({ queryKey: feedbackKeys.all });
// APR-1/APR-4 (INV-IA2): resolving a comment lowers the chair-pending
// aggregate; refresh /approvals' counter + the nav badge (one source).
qc.invalidateQueries({ queryKey: ["chair", "pending"] });
},
});
}
/** Hebrew labels for feedback categories */
export const CATEGORY_LABELS: Record<FeedbackCategory, string> = {
missing_content: "תוכן חסר",
wrong_tone: "טון שגוי",
wrong_structure: "מבנה שגוי",
factual_error: "שגיאה עובדתית",
style: "סגנון",
other: "אחר",
};
/** Tailwind color classes per category */
export const CATEGORY_COLORS: Record<FeedbackCategory, string> = {
missing_content: "bg-amber-100 text-amber-800 border-amber-200",
wrong_tone: "bg-purple-100 text-purple-800 border-purple-200",
wrong_structure: "bg-blue-100 text-blue-800 border-blue-200",
factual_error: "bg-red-100 text-red-800 border-red-200",
style: "bg-emerald-100 text-emerald-800 border-emerald-200",
other: "bg-gray-100 text-gray-800 border-gray-200",
};
/** Block ID labels */
export const BLOCK_LABELS: Record<string, string> = {
"block-alef": "א — כותרת מוסדית",
"block-bet": "ב — הרכב הוועדה",
"block-gimel": "ג — צדדים",
"block-dalet": "ד — החלטה",
"block-he": "ה — פתיחה",
"block-vav": "ו — רקע עובדתי",
"block-zayin": "ז — טענות הצדדים",
"block-chet": "ח — הליכים",
"block-tet": "ט — תכניות חלות",
"block-yod": "י — דיון והכרעה",
"block-yod-alef": "יא — סיכום",
"block-yod-bet": "יב — חתימות",
};