"""File-backed demo scenarios used by the PoC4 Streamlit screen. Scenario content is deliberately configuration, not executable routing policy. Changing this JSON changes only the menu shown to a demo user. """ from __future__ import annotations from dataclasses import dataclass import json from pathlib import Path from typing import Any, Mapping class ScenarioConfigError(RuntimeError): """A safe, user-facing error for invalid scenario configuration.""" @dataclass(frozen=True) class DemoScenario: scenario_id: str category: str title: str question: str # Compatibility aliases keep the Streamlit rendering independent from the # storage field names and make a future scenario source interchangeable. @property def question_id(self) -> str: return self.scenario_id @property def text(self) -> str: return self.question def load_demo_scenarios(path: Path) -> tuple[DemoScenario, ...]: """Read enabled scenarios and reject malformed or duplicated entries.""" try: payload: Any = json.loads(path.read_text(encoding="utf-8")) except (OSError, UnicodeError, ValueError): raise ScenarioConfigError(f"질문 시나리오 설정을 읽지 못했습니다: {path}") from None raw_scenarios = payload.get("scenarios") if isinstance(payload, Mapping) else None if not isinstance(raw_scenarios, list): raise ScenarioConfigError("질문 시나리오 설정에 scenarios 배열이 필요합니다.") scenarios: list[DemoScenario] = [] seen_ids: set[str] = set() for raw in raw_scenarios: if not isinstance(raw, Mapping) or raw.get("enabled", True) is not True: continue scenario_id = str(raw.get("id") or "").strip().upper() category = str(raw.get("category") or "일반").strip() title = str(raw.get("title") or "").strip() question = str(raw.get("question") or "").strip() if not scenario_id or not title or not question: raise ScenarioConfigError("각 질문 시나리오에는 id, title, question이 필요합니다.") if scenario_id in seen_ids: raise ScenarioConfigError(f"중복된 질문 시나리오 ID입니다: {scenario_id}") scenarios.append( DemoScenario( scenario_id=scenario_id, category=category, title=title, question=question, ) ) seen_ids.add(scenario_id) return tuple(scenarios)