каркас: домен, генерация типов фронта, стенд и карточки задач
Контракт в коде: КИО по нормативу, 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
|
|
|
|
"""Таксономия ошибок E1–E6 и компетенции.
|
|
|
|
|
|
|
|
|
|
|
|
Методика: docs/product/METHODOLOGY.md#таксономия-ошибок
|
|
|
|
|
|
Правило: каждая отметка несёт код и обоснование. Отметок без объяснения
|
|
|
|
|
|
не появляется ни в одном интерфейсе.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
from enum import StrEnum
|
2026-09-27 22:32:41 +03:00
|
|
|
|
from typing import Any
|
|
|
|
|
|
from uuid import UUID
|
каркас: домен, генерация типов фронта, стенд и карточки задач
Контракт в коде: КИО по нормативу, 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-27 22:32:41 +03:00
|
|
|
|
from pydantic import BaseModel, model_validator
|
каркас: домен, генерация типов фронта, стенд и карточки задач
Контракт в коде: КИО по нормативу, 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ErrorCode(StrEnum):
|
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
|
|
|
|
"""E — ошибки приёма вызова, D — ошибки диспетчера ДДС.
|
|
|
|
|
|
|
|
|
|
|
|
Две роли, две таксономии. D1–D6 взяты из памятки заказчика дословно:
|
|
|
|
|
|
она перечисляет типовые нарушения с реальными примерами, и преподаватель
|
|
|
|
|
|
узнаёт их формулировки (docs/spec/DATASET.md).
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
каркас: домен, генерация типов фронта, стенд и карточки задач
Контракт в коде: КИО по нормативу, 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
|
|
|
|
E1 = "E1"
|
|
|
|
|
|
E2 = "E2"
|
|
|
|
|
|
E3 = "E3"
|
|
|
|
|
|
E4 = "E4"
|
|
|
|
|
|
E5 = "E5"
|
|
|
|
|
|
E6 = "E6"
|
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 = "D1"
|
|
|
|
|
|
D2 = "D2"
|
|
|
|
|
|
D3 = "D3"
|
|
|
|
|
|
D4 = "D4"
|
|
|
|
|
|
D5 = "D5"
|
|
|
|
|
|
D6 = "D6"
|
каркас: домен, генерация типов фронта, стенд и карточки задач
Контракт в коде: КИО по нормативу, 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FindingSource(StrEnum):
|
2026-09-26 18:12:27 +03:00
|
|
|
|
"""Источник объяснимой отметки; `judge` оставлен для старых отчётов."""
|
каркас: домен, генерация типов фронта, стенд и карточки задач
Контракт в коде: КИО по нормативу, 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
|
|
|
|
|
|
|
|
|
|
SLOTS = "slots"
|
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
|
|
|
|
DISPATCHER = "dispatcher"
|
каркас: домен, генерация типов фронта, стенд и карточки задач
Контракт в коде: КИО по нормативу, 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
|
|
|
|
GROUND_TRUTH = "ground_truth"
|
|
|
|
|
|
TIMERS = "timers"
|
|
|
|
|
|
KIO = "kio"
|
|
|
|
|
|
CHAIN = "chain"
|
2026-09-26 18:12:27 +03:00
|
|
|
|
GRAMMAR = "grammar"
|
|
|
|
|
|
# Legacy value: historical saved reports may still contain it.
|
каркас: домен, генерация типов фронта, стенд и карточки задач
Контракт в коде: КИО по нормативу, 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
|
|
|
|
JUDGE = "judge"
|
|
|
|
|
|
INSTRUCTOR = "instructor"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ErrorSpec(BaseModel):
|
|
|
|
|
|
code: ErrorCode
|
|
|
|
|
|
title: str
|
|
|
|
|
|
detail: str
|
|
|
|
|
|
source: FindingSource
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ERRORS: dict[ErrorCode, ErrorSpec] = {
|
|
|
|
|
|
ErrorCode.E1: ErrorSpec(
|
|
|
|
|
|
code=ErrorCode.E1,
|
|
|
|
|
|
title="Пропущенный факт",
|
|
|
|
|
|
detail="Обязательный вопрос не прозвучал, факт не добыт",
|
|
|
|
|
|
source=FindingSource.SLOTS,
|
|
|
|
|
|
),
|
|
|
|
|
|
ErrorCode.E2: ErrorSpec(
|
|
|
|
|
|
code=ErrorCode.E2,
|
|
|
|
|
|
title="Неверная классификация или маршрутизация",
|
|
|
|
|
|
detail="Тип происшествия или ДДС определены неверно",
|
|
|
|
|
|
source=FindingSource.GROUND_TRUTH,
|
|
|
|
|
|
),
|
|
|
|
|
|
ErrorCode.E3: ErrorSpec(
|
|
|
|
|
|
code=ErrorCode.E3,
|
|
|
|
|
|
title="Нарушение временного норматива",
|
|
|
|
|
|
detail="Опрос дольше 75 с, ДДС не оповещена за 60 с и т. д.",
|
|
|
|
|
|
source=FindingSource.TIMERS,
|
|
|
|
|
|
),
|
|
|
|
|
|
ErrorCode.E4: ErrorSpec(
|
|
|
|
|
|
code=ErrorCode.E4,
|
2026-09-26 18:12:27 +03:00
|
|
|
|
title="Грамматическая ошибка",
|
|
|
|
|
|
detail="Нарушен включённый критерий грамматики описания КИО",
|
|
|
|
|
|
source=FindingSource.GRAMMAR,
|
каркас: домен, генерация типов фронта, стенд и карточки задач
Контракт в коде: КИО по нормативу, 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
|
|
|
|
),
|
|
|
|
|
|
ErrorCode.E5: ErrorSpec(
|
|
|
|
|
|
code=ErrorCode.E5,
|
|
|
|
|
|
title="Неполнота карточки",
|
|
|
|
|
|
detail="Обязательные поля КИО пусты или заполнены неверно",
|
|
|
|
|
|
source=FindingSource.KIO,
|
|
|
|
|
|
),
|
|
|
|
|
|
ErrorCode.E6: ErrorSpec(
|
|
|
|
|
|
code=ErrorCode.E6,
|
|
|
|
|
|
title="Неверное завершение",
|
|
|
|
|
|
detail="Карточка ушла в ДДС непригодной, снятие с контроля до подтверждения",
|
|
|
|
|
|
source=FindingSource.CHAIN,
|
|
|
|
|
|
),
|
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
|
|
|
|
ErrorCode.D1: ErrorSpec(
|
|
|
|
|
|
code=ErrorCode.D1,
|
|
|
|
|
|
title="Статус реагирования не проставлен",
|
|
|
|
|
|
detail="Карточка осталась без первичного статуса и ушла в «Не оповещено»",
|
|
|
|
|
|
source=FindingSource.DISPATCHER,
|
|
|
|
|
|
),
|
|
|
|
|
|
ErrorCode.D2: ErrorSpec(
|
|
|
|
|
|
code=ErrorCode.D2,
|
|
|
|
|
|
title="Статус не соответствует факту",
|
|
|
|
|
|
detail="«Принята» там, где работы проводиться не будут, и наоборот",
|
|
|
|
|
|
source=FindingSource.DISPATCHER,
|
|
|
|
|
|
),
|
|
|
|
|
|
ErrorCode.D3: ErrorSpec(
|
|
|
|
|
|
code=ErrorCode.D3,
|
|
|
|
|
|
title="Отказ от профильного происшествия",
|
|
|
|
|
|
detail="Служба есть в списке оповещения, значит происшествие её — отказ неправомерен",
|
|
|
|
|
|
source=FindingSource.DISPATCHER,
|
|
|
|
|
|
),
|
|
|
|
|
|
ErrorCode.D4: ErrorSpec(
|
|
|
|
|
|
code=ErrorCode.D4,
|
|
|
|
|
|
title="Нет комментария к отказу",
|
|
|
|
|
|
detail="«Не принята» и «Отказ от выполнения работ» без причины",
|
|
|
|
|
|
source=FindingSource.DISPATCHER,
|
|
|
|
|
|
),
|
|
|
|
|
|
ErrorCode.D5: ErrorSpec(
|
|
|
|
|
|
code=ErrorCode.D5,
|
|
|
|
|
|
title="Неполный комментарий",
|
2026-09-26 18:12:27 +03:00
|
|
|
|
detail="В комментарии не назван получатель переданных сведений",
|
|
|
|
|
|
source=FindingSource.DISPATCHER,
|
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
|
|
|
|
),
|
|
|
|
|
|
ErrorCode.D6: ErrorSpec(
|
|
|
|
|
|
code=ErrorCode.D6,
|
|
|
|
|
|
title="Нет статусов хода работ",
|
|
|
|
|
|
detail="Реагирование шло, но начало, прибытие и завершение не отмечены",
|
|
|
|
|
|
source=FindingSource.DISPATCHER,
|
|
|
|
|
|
),
|
каркас: домен, генерация типов фронта, стенд и карточки задач
Контракт в коде: КИО по нормативу, 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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Competency(StrEnum):
|
|
|
|
|
|
"""Шесть осей радара. Проекция уже посчитанных метрик, без пересчёта весов."""
|
|
|
|
|
|
|
|
|
|
|
|
INTAKE = "intake"
|
|
|
|
|
|
INTERVIEW = "interview"
|
|
|
|
|
|
CARD = "card"
|
|
|
|
|
|
ROUTING = "routing"
|
|
|
|
|
|
NORMS = "norms"
|
|
|
|
|
|
COMMUNICATION = "communication"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
COMPETENCY_LABELS: dict[Competency, str] = {
|
|
|
|
|
|
Competency.INTAKE: "Приём вызова",
|
|
|
|
|
|
Competency.INTERVIEW: "Опрос и добыча фактов",
|
|
|
|
|
|
Competency.CARD: "Заполнение КИО",
|
|
|
|
|
|
Competency.ROUTING: "Классификация и маршрутизация",
|
|
|
|
|
|
Competency.NORMS: "Соблюдение нормативов",
|
|
|
|
|
|
Competency.COMMUNICATION: "Коммуникация в стрессе",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-09-27 16:13:21 +03:00
|
|
|
|
class FindingDecision(StrEnum):
|
|
|
|
|
|
"""Решение преподавателя по отметке на разборе."""
|
|
|
|
|
|
|
|
|
|
|
|
CONFIRMED = "confirmed"
|
|
|
|
|
|
DISMISSED = "dismissed"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FindingReview(BaseModel):
|
|
|
|
|
|
"""Кто, когда и почему подтвердил или снял отметку. Сама отметка остаётся."""
|
|
|
|
|
|
|
|
|
|
|
|
decision: FindingDecision
|
|
|
|
|
|
reason: str
|
|
|
|
|
|
author: str
|
|
|
|
|
|
at: datetime
|
|
|
|
|
|
|
|
|
|
|
|
|
каркас: домен, генерация типов фронта, стенд и карточки задач
Контракт в коде: КИО по нормативу, 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
|
|
|
|
class Finding(BaseModel):
|
|
|
|
|
|
"""Отметка в разборе. `fact` и `norm` — то самое обоснование."""
|
|
|
|
|
|
|
|
|
|
|
|
code: ErrorCode
|
|
|
|
|
|
source: FindingSource
|
|
|
|
|
|
summary: str
|
|
|
|
|
|
fact: str
|
|
|
|
|
|
norm: str | None = None
|
|
|
|
|
|
ref: str | None = None
|
|
|
|
|
|
competency: Competency | None = None
|
|
|
|
|
|
transcript_ref: str | None = None
|
|
|
|
|
|
at: datetime | None = None
|
2026-09-27 16:13:21 +03:00
|
|
|
|
#: Метрика, чей провал объясняет отметка. Без неё связь с метриками идёт
|
|
|
|
|
|
#: по методике METRIC_MAP в пределах той же карточки и службы.
|
|
|
|
|
|
metric_key: str | None = None
|
|
|
|
|
|
#: Номер карточки очереди ДДС (с 1) и служба, к которым относится отметка.
|
|
|
|
|
|
card: int | None = None
|
|
|
|
|
|
service: str | None = None
|
|
|
|
|
|
#: Автор ручной отметки преподавателя (`source=instructor`).
|
|
|
|
|
|
author: str | None = None
|
2026-09-27 22:32:41 +03:00
|
|
|
|
#: Ключ идемпотентности ручной отметки: повтор запроса не добавляет её снова.
|
|
|
|
|
|
client_id: UUID | None = None
|
|
|
|
|
|
#: Все решения по отметке по порядку; действует последнее. Прежние не
|
|
|
|
|
|
#: затираются: «кто, когда, почему» нужно по каждой правке, а не по итогу.
|
|
|
|
|
|
reviews: list[FindingReview] = []
|
|
|
|
|
|
|
|
|
|
|
|
@model_validator(mode="before")
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def _single_review(cls, data: Any) -> Any:
|
|
|
|
|
|
# Разборы, сохранённые до истории решений, хранили одно поле `review`.
|
|
|
|
|
|
if isinstance(data, dict) and "review" in data:
|
|
|
|
|
|
data = dict(data)
|
|
|
|
|
|
single = data.pop("review")
|
|
|
|
|
|
if single and not data.get("reviews"):
|
|
|
|
|
|
data["reviews"] = [single]
|
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def review(self) -> FindingReview | None:
|
|
|
|
|
|
"""Действующее решение — последнее."""
|
|
|
|
|
|
return self.reviews[-1] if self.reviews else None
|