Implement DDS exercise, customer UI and local demo
This commit is contained in:
parent
cec84ffcd0
commit
0526b11f91
46 changed files with 2504 additions and 253 deletions
|
|
@ -1,7 +1,7 @@
|
|||
// HTTP-клиент и хуки. Типы ответов HTTP в generated.ts пока не попадают —
|
||||
// там только события WS; здесь описаны руками по docs/arch/CONTRACT.md#http-api.
|
||||
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import type { Level, SessionMode } from "@/shared/types/generated";
|
||||
|
||||
|
|
@ -32,6 +32,7 @@ export interface Health {
|
|||
embeddings_ready: boolean;
|
||||
scenarios_loaded: number;
|
||||
offline: boolean;
|
||||
demo_no_db?: boolean;
|
||||
}
|
||||
|
||||
export interface ScenarioSummary {
|
||||
|
|
@ -125,6 +126,60 @@ export function useSigns(prefix: string[]) {
|
|||
export const useTrainees = () =>
|
||||
useQuery({ queryKey: ["trainees"], queryFn: () => request<TraineeInfo[]>("/api/trainees") });
|
||||
|
||||
export interface GroupInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface GroupAnalytics {
|
||||
group: GroupInfo;
|
||||
enrolled_trainees: number;
|
||||
active_trainees: number;
|
||||
scored_attempts: number;
|
||||
average_score: number | null;
|
||||
errors: {
|
||||
code: string;
|
||||
title: string;
|
||||
affected_trainees: number;
|
||||
occurrences: number;
|
||||
rate_percent: number;
|
||||
recommendation: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export const useGroups = () =>
|
||||
useQuery({ queryKey: ["groups"], queryFn: () => request<GroupInfo[]>("/api/groups") });
|
||||
|
||||
export function useCreateGroup() {
|
||||
const cache = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (name: string) => request<GroupInfo>("/api/groups", {
|
||||
method: "POST", body: JSON.stringify({ name }),
|
||||
}),
|
||||
onSuccess: () => void cache.invalidateQueries({ queryKey: ["groups"] }),
|
||||
});
|
||||
}
|
||||
|
||||
export function useAssignTrainee() {
|
||||
const cache = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ groupId, traineeId }: { groupId: string; traineeId: string }) =>
|
||||
request<GroupInfo>(`/api/groups/${groupId}/trainees/${traineeId}`, { method: "PUT" }),
|
||||
onSuccess: () => {
|
||||
void cache.invalidateQueries({ queryKey: ["trainees"] });
|
||||
void cache.invalidateQueries({ queryKey: ["group-analytics"] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const useGroupAnalytics = (groupId: string | null) =>
|
||||
useQuery({
|
||||
queryKey: ["group-analytics", groupId],
|
||||
queryFn: () => request<GroupAnalytics>(`/api/groups/${groupId}/analytics`),
|
||||
enabled: Boolean(groupId),
|
||||
refetchInterval: 15_000,
|
||||
});
|
||||
|
||||
export const useProfile = (traineeId: string | null) =>
|
||||
useQuery({
|
||||
queryKey: ["profile", traineeId],
|
||||
|
|
|
|||
|
|
@ -87,12 +87,25 @@ export interface CardBriefing {
|
|||
card?: KIO | null;
|
||||
}
|
||||
|
||||
export interface CardNext {
|
||||
type: "card.next";
|
||||
card_id: string;
|
||||
}
|
||||
|
||||
/** Снимок КИО. После передачи не меняется — оператор не дописывает задним числом. */
|
||||
export interface CardReceived {
|
||||
type: "card.received";
|
||||
card: KIO;
|
||||
from_operator: string;
|
||||
at: string;
|
||||
card_index?: number;
|
||||
card_total?: number;
|
||||
}
|
||||
|
||||
export interface CardReply {
|
||||
type: "card.reply";
|
||||
card_id: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
/** Статус карточки целиком — считается из статусов служб, руками не ставится. */
|
||||
|
|
@ -134,6 +147,23 @@ export interface CrewSelect {
|
|||
/** Дежурно-диспетчерская служба, в которую уходит карточка. */
|
||||
export type DDSCode = "01" | "02" | "03" | "04" | "gkh";
|
||||
|
||||
export interface DdsCardReport {
|
||||
card_id: string;
|
||||
scenario_id: string;
|
||||
score_auto: number;
|
||||
reply_text: string;
|
||||
metrics: Array<Metric>;
|
||||
findings: Array<Finding>;
|
||||
actions?: Array<Record<string, unknown>>;
|
||||
duration_ms?: number;
|
||||
}
|
||||
|
||||
export interface DdsCardSummary {
|
||||
card_id: string;
|
||||
scenario_id: string;
|
||||
score_auto: number;
|
||||
}
|
||||
|
||||
/** Передача в ДДС. При маршрутизации по ЕКП служба уже есть в notify. */
|
||||
export interface DdsDispatch {
|
||||
type: "dds.dispatch";
|
||||
|
|
@ -387,6 +417,7 @@ export interface ScenarioPublish {
|
|||
export interface ScenarioStart {
|
||||
type: "scenario.start";
|
||||
scenario_id: string;
|
||||
scenario_ids?: Array<string> | null;
|
||||
trainee: string;
|
||||
trainee_id?: string | null;
|
||||
group_id?: string | null;
|
||||
|
|
@ -455,6 +486,7 @@ export interface SessionReport {
|
|||
transcript: Array<TranscriptEntry>;
|
||||
findings: Array<Finding>;
|
||||
metrics: Array<Metric>;
|
||||
card_results?: Array<DdsCardReport>;
|
||||
competencies: Array<CompetencyScore>;
|
||||
reference_questions: Array<HintShown>;
|
||||
missed_checklist: Array<string>;
|
||||
|
|
@ -507,6 +539,11 @@ export interface StationSnapshot {
|
|||
crew_options?: Array<string>;
|
||||
crew_selected?: string | null;
|
||||
phone_reports?: Array<PhoneReportRecord>;
|
||||
card_id?: string | null;
|
||||
card_index?: number;
|
||||
card_total?: number;
|
||||
reply_text?: string;
|
||||
completed_cards?: Array<DdsCardSummary>;
|
||||
}
|
||||
|
||||
/** Состояние АРМ ДДС после каждой отметки: что стоит и что доступно дальше. */
|
||||
|
|
@ -614,7 +651,7 @@ export type InstructorToServer = ScenarioStart | DirectorInject | ReferencePlay
|
|||
|
||||
export type ServerToStation = CardReceived | StationState | PhoneReport | TimerTick | SessionEnded | ScoreReady | ErrorEvent;
|
||||
|
||||
export type StationToServer = CardAck | CardBounce | ServiceStatusSet | CrewSelect | PhoneDial | StationFinish | ZoneDecision | CrewDispatched | CrewArrived;
|
||||
export type StationToServer = CardAck | CardBounce | ServiceStatusSet | CrewSelect | PhoneDial | StationFinish | CardReply | CardNext | ZoneDecision | CrewDispatched | CrewArrived;
|
||||
|
||||
export type EventName =
|
||||
"bg.start"
|
||||
|
|
@ -630,7 +667,9 @@ export type EventName =
|
|||
| "card.ack"
|
||||
| "card.bounce"
|
||||
| "card.briefing"
|
||||
| "card.next"
|
||||
| "card.received"
|
||||
| "card.reply"
|
||||
| "card.status"
|
||||
| "card.submit"
|
||||
| "crew.arrived"
|
||||
|
|
|
|||
Loading…
Reference in a new issue