Весь продукт стоял на допущении «звонок → карточка → выезд». В билетах заказчика оно нарушается намеренно: «поругался с продавцом Мегафон» — справка, а вызов из Волгоградской области передаётся в систему-112 другого субъекта. Курсант, заведший карточку на такой вызов, занял расчёт зря, и прежняя оценка этого не видела — наоборот, награждала за полноту заполнения. - Outcome в домене, поле outcome в сценарии, событие call.resolve у курсанта и две кнопки на АРМ. Отдельное действие, а не «положил трубку»: система должна отличить осознанное решение от брошенного вызова. - Метрика outcome (E2, вес 3 — лишний выезд дороже неточного признака). - Там, где карточка не заводится, метрики карточки не считаются вовсе. Полнота опроса считается: передать вызов не значит не опрашивать. - call.resolve останавливает норматив опроса, как и передача карточки. Попутно (lct-34): библиотека стала вложенной — scenarios/tickets/, поля ticket и position, чек-листы для медицины, полиции и ЖКХ. Перенесён билет 1 целиком: три вызова, три классификатора, третий — из другого региона. Найдено по ходу: модификаторы списка оповещения не были подключены ни к чему. В классификаторе скорая добавляется к массовой драке признаком «пострадавшие», но взять его было неоткуда. Теперь модификаторы берутся из карточки — victims_count, life_threat, evacuation_needed, fire.gasified, — и список оповещения пересобирается при правке любого из этих полей. 161 тест зелёный (13 новых), make typecheck чистый.
52 lines
2.6 KiB
Python
52 lines
2.6 KiB
Python
"""Какая метрика каким кодом ошибки и какой компетенцией размечается.
|
||
|
||
Это методика, а не вычисление: таблица читается глазами и сверяется
|
||
с docs/product/METHODOLOGY.md. Считает метрики gost.py, сюда он только смотрит.
|
||
"""
|
||
|
||
from app.domain.taxonomy import Competency, ErrorCode
|
||
|
||
#: Метрика → (код ошибки при провале, компетенция радара).
|
||
METRIC_MAP: dict[str, tuple[ErrorCode, Competency]] = {
|
||
"answer_time": (ErrorCode.E3, Competency.INTAKE),
|
||
"dds_chain": (ErrorCode.E6, Competency.CARD),
|
||
"callback": (ErrorCode.E3, Competency.INTAKE),
|
||
"checklist_completeness": (ErrorCode.E1, Competency.INTERVIEW),
|
||
"address_refined": (ErrorCode.E1, Competency.INTERVIEW),
|
||
"interview_time": (ErrorCode.E3, Competency.NORMS),
|
||
"outcome": (ErrorCode.E2, Competency.ROUTING),
|
||
"incident_signs": (ErrorCode.E2, Competency.ROUTING),
|
||
"incident_type": (ErrorCode.E2, Competency.ROUTING),
|
||
"dds_choice": (ErrorCode.E2, Competency.ROUTING),
|
||
"address": (ErrorCode.E5, Competency.CARD),
|
||
"victims_count": (ErrorCode.E5, Competency.CARD),
|
||
"required_fields": (ErrorCode.E5, Competency.CARD),
|
||
}
|
||
|
||
#: Вес метрики в детерминированной оценке.
|
||
#:
|
||
#: **Предварительные значения, требуют утверждения методистом.** Без весов все
|
||
#: метрики равны, и курсант, не задавший ни одного вопроса, но заполнивший
|
||
#: карточку руками, получает 87 из 100: «ответ за секунду» стоит столько же,
|
||
#: сколько «добыл все обязательные факты». Опрос — то, ради чего существует
|
||
#: тренажёр, поэтому он весит больше всего.
|
||
METRIC_WEIGHTS: dict[str, float] = {
|
||
"checklist_completeness": 4.0,
|
||
"address_refined": 2.0,
|
||
"outcome": 3.0,
|
||
"incident_signs": 2.0,
|
||
"incident_type": 2.0,
|
||
"dds_choice": 2.0,
|
||
"address": 2.0,
|
||
"required_fields": 2.0,
|
||
"interview_time": 1.5,
|
||
"victims_count": 1.0,
|
||
"answer_time": 1.0,
|
||
"callback": 1.0,
|
||
"dds_chain": 2.0,
|
||
}
|
||
|
||
#: Вес детерминированного слоя в итоговой оценке. Остальное — LLM-судья
|
||
#: на мягкие критерии (E4), и не больше (docs/arch/BACKEND.md).
|
||
DETERMINISTIC_WEIGHT = 0.6
|
||
JUDGE_WEIGHT = 0.4
|