lct-05: состояние сессии, таймеры по событиям, четыре канала
Таймеры объявлены таблицей «событие → старт/стоп», а не разбросаны по обработчикам: так видно целиком, что чем запускается, и норматив нельзя потерять по дороге. Опрос стартует на ответе курсанта и останавливается передачей в ДДС — событием, а не таймаутом. Канал наблюдателя без единого обработчика входящих: кадры читаются и выбрасываются, не разбираясь, только чтобы заметить разрыв. Без чтения задача сокета висела бы на очереди до первой отправки, а закрытая вкладка монитора оставляла бы подписку. Тест проверяет, что наблюдатель не может ни изменить карточку, ни завершить занятие. Пульт теперь заводит сессию в журнале: запущенное с него занятие жило только в памяти, и реплики с подсказками уходили в нарушение внешнего ключа — журнал об этом честно сообщал в лог. Тикер таймеров гасится при остановке приложения, иначе задачи переживают выключение и держат событийный цикл. Проверено вживую через uvicorn: монитор, подключённый посреди занятия, получает снимок с заполненной карточкой; такт таймера идёт раз в секунду.
This commit is contained in:
parent
6b2c96bab6
commit
e7a0798ba7
12 changed files with 1029 additions and 0 deletions
|
|
@ -29,6 +29,7 @@ async def create_session(
|
|||
mode: str,
|
||||
trainee_id: UUID | None = None,
|
||||
group_id: UUID | None = None,
|
||||
session_id: UUID | None = None,
|
||||
) -> Session:
|
||||
session = Session(
|
||||
scenario_id=scenario_id,
|
||||
|
|
@ -37,11 +38,43 @@ async def create_session(
|
|||
group_id=group_id,
|
||||
attempt=await next_attempt(db, trainee_id, scenario_id),
|
||||
)
|
||||
if session_id is not None:
|
||||
session.id = session_id
|
||||
db.add(session)
|
||||
await db.commit()
|
||||
return session
|
||||
|
||||
|
||||
async def ensure_session(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
session_id: UUID,
|
||||
scenario_id: str,
|
||||
mode: str,
|
||||
trainee_name: str | None = None,
|
||||
group_name: str | None = None,
|
||||
) -> Session:
|
||||
"""Занятие, запущенное с пульта, должно иметь строку в журнале.
|
||||
|
||||
Иначе реплики, подсказки и пометки не к чему привязать: они уходят
|
||||
в нарушение внешнего ключа, а профиль курсанта остаётся пустым.
|
||||
"""
|
||||
existing = await db.get(Session, session_id)
|
||||
if existing is not None:
|
||||
return existing
|
||||
|
||||
group = await ensure_group(db, group_name) if group_name else None
|
||||
trainee = await ensure_trainee(db, trainee_name, group) if trainee_name else None
|
||||
return await create_session(
|
||||
db,
|
||||
scenario_id=scenario_id,
|
||||
mode=mode,
|
||||
trainee_id=trainee.id if trainee else None,
|
||||
group_id=group.id if group else None,
|
||||
session_id=session_id,
|
||||
)
|
||||
|
||||
|
||||
async def get_session(db: AsyncSession, session_id: UUID) -> Session | None:
|
||||
return await db.get(Session, session_id)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue