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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
"""Оценка работы диспетчера ДДС: статусы реагирования.
|
|
|
|
|
|
|
|
|
|
|
|
Вторая роль — второй набор ошибок. D1–D6 взяты из памятки заказчика, где они
|
|
|
|
|
|
перечислены поимённо и с реальными примерами (docs/spec/DATASET.md). Формулировки
|
|
|
|
|
|
не переписаны: преподаватель, который эту памятку читал, должен узнать их в разборе.
|
|
|
|
|
|
|
2026-09-26 18:12:27 +03:00
|
|
|
|
Детерминированно считаются D1–D6. D5 проверяет структуру отдельного доклада и
|
|
|
|
|
|
получателя сведений; пропуск по каждому комментарию виден отдельно. К каждой ручной
|
|
|
|
|
|
отметке обязательны непустые поля «Основание» и «Сведения»; их наличие входит в
|
|
|
|
|
|
числовую метрику `dds_reply`, но не подтверждает истинность текста. Это не
|
|
|
|
|
|
привязывает статус к звонку или SIP: сведения можно получить по любому рабочему каналу.
|
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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from app.domain.statuses import (
|
|
|
|
|
|
COMMENT_REQUIRED,
|
|
|
|
|
|
PRIMARY,
|
2026-09-26 18:12:27 +03:00
|
|
|
|
REFUSAL_COMMENT_REQUIRED,
|
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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
SERVICE_STATUS_LABELS,
|
|
|
|
|
|
ServiceStatus,
|
|
|
|
|
|
StatusEntry,
|
|
|
|
|
|
current,
|
|
|
|
|
|
)
|
|
|
|
|
|
from app.domain.taxonomy import Competency, ErrorCode, Finding, FindingSource
|
2026-09-21 17:40:54 +03:00
|
|
|
|
from app.domain.events import Metric
|
2026-09-27 17:18:27 +03:00
|
|
|
|
from app.domain.timers import GOST_REF, TimerCode
|
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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
|
|
|
|
|
|
#: Статусы, означающие, что реагирование действительно шло.
|
|
|
|
|
|
PROGRESS = (ServiceStatus.RESPONDING, ServiceStatus.ARRIVED, ServiceStatus.WORKING)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _finding(code: ErrorCode, summary: str, fact: str, norm: str | None = None) -> Finding:
|
|
|
|
|
|
return Finding(
|
|
|
|
|
|
code=code,
|
|
|
|
|
|
source=FindingSource.DISPATCHER,
|
|
|
|
|
|
summary=summary,
|
|
|
|
|
|
fact=fact,
|
|
|
|
|
|
norm=norm,
|
|
|
|
|
|
ref="памятка «Работа на АРМ-112», раздел «Статусы реагирования»",
|
2026-09-26 18:12:27 +03:00
|
|
|
|
competency=(
|
|
|
|
|
|
Competency.COMMUNICATION if code is ErrorCode.D5 else Competency.CARD
|
|
|
|
|
|
),
|
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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-09-26 18:12:27 +03:00
|
|
|
|
_RECIPIENT_TERMS = (
|
|
|
|
|
|
"бригад",
|
|
|
|
|
|
"старш",
|
|
|
|
|
|
"дежурн",
|
|
|
|
|
|
"диспетчер",
|
|
|
|
|
|
"оператор",
|
|
|
|
|
|
"заявител",
|
|
|
|
|
|
"пострадавш",
|
|
|
|
|
|
"мвд",
|
|
|
|
|
|
"полици",
|
|
|
|
|
|
"мчс",
|
|
|
|
|
|
"скорая",
|
|
|
|
|
|
"медицинск",
|
|
|
|
|
|
"аварийн",
|
|
|
|
|
|
"служба 101",
|
|
|
|
|
|
"служба 102",
|
|
|
|
|
|
"служба 103",
|
|
|
|
|
|
"служба 104",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _has_recipient(text: str, service: str, crew: str | None) -> bool:
|
|
|
|
|
|
value = text.casefold()
|
|
|
|
|
|
if any(term in value for term in _RECIPIENT_TERMS):
|
|
|
|
|
|
return True
|
|
|
|
|
|
candidates = [service, crew or ""]
|
|
|
|
|
|
return any(len(item.strip()) >= 3 and item.strip().casefold() in value for item in candidates)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _has_basis_and_information(text: str) -> bool:
|
|
|
|
|
|
"""Require two explicit fields; this checks structure, not factual truth."""
|
|
|
|
|
|
fields: dict[str, str] = {}
|
|
|
|
|
|
for line in text.splitlines():
|
|
|
|
|
|
label, separator, value = line.partition(":")
|
|
|
|
|
|
if separator and label.strip().casefold() in {"основание", "сведения"}:
|
|
|
|
|
|
fields[label.strip().casefold()] = value.strip()
|
|
|
|
|
|
return bool(fields.get("основание") and fields.get("сведения"))
|
|
|
|
|
|
|
|
|
|
|
|
|
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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
def evaluate_dispatcher(
|
|
|
|
|
|
*,
|
|
|
|
|
|
entries: list[StatusEntry],
|
|
|
|
|
|
services: list[str],
|
2026-09-26 18:12:27 +03:00
|
|
|
|
crew_assignments: dict[str, str] | None = None,
|
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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
deadline_ms: int,
|
|
|
|
|
|
elapsed_ms: int | None,
|
2026-09-26 18:12:27 +03:00
|
|
|
|
reply_text: str = "",
|
|
|
|
|
|
expected_decision: str = "accept",
|
|
|
|
|
|
expected_decision_reason: str | None = None,
|
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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
) -> list[Finding]:
|
|
|
|
|
|
"""Отметки по работе диспетчера. Пустой список — работа без нарушений.
|
|
|
|
|
|
|
|
|
|
|
|
`services` — список оповещения карточки. Он же ответ на вопрос, профильно ли
|
|
|
|
|
|
происшествие: служба в списке — значит происшествие её, и отказ «не наша
|
|
|
|
|
|
компетенция» неправомерен.
|
|
|
|
|
|
"""
|
|
|
|
|
|
findings: list[Finding] = []
|
2026-09-26 18:12:27 +03:00
|
|
|
|
crew_assignments = crew_assignments or {}
|
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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
if not services:
|
|
|
|
|
|
return findings
|
|
|
|
|
|
|
|
|
|
|
|
for service in services:
|
|
|
|
|
|
marks = [entry for entry in entries if entry.service == service]
|
|
|
|
|
|
latest = current(entries, service)
|
|
|
|
|
|
|
2026-09-26 18:12:27 +03:00
|
|
|
|
# D5 — мягкая проверка памятки: если диспетчер оставил комментарий,
|
|
|
|
|
|
# в нём должен быть назван получатель сведений. Статусы и внешние
|
|
|
|
|
|
# звонки не используются как выдуманное доказательство.
|
|
|
|
|
|
missing_comment_statuses = [
|
|
|
|
|
|
SERVICE_STATUS_LABELS[mark.status]
|
|
|
|
|
|
for mark in marks
|
|
|
|
|
|
if mark.status in COMMENT_REQUIRED and not mark.comment.strip()
|
|
|
|
|
|
]
|
|
|
|
|
|
if missing_comment_statuses:
|
|
|
|
|
|
findings.append(_finding(
|
|
|
|
|
|
ErrorCode.D5,
|
|
|
|
|
|
f"{service}: к статусу не добавлены основание и сведения",
|
|
|
|
|
|
fact="не заполнены комментарии: " + ", ".join(missing_comment_statuses),
|
|
|
|
|
|
norm="к каждой ручной отметке добавить основание и содержание полученных сведений",
|
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
incomplete_comment_statuses = [
|
|
|
|
|
|
SERVICE_STATUS_LABELS[mark.status]
|
|
|
|
|
|
for mark in marks
|
|
|
|
|
|
if mark.status in COMMENT_REQUIRED
|
|
|
|
|
|
and mark.comment.strip()
|
|
|
|
|
|
and not _has_basis_and_information(mark.comment)
|
|
|
|
|
|
]
|
|
|
|
|
|
if incomplete_comment_statuses:
|
|
|
|
|
|
findings.append(_finding(
|
|
|
|
|
|
ErrorCode.D5,
|
|
|
|
|
|
f"{service}: комментарии к статусам не разделяют основание и сведения",
|
|
|
|
|
|
fact="неполные комментарии: " + ", ".join(incomplete_comment_statuses),
|
|
|
|
|
|
norm="в каждом комментарии заполнить отдельные поля «Основание» и «Сведения»",
|
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
comments_to_check = [
|
|
|
|
|
|
(SERVICE_STATUS_LABELS[mark.status], mark.comment.strip())
|
|
|
|
|
|
for mark in marks
|
|
|
|
|
|
if mark.comment.strip()
|
|
|
|
|
|
]
|
|
|
|
|
|
if reply_text.strip():
|
|
|
|
|
|
comments_to_check.append(("ответ ДДС", reply_text.strip()))
|
|
|
|
|
|
missing_recipients = [
|
|
|
|
|
|
f"{label}: {comment[:180]}"
|
|
|
|
|
|
for label, comment in comments_to_check
|
|
|
|
|
|
if not _has_recipient(comment, service, crew_assignments.get(service))
|
|
|
|
|
|
]
|
|
|
|
|
|
if missing_recipients:
|
|
|
|
|
|
findings.append(_finding(
|
|
|
|
|
|
ErrorCode.D5,
|
|
|
|
|
|
f"{service}: отдельный комментарий не указывает получателя сведений",
|
|
|
|
|
|
fact="; ".join(missing_recipients)[:500],
|
|
|
|
|
|
norm=(
|
|
|
|
|
|
"назвать, кому переданы сведения: бригаде, службе, "
|
|
|
|
|
|
"заявителю или иному адресату"
|
|
|
|
|
|
),
|
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
# D1 — первичного статуса нет либо он проставлен позже норматива.
|
|
|
|
|
|
primary = next((mark for mark in marks if mark.status in PRIMARY), None)
|
|
|
|
|
|
if primary is None:
|
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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
findings.append(
|
|
|
|
|
|
_finding(
|
|
|
|
|
|
ErrorCode.D1,
|
|
|
|
|
|
f"{service}: статус реагирования не проставлен",
|
|
|
|
|
|
fact=(
|
|
|
|
|
|
f"прошло {elapsed_ms // 1000} с"
|
|
|
|
|
|
if elapsed_ms is not None
|
|
|
|
|
|
else "карточка осталась без отметки"
|
|
|
|
|
|
),
|
|
|
|
|
|
norm=f"первичный статус в течение {deadline_ms // 1000} с",
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
2026-09-26 18:12:27 +03:00
|
|
|
|
if elapsed_ms is None or elapsed_ms > deadline_ms:
|
|
|
|
|
|
findings.append(
|
|
|
|
|
|
_finding(
|
|
|
|
|
|
ErrorCode.D1,
|
|
|
|
|
|
f"{service}: первичный статус проставлен с нарушением срока",
|
|
|
|
|
|
fact=(
|
|
|
|
|
|
f"прошло {elapsed_ms // 1000} с"
|
|
|
|
|
|
if elapsed_ms is not None
|
|
|
|
|
|
else "время первичной отметки не зафиксировано"
|
|
|
|
|
|
),
|
|
|
|
|
|
norm=f"первичный статус в течение {deadline_ms // 1000} с",
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
for mark in marks:
|
|
|
|
|
|
# D4 — отказ без комментария.
|
2026-09-26 18:12:27 +03:00
|
|
|
|
if mark.status in REFUSAL_COMMENT_REQUIRED and not mark.comment.strip():
|
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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
findings.append(
|
|
|
|
|
|
_finding(
|
|
|
|
|
|
ErrorCode.D4,
|
|
|
|
|
|
f"{service}: «{SERVICE_STATUS_LABELS[mark.status]}» без комментария",
|
|
|
|
|
|
fact="причина отказа не указана",
|
|
|
|
|
|
norm="комментарий к отказу обязателен",
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-09-26 18:12:27 +03:00
|
|
|
|
# D3 — отказ от профильного происшествия, когда эталон сценария
|
|
|
|
|
|
# требует принятия. Дубль/территориальное исключение задаются явно.
|
|
|
|
|
|
if latest is ServiceStatus.DECLINED and expected_decision == "accept":
|
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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
findings.append(
|
|
|
|
|
|
_finding(
|
|
|
|
|
|
ErrorCode.D3,
|
|
|
|
|
|
f"{service}: отказ от происшествия, которое в её компетенции",
|
|
|
|
|
|
fact="служба есть в списке оповещения по ЕКП",
|
2026-09-26 18:12:27 +03:00
|
|
|
|
norm="принять согласно эталону сценария",
|
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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
)
|
|
|
|
|
|
)
|
2026-09-26 18:12:27 +03:00
|
|
|
|
elif latest is ServiceStatus.ACCEPTED and expected_decision == "decline":
|
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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
findings.append(
|
|
|
|
|
|
_finding(
|
|
|
|
|
|
ErrorCode.D2,
|
2026-09-26 18:12:27 +03:00
|
|
|
|
f"{service}: принято вопреки эталону сценария",
|
|
|
|
|
|
fact="карточка принята службой",
|
|
|
|
|
|
norm=expected_decision_reason or "отказать с указанной причиной",
|
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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-09-26 18:12:27 +03:00
|
|
|
|
# D2 — «Принята» без назначения бригады/дальнейшего хода или ход без
|
|
|
|
|
|
# зафиксированной бригады. Заказчик уточнил: необходимые бригады ДДС
|
|
|
|
|
|
# выбирает вручную; канал получения докладов при этом не предписан.
|
|
|
|
|
|
if latest is ServiceStatus.ACCEPTED and expected_decision == "accept":
|
|
|
|
|
|
if service not in crew_assignments:
|
|
|
|
|
|
findings.append(
|
|
|
|
|
|
_finding(
|
|
|
|
|
|
ErrorCode.D2,
|
|
|
|
|
|
f"{service}: «Принята» без назначения бригады и хода реагирования",
|
|
|
|
|
|
fact="бригада не выбрана, после приёма статусов не было",
|
|
|
|
|
|
norm="необходимую бригаду выбирает ДДС; ход отмечается по факту",
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
findings.append(
|
|
|
|
|
|
_finding(
|
|
|
|
|
|
ErrorCode.D2,
|
|
|
|
|
|
f"{service}: «Принята», но о реагировании ничего не отмечено",
|
|
|
|
|
|
fact="после приёма статусов не было",
|
|
|
|
|
|
norm="статус должен соответствовать фактическому состоянию заявки",
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
elif latest is not ServiceStatus.DECLINED and any(
|
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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
mark.status in PROGRESS for mark in marks
|
2026-09-26 18:12:27 +03:00
|
|
|
|
) and service not in crew_assignments:
|
|
|
|
|
|
findings.append(
|
|
|
|
|
|
_finding(
|
|
|
|
|
|
ErrorCode.D2,
|
|
|
|
|
|
f"{service}: ход реагирования без назначения бригады",
|
|
|
|
|
|
fact="статусы хода работ проставлены, бригада не выбрана",
|
|
|
|
|
|
norm="необходимую бригаду выбирает ДДС; ход отмечается по факту",
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# D6 — ход работ неполон к моменту закрытия карточки/занятия. В памятке
|
|
|
|
|
|
# это приводит к повторным звонкам и скрывает от других служб факт реакции.
|
|
|
|
|
|
# REFUSED — отдельный допустимый терминальный статус с обязательной причиной.
|
|
|
|
|
|
missing_progress = [status for status in PROGRESS if status not in {m.status for m in marks}]
|
|
|
|
|
|
if (latest not in {ServiceStatus.DECLINED, ServiceStatus.REFUSED}
|
|
|
|
|
|
and (missing_progress or latest is not ServiceStatus.COMPLETED)):
|
|
|
|
|
|
missing = ", ".join(SERVICE_STATUS_LABELS[status] for status in missing_progress)
|
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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
findings.append(
|
|
|
|
|
|
_finding(
|
|
|
|
|
|
ErrorCode.D6,
|
2026-09-26 18:12:27 +03:00
|
|
|
|
f"{service}: ход реагирования не доведён до конца",
|
|
|
|
|
|
fact=(f"не отмечены: {missing}" if missing else "карточка не закрыта"),
|
|
|
|
|
|
norm=(
|
|
|
|
|
|
"отметить по факту начало реагирования, прибытие, проведение работ "
|
|
|
|
|
|
"и завершение; если работы не проводились — оформить отказ с причиной"
|
|
|
|
|
|
),
|
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 чистый.
2026-09-20 08:23:55 +03:00
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return findings
|
2026-09-21 17:40:54 +03:00
|
|
|
|
|
|
|
|
|
|
|
2026-09-26 18:12:27 +03:00
|
|
|
|
def dispatcher_metrics(
|
2026-09-27 17:18:27 +03:00
|
|
|
|
card,
|
2026-09-26 18:12:27 +03:00
|
|
|
|
deadline_ms: int,
|
|
|
|
|
|
expected_decision: str = "accept",
|
|
|
|
|
|
expected_decision_reason: str | None = None,
|
2026-09-26 21:38:09 +00:00
|
|
|
|
*,
|
|
|
|
|
|
services: list[str] | None = None,
|
2026-09-26 18:12:27 +03:00
|
|
|
|
) -> list[Metric]:
|
2026-09-21 17:40:54 +03:00
|
|
|
|
"""Числовая часть оценки ДДС; каждый проверяемый шаг имеет факт и норму.
|
|
|
|
|
|
|
2026-09-24 01:10:49 +03:00
|
|
|
|
Веса предварительные — до утверждения методистом. Телефон — возможный
|
|
|
|
|
|
источник информации, но не обязательный шлюз статуса.
|
2026-09-21 17:40:54 +03:00
|
|
|
|
"""
|
|
|
|
|
|
metrics: list[Metric] = []
|
2026-09-27 17:18:27 +03:00
|
|
|
|
# Через таймер, а не `primary.at - dispatched_at`: календарная разница
|
|
|
|
|
|
# включила бы паузу преподавателя в норматив (lct-39).
|
|
|
|
|
|
elapsed = card.timers.measured_ms(TimerCode.DDS_ACK)
|
|
|
|
|
|
for service in (services if services is not None else card.managed_services()):
|
|
|
|
|
|
marks = [mark for mark in card.status_log if mark.service == service]
|
2026-09-21 17:40:54 +03:00
|
|
|
|
statuses = {mark.status for mark in marks}
|
|
|
|
|
|
|
|
|
|
|
|
def add(key: str, title: str, passed: bool, fact: str, norm: str, weight: float = 1.0):
|
|
|
|
|
|
metrics.append(Metric(
|
|
|
|
|
|
key=key, title=f"{service}: {title}", fact=fact, norm=norm,
|
|
|
|
|
|
ref=GOST_REF if key == "dds_ack" else "памятка «Работа на АРМ-112», статусы реагирования",
|
|
|
|
|
|
passed=passed, weight=weight,
|
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
primary = next((mark for mark in marks if mark.status in PRIMARY), None)
|
|
|
|
|
|
add("dds_primary", "решение по карточке", primary is not None,
|
|
|
|
|
|
primary.status.value if primary else "решение отсутствует", "Принята или Не принята", 2.0)
|
2026-09-24 01:10:49 +03:00
|
|
|
|
add("dds_ack", f"первичная отметка за {deadline_ms // 1000} с",
|
2026-09-21 17:40:54 +03:00
|
|
|
|
elapsed is not None and elapsed <= deadline_ms,
|
|
|
|
|
|
f"{elapsed / 1000:.1f} с" if elapsed is not None else "не отмечено",
|
|
|
|
|
|
f"≤ {deadline_ms // 1000} с")
|
|
|
|
|
|
if primary is None:
|
|
|
|
|
|
continue
|
2026-09-26 18:12:27 +03:00
|
|
|
|
expected_status = (ServiceStatus.ACCEPTED if expected_decision == "accept"
|
|
|
|
|
|
else ServiceStatus.DECLINED)
|
|
|
|
|
|
expected_label = "Принята" if expected_decision == "accept" else "Не принята"
|
|
|
|
|
|
add("dds_decision", "решение по эталону сценария",
|
|
|
|
|
|
primary.status is expected_status,
|
|
|
|
|
|
primary.status.value,
|
|
|
|
|
|
expected_decision_reason or f"по эталону ожидается «{expected_label}»", 2.0)
|
|
|
|
|
|
if primary.status is ServiceStatus.DECLINED or expected_decision == "decline":
|
2026-09-21 17:40:54 +03:00
|
|
|
|
continue
|
2026-09-27 17:18:27 +03:00
|
|
|
|
crew = card.crew_assignments.get(service)
|
2026-09-26 18:12:27 +03:00
|
|
|
|
add("dds_crew", "назначение бригады", bool(crew),
|
|
|
|
|
|
crew or "бригада не выбрана",
|
|
|
|
|
|
"необходимую бригаду выбирает ДДС вручную", 2.0)
|
2026-09-21 17:40:54 +03:00
|
|
|
|
expected = {
|
2026-09-24 01:10:49 +03:00
|
|
|
|
ServiceStatus.RESPONDING, ServiceStatus.ARRIVED, ServiceStatus.WORKING,
|
2026-09-21 17:40:54 +03:00
|
|
|
|
}
|
2026-09-26 18:12:27 +03:00
|
|
|
|
refused = ServiceStatus.REFUSED in statuses
|
|
|
|
|
|
add("dds_progress", "ведение хода реагирования",
|
|
|
|
|
|
expected <= statuses or refused,
|
|
|
|
|
|
("отказ от выполнения работ" if refused else
|
|
|
|
|
|
", ".join(mark.status.value for mark in marks if mark.status in expected)
|
|
|
|
|
|
or "статусов хода нет"),
|
|
|
|
|
|
"отметить по факту ход работ либо оформить обоснованный отказ от их выполнения", 2.0)
|
2026-09-24 01:10:49 +03:00
|
|
|
|
add("dds_completion", "закрытие работ",
|
2026-09-26 18:12:27 +03:00
|
|
|
|
ServiceStatus.COMPLETED in statuses or refused,
|
|
|
|
|
|
("завершено" if ServiceStatus.COMPLETED in statuses else
|
|
|
|
|
|
"оформлен отказ от выполнения работ" if refused else "не завершено"),
|
|
|
|
|
|
"зафиксировать фактическое завершение работ или отказ от их выполнения")
|
|
|
|
|
|
notes = [mark.comment.strip() for mark in marks if mark.status in COMMENT_REQUIRED]
|
|
|
|
|
|
notes_complete = bool(notes) and all(
|
|
|
|
|
|
_has_basis_and_information(note) for note in notes
|
|
|
|
|
|
)
|
|
|
|
|
|
add("dds_reply", "основание статусов и сведения о ходе работ",
|
|
|
|
|
|
notes_complete,
|
|
|
|
|
|
"; ".join(notes) if notes else "комментарии к статусам не внесены",
|
|
|
|
|
|
"к каждой ручной отметке добавить основание и содержание полученных сведений")
|
2026-09-21 17:40:54 +03:00
|
|
|
|
return metrics
|