БД: 11 таблиц, первая миграция. Группы и связь trainee → group заложены
сразу, даже пустыми — размечать накопленные сессии задним числом значит
делать лишнюю миграцию. Номер попытки живёт в сессии, отдельной таблицы
попыток нет: дельта считается запросом по (trainee_id, scenario_id).
Сценарии: строгая схема — опечатка в имени поля падает на старте, а не
игнорируется молча. ground_truth собирается кодом, попытка задать
incident_type, dds или required_facts в YAML отвергается: иначе генератор
разведёт факты и эталон и курсанта оштрафуют за правильный ответ. Руками
задаются только нормализованные адрес и число пострадавших — из фразы
«улица Ленина, 14, квартира 47, 5-й этаж» кодом «улица Ленина, 14»
не достать.
GET /api/scenarios/{id} больше не отдаёт чек-лист. Это содержимое
подсказок: отдать его целиком значит выдать в контрольном режиме то,
чего там быть не должно, в обход выдачи по одному пункту.
Тесты базы поднимают свой движок на каждый тест: глобальный кэшируется
и привязывается к первому событийному циклу.
48 lines
1.8 KiB
Python
48 lines
1.8 KiB
Python
"""Сборка приложения. Роутеры подключаются по мере готовности — см. tasks/."""
|
|
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
|
|
from pathlib import Path
|
|
|
|
from app.api.http import scenarios as scenarios_api
|
|
from app.api.http import sessions
|
|
from app.config import get_settings
|
|
from app.scenarios import store
|
|
from app.scenarios.loader import ScenarioError
|
|
|
|
|
|
LIBRARY = Path(__file__).resolve().parents[2] / "scenarios"
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
# Библиотека проверяется на старте целиком: сломанный сценарий, найденный
|
|
# посреди занятия, — сценарий, которого не должно случиться.
|
|
try:
|
|
loaded = store.load_from_disk(LIBRARY)
|
|
except ScenarioError as exc:
|
|
raise RuntimeError(f"библиотека сценариев не прошла проверку: {exc}") from exc
|
|
app.state.scenarios_loaded = len(loaded)
|
|
|
|
# Прогрев моделей — карточка lct-06.
|
|
app.state.models_ready = False
|
|
yield
|
|
|
|
|
|
app = FastAPI(title="Учебный симулятор занятия для системы 112", lifespan=lifespan)
|
|
app.include_router(sessions.router)
|
|
app.include_router(scenarios_api.router)
|
|
|
|
|
|
@app.get("/api/health")
|
|
async def health() -> dict:
|
|
"""Готовность стенда. Фронт показывает экран «модели прогреваются»."""
|
|
settings = get_settings()
|
|
return {
|
|
"status": "ok",
|
|
"models_ready": getattr(app.state, "models_ready", False),
|
|
"scenarios_loaded": getattr(app.state, "scenarios_loaded", 0),
|
|
"offline": settings.offline,
|
|
}
|