lct-50: таймеры ДДС — 30 с до открытия карточки, 3 минуты до первой записи

This commit is contained in:
Ivan Gerasimov 2026-09-27 23:38:10 +03:00
commit 51ef99c472
18 changed files with 169 additions and 169 deletions

View file

@ -22,7 +22,7 @@ from app.domain.statuses import (
)
from app.domain.taxonomy import Competency, ErrorCode, Finding, FindingSource
from app.domain.events import Metric
from app.domain.timers import GOST_REF
from app.domain.timers import GOST_REF, TimerCode
#: Статусы, означающие, что реагирование действительно шло.
PROGRESS = (ServiceStatus.RESPONDING, ServiceStatus.ARRIVED, ServiceStatus.WORKING)
@ -161,37 +161,37 @@ def evaluate_dispatcher(
),
))
# D1 — первичного статуса нет либо он проставлен позже норматива.
# D1 — карточка открыта позже норматива или не открыта вовсе. 30 с идут
# от появления карточки в строке сообщений до её открытия (ответ
# заказчика, П.5); `elapsed_ms` — зафиксированное время открытия.
if elapsed_ms is None or elapsed_ms > deadline_ms:
findings.append(
_finding(
ErrorCode.D1,
(f"{service}: карточка открыта с нарушением срока"
if elapsed_ms is not None else f"{service}: карточка не открыта"),
fact=(
f"открыта через {elapsed_ms // 1000} с"
if elapsed_ms is not None
else "карточка не открыта до конца занятия"
),
norm=f"открыть карточку в течение {deadline_ms // 1000} с",
)
)
# D1 — первичного статуса нет: карточка ушла в «Не оповещено».
primary = next((mark for mark in marks if mark.status in PRIMARY), None)
if primary is None:
findings.append(
_finding(
ErrorCode.D1,
f"{service}: статус реагирования не проставлен",
fact=(
f"прошло {elapsed_ms // 1000} с"
if elapsed_ms is not None
else "карточка осталась без отметки"
),
norm=f"первичный статус в течение {deadline_ms // 1000} с",
fact="карточка осталась без первичного статуса",
norm="проставить «Принята» или «Не принята» с комментарием",
)
)
continue
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} с",
)
)
for mark in marks:
# D4 — отказ без комментария.
if mark.status in REFUSAL_COMMENT_REQUIRED and not mark.comment.strip():
@ -307,13 +307,14 @@ def dispatcher_metrics(
))
primary = next((mark for mark in marks if mark.status in PRIMARY), None)
elapsed = (int((primary.at - state.dispatched_at).total_seconds() * 1000)
if primary and state.dispatched_at else None)
# Время открытия — то, что зафиксировал таймер событием, а не разница
# отметок: 30 с по ответу заказчика идут до открытия карточки.
elapsed = state.timers.measured_ms(TimerCode.DDS_ACK)
add("dds_primary", "решение по карточке", primary is not None,
primary.status.value if primary else "решение отсутствует", "Принята или Не принята", 2.0)
add("dds_ack", f"первичная отметка за {deadline_ms // 1000} с",
add("dds_ack", f"открытие карточки за {deadline_ms // 1000} с",
elapsed is not None and elapsed <= deadline_ms,
f"{elapsed / 1000:.1f} с" if elapsed is not None else "не отмечено",
f"{elapsed / 1000:.1f} с" if elapsed is not None else "карточка не открыта",
f"≤ {deadline_ms // 1000} с")
if primary is None:
continue

View file

@ -37,7 +37,7 @@ def to_csv(report: SessionReport) -> bytes:
row("Занятие", "", "Сценарий", report.scenario_id)
row("Занятие", "", "Режим", report.mode.value)
row("Занятие", "", "Попытка", report.attempt)
row("Критерии", "", "Лимит решения ДДС, с", report.criteria.decision_time_limit_seconds)
row("Критерии", "", "Лимит открытия карточки ДДС, с", report.criteria.decision_time_limit_seconds)
row("Критерии", "", "Допустимо ошибок", report.criteria.allowed_errors)
row("Критерии", "", "Грамматика обязательна", "да" if report.criteria.require_correct_grammar else "нет")
row("Критерии", "", "Переопределения весов", report.criteria.score_weights or "стандартные")
@ -150,7 +150,7 @@ def to_pdf(report: SessionReport) -> bytes:
("Сценарий", report.scenario_id),
("Режим", report.mode.value),
("Попытка", report.attempt),
("Лимит решения ДДС", f"{report.criteria.decision_time_limit_seconds} с"),
("Лимит открытия карточки ДДС", f"{report.criteria.decision_time_limit_seconds} с"),
("Допустимо ошибок", report.criteria.allowed_errors),
("Грамматика обязательна", "да" if report.criteria.require_correct_grammar else "нет"),
("Переопределения весов", report.criteria.score_weights or "стандартные"),

View file

@ -44,11 +44,11 @@ CARD_FILL_TIME = TimeNorm(
)
DDS_WORK_TIME = TimeNorm(
key="dds_work_time",
title="Отработка карточки ДДС",
summary="ДДС: норматив времени отработки карточки не выполнен",
action="завершить",
unmeasured="время обработки не зафиксировано",
incomplete="карточка не завершена",
title="Первая запись по карточке ДДС",
summary="ДДС: первая запись по карточке позже норматива",
action="открыть карточку и добавить первую запись (статус и текст)",
unmeasured="карточка не открыта",
incomplete="первая запись не добавлена",
)