lct-hack/backend/scripts/start_lesson.py
Ivan Gerasimov f24031b5b2 lct-08: каркас фронта, типизированный сокет, захват микрофона
Направление канала зашито в тип: у наблюдателя Out = never, отправить
нечего и нельзя; у преподавателя нет входящих. Разделение прав
архитектурное и на фронте тоже. Пока соединения нет, события не копятся:
истина на сервере, после переподключения придёт актуальное состояние.

Ресемплинг 48 → 16 кГц усредняет по окну выходного сэмпла: без фильтра
всё выше 8 кГц наложилось бы на речь и испортило распознавание. Проверено
числами: секунда звука — ровно 50 кадров по 320 сэмплов из 48 и 44.1 кГц,
помеха 15 кГц подавлена до 7.8%. Эхоподавление и автоусиление браузера
выключены: гарнитура эхо не даёт, а обработка портит речь для STT.

Бэкенд принимает бинарные кадры по тому же сокету, что и события; кадр
не того размера отбрасывается с одним предупреждением, а не 50 в секунду.

Логирование приложения не было настроено: uvicorn настраивает только
свои логгеры, и весь INFO модулей app.* молча терялся — нашлось, когда
в логе не оказалось строки о принятых кадрах.

make lesson запускает занятие и печатает ссылки на экраны — пульта
преподавателя ещё нет, а открыть АРМ курсанта без занятия не с чем.

Путь из браузера с живым микрофоном не проверен: нужен человек, шаги
записаны в карточке.
2026-09-17 14:19:38 +03:00

42 lines
1.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""make lesson: запустить занятие и напечатать ссылки на экраны.
Нужен, пока нет пульта преподавателя (lct-17): без запущенного занятия
АРМ курсанта открыть не с чем. Занятие живёт в памяти сервера и после
выхода скрипта остаётся запущенным.
make lesson сценарий по умолчанию, тренировочный режим
make lesson s=fire-apartment-l2 m=exam
"""
import asyncio
import json
import sys
import uuid
import websockets
BACKEND = "ws://localhost:8000"
FRONTEND = "http://localhost:5173"
async def main(scenario_id: str, mode: str) -> None:
session_id = str(uuid.uuid4())
async with websockets.connect(f"{BACKEND}/ws/control/{session_id}") as control:
await control.send(json.dumps({
"type": "scenario.start",
"scenario_id": scenario_id,
"trainee": "Курсант",
"mode": mode,
}))
await asyncio.sleep(0.5) # дать серверу зарегистрировать занятие до закрытия сокета
print(f"занятие {session_id} — {scenario_id}, режим {mode}")
print(f" курсант: {FRONTEND}/trainee?session={session_id}")
print(f" монитор: {FRONTEND}/wall?session={session_id}")
print(f" преподаватель: {FRONTEND}/instructor?session={session_id}")
if __name__ == "__main__":
scenario = sys.argv[1] if len(sys.argv) > 1 and sys.argv[1] else "fire-apartment-l2"
mode = sys.argv[2] if len(sys.argv) > 2 and sys.argv[2] else "training"
asyncio.run(main(scenario, mode))