lct-50: таймеры ДДС — 30 с до открытия карточки, 3 минуты до первой записи

This commit is contained in:
Ivan Gerasimov 2026-09-27 23:38:10 +03:00
commit 51ef99c472
18 changed files with 169 additions and 169 deletions

View file

@ -71,9 +71,9 @@ export function Debrief({ report, big = false }: { report: SessionReport; big?:
{" · "}<a href={`/api/sessions/${report.session_id}/report.pdf`}>документ (PDF)</a>
</p>
<p className={report.passed ? "state-ok" : "state-violated"}>
{report.passed ? "Зачёт" : "Не зачтено"}: на разборе {report.failed_metrics} пунктов; допустимо пропустить {report.criteria.allowed_errors}. {report.exercise === "dds" && <>На первичное решение ДДС отводится
{report.passed ? "Зачёт" : "Не зачтено"}: на разборе {report.failed_metrics} пунктов; допустимо пропустить {report.criteria.allowed_errors}. {report.exercise === "dds" && <>На открытие карточки ДДС отводится
{` ${report.criteria.decision_time_limit_seconds} с`}</>}
{report.exercise === "dds" && <>; норматив отработки карточки ДДС —
{report.exercise === "dds" && <>; на первую запись по карточке —
{` ${report.criteria.dds_card_work_time_limit_seconds ?? 180} с`}</>}
{report.exercise === "card" && <>Норматив заполнения КИО —
{` ${report.criteria.card_fill_time_limit_seconds ?? 180} с`}</>}

View file

@ -25,13 +25,13 @@ const OPERATOR_METRICS: MetricOption[] = [
const DDS_METRICS: MetricOption[] = [
{ key: "dds_primary", label: "Первичное решение", hint: "Зафиксирован ли статус «Принята» или «Не принята»." },
{ key: "dds_ack", label: "Срок первичного решения", hint: "Соблюдение установленного лимита реакции." },
{ key: "dds_ack", label: "Открытие карточки", hint: "Карточка открыта в пределах лимита после появления в строке сообщений." },
{ key: "dds_decision", label: "Решение по эталону", hint: "Соответствие принятию или обоснованному отказу." },
{ key: "dds_crew", label: "Назначение бригады", hint: "Выбор необходимой бригады ДДС." },
{ key: "dds_progress", label: "Ход реагирования", hint: "Статусы по фактическому ходу работ или отказу." },
{ key: "dds_completion", label: "Завершение работ", hint: "Фиксация завершения или отказа от работ." },
{ key: "dds_reply", label: "Основание и сведения", hint: "Комментарии к статусам и содержание полученных сведений." },
{ key: "dds_work_time", label: "Время отработки карточки", hint: "Соблюдение лимита полного цикла карточки." },
{ key: "dds_work_time", label: "Первая запись", hint: "Первый статус с текстом в пределах лимита после открытия карточки." },
];
const CARD_METRICS = OPERATOR_METRICS.filter(({ key }) => [

View file

@ -12,8 +12,8 @@ const TITLES: Record<string, string> = {
answer: "Ответ на звонок",
interview: "Опрос",
dds_notify: "Оповещение ДДС",
dds_ack: "Подтверждение ДДС",
dds_work: "Отработка карточки ДДС",
dds_ack: "Открытие карточки ДДС",
dds_work: "Первая запись ДДС",
card_fill: "Заполнение карточки КИО",
zone_check: "Зона ответственности",
callback: "Обратный дозвон",

View file

@ -489,7 +489,7 @@ export function Dds() {
<td data-label="Статус службы"><span className="dds-cell-value">
<span>{item.managed_service || "ДДС"}: {SERVICE_STATUS_LABELS[item.service_status]}</span>
<small className={!item.timer_stopped && item.elapsed_ms > item.limit_ms ? "state-violated" : ""}>
{item.timer_stopped ? "решение за" : "до первичного решения"} {Math.ceil(item.elapsed_ms / 1000)} с
{item.timer_stopped ? "открыта за" : "не открыта"} {Math.ceil(item.elapsed_ms / 1000)} с
</small>
</span></td>
</tr>})}

View file

@ -364,9 +364,9 @@ export function Instructor() {
<strong>{card.active ? "Сейчас в работе: " : "Ожидает: "}{card.title}</strong>
{card.address && <span>{card.address}</span>}
<small>{card.managed_service ? `${card.managed_service} · ` : ""}{SERVICE_STATUS_LABELS[card.service_status]} · {CARD_STATUS_LABELS[card.card_status]}</small>
<small>{card.timer_stopped ? `Первичная реакция за ${Math.ceil(card.elapsed_ms / 1000)} с` :
card.elapsed_ms > card.limit_ms ? `Просрочена первичная реакция · ${Math.ceil((card.elapsed_ms - card.limit_ms) / 1000)} с` :
`На первичное решение осталось ${Math.ceil((card.limit_ms - card.elapsed_ms) / 1000)} с`}</small>
<small>{card.timer_stopped ? `Открыта за ${Math.ceil(card.elapsed_ms / 1000)} с` :
card.elapsed_ms > card.limit_ms ? `Просрочено открытие · ${Math.ceil((card.elapsed_ms - card.limit_ms) / 1000)} с` :
`До открытия осталось ${Math.ceil((card.limit_ms - card.elapsed_ms) / 1000)} с`}</small>
</article>)}
{item.dds_snapshot.pending_cards_count ? <p className="ref">Ещё поступят по сценарию: {item.dds_snapshot.pending_cards_count}</p> : null}
{item.dds_snapshot.log.length > 0 && <div className="active-session-log">
@ -387,7 +387,7 @@ export function Instructor() {
<td>{item.exercise === "dds" ? `Открытых карточек: ${item.dds_open_cards}` : "—"}</td>
<td className={item.dds_overdue_cards ? "state-violated" : "state-ok"}>
{item.dds_overdue_cards || item.dds_work_overdue_cards
? `Просрочено: первичная реакция ${item.dds_overdue_cards}; отработка карточки ${item.dds_work_overdue_cards}`
? `Просрочено: открытие ${item.dds_overdue_cards}; первая запись ${item.dds_work_overdue_cards}`
: "Нормативы не нарушены"}
</td>
<td className={worstSignalSeverity(item.signals) ? `state-${worstSignalSeverity(item.signals)}` : item.presence_known ? "state-ok" : undefined}>
@ -623,9 +623,9 @@ export function Instructor() {
<summary>Настроить нормативы и критерии оценки</summary>
<p className="ref">
{exercise === "dds"
? `По умолчанию: решение за ${decisionTimeLimit} с, отработка карточки за ${ddsCardWorkTimeLimit} с; новые карточки поступают каждые ${ddsArrivalIntervalSeconds} с.`
? `По умолчанию: открытие карточки за ${decisionTimeLimit} с, первая запись за ${ddsCardWorkTimeLimit} с; новые карточки поступают каждые ${ddsArrivalIntervalSeconds} с.`
: exercise === "card_dds"
? `По умолчанию: заполнение КИО за ${cardFillTimeLimit} с, отработка карточки ДДС за ${ddsCardWorkTimeLimit} с.`
? `По умолчанию: заполнение КИО за ${cardFillTimeLimit} с, первая запись ДДС за ${ddsCardWorkTimeLimit} с.`
: exercise === "card"
? `По умолчанию: заполнение КИО за ${cardFillTimeLimit} с.`
: "Для звонка действуют нормативы приёма вызова по ГОСТ."}
@ -644,8 +644,8 @@ export function Instructor() {
</label>
</>}
{(exercise === "dds" || exercise === "card_dds") && <label className="lesson-criterion">
Решение ДДС, секунд
<LessonNumberInput label="Лимит решения ДДС, секунд" value={decisionTimeLimit}
Открытие карточки ДДС, секунд
<LessonNumberInput label="Лимит открытия карточки ДДС, секунд" value={decisionTimeLimit}
min={5} max={300} fallback={30} onCommit={setDecisionTimeLimit} />
</label>}
{(exercise === "card" || exercise === "card_dds") && <label className="lesson-criterion">
@ -655,11 +655,11 @@ export function Instructor() {
<small className="ref">По умолчанию 180 секунд; в отчёте будут факт и отклонение от норматива.</small>
</label>}
{(exercise === "dds" || exercise === "card_dds") && <label className="lesson-criterion">
Норматив отработки карточки ДДС, секунд
<LessonNumberInput label="Норматив отработки карточки ДДС, секунд"
Первая запись по карточке ДДС, секунд
<LessonNumberInput label="Норматив первой записи по карточке ДДС, секунд"
value={ddsCardWorkTimeLimit} min={30} max={1800} fallback={180}
onCommit={setDdsCardWorkTimeLimit} />
<small className="ref">По умолчанию 180 секунд; не заменяет 30-секундное первичное решение.</small>
<small className="ref">По умолчанию 180 секунд от открытия до первого статуса с текстом; дальше работы по времени не нормируются.</small>
</label>}
<label className="lesson-criterion">
Допустимо нарушенных метрик