feat: исход вызова — не каждый звонок заканчивается карточкой (lct-36)
Весь продукт стоял на допущении «звонок → карточка → выезд». В билетах заказчика оно нарушается намеренно: «поругался с продавцом Мегафон» — справка, а вызов из Волгоградской области передаётся в систему-112 другого субъекта. Курсант, заведший карточку на такой вызов, занял расчёт зря, и прежняя оценка этого не видела — наоборот, награждала за полноту заполнения. - Outcome в домене, поле outcome в сценарии, событие call.resolve у курсанта и две кнопки на АРМ. Отдельное действие, а не «положил трубку»: система должна отличить осознанное решение от брошенного вызова. - Метрика outcome (E2, вес 3 — лишний выезд дороже неточного признака). - Там, где карточка не заводится, метрики карточки не считаются вовсе. Полнота опроса считается: передать вызов не значит не опрашивать. - call.resolve останавливает норматив опроса, как и передача карточки. Попутно (lct-34): библиотека стала вложенной — scenarios/tickets/, поля ticket и position, чек-листы для медицины, полиции и ЖКХ. Перенесён билет 1 целиком: три вызова, три классификатора, третий — из другого региона. Найдено по ходу: модификаторы списка оповещения не были подключены ни к чему. В классификаторе скорая добавляется к массовой драке признаком «пострадавшие», но взять его было неоткуда. Теперь модификаторы берутся из карточки — victims_count, life_threat, evacuation_needed, fire.gasified, — и список оповещения пересобирается при правке любого из этих полей. 161 тест зелёный (13 новых), make typecheck чистый.
This commit is contained in:
parent
d4f0d0a0f8
commit
b8dc39fd7d
22 changed files with 628 additions and 9 deletions
|
|
@ -1,6 +1,8 @@
|
|||
// АРМ курсанта: приём вызова и разговор голосом.
|
||||
// Карточка КИО — lct-10, таймеры — lct-11.
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import { KioCard } from "@/features/kio-card/KioCard";
|
||||
import { StandStatus } from "@/features/health/StandStatus";
|
||||
import { ModeBanner, hintsAllowed } from "@/features/mode-banner/ModeBanner";
|
||||
|
|
@ -18,7 +20,14 @@ const STATUS_LABEL: Record<string, string> = {
|
|||
closed: "отключено",
|
||||
};
|
||||
|
||||
const RESOLVE_LABELS = {
|
||||
consultation: "справочно-консультационная помощь",
|
||||
transfer_region: "передан в систему-112 другого региона",
|
||||
} as const;
|
||||
|
||||
export function Call() {
|
||||
const [resolveComment, setResolveComment] = useState("");
|
||||
const [resolved, setResolved] = useState<"consultation" | "transfer_region" | null>(null);
|
||||
const sessionId = sessionIdFromUrl();
|
||||
const call = useCall(sessionId);
|
||||
|
||||
|
|
@ -82,6 +91,42 @@ export function Call() {
|
|||
Передать в ДДС
|
||||
</button>
|
||||
</p>
|
||||
{/* Не каждый вызов заканчивается карточкой: справка и чужой регион —
|
||||
отдельные исходы, и курсант выбирает их сам (docs/spec/TICKETS.md). */}
|
||||
<details>
|
||||
<summary>Закрыть вызов без карточки</summary>
|
||||
<p>
|
||||
<input
|
||||
type="text"
|
||||
value={resolveComment}
|
||||
placeholder="что сказали заявителю, куда передали"
|
||||
onChange={(event) => setResolveComment(event.target.value)}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!call.micOn || resolved !== null}
|
||||
onClick={() => {
|
||||
call.resolve("consultation", resolveComment);
|
||||
setResolved("consultation");
|
||||
}}
|
||||
>
|
||||
Справка, выезд не нужен
|
||||
</button>{" "}
|
||||
<button
|
||||
type="button"
|
||||
disabled={!call.micOn || resolved !== null}
|
||||
onClick={() => {
|
||||
call.resolve("transfer_region", resolveComment);
|
||||
setResolved("transfer_region");
|
||||
}}
|
||||
>
|
||||
Передать в другой регион
|
||||
</button>
|
||||
</p>
|
||||
{resolved && <p className="warn">Вызов закрыт: {RESOLVE_LABELS[resolved]}</p>}
|
||||
</details>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Нормативы</h2>
|
||||
|
|
|
|||
Loading…
Reference in a new issue