From 9b975e4c0153a0a1ff84ad22442ec7411c672322 Mon Sep 17 00:00:00 2001 From: learncold Date: Fri, 10 Apr 2026 23:03:01 +0900 Subject: [PATCH] [Application] align v1.1 workspace UI design --- CMakeLists.txt | 3 + ...5\355\212\270 \352\265\254\354\241\260.md" | 21 +- src/application/MainWindow.cpp | 266 +++++++++++++----- src/application/MainWindow.h | 1 + src/domain/ProjectRepository.h | 32 +++ src/domain/ScenarioAuthoring.h | 63 +++++ src/domain/ScenarioTemplateCatalog.h | 35 +++ ... \354\235\275\353\212\224 \353\262\225.md" | 19 +- uml/application-analysis-workspace.puml | 82 ++++++ ...orkspace.puml \355\225\264\354\204\244.md" | 56 ++++ uml/application-authoring-workspace.puml | 78 +++++ ...orkspace.puml \355\225\264\354\204\244.md" | 56 ++++ uml/application-run-results-workflow.puml | 118 +++++--- ...workflow.puml \355\225\264\354\204\244.md" | 108 +++---- uml/application-workspace-state-model.puml | 80 ++++++ ...te-model.puml \355\225\264\354\204\244.md" | 56 ++++ uml/project-structure.puml | 28 +- ...tructure.puml \355\225\264\354\204\244.md" | 24 +- 18 files changed, 965 insertions(+), 161 deletions(-) create mode 100644 src/domain/ProjectRepository.h create mode 100644 src/domain/ScenarioAuthoring.h create mode 100644 src/domain/ScenarioTemplateCatalog.h create mode 100644 uml/application-analysis-workspace.puml create mode 100644 "uml/application-analysis-workspace.puml \355\225\264\354\204\244.md" create mode 100644 uml/application-authoring-workspace.puml create mode 100644 "uml/application-authoring-workspace.puml \355\225\264\354\204\244.md" create mode 100644 uml/application-workspace-state-model.puml create mode 100644 "uml/application-workspace-state-model.puml \355\225\264\354\204\244.md" diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b80702..749365c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,9 @@ add_library(safecrowd_domain STATIC src/domain/SafeCrowdDomain.cpp src/domain/Geometry2D.h src/domain/PopulationSpec.h + src/domain/ScenarioAuthoring.h + src/domain/ProjectRepository.h + src/domain/ScenarioTemplateCatalog.h src/domain/RawImportModel.h src/domain/CanonicalGeometry.h src/domain/DemoFixtureService.h diff --git "a/docs/architecture/\355\224\204\353\241\234\354\240\235\355\212\270 \352\265\254\354\241\260.md" "b/docs/architecture/\355\224\204\353\241\234\354\240\235\355\212\270 \352\265\254\354\241\260.md" index b2c8f7c..6e2f6e7 100644 --- "a/docs/architecture/\355\224\204\353\241\234\354\240\235\355\212\270 \352\265\254\354\241\260.md" +++ "b/docs/architecture/\355\224\204\353\241\234\354\240\235\355\212\270 \352\265\254\354\241\260.md" @@ -100,8 +100,11 @@ Project/ - 공간 구조 - 시나리오 데이터 - `FacilityLayout`, `PopulationSpec`, `EnvironmentState`, `ControlPlan`, `ExecutionConfig` 입력 계약 +- `ScenarioDraft` 같은 application authoring 계약과 baseline/alternative/recommended 구분 - `FacilityLayout` 안의 `Room`, `Door`, `Connector`, `ControlZone`, `MeasurementRegionSpec` 같은 실행 계약 - baseline과 대안 비교를 위한 `ScenarioVariation` +- `ScenarioTemplateCatalog` 같은 authoring helper 계약 +- `ProjectRepository`, `ResultRepository` 같은 저장 경계 계약 - 시나리오 보정(`ScenarioCalibrationService`) - 단일 실행 세션(`SimulationSession`)과 배치 실행(`ScenarioBatchRunner`) - 시뮬레이션 규칙 @@ -120,10 +123,12 @@ Project/ 포함할 것: - `main()` - 메인 윈도우 -- 시나리오 편집 UI -- 실행/정지 버튼 -- 결과 표시 화면 -- 추천안 검토 및 적용 화면 +- `Project`, `Authoring`, `Run`, `Analysis` workspace shell +- import review, layout correction canvas, scenario library, template picker, scenario editor, readiness / diff UI +- run queue, 실행/정지 버튼, batch progress, live viewport, heatmap overlay +- 결과 표시 화면, variation summary, comparison, export, recommendation 검토 및 적용 화면 +- UI state gating과 패널 활성 조건 +- `ProjectRepository`와 `ResultRepository`를 구분해 domain 호출과 화면을 연결하는 코드 - domain 호출과 화면 연결 넣지 말아야 할 것: @@ -172,7 +177,10 @@ Qt viewport가 엔진 렌더러와 직접 연결되어야 하면 `application -> - `uml/domain-control-model.puml` : 행동, 트리거, occupant tag, route choice policy, `ControlZone` 대상 제어 구조 - `uml/engine-routing-and-connectors.puml` : topology snapshot, connector, path cost, `ControlZone` access override, measurement 경계 구조 - `uml/domain-result-artifacts.puml` : run, variation, comparison, cumulative 결과 아티팩트 구조 -- `uml/application-run-results-workflow.puml` : persisted artifact 기준의 작성, 실행, 비교, 내보내기, 추천 흐름 +- `uml/application-run-results-workflow.puml` : project persistence와 persisted artifact 기준의 전체 작성, 실행, 비교, 내보내기, 추천 흐름 +- `uml/application-authoring-workspace.puml` : import 검토/보정, 템플릿, 시나리오 작성, readiness / diff UI 구조 +- `uml/application-analysis-workspace.puml` : 실행, live viewport, heatmap, 결과 요약, 비교, 추천, 내보내기 UI 구조 +- `uml/application-workspace-state-model.puml` : workspace 상태 전이와 패널 활성 규칙 각 UML 해설 문서는 다음 파일에 둔다. - `uml/project-structure.puml 해설.md` @@ -185,6 +193,9 @@ Qt viewport가 엔진 렌더러와 직접 연결되어야 하면 `application -> - `uml/engine-routing-and-connectors.puml 해설.md` - `uml/domain-result-artifacts.puml 해설.md` - `uml/application-run-results-workflow.puml 해설.md` +- `uml/application-authoring-workspace.puml 해설.md` +- `uml/application-analysis-workspace.puml 해설.md` +- `uml/application-workspace-state-model.puml 해설.md` --- diff --git a/src/application/MainWindow.cpp b/src/application/MainWindow.cpp index 92c3272..3cc01ee 100644 --- a/src/application/MainWindow.cpp +++ b/src/application/MainWindow.cpp @@ -1,10 +1,12 @@ #include "application/MainWindow.h" +#include #include #include #include #include #include +#include #include #include #include @@ -31,6 +33,24 @@ QString stateToString(safecrowd::engine::EngineState state) { return "Unknown"; } +QString workspaceStageToString(const safecrowd::domain::SimulationSummary& summary) { + using safecrowd::engine::EngineState; + + if (summary.state == EngineState::Running) { + return "BatchRunning (runtime prototype)"; + } + + if (summary.state == EngineState::Paused) { + return "BatchPaused (runtime prototype)"; + } + + if (summary.frameIndex > 0 || summary.fixedStepIndex > 0) { + return "ResultsAvailable (aggregation placeholder)"; + } + + return "ScenarioReady (authoring placeholders)"; +} + QLabel* createBodyLabel(const QString& text, QWidget* parent) { auto* label = new QLabel(text, parent); label->setWordWrap(true); @@ -44,6 +64,15 @@ QLabel* createValueLabel(QWidget* parent) { return label; } +QGroupBox* createInfoGroup(const QString& title, const QString& body, QWidget* parent) { + auto* group = new QGroupBox(title, parent); + auto* layout = new QVBoxLayout(group); + layout->setSpacing(8); + layout->addWidget(createBodyLabel(body, group)); + layout->addStretch(); + return group; +} + } // namespace namespace safecrowd::application { @@ -52,40 +81,110 @@ MainWindow::MainWindow(safecrowd::domain::SafeCrowdDomain& domain, QWidget* pare : QMainWindow(parent), domain_(domain) { auto* centralWidget = new QWidget(this); - auto* rootLayout = new QHBoxLayout(centralWidget); + auto* rootLayout = new QVBoxLayout(centralWidget); rootLayout->setContentsMargins(18, 18, 18, 18); - rootLayout->setSpacing(16); - - auto* workspaceGroup = new QGroupBox("Project Workspace", centralWidget); - auto* workspaceLayout = new QVBoxLayout(workspaceGroup); - workspaceLayout->setSpacing(12); - workspaceLayout->addWidget(createBodyLabel( - "1. Import & Validate
" - "DXF and facility topology import, review, and manual correction will surface here.", - workspaceGroup)); - workspaceLayout->addWidget(createBodyLabel( - "2. Scenario Editor
" - "Baseline and variation authoring stay in the same workspace but outside the run panel.", - workspaceGroup)); - workspaceLayout->addWidget(createBodyLabel( - "3. Results & Recommendation
" - "Run summaries, comparison, export, and recommendation remain downstream of persisted artifacts.", - workspaceGroup)); - workspaceLayout->addStretch(); - - auto* workspaceColumn = new QVBoxLayout(); - workspaceColumn->setSpacing(16); - workspaceColumn->addWidget(createBodyLabel( - "SafeCrowd Workspace Prototype
" - "This shell now mirrors the documented workflow. Only playback control is wired live today; " - "the rest of the workspace is reserved so future application features land in explicit sections.", + rootLayout->setSpacing(14); + + rootLayout->addWidget(createBodyLabel( + "SafeCrowd Workspace IA Shell
" + "This window now mirrors the documented Project, Authoring, Run, and " + "Analysis workspaces. Only playback control is wired live today; repository, template, " + "and persisted-result flows remain placeholder sections for the next application/domain pass.", centralWidget)); - auto* runControlGroup = new QGroupBox("Run Control Panel", centralWidget); + auto* workspaceTabs = new QTabWidget(centralWidget); + workspaceTabs->setDocumentMode(true); + rootLayout->addWidget(workspaceTabs, 1); + + auto* projectPage = new QWidget(workspaceTabs); + auto* projectLayout = new QGridLayout(projectPage); + projectLayout->setContentsMargins(0, 0, 0, 0); + projectLayout->setHorizontalSpacing(12); + projectLayout->setVerticalSpacing(12); + projectLayout->setColumnStretch(0, 1); + projectLayout->setColumnStretch(1, 1); + projectLayout->addWidget(createInfoGroup( + "Project Navigator", + "Scope
" + "Create, open, and review a workspace that keeps layout, scenario family, run metadata, and " + "artifact indexes together.

" + "Planned actions
" + "Recent projects, import entry, reimport, and project-level navigation live here.", + projectPage), 0, 0); + projectLayout->addWidget(createInfoGroup( + "Project Save/Open", + "ProjectRepository
" + "Workspace restore will load approved layout, scenario family, run/variation metadata, and the " + "canonical artifact index through the project repository.

" + "Current shell
" + "Persistence is not wired yet; this section exists to keep project restore separate from analysis storage.", + projectPage), 0, 1); + projectLayout->addWidget(createInfoGroup( + "Repository Boundaries", + "ProjectRepository keeps project context, authoring drafts, and artifact indexes.
" + "ResultRepository remains analysis-only and feeds run summaries, comparison, export, and " + "recommendation evidence after results have been persisted.", + projectPage), 1, 0, 1, 2); + + auto* authoringPage = new QWidget(workspaceTabs); + auto* authoringLayout = new QGridLayout(authoringPage); + authoringLayout->setContentsMargins(0, 0, 0, 0); + authoringLayout->setHorizontalSpacing(12); + authoringLayout->setVerticalSpacing(12); + authoringLayout->setColumnStretch(0, 1); + authoringLayout->setColumnStretch(1, 1); + authoringLayout->addWidget(createInfoGroup( + "Import Workflow UI", + "DXF import, reimport, and source selection enter here before any scenario authoring begins.", + authoringPage), 0, 0); + authoringLayout->addWidget(createInfoGroup( + "Issue Review Panel", + "Blocking topology issues, warnings, approval state, and traceable problem locations surface here before run is enabled.", + authoringPage), 0, 1); + authoringLayout->addWidget(createInfoGroup( + "Layout Canvas + Inspector", + "Manual correction stays a 2D topology editor with inspector support, not a full CAD environment.", + authoringPage), 1, 0); + authoringLayout->addWidget(createInfoGroup( + "Scenario Library", + "Baseline, alternatives, and recommended drafts remain distinct so lineage is clear before comparison and scenarioize flows.", + authoringPage), 1, 1); + authoringLayout->addWidget(createInfoGroup( + "Scenario Template Picker", + "Template cards will expose intended use, risk axis, and layout prerequisites through ScenarioTemplateCatalog quick starts.", + authoringPage), 2, 0); + authoringLayout->addWidget(createInfoGroup( + "Scenario Editor Tabs", + "Population, Environment, Control, and Execution contracts stay separated here so authoring does not collapse into one form.", + authoringPage), 2, 1); + authoringLayout->addWidget(createInfoGroup( + "Readiness Panel", + "Required field gaps, remaining blockers, and run gating are centralized here instead of being hidden behind disabled run buttons.", + authoringPage), 3, 0); + authoringLayout->addWidget(createInfoGroup( + "Variation Diff List", + "Changed items versus baseline track route cost assumptions, control changes, inflow settings, visibility conditions, and template origin.", + authoringPage), 3, 1); + + auto* runPage = new QWidget(workspaceTabs); + auto* runLayout = new QGridLayout(runPage); + runLayout->setContentsMargins(0, 0, 0, 0); + runLayout->setHorizontalSpacing(12); + runLayout->setVerticalSpacing(12); + runLayout->setColumnStretch(0, 1); + runLayout->setColumnStretch(1, 1); + + auto* runQueueGroup = createInfoGroup( + "Run Queue", + "Selected variations, repeat count, and seed contract will be staged here before the batch runner starts or replays a scenario family.", + runPage); + + auto* runControlGroup = new QGroupBox("Run Control Panel", runPage); auto* runControlLayout = new QVBoxLayout(runControlGroup); runControlLayout->setSpacing(10); runControlLayout->addWidget(createBodyLabel( - "Playback control remains the active path into the current runtime prototype.", + "Playback control remains the only live path in the current prototype. The documented batch queue, repeat runs, " + "and variation selection stay visible here as placeholders until domain orchestration is wired.", runControlGroup)); auto* buttonLayout = new QHBoxLayout(); @@ -97,51 +196,89 @@ MainWindow::MainWindow(safecrowd::domain::SafeCrowdDomain& domain, QWidget* pare buttonLayout->addWidget(stopButton_); runControlLayout->addLayout(buttonLayout); runControlLayout->addWidget(createBodyLabel( - "Planned next: execution readiness checks, repeat runs, and variation selection.", + "Planned next
" + "Run queue handoff, repeat execution, variation selection, and readiness-aware enablement.", runControlGroup)); - - auto* runtimeStatusGroup = new QGroupBox("Runtime Status", centralWidget); + runControlLayout->addStretch(); + + auto* batchProgressGroup = new QGroupBox("Batch Progress", runPage); + auto* batchProgressLayout = new QFormLayout(batchProgressGroup); + batchProgressLayout->setLabelAlignment(Qt::AlignLeft); + batchProgressLayout->setFormAlignment(Qt::AlignTop | Qt::AlignLeft); + workspaceStageValue_ = createValueLabel(batchProgressGroup); + runValue_ = createValueLabel(batchProgressGroup); + variationValue_ = createValueLabel(batchProgressGroup); + batchProgressLayout->addRow("Workspace stage", workspaceStageValue_); + batchProgressLayout->addRow("Current run", runValue_); + batchProgressLayout->addRow("Variation", variationValue_); + + auto* runtimeStatusGroup = new QGroupBox("Runtime Status", runPage); auto* runtimeStatusLayout = new QFormLayout(runtimeStatusGroup); runtimeStatusLayout->setLabelAlignment(Qt::AlignLeft); runtimeStatusLayout->setFormAlignment(Qt::AlignTop | Qt::AlignLeft); - runtimeStateValue_ = createValueLabel(runtimeStatusGroup); frameValue_ = createValueLabel(runtimeStatusGroup); fixedStepValue_ = createValueLabel(runtimeStatusGroup); alphaValue_ = createValueLabel(runtimeStatusGroup); - runValue_ = createValueLabel(runtimeStatusGroup); - variationValue_ = createValueLabel(runtimeStatusGroup); - runtimeStatusLayout->addRow("Engine state", runtimeStateValue_); runtimeStatusLayout->addRow("Rendered frames", frameValue_); runtimeStatusLayout->addRow("Fixed steps", fixedStepValue_); runtimeStatusLayout->addRow("Interpolation alpha", alphaValue_); - runtimeStatusLayout->addRow("Current run", runValue_); - runtimeStatusLayout->addRow("Variation", variationValue_); - - auto* resultsGroup = new QGroupBox("Results Pipeline", centralWidget); - auto* resultsLayout = new QVBoxLayout(resultsGroup); - resultsLayout->setSpacing(12); - resultsLayout->addWidget(createBodyLabel( - "Run Results Panel
" - "Single-run and variation summaries will read persisted artifacts first.", - resultsGroup)); - resultsLayout->addWidget(createBodyLabel( - "Comparison View
" - "Baseline versus alternative comparisons stay separate from live runtime state.", - resultsGroup)); - resultsLayout->addWidget(createBodyLabel( - "Export & Recommendation
" - "Artifact export and recommendation evidence remain downstream consumers of saved results.", - resultsGroup)); - - workspaceColumn->addWidget(runControlGroup); - workspaceColumn->addWidget(runtimeStatusGroup); - workspaceColumn->addWidget(resultsGroup); - workspaceColumn->addStretch(); - - rootLayout->addWidget(workspaceGroup, 5); - rootLayout->addLayout(workspaceColumn, 7); + + auto* liveViewportGroup = createInfoGroup( + "Live Viewport", + "Runtime snapshots belong here. This surface is intentionally separate from persisted comparison and recommendation evidence.", + runPage); + auto* heatmapOverlayGroup = createInfoGroup( + "Heatmap Overlay", + "Live overlays will toggle here during playback. Persisted heatmap layers move to Analysis once summaries are stored.", + runPage); + + runLayout->addWidget(runQueueGroup, 0, 0); + runLayout->addWidget(runControlGroup, 0, 1); + runLayout->addWidget(batchProgressGroup, 1, 0); + runLayout->addWidget(runtimeStatusGroup, 1, 1); + runLayout->addWidget(liveViewportGroup, 2, 0); + runLayout->addWidget(heatmapOverlayGroup, 2, 1); + + auto* analysisPage = new QWidget(workspaceTabs); + auto* analysisLayout = new QGridLayout(analysisPage); + analysisLayout->setContentsMargins(0, 0, 0, 0); + analysisLayout->setHorizontalSpacing(12); + analysisLayout->setVerticalSpacing(12); + analysisLayout->setColumnStretch(0, 1); + analysisLayout->setColumnStretch(1, 1); + analysisLayout->addWidget(createInfoGroup( + "Run Results Panel", + "Single-run summaries will read persisted RunResult artifacts first, not transient runtime state.", + analysisPage), 0, 0); + analysisLayout->addWidget(createInfoGroup( + "Variation Summary", + "Repeated-run aggregates, seed-aware variation context, and visibility condition traces will summarize here.", + analysisPage), 0, 1); + analysisLayout->addWidget(createInfoGroup( + "Comparison View", + "Baseline versus alternatives stays a persisted ScenarioComparison and CumulativeArtifact reader, not an ad hoc delta calculator.", + analysisPage), 1, 0, 1, 2); + analysisLayout->addWidget(createInfoGroup( + "Recommendation Drawer", + "Recommendation evidence and scenarioize actions remain downstream of persisted comparison artifacts.", + analysisPage), 2, 0); + analysisLayout->addWidget(createInfoGroup( + "Export Dialog", + "Canonical artifact bundle export will stay disabled until comparison-ready persisted results exist.", + analysisPage), 2, 1); + + workspaceTabs->addTab(projectPage, "Project"); + workspaceTabs->addTab(authoringPage, "Authoring"); + workspaceTabs->addTab(runPage, "Run"); + workspaceTabs->addTab(analysisPage, "Analysis"); + workspaceTabs->setCurrentWidget(runPage); + + rootLayout->addWidget(createBodyLabel( + "Current prototype note: persistence, template instantiation, and persisted analysis remain design-level placeholders. " + "Only the playback buttons and runtime counters below are wired to the domain runtime today.", + centralWidget)); tickTimer_ = new QTimer(this); tickTimer_->setInterval(16); @@ -153,7 +290,7 @@ MainWindow::MainWindow(safecrowd::domain::SafeCrowdDomain& domain, QWidget* pare setCentralWidget(centralWidget); setWindowTitle("SafeCrowd Workspace"); - resize(980, 560); + resize(1200, 760); refreshRuntimePanel(); } @@ -185,17 +322,18 @@ void MainWindow::refreshRuntimePanel() { using safecrowd::engine::EngineState; const auto summary = domain_.summary(); + workspaceStageValue_->setText(workspaceStageToString(summary)); runtimeStateValue_->setText(stateToString(summary.state)); frameValue_->setText(QString::number(summary.frameIndex)); fixedStepValue_->setText(QString::number(summary.fixedStepIndex)); alphaValue_->setText(QString::number(summary.alpha, 'f', 2)); if (summary.state == EngineState::Running || summary.state == EngineState::Paused) { - runValue_->setText("Prototype run 1 / 1"); + runValue_->setText("Prototype run 1 / repeat placeholder"); } else if (summary.frameIndex > 0 || summary.fixedStepIndex > 0) { runValue_->setText("Last prototype run retained"); } else { - runValue_->setText("Ready for first run"); + runValue_->setText("Queue not started"); } variationValue_->setText("Baseline placeholder (domain wiring pending)"); diff --git a/src/application/MainWindow.h b/src/application/MainWindow.h index e9e8acc..120655a 100644 --- a/src/application/MainWindow.h +++ b/src/application/MainWindow.h @@ -27,6 +27,7 @@ class MainWindow : public QMainWindow { QPushButton* startButton_{nullptr}; QPushButton* pauseButton_{nullptr}; QPushButton* stopButton_{nullptr}; + QLabel* workspaceStageValue_{nullptr}; QLabel* runtimeStateValue_{nullptr}; QLabel* frameValue_{nullptr}; QLabel* fixedStepValue_{nullptr}; diff --git a/src/domain/ProjectRepository.h b/src/domain/ProjectRepository.h new file mode 100644 index 0000000..3d13337 --- /dev/null +++ b/src/domain/ProjectRepository.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +#include "domain/ScenarioAuthoring.h" + +namespace safecrowd::domain { + +struct ProjectArtifactReference { + std::string artifactKind{}; + std::string artifactId{}; + std::string storageKey{}; +}; + +struct ProjectWorkspaceRecord { + ProjectWorkspaceSnapshot workspace{}; + std::vector artifactIndex{}; + std::vector runIds{}; + std::vector variationKeys{}; +}; + +class ProjectRepository { +public: + virtual ~ProjectRepository() = default; + + virtual std::optional loadProject(const std::string& projectId) const = 0; + virtual void saveProject(const ProjectWorkspaceRecord& record) = 0; +}; + +} // namespace safecrowd::domain diff --git a/src/domain/ScenarioAuthoring.h b/src/domain/ScenarioAuthoring.h new file mode 100644 index 0000000..28be1b0 --- /dev/null +++ b/src/domain/ScenarioAuthoring.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include + +#include "domain/FacilityLayout2D.h" +#include "domain/PopulationSpec.h" + +namespace safecrowd::domain { + +enum class ScenarioRole { + Baseline, + Alternative, + Recommended, +}; + +struct EnvironmentState { + bool reducedVisibility{false}; + std::string familiarityProfile{}; + std::string guidanceProfile{}; +}; + +struct OperationalEventDraft { + std::string id{}; + std::string name{}; + std::string triggerSummary{}; + std::string targetSummary{}; +}; + +struct ControlPlan { + std::vector events{}; +}; + +struct ExecutionConfig { + double timeLimitSeconds{0.0}; + double sampleIntervalSeconds{0.0}; + std::uint32_t repeatCount{1}; + std::uint32_t baseSeed{0}; + bool recordOccupantHistory{false}; +}; + +struct ScenarioDraft { + std::string scenarioId{}; + std::string name{}; + ScenarioRole role{ScenarioRole::Alternative}; + PopulationSpec population{}; + EnvironmentState environment{}; + ControlPlan control{}; + ExecutionConfig execution{}; + std::string sourceTemplateId{}; + std::vector variationDiffKeys{}; + std::vector blockingIssues{}; +}; + +struct ProjectWorkspaceSnapshot { + std::string projectId{}; + std::string displayName{}; + FacilityLayout2D layout{}; + std::vector scenarios{}; +}; + +} // namespace safecrowd::domain diff --git a/src/domain/ScenarioTemplateCatalog.h b/src/domain/ScenarioTemplateCatalog.h new file mode 100644 index 0000000..963c0fd --- /dev/null +++ b/src/domain/ScenarioTemplateCatalog.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +#include "domain/ScenarioAuthoring.h" + +namespace safecrowd::domain { + +struct ScenarioTemplateDescriptor { + std::string templateId{}; + std::string name{}; + std::string intendedUse{}; + std::vector requiredLayoutFeatures{}; + std::vector focusRiskAxes{}; +}; + +struct ScenarioTemplateDraft { + ScenarioDraft scenario{}; + std::vector highlightedFields{}; + std::vector unmetRequirements{}; +}; + +class ScenarioTemplateCatalog { +public: + virtual ~ScenarioTemplateCatalog() = default; + + virtual std::vector listTemplates() const = 0; + virtual std::optional instantiate( + const std::string& templateId, + const FacilityLayout2D& layout) const = 0; +}; + +} // namespace safecrowd::domain diff --git "a/uml/PlantUML \354\235\275\353\212\224 \353\262\225.md" "b/uml/PlantUML \354\235\275\353\212\224 \353\262\225.md" index 5f1c48a..389f611 100644 --- "a/uml/PlantUML \354\235\275\353\212\224 \353\262\225.md" +++ "b/uml/PlantUML \354\235\275\353\212\224 \353\262\225.md" @@ -3,9 +3,10 @@ ## 목적 이 문서는 `uml/` 폴더의 PlantUML 다이어그램을 렌더링해서 볼 때, 각 요소와 선이 무엇을 뜻하는지 빠르게 이해할 수 있도록 정리한 문서이다. -이 프로젝트에서는 크게 두 종류를 본다. +이 프로젝트에서는 크게 세 종류를 본다. - 상위 구조를 보여주는 **component/package 중심 그림** - 엔진 내부 타입 관계를 보여주는 **class diagram** +- UI 게이팅과 흐름을 보여주는 **state diagram** --- @@ -47,6 +48,11 @@ - 이 프로젝트 예시: `WorldCommands`가 즉시 mutation하지 않는다는 설명, `EcsCore`가 재사용 가능한 엔진 코어라는 설명 - 읽는 법: 선과 박스만 보면 놓치기 쉬운 설계 규칙을 보충하는 텍스트다. +### 상태 원과 상태 박스 +- 의미: 화면이나 워크플로가 어떤 상태를 거치며 전이되는지 표현한다. +- 이 프로젝트 예시: `NoProject`, `ScenarioReady`, `ComparisonReady` +- 읽는 법: "지금 UI가 어느 조건을 만족한 상태인가"와 "무슨 이벤트로 다음 상태로 가는가"를 본다. + --- ## 2. 선과 화살표 읽는 법 @@ -98,6 +104,13 @@ PlantUML에서 선은 크게 두 가지를 같이 본다. - 예시: - `PackedComponentStorage ..|> IComponentStorage` +### 상태도에서의 `-->` +- 의미: 상태 전이 +- 읽는 법: "`A` 상태에서 어떤 이벤트나 조건이 만족되면 `B` 상태로 이동한다" +- 예시: + - `ScenarioReady --> BatchRunning : Run clicked` + - `ComparisonReady --> RecommendationReady : ScenarioComparison + CumulativeArtifact ready` + ### `..>` - 의미가 같은 표기가 여러 문맥에서 쓰일 수 있음 - 이 프로젝트에서는 주로 "의존" 또는 "간접 연결" 정도로 보면 된다. @@ -203,6 +216,10 @@ PlantUML에서 선은 크게 두 가지를 같이 본다. - 이 프로젝트 UML에서는 노트에 실제 구현 규칙이 많이 들어 있다. - 예를 들어 deferred mutation, reusable engine, query cache 후순위 같은 정보는 노트를 봐야 정확히 이해된다. +### 상태도는 어떤 용도로 보나? +- application state diagram은 화면 구성을 보여주기보다 "버튼과 패널이 언제 활성화되는가"를 읽는 용도로 본다. +- 따라서 상태명과 전이 라벨은 UI 문구이면서 동시에 요구사항 계약이기도 하다. + --- ## 7. 한 줄 요약 diff --git a/uml/application-analysis-workspace.puml b/uml/application-analysis-workspace.puml new file mode 100644 index 0000000..3d2a66b --- /dev/null +++ b/uml/application-analysis-workspace.puml @@ -0,0 +1,82 @@ +@startuml application-analysis-workspace +title SafeCrowd Application Analysis Workspace + +left to right direction +skinparam componentStyle rectangle +skinparam packageStyle rectangle +skinparam shadowing false +skinparam linetype ortho + +package "application" { + [Run Control\n(run / pause / stop)] as RunControl + [Batch Progress\n(run / variation progress)] as BatchProgress + [Live Viewport\n(runtime playback)] as LiveViewport + [Heatmap Selector\n(live / persisted layers)] as HeatmapSelector + [Run Results Panel\n(single-run summary)] as RunResultsPanel + [Variation Summary\n(repeated-run aggregate)] as VariationSummary + [Comparison View\n(baseline vs alternatives)] as ComparisonView + [Recommendation Drawer\n(evidence / scenarioize)] as RecommendationDrawer + [Export Dialog\n(canonical artifact bundle)] as ExportDialog +} + +package "domain" { + [ScenarioBatchRunner\n(batch orchestration)] as BatchRunner + [SimulationSession\n(single run lifecycle)] as SimulationSession + [ResultRepository\n(run / variation /\ncomparison / cumulative)] as ResultRepository + [ResultAggregator\n(update persisted summaries)] as ResultAggregator + [AlternativeRecommendationService\n(candidate generation)] as RecommendationService +} + +package "engine" { + [EngineRuntime\n(playback control)] as EngineRuntime + [IRenderBridge\n(frame sync)] as RenderBridge +} + +RunControl --> BatchRunner : start / pause / stop batch +BatchRunner --> BatchProgress : publish current run / variation +BatchRunner --> SimulationSession : spawn run +SimulationSession --> EngineRuntime : initialize / play / stop +EngineRuntime --> RenderBridge : live frame snapshots +RenderBridge --> LiveViewport : present runtime state + +HeatmapSelector --> LiveViewport : toggle live overlay +HeatmapSelector --> ResultRepository : load persisted heatmap layer + +BatchRunner --> ResultRepository : persist run artifacts +BatchRunner --> ResultAggregator : compute higher-level artifacts +ResultAggregator --> ResultRepository : persist variation / comparison / cumulative artifacts + +RunResultsPanel --> ResultRepository : load RunResult summaries +VariationSummary --> ResultRepository : load VariationSummary +RunResultsPanel --> VariationSummary : inspect repeated-run aggregate +VariationSummary --> ComparisonView : open baseline comparison +ComparisonView --> ResultRepository : load ScenarioComparison / CumulativeArtifact + +ComparisonView --> RecommendationDrawer : inspect recommendation evidence +RecommendationDrawer --> RecommendationService : request candidates / scenarioize +RecommendationService --> ResultRepository : read ScenarioComparison / CumulativeArtifact + +ComparisonView --> ExportDialog : export selected bundle +ExportDialog --> ResultRepository : resolve canonical artifact set + +note bottom of LiveViewport + Live viewport consumes runtime snapshots only. + It must not calculate comparison deltas or + infer recommendation evidence from transient state. +end note + +note bottom of ComparisonView + Comparison, recommendation, and export + are persisted-analysis workflows. + They stay disabled until the required + stored artifacts exist. +end note + +note bottom of HeatmapSelector + The same selector can switch between + live overlays during playback and + persisted layers after aggregation, + but the data source is explicit. +end note + +@enduml diff --git "a/uml/application-analysis-workspace.puml \355\225\264\354\204\244.md" "b/uml/application-analysis-workspace.puml \355\225\264\354\204\244.md" new file mode 100644 index 0000000..f3f49e6 --- /dev/null +++ "b/uml/application-analysis-workspace.puml \355\225\264\354\204\244.md" @@ -0,0 +1,56 @@ +# SafeCrowd UML 설계 해설 - application-analysis-workspace.puml + +대상 파일: `uml/application-analysis-workspace.puml` + +## 문서 목적 +이 문서는 SafeCrowd의 실행 후반과 결과 분석 UI를 설명한다. 핵심은 `live playback`과 `persisted artifact 기반 분석`을 한 화면 군 안에서 연결하되, 데이터 출처를 섞지 않는 것이다. + +## `Run Control` +- 개요: batch 실행의 시작, 일시정지, 정지를 요청하는 패널이다. +- 목적: application이 엔진 세부 제어 대신 `ScenarioBatchRunner`를 통해 실행을 조정하게 한다. +- 유의사항: comparison이나 export 조건 판단을 여기서 맡지 않는다. + +## `Batch Progress` +- 개요: 현재 run 번호, variation 식별자, 반복 실행 진행률을 보여 주는 패널이다. +- 목적: 사용자가 반복 실행과 batch 상태를 실시간으로 추적하게 한다. +- 유의사항: 결과 집계가 끝나기 전과 후의 상태를 구분해서 보여 준다. + +## `Live Viewport` +- 개요: 실행 중 runtime snapshot을 재생하는 시각화 영역이다. +- 목적: 현재 움직임과 즉시적인 overlay를 확인하게 한다. +- 유의사항: persisted comparison이나 recommendation evidence를 직접 계산하지 않는다. + +## `Heatmap Selector` +- 개요: heatmap과 위험 레이어의 데이터 출처와 표시 종류를 고르는 컨트롤이다. +- 목적: live overlay와 post-run heatmap을 같은 UI affordance로 연결한다. +- 유의사항: 데이터 출처가 `IRenderBridge` 기반인지 `ResultRepository` 기반인지 명확해야 한다. + +## `Run Results Panel` +- 개요: 단일 실행 요약을 읽는 첫 결과 패널이다. +- 목적: run 종료 직후의 핵심 지표를 persisted 형태로 먼저 보여 준다. +- 유의사항: live 상태를 그대로 붙이는 패널이 아니라 저장된 `RunResult` 소비자다. + +## `Variation Summary` +- 개요: 같은 variation에 속한 반복 실행 집계를 보여 주는 패널이다. +- 목적: deterministic single run과 repeated-run aggregate를 구분한다. +- 유의사항: `Comparison View`로 넘어가기 전에 repeated-run 맥락을 먼저 고정한다. + +## `Comparison View` +- 개요: baseline과 alternative variation을 나란히 비교하는 핵심 분석 패널이다. +- 목적: `ScenarioComparison`과 `CumulativeArtifact`를 기준으로 변화량과 근거를 제시한다. +- 유의사항: ad hoc domain delta 계산기로 쓰지 않는다. + +## `Recommendation Drawer` +- 개요: 추천 후보, 근거, 시나리오화 액션을 보여 주는 패널이다. +- 목적: 결과 분석 뒤에 이어지는 설명 가능한 운영 대안 검토 흐름을 만든다. +- 유의사항: 추천 입력은 persisted comparison/cumulative artifact에 한정한다. + +## `Export Dialog` +- 개요: canonical 결과 번들을 외부 공유용으로 내보내는 대화상자다. +- 목적: comparison과 recommendation이 보는 같은 결과 계약을 export에도 재사용한다. +- 유의사항: 결과가 충분하지 않으면 비활성 상태를 유지한다. + +## `ResultRepository` / `ResultAggregator` +- 개요: `ResultRepository`는 run/variation/comparison/cumulative artifact 저장소이고, `ResultAggregator`는 상위 결과를 생성하는 서비스다. +- 목적: 분석 패널들이 같은 persisted 결과 계약을 읽게 한다. +- 유의사항: comparison, export, recommendation의 선행 조건은 이 저장소에 결과가 존재하는지로 판단한다. diff --git a/uml/application-authoring-workspace.puml b/uml/application-authoring-workspace.puml new file mode 100644 index 0000000..9bdfce0 --- /dev/null +++ b/uml/application-authoring-workspace.puml @@ -0,0 +1,78 @@ +@startuml application-authoring-workspace +title SafeCrowd Application Authoring Workspace + +left to right direction +skinparam componentStyle rectangle +skinparam packageStyle rectangle +skinparam shadowing false +skinparam linetype ortho + +package "application" { + [Project Navigator\n(new / open / save / recent)] as ProjectNavigator + [Project Save/Open\n(workspace persistence)] as ProjectIO + [Import Workflow UI\n(file select / reimport)] as ImportWorkflow + [Issue Review Panel\n(errors / warnings / approval)] as IssueReview + [Layout Canvas + Inspector\n(topology correction)] as LayoutCanvas + [Scenario Library\n(baseline / alternatives /\nrecommended drafts)] as ScenarioLibrary + [Scenario Template Picker\n(template cards / prerequisites)] as TemplatePicker + [Scenario Editor Tabs\n(population / environment /\ncontrol / execution)] as ScenarioEditorTabs + [Readiness Panel\n(required fields / blockers)] as ReadinessPanel + [Variation Diff List\n(changed items vs baseline)] as VariationDiffList +} + +package "domain" { + [ProjectRepository\n(layout / scenario workspace)] as ProjectRepository + [ImportOrchestrator\n(import / validation)] as ImportOrchestrator + [ScenarioTemplateCatalog\n(template defaults /\nprerequisite checks)] as TemplateCatalog +} + +ProjectNavigator --> ProjectIO : open / save project +ProjectIO --> ProjectRepository : load / persist workspace + +ProjectNavigator --> ImportWorkflow : import or reimport layout +ImportWorkflow --> ImportOrchestrator : import file / choose rules +ImportOrchestrator --> IssueReview : layout + issues +IssueReview --> LayoutCanvas : inspect blocker location +LayoutCanvas --> ProjectRepository : save corrected layout + +ProjectNavigator --> ScenarioLibrary : browse scenario family +ScenarioLibrary --> TemplatePicker : quick-start new scenario +TemplatePicker --> TemplateCatalog : list / instantiate templates +TemplateCatalog --> ScenarioEditorTabs : seed authoring defaults + +ScenarioLibrary --> ScenarioEditorTabs : open baseline / alternative / recommended +ScenarioEditorTabs --> ReadinessPanel : validate inputs +ScenarioEditorTabs --> VariationDiffList : record baseline deltas +ScenarioEditorTabs --> ProjectRepository : save drafts / labels + +VariationDiffList --> ScenarioLibrary : trace scenario role +ReadinessPanel --> ScenarioEditorTabs : highlight missing fields +ReadinessPanel --> ScenarioLibrary : expose run-ready state + +note bottom of IssueReview + Blocking import issues live here first. + Run stays disabled until the layout is reviewed + and remaining blockers are cleared or accepted. +end note + +note bottom of TemplatePicker + Template cards show intended use, risk axis, + and required layout features such as + multiple exits, connectors, or control zones. + Missing prerequisites must be visible before apply. +end note + +note bottom of ScenarioLibrary + Baseline, alternatives, and recommended drafts + stay distinct in the library so comparison and + scenarioize flows keep their lineage clear. +end note + +note bottom of VariationDiffList + Diff tracking records changed inputs such as + control events, route cost assumptions, + inflow settings, visibility conditions, + and template origin metadata. +end note + +@enduml diff --git "a/uml/application-authoring-workspace.puml \355\225\264\354\204\244.md" "b/uml/application-authoring-workspace.puml \355\225\264\354\204\244.md" new file mode 100644 index 0000000..903dac2 --- /dev/null +++ "b/uml/application-authoring-workspace.puml \355\225\264\354\204\244.md" @@ -0,0 +1,56 @@ +# SafeCrowd UML 설계 해설 - application-authoring-workspace.puml + +대상 파일: `uml/application-authoring-workspace.puml` + +## 문서 목적 +이 문서는 SafeCrowd의 authoring 단계 UI를 설명한다. 목표는 `레이아웃 검토/보정`, `시나리오 생성`, `템플릿 빠른 시작`, `실행 준비 상태 확인`이 어디에서 이뤄지는지 고정하는 것이다. + +## `Project Navigator` +- 개요: 프로젝트를 새로 만들고, 다시 열고, 저장하는 authoring 진입점이다. +- 목적: import와 scenario authoring이 항상 같은 workspace 컨텍스트 안에서 시작되게 한다. +- 유의사항: 저장 구현 책임은 `ProjectRepository`에 있다. + +## `Import Workflow UI` +- 개요: 파일 선택과 재import 요청을 시작하는 패널이다. +- 목적: 외부 파일 선택과 도메인 import 경계를 분리한다. +- 유의사항: importer 세부 구현 선택을 UI에서 오래 붙잡지 않는다. + +## `Issue Review Panel` +- 개요: import 결과의 오류, 경고, 승인 상태를 보여 주는 검토 패널이다. +- 목적: 실행 차단 이슈를 시나리오 편집 단계로 넘기기 전에 먼저 노출한다. +- 유의사항: `실행 불가` 판단은 이 패널과 `Readiness Panel`이 함께 만든다. + +## `Layout Canvas + Inspector` +- 개요: 2D topology correction을 위한 보정 화면이다. +- 목적: full CAD 편집기 없이도 door, connection, blocker를 실행 가능한 형태로 고칠 수 있게 한다. +- 유의사항: geometry-heavy editor로 키우지 않고 topology 보정 보조 도구로 유지한다. + +## `Scenario Library` +- 개요: baseline, alternative, recommended draft를 모아 보여 주는 목록이다. +- 목적: 비교와 추천 시나리오화가 원본 lineage를 잃지 않게 한다. +- 유의사항: baseline 구분과 추천 초안 구분이 리스트에서 명확해야 한다. + +## `Scenario Template Picker` +- 개요: 템플릿 카드와 적용 전제조건을 보여 주는 빠른 시작 패널이다. +- 목적: 비전문가가 어떤 변수부터 손대야 하는지 몰라서 authoring을 중단하지 않게 한다. +- 유의사항: 전제조건 부족 경고를 적용 후가 아니라 적용 전에 보여 준다. + +## `Scenario Editor Tabs` +- 개요: population, environment, control, execution 입력을 나눠 편집하는 패널이다. +- 목적: authoring contract를 요구사항 범위에 맞게 분리해서 보여 준다. +- 유의사항: 결과 비교나 추천 계산 책임을 여기로 끌어오지 않는다. + +## `Readiness Panel` +- 개요: 필수 입력 누락, 남은 차단 이슈, 실행 가능 여부를 모아 보여 주는 패널이다. +- 목적: run 버튼이 숨은 규칙으로 비활성화되지 않게 한다. +- 유의사항: 누락 필드는 이 패널에서 바로 편집 탭으로 되돌아갈 수 있어야 한다. + +## `Variation Diff List` +- 개요: baseline 대비 변경 항목을 기록하는 패널이다. +- 목적: alternative 시나리오의 차이를 비교 화면이 아니라 authoring 단계에서도 투명하게 유지한다. +- 유의사항: control, visibility, inflow, route cost, template source까지 기록 범위에 포함한다. + +## `ProjectRepository` / `ScenarioTemplateCatalog` +- 개요: `ProjectRepository`는 workspace 저장 계약이고, `ScenarioTemplateCatalog`는 템플릿 기본값과 전제조건을 반환하는 domain 계약이다. +- 목적: authoring UI가 파일 경로나 템플릿 규칙을 직접 소유하지 않게 한다. +- 유의사항: 두 계약 모두 application의 UI 배치와는 분리된 domain 책임으로 본다. diff --git a/uml/application-run-results-workflow.puml b/uml/application-run-results-workflow.puml index 122a8e5..254ef50 100644 --- a/uml/application-run-results-workflow.puml +++ b/uml/application-run-results-workflow.puml @@ -1,5 +1,5 @@ @startuml application-run-results-workflow -title SafeCrowd Application Run and Results Workflow +title SafeCrowd Application End-to-End Workspace Workflow left to right direction skinparam componentStyle rectangle @@ -8,76 +8,120 @@ skinparam shadowing false skinparam linetype ortho package "application" { - [Project Workspace\n(project open / save)] as Workspace - [Scenario Editor\n(layout / population / control)] as ScenarioEditor - [Run Control Panel\n(run / pause / stop / repeat)] as RunControl + [Project Workspace\n(shared project context)] as Workspace + [Project Navigator\n(new / open / save / recent)] as ProjectNavigator + [Project Save/Open\n(project persistence actions)] as ProjectIO + [Import Workflow UI\n(file select / reimport)] as ImportWorkflow + [Import Review\n(issues / approval / blocking)] as ImportReview + [Layout Correction\n(canvas / inspector)] as LayoutCorrection + [Scenario Library\n(baseline / alternatives /\nrecommended drafts)] as ScenarioLibrary + [Template Picker\n(template cards / prerequisites)] as TemplatePicker + [Scenario Editor\n(population / environment /\ncontrol / execution)] as ScenarioEditor + [Readiness Panel\n(blockers / run gate)] as ReadinessPanel + [Run Queue\n(selected variations / repeats)] as RunQueue + [Run Control Panel\n(run / pause / stop)] as RunControl + [Batch Progress\n(run / variation progress)] as BatchProgress [Live Viewport\n(runtime playback)] as LiveViewport - [Run Results Panel\n(run / variation summary)] as RunResultsPanel + [Heatmap Overlay\n(live / persisted toggle)] as HeatmapOverlay + [Run Results Panel\n(run summary)] as RunResultsPanel + [Variation Summary\n(repeated-run aggregate)] as VariationSummary [Comparison View\n(baseline vs alternatives)] as ComparisonView - [Export Dialog\n(report / artifact bundle)] as ExportDialog - [Recommendation Panel\n(evidence / scenarioize)] as RecommendationPanel + [Recommendation Drawer\n(evidence / scenarioize)] as RecommendationDrawer + [Export Dialog\n(canonical artifact bundle)] as ExportDialog } package "domain" { - [ScenarioBatchRunner\n(spawn baseline / variations)] as BatchRunner + [ProjectRepository\n(layout / scenarios /\nrun metadata / artifact index)] as ProjectRepository + [ImportOrchestrator\n(import / validation)] as ImportOrchestrator + [ScenarioTemplateCatalog\n(template defaults)] as TemplateCatalog + [ScenarioBatchRunner\n(queue / batch orchestration)] as BatchRunner [SimulationSession\n(single run lifecycle)] as SimulationSession - [ResultRepository\n(load / persist artifacts)] as ResultRepository - [ResultAggregator\n(build persisted artifacts)] as ResultAggregator + [ResultRepository\n(run / variation /\ncomparison / cumulative)] as ResultRepository + [ResultAggregator\n(update persisted summaries)] as ResultAggregator [AlternativeRecommendationService\n(candidate generation)] as RecommendationService } package "engine" { [EngineRuntime\n(playback control)] as EngineRuntime - [IRenderBridge\nframe sync] as RenderBridge + [IRenderBridge\n(frame sync)] as RenderBridge } -Workspace --> ScenarioEditor : author scenario family -Workspace --> ResultRepository : reopen saved project -ScenarioEditor --> RunControl : submit baseline / variation set -RunControl --> BatchRunner : execute batch +Workspace --> ProjectNavigator : navigate project lifecycle +ProjectNavigator --> ProjectIO : save / reopen workspace +ProjectIO --> ProjectRepository : load / persist project workspace + +ProjectNavigator --> ImportWorkflow : import or reimport layout +ImportWorkflow --> ImportOrchestrator : import file / choose rules +ImportOrchestrator --> ImportReview : layout + issues +ImportReview --> LayoutCorrection : inspect / fix blockers +LayoutCorrection --> ProjectRepository : save corrected layout + +ProjectNavigator --> ScenarioLibrary : browse scenario family +ScenarioLibrary --> TemplatePicker : quick-start new scenario +TemplatePicker --> TemplateCatalog : list / instantiate templates +TemplateCatalog --> ScenarioEditor : seed authoring defaults +ScenarioLibrary --> ScenarioEditor : open baseline / alternative / recommended draft +ScenarioEditor --> ReadinessPanel : validate required inputs +ReadinessPanel --> RunQueue : enqueue valid scenarios +ScenarioEditor --> ProjectRepository : save scenario family changes + +RunQueue --> BatchRunner : selected variations / repeats +RunControl --> BatchRunner : start / pause / stop batch +BatchRunner --> BatchProgress : publish current run / variation state BatchRunner --> SimulationSession : spawn run SimulationSession --> EngineRuntime : initialize / play / stop EngineRuntime --> RenderBridge : live frame snapshots RenderBridge --> LiveViewport : present runtime state +HeatmapOverlay --> LiveViewport : live overlay selection BatchRunner --> ResultRepository : persist run artifacts BatchRunner --> ResultAggregator : update higher-level artifacts ResultAggregator --> ResultRepository : persist variation / comparison / cumulative artifacts -RunResultsPanel --> ResultRepository : load run / variation summaries -RunResultsPanel --> ComparisonView : open baseline comparison -ComparisonView --> ResultRepository : load comparison / cumulative artifacts +RunResultsPanel --> ResultRepository : load run summaries +VariationSummary --> ResultRepository : load repeated-run aggregates +RunResultsPanel --> VariationSummary : inspect aggregated run set +HeatmapOverlay --> ResultRepository : load persisted heatmap layer +VariationSummary --> ComparisonView : open baseline comparison +ComparisonView --> ResultRepository : load ScenarioComparison / CumulativeArtifact ComparisonView --> ExportDialog : export selected artifact bundle -ExportDialog --> ResultRepository : resolve files +ExportDialog --> ResultRepository : resolve canonical export set -ComparisonView --> RecommendationPanel : inspect recommendation evidence -RecommendationPanel --> RecommendationService : read evidence / scenarioize +ComparisonView --> RecommendationDrawer : inspect recommendation evidence +RecommendationDrawer --> RecommendationService : read evidence / scenarioize RecommendationService --> ResultRepository : read ScenarioComparison / CumulativeArtifact -RecommendationPanel --> ScenarioEditor : open recommended variation draft +RecommendationDrawer --> ScenarioLibrary : create recommended variation draft note bottom of Workspace - Authoring and analysis live in one project workspace, - but the workflow still separates scenario editing - from results browsing and export. + The workspace keeps Project, Authoring, Run, and Analysis + in one context, but each step still has its own gate. + Layout review, scenario readiness, and persisted results + control which panels are available next. end note -note bottom of RunResultsPanel - Application first lands on run / variation summaries. - Comparison, export, and recommendation are downstream - consumers of persisted result artifacts, - not direct engine state readers. +note bottom of ProjectRepository + ProjectRepository stores layout, scenario family, + run / variation metadata, and the canonical artifact index. + It is distinct from the result repository on purpose. +end note + +note bottom of ResultRepository + ResultRepository is analysis-only storage for persisted + run / variation / comparison / cumulative artifacts. + Reopening a project should not bypass ProjectRepository. end note note bottom of ComparisonView - Comparison reads persisted comparison artifacts. - It should not trigger ad hoc domain-side - delta calculation as part of basic rendering. + Comparison, export, and recommendation are downstream + consumers of persisted artifacts only. + The view must not trigger ad hoc delta calculation + from live engine state as part of normal rendering. end note -note bottom of RecommendationPanel - Recommendation stays after the result pipeline. - It should consume stable cumulative artifacts and - comparison deltas rather than ad hoc live metrics. +note bottom of HeatmapOverlay + Live overlays ride the render bridge during playback. + Post-run heatmaps come from persisted result artifacts + so analysis stays reproducible after the run ends. end note @enduml diff --git "a/uml/application-run-results-workflow.puml \355\225\264\354\204\244.md" "b/uml/application-run-results-workflow.puml \355\225\264\354\204\244.md" index e40918a..28239ae 100644 --- "a/uml/application-run-results-workflow.puml \355\225\264\354\204\244.md" +++ "b/uml/application-run-results-workflow.puml \355\225\264\354\204\244.md" @@ -3,54 +3,64 @@ 대상 파일: `uml/application-run-results-workflow.puml` ## 문서 목적 -이 문서는 application 레이어에서 시나리오 작성, 실행, 결과 확인, 비교, 내보내기, 추천 검토가 어떤 순서로 이어지는지 설명한다. 핵심은 화면이 persisted artifact를 읽고, domain 서비스가 상위 결과 아티팩트를 생성하거나 소비한다는 점이다. - -## `Project Workspace` -- 개요: 프로젝트 열기와 저장의 루트 작업 공간이다. -- 목적: authoring과 analysis가 같은 프로젝트 맥락 안에서 이어지게 한다. -- 유의사항: 모든 기능을 한 화면에 몰아넣는 뜻이 아니라 공통 컨텍스트를 공유한다는 의미다. - -## `Scenario Editor` -- 개요: 레이아웃, population, control plan을 수정하는 작성 화면이다. -- 목적: baseline과 variation을 같은 authoring 흐름 안에서 관리한다. -- 유의사항: 결과 비교와 추천 근거 계산을 직접 맡지 않는다. - -## `Run Control Panel` -- 개요: 실행, 일시정지, 정지, 반복 실행 요청을 담당하는 패널이다. -- 목적: 작성 단계와 실행 단계의 책임을 분리한다. -- 유의사항: 엔진 직접 제어보다 batch orchestration 요청의 진입점으로 본다. - -## `Run Results Panel` -- 개요: 단일 run 또는 variation 요약을 먼저 보여 주는 결과 패널이다. -- 목적: 사용자를 바로 비교 화면으로 보내기 전에 저장된 결과를 단계적으로 읽게 한다. -- 유의사항: live engine state가 아니라 `ResultRepository`의 persisted artifact를 읽는다. - -## `Comparison View` -- 개요: baseline과 대안을 비교하는 핵심 분석 화면이다. -- 목적: 저장된 comparison/cumulative artifact를 기준으로 delta와 근거를 보여 준다. -- 유의사항: 화면 렌더링 시점의 ad hoc domain 계산기로 쓰지 않는다. - -## `ResultRepository` -- 개요: run, variation, comparison, cumulative artifact를 저장하고 다시 여는 서비스다. -- 목적: 모든 결과 화면이 같은 저장 진입점을 사용하게 한다. -- 유의사항: 각 화면이 파일 경로를 직접 관리하지 않게 한다. - -## `ResultAggregator` -- 개요: run 결과로부터 variation/comparison/cumulative artifact를 생성하거나 갱신하는 도메인 서비스다. -- 목적: comparison과 recommendation이 같은 저장된 결과 계약을 읽게 한다. -- 유의사항: 비교 화면이 열릴 때마다 delta를 즉석 계산하는 역할로 쓰지 않는다. - -## `Recommendation Panel` -- 개요: 추천 후보와 근거를 확인하고 시나리오화하는 화면이다. -- 목적: 추천 기능을 결과 파이프라인 뒤에 두어 근거가 분명한 흐름을 만든다. -- 유의사항: panel이 직접 추천 규칙을 가지지 않고 `AlternativeRecommendationService`를 호출한다. +이 문서는 SafeCrowd application 레이어의 전체 사용자 여정을 한 장으로 설명한다. 핵심은 `불러오기/검토/보정 -> 시나리오 작성 -> 실행/반복 실행 -> 실시간 확인 -> persisted 결과 분석 -> 추천/내보내기`가 하나의 프로젝트 맥락 안에서 이어지되, 저장 계약과 화면 책임은 분리된다는 점이다. + +## `Project Workspace` / `Project Navigator` +- 개요: `Project`, `Authoring`, `Run`, `Analysis`를 묶는 공통 컨텍스트다. +- 목적: 사용자가 프로젝트를 다시 열었을 때 레이아웃, 시나리오 가족, 실행 메타데이터, 결과 인덱스를 같은 맥락에서 이어 가게 한다. +- 유의사항: 공통 맥락을 공유한다고 해서 모든 기능을 하나의 패널에 몰아넣는 뜻은 아니다. + +## `Project Save/Open` +- 개요: 프로젝트 수준 저장과 다시 열기 진입점이다. +- 목적: 작업 공간 복원은 `ResultRepository`가 아니라 `ProjectRepository`를 통해 수행되게 한다. +- 유의사항: 결과 아티팩트 조회와 프로젝트 복원을 같은 저장소 책임으로 합치지 않는다. + +## `Import Workflow` / `Import Review` / `Layout Correction` +- 개요: 구조 데이터를 불러오고, 차단 이슈를 검토하고, 2D canvas + inspector 기반으로 topology를 보정하는 앞단 흐름이다. +- 목적: 실행 전에 `승인된 레이아웃`이라는 명시적 게이트를 만든다. +- 유의사항: 수동 보정은 full CAD editor가 아니라 topology correction 보조 도구로 유지한다. + +## `Scenario Library` / `Template Picker` / `Scenario Editor` / `Readiness Panel` +- 개요: baseline, alternative, recommended draft를 관리하고 템플릿 기반 빠른 시작과 상세 편집을 연결하는 authoring 흐름이다. +- 목적: 비전문가도 템플릿으로 초안을 만들고, 필요한 입력이 누락되면 `Readiness Panel`에서 바로 보게 한다. +- 유의사항: 실행 가능 여부 판단은 run 버튼 주변에 흩어 놓지 않고 `Readiness Panel`에 모은다. + +## `Run Queue` / `Run Control Panel` / `Batch Progress` +- 개요: variation 선택, 반복 실행 횟수, seed 계약, 현재 run/variation 진행 상태를 다루는 실행 제어 영역이다. +- 목적: authoring 화면이 배치 실행 세부 상태를 직접 관리하지 않게 한다. +- 유의사항: 실행 제어는 엔진 직접 제어가 아니라 `ScenarioBatchRunner`에 대한 요청으로 본다. + +## `Live Viewport` / `Heatmap Overlay` +- 개요: 실행 중 runtime snapshot을 보여 주고, playback 위에 live overlay를 얹는 시각화 영역이다. +- 목적: 실시간 관찰과 종료 후 분석을 같은 캔버스 감각으로 연결한다. +- 유의사항: live overlay와 persisted heatmap은 데이터 출처가 다르므로 같은 계산 경로로 취급하지 않는다. + +## `Run Results Panel` / `Variation Summary` / `Comparison View` +- 개요: 단일 run 요약, 반복 실행 집계, baseline 대비 대안 비교를 단계적으로 여는 분석 영역이다. +- 목적: 사용자를 바로 비교 화면으로 보내지 않고, persisted artifact를 읽는 순서를 명확히 유지한다. +- 유의사항: `Comparison View`는 `ScenarioComparison`과 `CumulativeArtifact`를 읽는 소비자이지 ad hoc delta 계산기가 아니다. + +## `Recommendation Drawer` / `Export Dialog` +- 개요: 추천 근거 검토와 canonical bundle 내보내기를 담당하는 후행 분석 영역이다. +- 목적: 비교 결과와 누적 아티팩트를 근거로 운영 대안을 설명 가능하게 제시하고, 같은 번들을 외부 공유에도 재사용하게 한다. +- 유의사항: 추천과 내보내기는 run 종료 직후의 live metric이 아니라 저장 완료된 결과 아티팩트를 입력으로 사용한다. + +## `ProjectRepository` +- 개요: 레이아웃, 시나리오 가족, run/variation 메타데이터, artifact index를 저장하고 다시 여는 도메인 계약이다. +- 목적: 프로젝트 복원이 결과 저장소 구현에 종속되지 않게 한다. +- 유의사항: project workspace의 진입점이지, raw run summary를 직접 분석하는 저장소가 아니다. + +## `ResultRepository` / `ResultAggregator` +- 개요: `ResultRepository`는 persisted 결과를 저장/조회하고, `ResultAggregator`는 run 결과로부터 variation/comparison/cumulative artifact를 생성한다. +- 목적: comparison, export, recommendation이 같은 저장 계약을 읽게 만든다. +- 유의사항: run 화면이 열릴 때마다 도메인 계산을 다시 수행하지 않게 한다. + +## `ScenarioTemplateCatalog` +- 개요: 템플릿 카드 목록과 authoring 기본값 번들을 돌려주는 도메인 authoring helper다. +- 목적: application은 카드 배치와 설명 UI만 맡고, 템플릿 기본값 구성은 domain 계약으로 둔다. +- 유의사항: 템플릿 적용 가능 여부 판단도 레이아웃 전제조건과 함께 반환하는 편이 안전하다. ## `AlternativeRecommendationService` -- 개요: `ScenarioComparison`과 `CumulativeArtifact`를 읽어 운영 대안 후보를 만드는 도메인 서비스다. -- 목적: 추천을 UI 헬퍼가 아니라 도메인 정책 서비스로 유지한다. -- 유의사항: live metric이나 엔진 내부 상태를 직접 읽는 경로를 만들지 않는다. - -## `EngineRuntime` / `IRenderBridge` -- 개요: 실행 중 playback과 viewport 동기화를 담당하는 엔진 경계다. -- 목적: application이 직접 ECS를 만지지 않고도 live 상태를 관찰하게 한다. -- 유의사항: 결과 비교, export, recommendation은 runtime와 직접 연결하지 않는다. +- 개요: `ScenarioComparison`과 `CumulativeArtifact`를 근거로 추천 후보를 생성하고 시나리오화 가능한 변경 묶음을 돌려주는 도메인 서비스다. +- 목적: 추천 규칙을 UI 헬퍼가 아니라 domain 정책으로 유지한다. +- 유의사항: live runtime state를 직접 읽는 경로를 만들지 않는다. diff --git a/uml/application-workspace-state-model.puml b/uml/application-workspace-state-model.puml new file mode 100644 index 0000000..1b86e4e --- /dev/null +++ b/uml/application-workspace-state-model.puml @@ -0,0 +1,80 @@ +@startuml application-workspace-state-model +title SafeCrowd Application Workspace State Model + +hide empty description +skinparam shadowing false +skinparam linetype ortho + +[*] --> NoProject + +NoProject --> LayoutNeedsReview : import layout / open pending project +NoProject --> LayoutReady : open approved project + +LayoutNeedsReview --> LayoutReady : review approved\nblockers cleared +LayoutNeedsReview --> NoProject : close project + +LayoutReady --> ScenarioDraftInvalid : new scenario / invalid edit +LayoutReady --> ScenarioReady : open valid scenario +LayoutReady --> NoProject : close project + +ScenarioDraftInvalid --> ScenarioReady : required fields satisfied +ScenarioDraftInvalid --> LayoutNeedsReview : reimport adds blockers +ScenarioDraftInvalid --> NoProject : close project + +ScenarioReady --> BatchRunning : Run clicked\n[approved layout && valid scenario] +ScenarioReady --> LayoutNeedsReview : layout changed / review needed +ScenarioReady --> NoProject : close project + +BatchRunning --> BatchPaused : Pause clicked +BatchPaused --> BatchRunning : Resume clicked +BatchRunning --> AggregationPending : batch finished +BatchRunning --> ScenarioReady : Stop clicked +BatchPaused --> ScenarioReady : Stop clicked + +AggregationPending --> ResultsAvailable : RunResult + VariationSummary persisted +AggregationPending --> ScenarioReady : scenario changed / rerun requested + +ResultsAvailable --> ComparisonReady : baseline + alternative summary available +ResultsAvailable --> ScenarioReady : edit scenario / rerun + +ComparisonReady --> RecommendationReady : ScenarioComparison + CumulativeArtifact ready +ComparisonReady --> ResultsAvailable : alternative deselected +ComparisonReady --> ScenarioReady : scenario family changed + +RecommendationReady --> ScenarioReady : scenarioize recommendation / edit draft +RecommendationReady --> ComparisonReady : dismiss recommendation +RecommendationReady --> NoProject : close project + +note right of LayoutNeedsReview + Run stays disabled while blocking import issues + or unapproved topology changes remain. +end note + +note right of ScenarioReady + Run is enabled only when the layout is approved + and the selected scenario passes readiness checks. +end note + +note right of AggregationPending + Live playback can be finished while persisted + variation / comparison artifacts are still + being assembled for downstream analysis. +end note + +note right of ResultsAvailable + Run Results and Variation Summary can open here. + Export still waits for the canonical bundle. +end note + +note right of ComparisonReady + Comparison is enabled only when a baseline and + at least one alternative VariationSummary exist. +end note + +note right of RecommendationReady + Recommendation is enabled only when both + ScenarioComparison and CumulativeArtifact exist. + Export is enabled when the canonical bundle is ready. +end note + +@enduml diff --git "a/uml/application-workspace-state-model.puml \355\225\264\354\204\244.md" "b/uml/application-workspace-state-model.puml \355\225\264\354\204\244.md" new file mode 100644 index 0000000..7120643 --- /dev/null +++ "b/uml/application-workspace-state-model.puml \355\225\264\354\204\244.md" @@ -0,0 +1,56 @@ +# SafeCrowd UML 설계 해설 - application-workspace-state-model.puml + +대상 파일: `uml/application-workspace-state-model.puml` + +## 문서 목적 +이 문서는 SafeCrowd application UI의 상태 게이팅 규칙을 설명한다. 핵심은 패널을 단순히 숨기거나 보이는 문제가 아니라, `언제 어떤 화면과 버튼이 활성화되는가`를 요구사항 수준으로 고정하는 것이다. + +## `NoProject` +- 개요: 아직 프로젝트가 열리지 않은 초기 상태다. +- 목적: import 또는 프로젝트 다시 열기 전에는 어떤 authoring/analysis 패널도 활성화하지 않는다. +- 유의사항: 최근 프로젝트 목록만 보일 수 있다. + +## `LayoutNeedsReview` +- 개요: 레이아웃 import 후 검토나 보정이 남아 있는 상태다. +- 목적: 실행 차단 이슈를 scenario authoring 이전에 분리한다. +- 유의사항: blocking issue가 남아 있으면 run은 비활성 상태를 유지한다. + +## `LayoutReady` +- 개요: 레이아웃이 승인되어 authoring을 시작할 수 있는 상태다. +- 목적: scenario draft 편집의 출발점을 만든다. +- 유의사항: 유효한 scenario가 아직 없을 수 있다. + +## `ScenarioDraftInvalid` +- 개요: 시나리오 초안은 있지만 필수 입력이 누락되었거나 충돌이 있는 상태다. +- 목적: `Readiness Panel`에서 어떤 입력이 부족한지 드러낸다. +- 유의사항: 저장은 허용할 수 있어도 run은 허용하지 않는다. + +## `ScenarioReady` +- 개요: 승인된 레이아웃과 유효한 scenario draft가 결합된 상태다. +- 목적: run 버튼의 기본 활성 상태를 정의한다. +- 유의사항: `approved layout && valid scenario`가 아닌 경우로 되돌아가면 다시 비활성화한다. + +## `BatchRunning` / `BatchPaused` +- 개요: batch 실행 중이거나 일시정지된 상태다. +- 목적: 실시간 진행률, 현재 run, 현재 variation을 명시적으로 추적한다. +- 유의사항: 이 상태의 analysis는 live playback 중심이며 persisted comparison은 아직 선행 조건이 충족되지 않을 수 있다. + +## `AggregationPending` +- 개요: 실행은 끝났지만 variation/comparison/cumulative artifact 조립이 아직 끝나지 않은 상태다. +- 목적: run 종료와 분석 가능 시점을 분리한다. +- 유의사항: UI는 이 구간에서 결과 요약 일부와 비활성 이유를 함께 보여 주는 편이 안전하다. + +## `ResultsAvailable` +- 개요: `RunResult`와 `VariationSummary`가 준비된 상태다. +- 목적: run summary와 repeated-run aggregate를 먼저 여는 기준점을 제공한다. +- 유의사항: export와 recommendation은 아직 더 강한 선행 조건을 요구한다. + +## `ComparisonReady` +- 개요: baseline과 1개 이상 alternative의 요약이 준비되어 비교가 가능한 상태다. +- 목적: 비교 화면 진입 게이트를 명시한다. +- 유의사항: scenario family가 바뀌거나 alternative가 사라지면 다시 `ResultsAvailable` 또는 `ScenarioReady`로 되돌아간다. + +## `RecommendationReady` +- 개요: `ScenarioComparison`과 `CumulativeArtifact`가 모두 준비되어 추천과 canonical export가 가능한 상태다. +- 목적: 추천, 내보내기, 추천 시나리오화의 진입 조건을 고정한다. +- 유의사항: recommendation과 export를 live runtime 상태에 연결하지 않는다. diff --git a/uml/project-structure.puml b/uml/project-structure.puml index d78d54e..c64a7c6 100644 --- a/uml/project-structure.puml +++ b/uml/project-structure.puml @@ -8,16 +8,20 @@ skinparam shadowing false skinparam linetype ortho package "application" { - [safecrowd_app\n(Qt App)] as App + [safecrowd_app\n(Qt App / workspace shell)] as App } package "domain" { + [ProjectRepository\n(layout / scenario workspace /\nartifact index)] as ProjectRepository [ScenarioDefinition\n(layout / population spec /\nenvironment / control)] as ScenarioDefinition + [ScenarioTemplateCatalog\n(authoring defaults /\nquick-start templates)] as TemplateCatalog [ExecutionConfig\n(time limit / sample /\nseed / repeats)] as ExecutionConfig [ScenarioCalibrationService\n(UI input -> internal params)] as Calibration [SimulationSession\n(single run orchestration)] as SimulationSession [ScenarioBatchRunner\n(baseline / alternatives /\nrepeated runs)] as BatchRunner [ResultAggregator\n(persisted summary /\ncomparison / cumulative)] as ResultAggregator + [ResultRepository\n(run / variation /\ncomparison / cumulative)] as ResultRepository + [AlternativeRecommendationService\n(evidence-based candidates)] as RecommendationService } package "engine" { @@ -31,14 +35,21 @@ package "engine" { [ECS Core\n(EntityRegistry / ComponentRegistry /\nPackedComponentStorage)] as EcsCore } -App --> ScenarioDefinition : edit / persist +App --> ProjectRepository : open / save workspace +ProjectRepository --> ScenarioDefinition : load / persist authoring aggregate +App --> ScenarioDefinition : edit authoring state +App --> TemplateCatalog : instantiate templates App --> BatchRunner : run / compare +App --> ResultRepository : load persisted analysis ExecutionConfig --> BatchRunner : experiment contract ScenarioDefinition --> BatchRunner : baseline / alternatives BatchRunner --> Calibration : preset resolution Calibration --> SimulationSession : internal params BatchRunner --> SimulationSession : spawn runs BatchRunner --> ResultAggregator : build / update persisted artifacts +ResultAggregator --> ResultRepository : persist summaries +RecommendationService --> ResultRepository : read comparison / cumulative artifacts +App --> RecommendationService : scenarioize recommendation SimulationSession --> EngineRuntime : initialize / play / stop SimulationSession --> EngineWorld : query / resources / commands App --> EngineRuntime : viewport only\n(optional) @@ -71,7 +82,18 @@ note right of ResultAggregator end note note right of App - Qt UI and app entry point + Qt UI and app entry point. + The application exposes Project / Authoring / + Run / Analysis workspaces and gates panel access + through layout review, scenario readiness, + and persisted-result availability. +end note + +note right of ProjectRepository + ProjectRepository and ResultRepository stay separate. + Workspace restore goes through the project repository, + while persisted run / comparison analysis + goes through the result repository. end note @enduml diff --git "a/uml/project-structure.puml \355\225\264\354\204\244.md" "b/uml/project-structure.puml \355\225\264\354\204\244.md" index 24da541..49df539 100644 --- "a/uml/project-structure.puml \355\225\264\354\204\244.md" +++ "b/uml/project-structure.puml \355\225\264\354\204\244.md" @@ -7,14 +7,24 @@ ## `safecrowd_app (Qt App)` - 개요: Qt 기반 실행 파일과 사용자 인터페이스 진입점이다. -- 목적: 시나리오 편집, 실행 제어, 결과 열람 같은 사용자 상호작용을 담당한다. -- 유의사항: 비교 계산, 추천 규칙, ECS 세부 로직을 UI에 두지 않는다. +- 목적: `Project`, `Authoring`, `Run`, `Analysis` 네 작업 영역을 묶어 사용자 흐름을 조정한다. +- 유의사항: 비교 계산, 추천 규칙, ECS 세부 로직, 저장 포맷 세부를 UI에 두지 않는다. + +## `ProjectRepository` +- 개요: 레이아웃, 시나리오 workspace, run/variation 메타데이터, artifact index를 저장하고 다시 여는 도메인 계약이다. +- 목적: 프로젝트 복원과 결과 조회를 다른 책임으로 분리한다. +- 유의사항: persisted 결과 분석 진입점으로 쓰지 않는다. ## `ScenarioDefinition` - 개요: SafeCrowd의 authoring aggregate다. - 목적: `FacilityLayout`, `PopulationSpec`, `EnvironmentState`, `ControlPlan`, `ExecutionConfig`를 한 실행 단위로 묶는다. - 유의사항: `PopulationProfile`은 상위 입력 계약이 아니라 `PopulationSpec`의 하위 요소로 유지한다. +## `ScenarioTemplateCatalog` +- 개요: authoring 기본값과 템플릿 빠른 시작 구성을 돌려주는 도메인 helper다. +- 목적: 템플릿 카드 설명과 기본값 조합 규칙을 UI에서 분리한다. +- 유의사항: 템플릿은 application의 카드 배치가 아니라 domain 계약으로 유지한다. + ## `ExecutionConfig` - 개요: 제한시간, 샘플링 주기, seed, 반복 횟수 같은 실행 계약이다. - 목적: 같은 시나리오를 어떤 조건으로 반복 실행했는지 재현 가능하게 남긴다. @@ -40,6 +50,16 @@ - 목적: 결과 화면, 내보내기, 추천이 같은 저장된 결과 계약을 읽게 만든다. - 유의사항: 화면 렌더링 시점의 ad hoc delta 계산기로 쓰지 않는다. +## `ResultRepository` +- 개요: run, variation, comparison, cumulative artifact를 저장하고 다시 여는 도메인 계약이다. +- 목적: 결과 화면, 내보내기, 추천이 같은 persisted 결과 진입점을 사용하게 한다. +- 유의사항: 프로젝트 복원은 이 저장소가 아니라 `ProjectRepository`를 통해 수행한다. + +## `AlternativeRecommendationService` +- 개요: persisted comparison과 cumulative artifact를 읽어 운영 대안 후보를 생성하는 도메인 서비스다. +- 목적: 추천을 설명 가능한 domain 정책으로 유지한다. +- 유의사항: live runtime state를 직접 읽는 추천 경로를 만들지 않는다. + ## `EngineRuntime` - 개요: 엔진 실행 루프와 수명주기를 관리하는 API 진입점이다. - 목적: 도메인이 엔진 내부 구현을 몰라도 initialize/play/stop 같은 제어를 수행하게 한다.