feat: статусы реагирования на АРМ ДДС и ошибки диспетчера D1–D6 (lct-33)
ТЗ называет обучаемого оператором ДДС, а его работа в системе-112 состоит из одного действия: получить карточку и правильно проставить статус реагирования с комментарием. Памятка заказчика посвящена этому целиком. Наш АРМ умел два статуса из девяти. - domain/statuses.py: девять статусов реагирования с автоматом переходов, семь статусов карточки, обязательные комментарии к отказам. Формулировки из памятки дословно — диспетчер должен узнать слова своего боевого АРМ. - Автомат на сервере: фронт рисует только пришедшее в available. Отвергнутая отметка не попадает в журнал и возвращается текстом для курсанта. - scoring/dispatcher.py: D1, D2, D3, D4, D6 детерминированно. D3 (отказ от профильного происшествия) проверяется по списку оповещения из ЕКП — без классификатора его было бы не на чем посчитать. D5 оставлен судье. - Отметки D идут в отчёт рядом с E: в живой цепочке 112 → ДДС участвуют обе роли. - Экран ДДС: таблица служб со статусами, поле комментария у отказов, журнал отметок, статус карточки красным в трёх случаях из семи. Сквозной тест повис на ожидании station.state и вскрыл продуктовый дефект: снимок уходил только в ответ на действие диспетчера, то есть список оповещения он видел лишь после того, как что-то нажмёт. Теперь station.state отправляется сразу за card.received. Упрощения записаны в карточке: статусы «Проверена» и «Не завершено» не считаются — первый требует главного специалиста, второй 48 часов. 148 тестов зелёных (23 новых), make typecheck чистый.
This commit is contained in:
parent
842a333d16
commit
d4f0d0a0f8
14 changed files with 953 additions and 9 deletions
|
|
@ -11,9 +11,10 @@ import logging
|
|||
from uuid import UUID
|
||||
|
||||
from app.domain.events import ScoreReady
|
||||
from app.domain.timers import TimerCode
|
||||
from app.domain.timers import NORMATIVES, TimerCode
|
||||
from app.scenarios import store
|
||||
from app.scoring.competency import radar
|
||||
from app.scoring.dispatcher import evaluate_dispatcher
|
||||
from app.scoring.gost import evaluate
|
||||
from app.session.hub import hub
|
||||
|
||||
|
|
@ -35,6 +36,17 @@ async def finish(session_id: UUID, state) -> None:
|
|||
end_reason=state.end_reason,
|
||||
bounced_fields=state.bounced_fields,
|
||||
)
|
||||
# Работа диспетчера — вторая роль и вторая таксономия. Отметки D1–D6 идут
|
||||
# рядом с E1–E6, а не вместо: в живой цепочке 112 → ДДС в одном занятии
|
||||
# участвуют оба (docs/spec/DATASET.md#статусы-реагирования).
|
||||
dispatcher_findings = evaluate_dispatcher(
|
||||
entries=state.status_log,
|
||||
services=state.notified_services(),
|
||||
deadline_ms=NORMATIVES[TimerCode.DDS_ACK].limit_ms,
|
||||
elapsed_ms=state.timers.measured_ms(TimerCode.DDS_ACK),
|
||||
)
|
||||
result.findings.extend(dispatcher_findings)
|
||||
|
||||
# Сводка числами: по ней считается дельта между попытками в профиле.
|
||||
# Вытаскивать её разбором текста метрик («94 с») — путь к тихим ошибкам.
|
||||
required = scenario.ground_truth.required_facts
|
||||
|
|
@ -56,6 +68,8 @@ async def finish(session_id: UUID, state) -> None:
|
|||
"findings": [finding.model_dump(mode="json") for finding in result.findings],
|
||||
"competencies": [item.model_dump() for item in radar(result.metrics)],
|
||||
"unavailable": result.unavailable,
|
||||
# Статус карточки — готовая красная метка, понятная любому диспетчеру.
|
||||
"card_status": state.station_snapshot().card.value,
|
||||
}
|
||||
log.info("сессия %s: оценка %.1f, отметок %d", session_id, result.score, len(result.findings))
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue