feat(style-acq T6+T13): פנקס-התאמה + מדד מרחק-סגנון ב-UI

ה"איך מנהלים/רואים את הלמידה": טאב "למידה" ב-/training.

- app.py: GET /api/learning/pairs (פנקס-ההתאמה — כל ההחלטות + סטטוס draft↔final,
  INV-LRN4) + GET /api/learning/style-distance/{case} (מדד T7).
- web-ui: learning.ts hooks + LearningPanel (טבלת פנקס; לחיצה על תיק →
  מדד מרחק-הסגנון: שינוי draft→final, סטיית יחסי-זהב, אנטי-דפוסים) + טאב ב-/training.

מכסה גם את T6 (רשימת כל ההחלטות הנסגרות מול הסופי). ללא endpoint-schema חדש
לטיפוסים מחוללים (טיפוסים ידניים). G9, INV-LRN4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 19:13:10 +00:00
parent e4fbda6c1f
commit ee76455a9a
4 changed files with 221 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
/**
* Style-acquisition learning surface (T6/T13) — reconciliation ledger + style-distance.
* Backs the /training "למידה" tab. Endpoints under /api/learning.
*/
import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "./client";
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,
});
}