lct-hack/backend/app/scenarios/store.py
2026-09-21 17:40:54 +03:00

122 lines
4 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.

"""Библиотека в памяти процесса и её заливка в БД.
В памяти живёт то, что читает голосовой контур и оценка; в БД — то, из чего
преподаватель выбирает сценарий и что переживает перезапуск.
"""
from pathlib import Path
from uuid import uuid4
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.models import Scenario as ScenarioRow
from app.scenarios.loader import load_library
from app.scenarios.schema import Scenario
from app.scenarios import editor
_library: dict[str, Scenario] = {}
def set_library(scenarios: list[Scenario]) -> None:
_library.clear()
_library.update({scenario.id: scenario for scenario in scenarios})
def get(scenario_id: str) -> Scenario | None:
return _library.get(scenario_id)
def all_scenarios() -> list[Scenario]:
return list(_library.values())
def publish(scenario: Scenario) -> None:
"""Сделать утверждённую версию доступной запуску занятия немедленно."""
_library[scenario.id] = scenario
def load_from_disk(root: Path) -> list[Scenario]:
scenarios = load_library(root)
set_library(scenarios)
return scenarios
async def seed(db: AsyncSession, scenarios: list[Scenario]) -> int:
"""Залить библиотеку в БД. Повторный запуск обновляет, а не дублирует."""
for scenario in scenarios:
row = await db.scalar(select(ScenarioRow).where(ScenarioRow.id == scenario.id))
payload = scenario.model_dump(mode="json")
if row is None:
row = ScenarioRow(id=scenario.id)
db.add(row)
row.title = scenario.title
row.incident_type = scenario.type.value
row.level = scenario.level.value
row.topics = scenario.topics
row.modes = scenario.modes
row.body = payload
await db.commit()
return len(scenarios)
async def restore_published(db: AsyncSession) -> int:
"""После старта вернуть утверждённые преподавателем сценарии из БД.
Вызвать после ``load_from_disk``. Базовую библиотеку не перезаписываем:
её источником остаются проверенные YAML из репозитория.
"""
rows = await db.scalars(select(ScenarioRow).where(ScenarioRow.status == "published"))
restored = 0
for row in rows:
if row.id in _library:
continue
_library[row.id] = Scenario.model_validate(row.body)
restored += 1
return restored
async def create_draft(
db: AsyncSession, *, source: Scenario, title: str | None = None
) -> ScenarioRow:
draft_id = f"draft-{uuid4().hex}"
body = editor.template_copy(source, draft_id, title)
row = ScenarioRow(
id=draft_id,
title=body["title"],
incident_type=body["type"],
level=body["level"],
topics=body["topics"],
modes=body["modes"],
status="draft",
body=body,
)
db.add(row)
await db.commit()
return row
async def draft(db: AsyncSession, scenario_id: str) -> ScenarioRow | None:
row = await db.get(ScenarioRow, scenario_id)
return row if row is not None and row.status == "draft" else None
async def update_draft(db: AsyncSession, row: ScenarioRow, patch: dict) -> ScenarioRow:
row.body = editor.merge_patch(row.body, patch)
row.title = str(row.body.get("title") or "")[:200]
await db.commit()
return row
async def approve_draft(db: AsyncSession, row: ScenarioRow) -> Scenario:
scenario = editor.validate(row.body)
row.title = scenario.title
row.incident_type = scenario.type.value
row.level = scenario.level.value
row.topics = scenario.topics
row.modes = scenario.modes
row.body = scenario.model_dump(mode="json")
row.status = "published"
await db.commit()
publish(scenario)
return scenario