lct-18: профиль курсанта — радар, история попыток, дельта

Работа над ошибками становится измеримым циклом: посмотрел разбор →
попробовал снова → увидел дельту по оценке, времени опроса и добытым
фактам.

В отчёт занятия добавлена сводка числами — время опроса, факты,
подсказки, коды ошибок. Без неё дельту пришлось бы вытаскивать разбором
текста метрик («94 с»), а это путь к тихим ошибкам.

Радар — среднее по попыткам: одна неудачная попытка не определяет
курсанта.

Проверено сквозным тестом на живой базе: два занятия одного курсанта
дают две попытки и посчитанную дельту. Проверка доступности базы в тесте
была ложной — синхронного драйвера в проекте нет, и тест молча
пропускался.
This commit is contained in:
Ivan Gerasimov 2026-09-17 21:42:12 +03:00
commit 38082e2af3
8 changed files with 412 additions and 0 deletions

View file

@ -70,6 +70,51 @@ export const useSessions = (params: { trainee?: string; group?: string; mode?: S
});
};
export interface TraineeInfo {
id: string;
name: string;
group: string | null;
}
export interface Attempt {
session_id: string;
scenario_id: string;
mode: SessionMode;
attempt: number;
created_at: string;
score: number | null;
interview_ms: number | null;
facts_got: number | null;
facts_required: number | null;
hints: number | null;
codes: Record<string, number>;
}
export interface Profile {
trainee: TraineeInfo;
attempts: Attempt[];
competencies: Record<string, number>;
deltas: {
scenario_id: string;
from_attempt: number;
to_attempt: number;
score: number | null;
interview_ms: number | null;
facts_got: number | null;
}[];
hints_total: number;
}
export const useTrainees = () =>
useQuery({ queryKey: ["trainees"], queryFn: () => request<TraineeInfo[]>("/api/trainees") });
export const useProfile = (traineeId: string | null) =>
useQuery({
queryKey: ["profile", traineeId],
queryFn: () => request<Profile>(`/api/trainees/${traineeId}/profile`),
enabled: Boolean(traineeId),
});
export const useCreateSession = () =>
useMutation({
mutationFn: (body: { scenario_id: string; mode: SessionMode; trainee?: string; group?: string }) =>