122 lines
4 KiB
Python
122 lines
4 KiB
Python
"""Библиотека в памяти процесса и её заливка в БД.
|
||
|
||
В памяти живёт то, что читает голосовой контур и оценка; в БД — то, из чего
|
||
преподаватель выбирает сценарий и что переживает перезапуск.
|
||
"""
|
||
|
||
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
|