каркас: домен, генерация типов фронта, стенд и карточки задач
Контракт в коде: КИО по нормативу, 43 события WS по шести союзам направлений,
таймеры ГОСТ, таксономия E1–E6, шесть компетенций радара. make types гоняет
их через JSON Schema в generated.ts, тест ловит забытую регенерацию.
Стенд: docker compose postgres + backend, health отвечает, база досягаема
из контейнера бэкенда. Makefile не врёт — нереализованные цели падают
и называют карточку, которая их закроет.
frontend/.npmrc: под WSL2 npm install зависает на дефолтных 15 сокетах.
2026-09-15 19:35:03 +03:00
|
|
|
|
"""Таймеры ГОСТ Р 22.7.03-2021: коды, нормативы, состояние.
|
|
|
|
|
|
|
|
|
|
|
|
Норматив: docs/spec/NORMATIVES.md#временные-нормативы-приёма-вызова
|
|
|
|
|
|
Правило: таймеры останавливаются событиями, а не таймаутами
|
|
|
|
|
|
(docs/arch/CONTRACT.md) — иначе метрика недоказуема.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from enum import StrEnum
|
|
|
|
|
|
|
|
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
|
|
|
|
|
|
GOST_REF = "ГОСТ Р 22.7.03-2021"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TimerCode(StrEnum):
|
|
|
|
|
|
"""Коды одинаковы на бэке, фронте и в отчёте."""
|
|
|
|
|
|
|
|
|
|
|
|
ANSWER = "answer"
|
|
|
|
|
|
INTERVIEW = "interview"
|
|
|
|
|
|
DDS_NOTIFY = "dds_notify"
|
|
|
|
|
|
DDS_ACK = "dds_ack"
|
2026-09-26 18:12:27 +03:00
|
|
|
|
DDS_WORK = "dds_work"
|
|
|
|
|
|
CARD_FILL = "card_fill"
|
каркас: домен, генерация типов фронта, стенд и карточки задач
Контракт в коде: КИО по нормативу, 43 события WS по шести союзам направлений,
таймеры ГОСТ, таксономия E1–E6, шесть компетенций радара. make types гоняет
их через JSON Schema в generated.ts, тест ловит забытую регенерацию.
Стенд: docker compose postgres + backend, health отвечает, база досягаема
из контейнера бэкенда. Makefile не врёт — нереализованные цели падают
и называют карточку, которая их закроет.
frontend/.npmrc: под WSL2 npm install зависает на дефолтных 15 сокетах.
2026-09-15 19:35:03 +03:00
|
|
|
|
ZONE_CHECK = "zone_check"
|
|
|
|
|
|
CALLBACK = "callback"
|
|
|
|
|
|
CLOSE = "close"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TimerState(StrEnum):
|
|
|
|
|
|
"""Единственное место, где цвет несёт смысл (docs/arch/FRONTEND.md)."""
|
|
|
|
|
|
|
|
|
|
|
|
OK = "ok"
|
|
|
|
|
|
WARN = "warn"
|
|
|
|
|
|
VIOLATED = "violated"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Normative(BaseModel):
|
|
|
|
|
|
"""Норматив одной операции приёма вызова."""
|
|
|
|
|
|
|
|
|
|
|
|
code: TimerCode
|
|
|
|
|
|
title: str
|
|
|
|
|
|
limit_ms: int
|
|
|
|
|
|
attempts: int = 1
|
|
|
|
|
|
ref: str = GOST_REF
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#: Значения по умолчанию. Рабочие лимиты берутся из конфига (app.config),
|
|
|
|
|
|
#: не из кода — норматив может отличаться у заказчика.
|
|
|
|
|
|
NORMATIVES: dict[TimerCode, Normative] = {
|
|
|
|
|
|
TimerCode.ANSWER: Normative(
|
|
|
|
|
|
code=TimerCode.ANSWER, title="Ответ на звонок", limit_ms=8_000
|
|
|
|
|
|
),
|
|
|
|
|
|
TimerCode.INTERVIEW: Normative(
|
|
|
|
|
|
code=TimerCode.INTERVIEW, title="Опрос / идентификация ситуации", limit_ms=75_000
|
|
|
|
|
|
),
|
|
|
|
|
|
TimerCode.DDS_NOTIFY: Normative(
|
|
|
|
|
|
code=TimerCode.DDS_NOTIFY, title="Оповещение ДДС", limit_ms=60_000
|
|
|
|
|
|
),
|
|
|
|
|
|
TimerCode.DDS_ACK: Normative(
|
docs: датасет заказчика — разбор, расшифровка билетов, ревизия карточек
Получены первичные данные, названные в ТЗ: памятка по АРМ-112, классификатор
происшествий на 1283 кода и 32 экзаменационных билета. Файлы в docs/spec/source/
под гитом: документ, на который ссылается норматив, должен приезжать вместе с кодом.
Что изменилось по существу:
- Норматив подтверждения карточки был 4 секунды со ссылкой на ГОСТ, где этого числа
нет. Памятка и ТЗ независимо называют 30 секунд — исправлено в domain/timers.py
с правильной ссылкой (ПП РФ № 1931), в контракте и в generated.ts.
- Оператор в боевом АРМ-112 службу не выбирает: он проставляет признаки, а список
оповещения из 5–11 служб считается по ЕКП. Наше поле dds моделирует не ту работу.
- Сторона ДДС описана памяткой подробнее, чем реализована: девять статусов
реагирования автоматом, семь статусов карточки, обязательные комментарии
к отказам и таксономия ошибок с примерами.
- 96 учебных вызовов из билетов расшифрованы слово в слово в docs/spec/TICKETS.md;
сложность в них делается адресом и профильностью, а не сюжетом.
Документы: DATASET.md, TICKETS.md, правки NORMATIVES.md (НПА, ЕКП, статусы),
GAP.md (пункты 13–15), TZ.md (предположение «датасета не будет» не подтвердилось).
Карточки: новые 32–36 (классификатор, статусы реагирования, билеты, уточнение
адреса, непрофильные вызовы); в девятнадцати существующих — раздел «Датасет»
с расхождениями, включая выполненные: lct-01 моделирует не ту карточку КИО,
lct-12 сравнивает адрес так, что уточнивший его курсант получает расхождение
с эталоном, lct-10 не содержит трёх блоков боевого АРМ.
2026-09-19 19:42:48 +03:00
|
|
|
|
# 30 секунд — ПП РФ № 1931 и памятка АРМ-112, а не ГОСТ: до получения
|
|
|
|
|
|
# датасета здесь стояло 4 секунды, взятые по догадке (docs/spec/DATASET.md).
|
2026-09-27 23:38:10 +03:00
|
|
|
|
# Отсчёт — от появления карточки до её открытия (ответ заказчика, П.5).
|
docs: датасет заказчика — разбор, расшифровка билетов, ревизия карточек
Получены первичные данные, названные в ТЗ: памятка по АРМ-112, классификатор
происшествий на 1283 кода и 32 экзаменационных билета. Файлы в docs/spec/source/
под гитом: документ, на который ссылается норматив, должен приезжать вместе с кодом.
Что изменилось по существу:
- Норматив подтверждения карточки был 4 секунды со ссылкой на ГОСТ, где этого числа
нет. Памятка и ТЗ независимо называют 30 секунд — исправлено в domain/timers.py
с правильной ссылкой (ПП РФ № 1931), в контракте и в generated.ts.
- Оператор в боевом АРМ-112 службу не выбирает: он проставляет признаки, а список
оповещения из 5–11 служб считается по ЕКП. Наше поле dds моделирует не ту работу.
- Сторона ДДС описана памяткой подробнее, чем реализована: девять статусов
реагирования автоматом, семь статусов карточки, обязательные комментарии
к отказам и таксономия ошибок с примерами.
- 96 учебных вызовов из билетов расшифрованы слово в слово в docs/spec/TICKETS.md;
сложность в них делается адресом и профильностью, а не сюжетом.
Документы: DATASET.md, TICKETS.md, правки NORMATIVES.md (НПА, ЕКП, статусы),
GAP.md (пункты 13–15), TZ.md (предположение «датасета не будет» не подтвердилось).
Карточки: новые 32–36 (классификатор, статусы реагирования, билеты, уточнение
адреса, непрофильные вызовы); в девятнадцати существующих — раздел «Датасет»
с расхождениями, включая выполненные: lct-01 моделирует не ту карточку КИО,
lct-12 сравнивает адрес так, что уточнивший его курсант получает расхождение
с эталоном, lct-10 не содержит трёх блоков боевого АРМ.
2026-09-19 19:42:48 +03:00
|
|
|
|
code=TimerCode.DDS_ACK,
|
2026-09-27 23:38:10 +03:00
|
|
|
|
title="Открытие карточки ДДС",
|
docs: датасет заказчика — разбор, расшифровка билетов, ревизия карточек
Получены первичные данные, названные в ТЗ: памятка по АРМ-112, классификатор
происшествий на 1283 кода и 32 экзаменационных билета. Файлы в docs/spec/source/
под гитом: документ, на который ссылается норматив, должен приезжать вместе с кодом.
Что изменилось по существу:
- Норматив подтверждения карточки был 4 секунды со ссылкой на ГОСТ, где этого числа
нет. Памятка и ТЗ независимо называют 30 секунд — исправлено в domain/timers.py
с правильной ссылкой (ПП РФ № 1931), в контракте и в generated.ts.
- Оператор в боевом АРМ-112 службу не выбирает: он проставляет признаки, а список
оповещения из 5–11 служб считается по ЕКП. Наше поле dds моделирует не ту работу.
- Сторона ДДС описана памяткой подробнее, чем реализована: девять статусов
реагирования автоматом, семь статусов карточки, обязательные комментарии
к отказам и таксономия ошибок с примерами.
- 96 учебных вызовов из билетов расшифрованы слово в слово в docs/spec/TICKETS.md;
сложность в них делается адресом и профильностью, а не сюжетом.
Документы: DATASET.md, TICKETS.md, правки NORMATIVES.md (НПА, ЕКП, статусы),
GAP.md (пункты 13–15), TZ.md (предположение «датасета не будет» не подтвердилось).
Карточки: новые 32–36 (классификатор, статусы реагирования, билеты, уточнение
адреса, непрофильные вызовы); в девятнадцати существующих — раздел «Датасет»
с расхождениями, включая выполненные: lct-01 моделирует не ту карточку КИО,
lct-12 сравнивает адрес так, что уточнивший его курсант получает расхождение
с эталоном, lct-10 не содержит трёх блоков боевого АРМ.
2026-09-19 19:42:48 +03:00
|
|
|
|
limit_ms=30_000,
|
2026-09-27 23:38:10 +03:00
|
|
|
|
ref="ПП РФ № 1931; ответ заказчика, П.5",
|
каркас: домен, генерация типов фронта, стенд и карточки задач
Контракт в коде: КИО по нормативу, 43 события WS по шести союзам направлений,
таймеры ГОСТ, таксономия E1–E6, шесть компетенций радара. make types гоняет
их через JSON Schema в generated.ts, тест ловит забытую регенерацию.
Стенд: docker compose postgres + backend, health отвечает, база досягаема
из контейнера бэкенда. Makefile не врёт — нереализованные цели падают
и называют карточку, которая их закроет.
frontend/.npmrc: под WSL2 npm install зависает на дефолтных 15 сокетах.
2026-09-15 19:35:03 +03:00
|
|
|
|
),
|
2026-09-26 18:12:27 +03:00
|
|
|
|
TimerCode.CARD_FILL: Normative(
|
|
|
|
|
|
code=TimerCode.CARD_FILL,
|
|
|
|
|
|
title="Заполнение карточки КИО",
|
|
|
|
|
|
limit_ms=180_000,
|
|
|
|
|
|
ref="ТЗ задачи № 9; сессия вопросов и ответов 16.09.2026",
|
|
|
|
|
|
),
|
|
|
|
|
|
TimerCode.DDS_WORK: Normative(
|
|
|
|
|
|
code=TimerCode.DDS_WORK,
|
2026-09-27 23:38:10 +03:00
|
|
|
|
title="Первая запись по карточке ДДС",
|
2026-09-26 18:12:27 +03:00
|
|
|
|
limit_ms=180_000,
|
2026-09-27 23:38:10 +03:00
|
|
|
|
ref="ТЗ задачи № 9; ответ заказчика, П.5",
|
2026-09-26 18:12:27 +03:00
|
|
|
|
),
|
каркас: домен, генерация типов фронта, стенд и карточки задач
Контракт в коде: КИО по нормативу, 43 события WS по шести союзам направлений,
таймеры ГОСТ, таксономия E1–E6, шесть компетенций радара. make types гоняет
их через JSON Schema в generated.ts, тест ловит забытую регенерацию.
Стенд: docker compose postgres + backend, health отвечает, база досягаема
из контейнера бэкенда. Makefile не врёт — нереализованные цели падают
и называют карточку, которая их закроет.
frontend/.npmrc: под WSL2 npm install зависает на дефолтных 15 сокетах.
2026-09-15 19:35:03 +03:00
|
|
|
|
TimerCode.ZONE_CHECK: Normative(
|
|
|
|
|
|
code=TimerCode.ZONE_CHECK, title="Проверка зоны ответственности", limit_ms=30_000
|
|
|
|
|
|
),
|
|
|
|
|
|
TimerCode.CALLBACK: Normative(
|
|
|
|
|
|
code=TimerCode.CALLBACK,
|
|
|
|
|
|
title="Обратный дозвон при обрыве",
|
|
|
|
|
|
limit_ms=10_000,
|
|
|
|
|
|
attempts=3,
|
|
|
|
|
|
),
|
|
|
|
|
|
TimerCode.CLOSE: Normative(
|
|
|
|
|
|
code=TimerCode.CLOSE, title="Снятие с контроля", limit_ms=300_000
|
|
|
|
|
|
),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#: Доля норматива, после которой таймер жёлтый.
|
|
|
|
|
|
WARN_RATIO = 0.8
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def state_for(elapsed_ms: int, limit_ms: int) -> TimerState:
|
|
|
|
|
|
"""Состояние таймера по факту и нормативу."""
|
|
|
|
|
|
if elapsed_ms > limit_ms:
|
|
|
|
|
|
return TimerState.VIOLATED
|
|
|
|
|
|
if elapsed_ms >= limit_ms * WARN_RATIO:
|
|
|
|
|
|
return TimerState.WARN
|
|
|
|
|
|
return TimerState.OK
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TimerSnapshot(BaseModel):
|
|
|
|
|
|
"""Один таймер в событии `timer.tick`."""
|
|
|
|
|
|
|
|
|
|
|
|
code: TimerCode
|
|
|
|
|
|
elapsed_ms: int
|
|
|
|
|
|
limit_ms: int
|
|
|
|
|
|
state: TimerState
|
|
|
|
|
|
attempt: int = Field(default=1, description="Номер попытки для callback")
|
|
|
|
|
|
stopped: bool = Field(default=False, description="Остановлен событием, не тикает")
|