All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 13s
גל-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>
126 lines
4.0 KiB
TypeScript
126 lines
4.0 KiB
TypeScript
/**
|
|
* Style-acquisition learning surface (T6/T13) — reconciliation ledger + style-distance.
|
|
* Backs the /training "למידה" tab. Endpoints under /api/learning.
|
|
*/
|
|
|
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import { apiRequest } from "./client";
|
|
import { trainingKeys } from "./training";
|
|
import { methodologyKeys } from "./methodology";
|
|
|
|
export type DraftFinalPair = {
|
|
id: string;
|
|
case_id: string | null;
|
|
case_number: string;
|
|
title: string;
|
|
status: "final_received" | "analyzed" | "lessons_folded" | string;
|
|
change_percent: number | null;
|
|
created_at: string | null;
|
|
updated_at: string | null;
|
|
};
|
|
|
|
export type StyleDistance = {
|
|
case_number: string;
|
|
outcome: string;
|
|
golden_ratio_adherence: {
|
|
outcome: string;
|
|
total_words: number;
|
|
sections: Record<string, { actual_pct: number; target: [number, number]; deviation_pp: number }>;
|
|
max_deviation: number | null;
|
|
};
|
|
anti_pattern_hits: { total: number; by_pattern: Record<string, { count: number; note: string }> };
|
|
draft_to_final_diff: { change_percent?: number } | null;
|
|
pair_status: string | null;
|
|
summary: {
|
|
ratio_max_deviation_pp: number | null;
|
|
anti_pattern_total: number;
|
|
change_percent: number | null;
|
|
};
|
|
error?: string;
|
|
};
|
|
|
|
export const learningKeys = {
|
|
all: ["learning"] as const,
|
|
pairs: (status: string) => [...learningKeys.all, "pairs", status] as const,
|
|
distance: (caseNumber: string) => [...learningKeys.all, "distance", caseNumber] as const,
|
|
};
|
|
|
|
export function useReconciliationLedger(status = "") {
|
|
return useQuery({
|
|
queryKey: learningKeys.pairs(status),
|
|
queryFn: ({ signal }) =>
|
|
apiRequest<{ items: DraftFinalPair[]; count: number }>(
|
|
`/api/learning/pairs${status ? `?status=${status}` : ""}`,
|
|
{ signal },
|
|
),
|
|
staleTime: 15_000,
|
|
});
|
|
}
|
|
|
|
export function useStyleDistance(caseNumber: string | null) {
|
|
return useQuery({
|
|
queryKey: learningKeys.distance(caseNumber ?? ""),
|
|
queryFn: ({ signal }) =>
|
|
apiRequest<StyleDistance>(
|
|
`/api/learning/style-distance/${encodeURIComponent(caseNumber!)}`,
|
|
{ signal },
|
|
),
|
|
enabled: Boolean(caseNumber),
|
|
staleTime: 15_000,
|
|
});
|
|
}
|
|
|
|
// ── T14: curator distillation proposal + chair approval gate ──────
|
|
|
|
export type DistillationChange = {
|
|
type?: string;
|
|
domain?: string;
|
|
block?: string;
|
|
description?: string;
|
|
draft_text?: string;
|
|
final_text?: string;
|
|
lesson?: string;
|
|
};
|
|
|
|
export type PairDetail = {
|
|
id: string;
|
|
case_number: string;
|
|
title: string;
|
|
status: string;
|
|
diff_stats: { change_percent?: number } | null;
|
|
overall_assessment: string;
|
|
changes: DistillationChange[]; // style_method only (server-filtered)
|
|
new_expressions: string[];
|
|
};
|
|
|
|
export function usePairDetail(pairId: string | null) {
|
|
return useQuery({
|
|
queryKey: [...learningKeys.all, "pair", pairId ?? ""] as const,
|
|
queryFn: ({ signal }) =>
|
|
apiRequest<PairDetail>(`/api/learning/pairs/${pairId}`, { signal }),
|
|
enabled: Boolean(pairId),
|
|
staleTime: 15_000,
|
|
});
|
|
}
|
|
|
|
export function usePromoteLearning(pairId: string) {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (body: { lessons: string[]; phrases: string[] }) =>
|
|
apiRequest<{ id: string; status: string; folded_lessons: number; folded_phrases: number }>(
|
|
`/api/learning/pairs/${pairId}/promote`,
|
|
{ method: "POST", body },
|
|
),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: learningKeys.all });
|
|
// LRN-10 (INV-IA2): promote flips folded lessons' review_status, so the
|
|
// per-corpus LessonsTab must refetch (lessons key is corpus-scoped —
|
|
// invalidate the whole "lessons" prefix since promote is pair-scoped).
|
|
qc.invalidateQueries({ queryKey: [...trainingKeys.all, "lessons"] });
|
|
// MET-1 (INV-IA2): promote also writes discussion_rules + transition_
|
|
// phrases['universal'] — /methodology (staleTime 30s) would stay stale.
|
|
qc.invalidateQueries({ queryKey: methodologyKeys.all });
|
|
},
|
|
});
|
|
}
|