lct-39: пауза не искажает оценку ДДС и не блокирует завершённое занятие

This commit is contained in:
kaifarikman 2026-09-27 17:18:27 +03:00
commit 2304d94877
5 changed files with 202 additions and 10 deletions

View file

@ -256,7 +256,9 @@ async def active(
),
dds_work_overdue_cards=sum(
(timer := card.timers.timers.get(TimerCode.DDS_WORK)) is not None
and timer.started_at is not None
# Пауза держит `started_at=None`, хотя таймер уже шёл: судить
# по нему одному спрятало бы уже случившееся нарушение (lct-39).
and (timer.started_at is not None or timer.paused)
and not timer.stopped
and timer.current_ms(time.monotonic()) > card.timers.limits[TimerCode.DDS_WORK]
for card in state.desk.cards.values()

View file

@ -31,6 +31,7 @@ from app.domain.events import (
PatchSource,
ScoreReady,
SessionMode,
SessionPaused,
StationState,
TimerTick,
TextTurnAccepted,
@ -398,6 +399,10 @@ async def call(ws: WebSocket, session_id: UUID) -> None:
_who, state = entered
with hub.trainee(session_id) as queue:
if state.paused:
# Переподключение не ждёт следующего `session.pause`: баннер должен
# быть на экране сразу, раз сервер уже отклоняет команды (lct-39).
hub.to_trainee(session_id, SessionPaused(paused=True))
if state.exercise is Exercise.CARD:
from app.api.ws.control import card_briefing
@ -440,6 +445,10 @@ async def call(ws: WebSocket, session_id: UUID) -> None:
# Бинарные кадры — аудио, текстовые — события. Направление определяется
# каналом, обёртки JSON вокруг звука нет (docs/arch/CONTRACT.md).
if message.get("bytes") is not None:
if state.paused:
# JSON-команды отказывает `_handle`; бинарные кадры сюда
# не заходят, поэтому пауза глушит звук здесь же (lct-39).
continue
if state.exercise is Exercise.CALL:
_on_audio(session_id, state, message["bytes"])
else: