blackopsrepl commited on
Commit
a38dd50
Β·
1 Parent(s): 9b2479e

docs(app): document hospital crates.io workflow

Browse files

Document the hospital app structure, planning_model manifest, retained runtime behavior, validation commands, and Docker workflow across the canonical repo docs.

Update the dependency background to published solverforge 0.9.0 and solverforge-ui 0.6.1, with no sibling path setup required.

Files changed (4) hide show
  1. AGENTS.md +112 -0
  2. PRD.md +172 -0
  3. README.md +306 -0
  4. WIREFRAME.md +276 -0
AGENTS.md ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Repository Guidelines
2
+
3
+ ## Project Structure & Module Organization
4
+
5
+ `src/` follows the current `solverforge-cli` app shape: `api/` for HTTP
6
+ routes, SSE, and DTOs; `solver/` for retained-job orchestration; `domain/mod.rs`
7
+ for the current `solverforge::planning_model!` manifest; `domain/` for the
8
+ exported model modules; `constraints/mod.rs` for constraint assembly; and
9
+ `constraints/*.rs` for the individual score rules. `data/mod.rs` is the stable
10
+ wrapper; `data/data_seed.rs` is the thin public data surface; and
11
+ `data/data_seed/` holds the deterministic sample dataset modules, including the
12
+ public entrypoints and the `LARGE` instance builder. `domain/plan.rs` owns
13
+ `Plan`, the scalar-variable `Shift`, and the scalar nearby hook functions;
14
+ `domain/employee.rs` and `domain/care_hub.rs` hold supporting domain types.
15
+ `static/` holds the browser app (`static/app/**/*.mjs`) and generated UI config.
16
+ Frontend tests live in `tests/frontend/`. Container packaging is defined by
17
+ `Dockerfile`.
18
+
19
+ This project depends on the published `solverforge` and `solverforge-ui` crates.
20
+
21
+ ## Build, Test, and Development Commands
22
+
23
+ - `make help` β€” show the supported local development and validation commands.
24
+ - `make run-release` β€” run the app locally on `:7860`.
25
+ - `make test` β€” run the standard Rust and frontend validation surface.
26
+ - `make ci-local` β€” run the Space-oriented local CI pipeline: fmt, clippy,
27
+ release build, standard tests, and Docker image build.
28
+ - `make test-slow` β€” run the ignored large-demo acceptance solve.
29
+ - `make pre-release` β€” run `make ci-local` plus the slow acceptance solve.
30
+ - `make space-build` β€” build the Docker image used by the Docker-based Space
31
+ deployment path.
32
+ - `cargo run --release --bin solverforge-hospital` β€” run the app locally on
33
+ `:7860`.
34
+ - `cargo test` β€” run Rust unit and integration tests.
35
+ - `cargo test large_demo_solves_to_feasible_terminal_state -- --ignored --nocapture`
36
+ β€” slow end-to-end solver acceptance test.
37
+ - `find static/app -name '*.mjs' -print0 | xargs -0 -n1 node --check` β€”
38
+ syntax-check frontend modules.
39
+ - `node --test tests/frontend/*.test.js` β€” run frontend tests.
40
+ - `docker build -f Dockerfile -t solverforge-hospital .` β€” build the image from
41
+ the repository root context.
42
+
43
+ ## Coding Style & Naming Conventions
44
+
45
+ Use Rust 2021 style with `cargo fmt`; keep imports and formatting
46
+ rustfmt-compatible. Prefer small, explicit functions over clever indirection.
47
+ Rust module and file names are `snake_case`; types are `UpperCamelCase`; tests
48
+ should describe behavior plainly. Frontend modules are plain ES modules in
49
+ `snake-case` filenames. Keep generator logic deterministic: do not introduce
50
+ random behavior without a fixed seed and an explicit reason.
51
+
52
+ ## Documentation And Commenting Policy
53
+
54
+ Assume a beginner reader who is new to SolverForge and new to optimization
55
+ modeling.
56
+
57
+ - Treat `README.md`, `WIREFRAME.md`, this file, `solver.toml` comments, and the
58
+ visible API help in `static/app/shell/api-guide.mjs` as one canonical
59
+ documentation surface. When one changes, audit the others that describe the
60
+ same behavior.
61
+ - `PRD.md` is background/planning context only. It may be useful, but do not
62
+ treat it as canonical current-state documentation.
63
+ - Add module-level docs or comments for every new module that explain its role
64
+ in the app and where it sits in the data flow.
65
+ - Add function comments when the function does real coordination work, rebuilds
66
+ invariants, shapes demo data, converts between layers, or otherwise does
67
+ something a beginner would not infer immediately from the signature.
68
+ - Write comments that explain intent, domain meaning, invariants, and runtime
69
+ consequences. Do not write comments that merely restate syntax.
70
+ - Keep comments truthful. If behavior changes, update or delete the stale
71
+ comment in the same patch.
72
+ - When docs mention versions, counts, routes, solver policy, or validation
73
+ expectations, verify those facts against the current code and tests in the
74
+ same turn.
75
+ - Prefer present-tense current-state docs after a refactor lands. Do not leave
76
+ future-tense planning language in repo docs unless the file is intentionally a
77
+ still-pending plan.
78
+ - When onboarding surfaces change, keep `README.md`, `WIREFRAME.md`, and this
79
+ file aligned. Update `PRD.md` only if its background note would otherwise
80
+ become misleading.
81
+
82
+ The standard to aim for is: a new reader should be able to understand why a
83
+ piece of code exists before they need to understand every line of how it works.
84
+
85
+ This repo does not have a canonical hosted GitHub Actions workflow yet. Treat
86
+ the Makefile targets, especially `make ci-local` and `make pre-release`, as the
87
+ authoritative validation surface for the current Hugging Face Space-oriented
88
+ deployment path.
89
+
90
+ ## Testing Guidelines
91
+
92
+ Add Rust tests next to the behavior they protect, usually in `src/...`
93
+ `#[cfg(test)]` modules. Frontend behavior belongs in `tests/frontend/` and
94
+ should use the existing fake DOM support in `tests/support/`. If you change
95
+ solver behavior, run both `cargo test` and the ignored large-demo solve. If you
96
+ change UI modules, run the Node syntax check and frontend tests.
97
+
98
+ ## Commit & Pull Request Guidelines
99
+
100
+ Follow the workspace commit style seen upstream: conventional prefixes such as
101
+ `fix(...)`, `feat(...)`, `refactor(...)`, `test(...)`, and `chore(...)` (for
102
+ example, `fix(runtime): route pure scalar construction to descriptor path`).
103
+ PRs should state user-visible impact, changed config or API surface, and the
104
+ exact validation commands run. Include screenshots only for visible UI changes.
105
+
106
+ ## Configuration & Runtime Notes
107
+
108
+ `solver.toml` is embedded from `domain/plan.rs` via
109
+ `#[planning_solution(..., solver_toml = "../../solver.toml")]`; treat it as
110
+ the runtime source of truth. Keep `solverforge.app.toml`,
111
+ `static/sf-config.json`, and Docker/runtime port settings aligned with any port
112
+ or route changes.
PRD.md ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # PRD: Current `solverforge-hospital` Product Contract
2
+
3
+ This file is background/planning context. It is not canonical for current
4
+ behavior. The current source of truth is the code, `solver.toml`, `README.md`,
5
+ `WIREFRAME.md`, and `AGENTS.md`.
6
+
7
+ ## Summary
8
+
9
+ `solverforge-hospital` is now a `solverforge 0.9.0` and `solverforge-ui 0.6.1`
10
+ example. This repo demonstrates current retained-runtime integration, current
11
+ stock UI-shell usage, and current scalar nearby-selection support.
12
+
13
+ The objective of the repo is not to force every upstream neighborhood family
14
+ into the hospital example. The objective is to keep the example truthful,
15
+ current, teachable, and productive on its own dataset.
16
+
17
+ ## Product Shape
18
+
19
+ ### Backend
20
+
21
+ - Rust/Axum retained-job backend
22
+ - `Plan` planning solution in the current `solverforge-cli` backend shape
23
+ - scalar planning variable: `Shift.employee_idx`
24
+ - exact runtime telemetry from `SolverTelemetry`
25
+ - hospital-specific transport projection for UI metrics
26
+
27
+ ### Frontend
28
+
29
+ - stock `SF.createBackend({ type: "axum" })`
30
+ - stock `SF.createHeader()`
31
+ - stock `SF.createStatusBar()`
32
+ - stock `SF.createSolver()`
33
+ - stock `SF.rail.createTimeline()`
34
+
35
+ ### Data
36
+
37
+ - deterministic `LARGE` dataset
38
+ - `50` employees
39
+ - `688` shifts
40
+ - feasible and stable under the shipped solver policy
41
+
42
+ ## 0.9.0 Alignment Decisions
43
+
44
+ ### Versions
45
+
46
+ All surfaced versions align to:
47
+
48
+ - `solverforge` `0.9.0`
49
+ - `solverforge-ui` `0.6.1`
50
+ - app/chart metadata `0.9.0`
51
+
52
+ No active docs or package metadata should describe the app as an `0.8.x`
53
+ example.
54
+
55
+ ### Nearby selection
56
+
57
+ Hospital exercises the scalar nearby-selection surface shipped in
58
+ `solverforge 0.9.0`.
59
+
60
+ To make nearby honest instead of guessed, the domain adds:
61
+
62
+ - `CareHub`
63
+ - `Employee.home_hub`
64
+ - `Shift.care_hub`
65
+ - scalar nearby meters on `Shift.employee_idx`
66
+
67
+ Those meters bias:
68
+
69
+ - shift -> employee by care-hub fit, required-skill fit, and obvious
70
+ infeasibility
71
+ - shift -> shift by care-hub proximity and start-time band
72
+
73
+ ### Shipped solver policy
74
+
75
+ The shipped [solver.toml](./solver.toml) is:
76
+
77
+ - `construction_heuristic(cheapest_insertion)`
78
+ - explicit `local_search`
79
+ - explicit scalar nearby neighborhoods:
80
+ - `nearby_change_move_selector`
81
+ - `nearby_swap_move_selector`
82
+ - `late_acceptance`
83
+ - `accepted_count` forager
84
+
85
+ Reason:
86
+
87
+ - this uses the new 0.9.0 scalar nearby surface directly
88
+ - it produces real post-construction improvement on the large demo
89
+ - it remains feasible and fast enough for a 30-second example run
90
+
91
+ Hospital does not ship a broader scalar union here just because upstream now
92
+ supports it. On this dataset:
93
+
94
+ - adding pillar and ruin-recreate makes the first local-search step too large
95
+ - `VND` can absorb the full global time budget before later phases run
96
+
97
+ Those are runtime/product facts, not reasons to misdocument the app.
98
+
99
+ ### Truthfulness about upstream surface
100
+
101
+ SolverForge `0.9.0` ships more scalar neighborhoods than hospital currently
102
+ uses. Hospital documents that honestly:
103
+
104
+ - upstream ships nearby, pillar, and ruin-recreate for scalar variables
105
+ - hospital currently opts into nearby-only local search because it is the most
106
+ productive configuration for this example
107
+
108
+ ## Repository Surface
109
+
110
+ ### Source
111
+
112
+ - `src/domain/mod.rs` is the current `planning_model!` manifest; `src/domain/plan.rs`
113
+ owns `Plan`, the scalar-variable `Shift`, and nearby meters;
114
+ `src/domain/employee.rs` and `src/domain/care_hub.rs` own the supporting
115
+ problem fact and proximity enum
116
+ - `src/constraints/mod.rs` is the canonical CLI-style assembler for
117
+ `create_constraints`; each hospital score rule lives in its own
118
+ `src/constraints/*.rs` module
119
+ - `src/data/mod.rs` follows the CLI stable-wrapper shape;
120
+ `src/data/data_seed.rs` orchestrates the generated dataset and
121
+ `src/data/data_seed/` owns the focused generator modules; the generator
122
+ assigns deterministic home hubs from workforce skill bundles
123
+ - `static/app/shell/config-loader.mjs` uses the explicit Axum backend adapter
124
+ - `solver.toml` reflects the shipped nearby-based solver policy
125
+
126
+ ### Docs
127
+
128
+ - `README.md` markets the repo as `0.9.0` / `0.6.1` and acts as the
129
+ beginner-facing quick start
130
+ - `WIREFRAME.md` explains architecture and request/data flow
131
+ - this PRD explains why the shipped solver policy stays narrow
132
+ - contributor docs keep `solver.toml` as the runtime source of truth
133
+ - the browser-visible API guide in `static/app/shell/api-guide.mjs` stays in
134
+ sync with `src/api/routes.rs`
135
+ - inline comments explain non-obvious coordination and invariants for beginners
136
+
137
+ ## Validation
138
+
139
+ Required checks:
140
+
141
+ - `cargo test --quiet`
142
+ - `cargo test large_demo_solves_to_feasible_terminal_state -- --ignored --nocapture`
143
+ - `find static/app -name '*.mjs' -print0 | xargs -0 -n1 node --check`
144
+ - `node --test tests/frontend/*.test.js`
145
+
146
+ Expected current outcome:
147
+
148
+ - large demo finishes `0hard`
149
+ - local search performs real post-construction improvement over construction
150
+ - docs, API guide, and config comments describe the same current product shape
151
+
152
+ ## Non-Goals
153
+
154
+ This pass does not:
155
+
156
+ - redesign the hospital domain
157
+ - rewrite `solverforge-ui`
158
+ - force every `0.9.0` scalar neighborhood family into the app
159
+ - add benchmark tooling
160
+ - change the planning direction away from `shift -> employee`
161
+
162
+ ## Final Intent
163
+
164
+ `solverforge-hospital` reads as a clean example of:
165
+
166
+ - current retained-runtime backend integration
167
+ - current stock `solverforge-ui` shell usage
168
+ - current scalar nearby-selection support in `solverforge 0.9.0`
169
+
170
+ Nothing in the repo should imply that hospital is pinned to an older release
171
+ line or that unshipped broader scalar neighborhoods are the recommended
172
+ hospital policy.
README.md ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: SolverForge Hospital
3
+ emoji: πŸ₯
4
+ colorFrom: orange
5
+ colorTo: red
6
+ sdk: docker
7
+ app_port: 7860
8
+ pinned: false
9
+ license: apache-2.0
10
+ short_description: SolverForge 0.9.0 hospital scheduling example with deterministic demo data and a retained runtime
11
+ ---
12
+
13
+ # SolverForge Hospital
14
+
15
+ `solverforge-hospital` is a beginner-friendly example of a real SolverForge app.
16
+ It answers one concrete question:
17
+
18
+ "Given a hospital workforce and a month of shifts, which employee should cover each shift?"
19
+
20
+ ## Documentation Map
21
+
22
+ Each top-level document has a different job:
23
+
24
+ - `README.md`
25
+ Quick start, concepts, API surface, and the shortest learning path.
26
+ - `WIREFRAME.md`
27
+ Architecture and request/data flow across backend, runtime, and frontend.
28
+ - `PRD.md`
29
+ Background/planning notes only. It is not canonical for current behavior.
30
+ - `AGENTS.md`
31
+ Contribution rules, validation commands, and the documentation standard for future edits.
32
+ - `Makefile`
33
+ The beginner-friendly command surface for local development, validation, and
34
+ Docker-based Hugging Face Space preparation.
35
+
36
+ If you are new to SolverForge, read this repo as three layers:
37
+
38
+ 1. The planning-model manifest and model modules in `src/domain/`
39
+ 2. The score rules in `src/constraints/`
40
+ 3. The runtime and browser app in `src/api/`, `src/solver/`, and `static/app/`
41
+
42
+ ## What SolverForge Is Doing Here
43
+
44
+ SolverForge is the optimization engine. In this app:
45
+
46
+ - `Employee` is a problem fact: input data the solver does not move
47
+ - `Shift` is the planning entity: the thing the solver assigns
48
+ - `Shift.employee_idx` is the planning variable: the actual choice the solver makes
49
+ - the constraints decide whether an assignment is legal and whether it is good
50
+ - `solver.toml` tells the runtime how to search for a better assignment
51
+
52
+ The app ships one serious demo instance rather than many toy presets:
53
+
54
+ - fixed random seed
55
+ - fixed 28-day schedule horizon
56
+ - 50 employees
57
+ - 688 shifts
58
+ - 8-hour shifts
59
+ - deterministic generator
60
+ - retained-job runtime with pause, resume, cancel, snapshot, and analysis
61
+
62
+ ## Read The Code In This Order
63
+
64
+ If you want to learn the codebase, this order is the shortest path:
65
+
66
+ 1. [src/domain/employee.rs](src/domain/employee.rs)
67
+ `Employee` is the input fact model.
68
+ 2. [src/domain/care_hub.rs](src/domain/care_hub.rs)
69
+ `CareHub` explains how the app models service-line proximity.
70
+ 3. [src/domain/mod.rs](src/domain/mod.rs)
71
+ This is the `planning_model!` manifest that lists and exports the model modules.
72
+ 4. [src/domain/plan.rs](src/domain/plan.rs)
73
+ `Shift`, `Plan`, nearby search meters, and transport normalization live here.
74
+ 5. [src/constraints/mod.rs](src/constraints/mod.rs)
75
+ This lists every scoring rule.
76
+ 6. [src/constraints/*.rs](src/constraints)
77
+ Each file explains one real scheduling rule.
78
+ 7. [src/data/data_seed/entrypoints.rs](src/data/data_seed/entrypoints.rs)
79
+ This shows the public demo-data surface.
80
+ 8. [src/data/data_seed/large.rs](src/data/data_seed/large.rs)
81
+ This assembles the published benchmark instance.
82
+ 9. [src/solver/service.rs](src/solver/service.rs)
83
+ This is the retained-job runtime facade.
84
+ 10. [src/api/routes.rs](src/api/routes.rs) and [src/api/sse.rs](src/api/sse.rs)
85
+ These expose the HTTP and live-event contracts.
86
+ 11. [static/app/main.mjs](static/app/main.mjs)
87
+ This is the browser boot sequence.
88
+ 12. [static/app/shell/](static/app/shell) and [static/app/schedule/](static/app/schedule)
89
+ These adapt stock `solverforge-ui` pieces to the hospital example.
90
+
91
+ ## Project Shape
92
+
93
+ - `src/domain/`
94
+ `planning_model!` manifest, planning model, derived fields, nearby meters.
95
+ - `src/constraints/`
96
+ Hard and soft scoring rules.
97
+ - `src/data/`
98
+ Deterministic demo-data generator.
99
+ - `src/solver/`
100
+ Retained-job orchestration over `SolverManager<Plan>`.
101
+ - `src/api/`
102
+ REST routes, DTOs, and SSE endpoint.
103
+ - `static/app/`
104
+ Browser code built as plain ES modules on top of `solverforge-ui`.
105
+ - `tests/frontend/`
106
+ Browserless UI tests using the fake DOM in `tests/support/`.
107
+
108
+ ## Quick Start
109
+
110
+ This repo depends only on published crates.io releases:
111
+
112
+ - `solverforge = 0.9.0`
113
+ - `solverforge-ui = 0.6.1`
114
+
115
+ Run the app:
116
+
117
+ ```sh
118
+ make run-release
119
+ ```
120
+
121
+ Then open `http://localhost:7860`.
122
+
123
+ If you want to inspect the command surface first:
124
+
125
+ ```sh
126
+ make help
127
+ ```
128
+
129
+ ## What You Will See
130
+
131
+ The browser UI does five things:
132
+
133
+ 1. Loads `static/sf-config.json`
134
+ 2. Loads `static/generated/ui-model.json`
135
+ 3. Fetches the `LARGE` demo dataset from `/demo-data/LARGE`
136
+ 4. Renders two schedule views:
137
+ `By location` and `By employee`
138
+ 5. Starts a retained solving job when you click Solve
139
+
140
+ The frontend is intentionally thin. It mostly uses stock `solverforge-ui` pieces:
141
+
142
+ - `SF.createBackend({ type: "axum" })`
143
+ - `SF.createHeader()`
144
+ - `SF.createStatusBar()`
145
+ - `SF.createSolver()`
146
+ - `SF.rail.createTimeline()`
147
+
148
+ The app also exposes a visible "REST API" guide inside the browser shell. That
149
+ guide is generated from `static/app/shell/api-guide.mjs` and is expected to
150
+ match the routes in `src/api/routes.rs` and the route list below.
151
+
152
+ ## Run Validation
153
+
154
+ Standard local validation:
155
+
156
+ ```sh
157
+ make test
158
+ ```
159
+
160
+ Space-style local CI simulation:
161
+
162
+ ```sh
163
+ make ci-local
164
+ ```
165
+
166
+ Slow acceptance solve:
167
+
168
+ ```sh
169
+ make test-slow
170
+ ```
171
+
172
+ `make ci-local` is the main pre-push validation path for this repo. It checks
173
+ formatting, runs clippy, builds the release binary, runs the standard Rust and
174
+ frontend test surface, and builds the Docker image that the Space-style deploy
175
+ path expects. There is no hosted GitHub Actions workflow to mirror yet.
176
+
177
+ ## Demo Dataset Intent
178
+
179
+ The generator is not random filler data. It is designed to publish a problem
180
+ that is:
181
+
182
+ - hard-feasible
183
+ - deterministic
184
+ - narrow enough that candidate choice matters
185
+ - rich enough that soft-score improvements still exist after construction
186
+
187
+ The hidden witness roster in `src/data/data_seed/witness.rs` is especially
188
+ important: it gives the generator an internal feasible assignment that is used
189
+ to shape unavailability and preferences, but it is never shown to the solver.
190
+
191
+ The `LARGE` dataset is built once and cached in memory because it is immutable
192
+ and deterministic. Each request still receives an owned `Plan`, but the server
193
+ does not regenerate the same public benchmark from scratch every time.
194
+
195
+ ## REST API
196
+
197
+ - `GET /health`
198
+ - `GET /info`
199
+ - `GET /demo-data`
200
+ - `GET /demo-data/{id}`
201
+ - `POST /jobs`
202
+ - `GET /jobs/{id}`
203
+ - `GET /jobs/{id}/status`
204
+ - `GET /jobs/{id}/snapshot`
205
+ - `GET /jobs/{id}/analysis`
206
+ - `POST /jobs/{id}/pause`
207
+ - `POST /jobs/{id}/resume`
208
+ - `POST /jobs/{id}/cancel`
209
+ - `DELETE /jobs/{id}`
210
+ - `GET /jobs/{id}/events`
211
+
212
+ Lifecycle semantics:
213
+
214
+ - `pause` requests an exact runtime-managed pause and checkpoint
215
+ - `resume` continues from the retained checkpoint
216
+ - the user-facing Stop control calls `cancel` to stop a live or paused job
217
+ - `delete` removes a terminal retained job before the next fresh solve
218
+ - `GET /jobs/{id}` and `GET /jobs/{id}/status` return the same summary payload
219
+ - `snapshot_revision={n}` is optional on both snapshot and analysis requests
220
+ - reconnects bootstrap from current runtime status plus retained snapshot
221
+ revision, not from cached SSE text
222
+
223
+ ## Payload Shape
224
+
225
+ The transport payload mirrors the domain model directly. The important field is
226
+ `employeeIdx`, which is the scalar planning assignment chosen for each shift.
227
+
228
+ ```json
229
+ {
230
+ "employees": [
231
+ {
232
+ "id": "employee-0",
233
+ "name": "Alex Smith",
234
+ "homeHub": "critical_care",
235
+ "skills": ["Critical care doctor"],
236
+ "unavailableDates": [],
237
+ "undesiredDates": [],
238
+ "desiredDates": []
239
+ }
240
+ ],
241
+ "shifts": [
242
+ {
243
+ "id": "shift-1",
244
+ "start": "2024-01-01T08:00:00",
245
+ "end": "2024-01-01T16:00:00",
246
+ "location": "Critical care",
247
+ "careHub": "critical_care",
248
+ "requiredSkill": "Critical care doctor",
249
+ "employeeIdx": 0
250
+ }
251
+ ],
252
+ "score": "0hard/0soft"
253
+ }
254
+ ```
255
+
256
+ Telemetry is intentionally a UI-facing projection, not the raw runtime type.
257
+ Fields like `elapsedMs`, `movesPerSecond`, and `acceptanceRate` are derived for
258
+ display.
259
+
260
+ ## Solver Policy
261
+
262
+ The runtime source of truth is [solver.toml](./solver.toml).
263
+
264
+ The currently shipped policy is deliberately narrow:
265
+
266
+ - `construction_heuristic = cheapest_insertion`
267
+ - one local-search phase
268
+ - nearby scalar change/swap selectors
269
+ - `late_acceptance`
270
+ - `accepted_count`
271
+
272
+ That is not an accident. A candidate sweep against broader scalar phase-2/3
273
+ variants showed that the current nearby baseline remained the best balanced
274
+ policy for this dataset and 30-second budget.
275
+
276
+ The canonical description of current behavior is `solver.toml`, the code, this
277
+ README, and `WIREFRAME.md`, not `PRD.md`.
278
+
279
+ ## Constraints
280
+
281
+ Hard constraints:
282
+
283
+ - Assigned shift
284
+ - Required skill
285
+ - Overlapping shift
286
+ - At least 10 hours between 2 shifts
287
+ - One shift per day
288
+ - Unavailable employee
289
+
290
+ Soft constraints:
291
+
292
+ - Undesired day for employee
293
+ - Desired day for employee
294
+ - Balance employee assignments
295
+
296
+ ## Docker
297
+
298
+ Build from this repository root:
299
+
300
+ ```sh
301
+ make space-build
302
+ make space-run
303
+ ```
304
+
305
+ The Docker image resolves `solverforge` and `solverforge-ui` from crates.io, so
306
+ no extra source checkout is required in the build context.
WIREFRAME.md ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # solverforge-hospital WIREFRAME
2
+
3
+ This file is the architectural map for beginners.
4
+
5
+ If `README.md` tells you how to run the app, this document tells you how the
6
+ pieces fit together and in which order to read them.
7
+
8
+ ## Documentation Roles
9
+
10
+ The docs in this repo are meant to work together rather than compete:
11
+
12
+ - `README.md`
13
+ Quick start, concepts, route list, and user-facing orientation.
14
+ - `WIREFRAME.md`
15
+ Architecture, execution flow, and file-map walkthrough.
16
+ - `PRD.md`
17
+ Background/planning notes only. It is not canonical for current behavior.
18
+ - `AGENTS.md`
19
+ Rules for keeping code, comments, tests, and docs aligned in future changes.
20
+ - `Makefile`
21
+ The shared developer command surface, including the local Space validation
22
+ pipeline.
23
+
24
+ ## What This Repo Is Teaching
25
+
26
+ `solverforge-hospital` is a complete SolverForge example, not just a scoring
27
+ snippet.
28
+
29
+ It shows how to build a planning app where:
30
+
31
+ - the domain model is small and explicit
32
+ - the score rules are readable one file at a time
33
+ - the dataset is deterministic and intentionally shaped
34
+ - the solver runs as a retained background job
35
+ - the browser UI watches the solve through REST and SSE
36
+
37
+ The planning question is:
38
+
39
+ "For each hospital shift, which employee should be assigned?"
40
+
41
+ ## SolverForge Concepts In Plain Language
42
+
43
+ - `Employee`
44
+ Input data. The solver reads it, but does not move it.
45
+ - `Shift`
46
+ The thing the solver is allowed to assign.
47
+ - `employee_idx`
48
+ The one real decision variable in this app. It points from a shift to one
49
+ employee inside `Plan.employees`.
50
+ - hard score
51
+ Rules that must not be broken, such as missing skills or overlapping shifts.
52
+ - soft score
53
+ Preferences and quality goals, such as honoring desired days or balancing
54
+ workload.
55
+ - retained job
56
+ A solve that keeps living in memory after it starts, so the UI can poll it,
57
+ pause it, resume it, stop it through runtime cancel, or inspect snapshots.
58
+ Delete is terminal cleanup before the next fresh Solve, not the Stop action.
59
+
60
+ ## Read Order
61
+
62
+ If you are new to this repo, read files in this order:
63
+
64
+ 1. `src/domain/employee.rs`
65
+ Learn the input facts first.
66
+ 2. `src/domain/care_hub.rs`
67
+ Learn the hospital service-line grouping used by nearby search.
68
+ 3. `src/domain/mod.rs`
69
+ See the `planning_model!` manifest that lists and exports the model modules.
70
+ 4. `src/domain/plan.rs`
71
+ Learn the planning entity, planning variable, nearby meters, and derived
72
+ fields.
73
+ 5. `src/constraints/mod.rs`
74
+ See the full score model at a glance.
75
+ 6. `src/constraints/*.rs`
76
+ Read one scheduling rule per file.
77
+ 7. `src/data/data_seed/entrypoints.rs`
78
+ See the public demo-data surface.
79
+ 8. `src/data/data_seed/large.rs`
80
+ See how the published dataset is assembled.
81
+ 9. `src/solver/service.rs`
82
+ See how a domain solve becomes a retained runtime job.
83
+ 10. `src/api/routes.rs` and `src/api/sse.rs`
84
+ See the HTTP contract.
85
+ 11. `static/app/main.mjs`
86
+ See the browser boot sequence.
87
+ 12. `static/app/shell/` and `static/app/schedule/`
88
+ See how stock `solverforge-ui` components are adapted to this hospital demo.
89
+
90
+ ## Runtime Flow
91
+
92
+ The shortest way to understand the app is to follow one request all the way
93
+ through:
94
+
95
+ 1. The browser loads `static/index.html`.
96
+ 2. `static/app/main.mjs` loads config, the generated UI model, and the `LARGE`
97
+ demo dataset.
98
+ 3. The frontend turns the returned `PlanDto` into schedule rails and side-panel
99
+ summaries.
100
+ 4. When the user clicks Solve, the frontend sends the current plan to
101
+ `POST /jobs`.
102
+ 5. `src/api/routes.rs` converts that HTTP request into a `PlanDto`.
103
+ 6. `PlanDto::to_domain()` rebuilds the in-memory `Plan`, including derived
104
+ helper fields the solver expects.
105
+ 7. `SolverService` starts a retained solve through `SolverManager<Plan>`.
106
+ 8. The solver emits lifecycle, phase, telemetry, best-solution, and analysis
107
+ events.
108
+ 9. `src/solver/service.rs` converts those runtime events into the JSON event
109
+ shapes expected by the UI.
110
+ 10. The browser consumes those events over `/jobs/{id}/events` and updates the
111
+ visible status, timeline, and analysis panels.
112
+
113
+ The browser shell also contains a visible REST API guide. That makes
114
+ `static/app/shell/api-guide.mjs` part of the documentation surface, not just a
115
+ UI helper.
116
+
117
+ ## File Map
118
+
119
+ ```text
120
+ .
121
+ β”œβ”€β”€ Cargo.toml
122
+ β”‚ Rust crate metadata and crates.io dependencies.
123
+ β”œβ”€β”€ solver.toml
124
+ β”‚ Embedded solver policy. This is the runtime source of truth for search.
125
+ β”œβ”€β”€ solverforge.app.toml
126
+ β”‚ App metadata used by the surrounding SolverForge tooling.
127
+ β”œβ”€β”€ Dockerfile
128
+ β”‚ Container build for running the app outside the dev checkout.
129
+ β”œβ”€β”€ Makefile
130
+ β”‚ Local build, validation, and Docker/Space workflow wrapper.
131
+ β”œβ”€β”€ README.md
132
+ β”‚ Beginner run guide, API overview, and learning path.
133
+ β”œβ”€β”€ PRD.md
134
+ β”‚ Background/planning notes; not the source of truth for current behavior.
135
+ β”œβ”€β”€ WIREFRAME.md
136
+ β”‚ This architectural walkthrough.
137
+ β”œβ”€β”€ AGENTS.md
138
+ β”‚ Repo-specific contributor and documentation rules.
139
+ β”œβ”€β”€ src/
140
+ β”‚ β”œβ”€β”€ lib.rs
141
+ β”‚ β”‚ Crate root and public module surface.
142
+ β”‚ β”œβ”€β”€ main.rs
143
+ β”‚ β”‚ Axum server bootstrap, CORS, static serving, and route composition.
144
+ β”‚ β”œβ”€β”€ domain/
145
+ β”‚ β”‚ `planning_model!` manifest plus problem model modules.
146
+ β”‚ β”œβ”€β”€ constraints/
147
+ β”‚ β”‚ One scheduling rule per file plus the assembler in `mod.rs`.
148
+ β”‚ β”œβ”€β”€ data/
149
+ β”‚ β”‚ Deterministic demo-data generator and published entrypoints.
150
+ β”‚ β”œβ”€β”€ solver/
151
+ β”‚ β”‚ Retained-job facade over the SolverForge runtime.
152
+ β”‚ └── api/
153
+ β”‚ DTOs, REST routes, and SSE streaming.
154
+ β”œβ”€β”€ static/
155
+ β”‚ β”œβ”€β”€ index.html
156
+ β”‚ β”‚ Browser entrypoint.
157
+ β”‚ β”œβ”€β”€ sf-config.json
158
+ β”‚ β”‚ Runtime UI config for the stock frontend shell.
159
+ β”‚ β”œβ”€β”€ generated/ui-model.json
160
+ β”‚ β”‚ Generated view metadata used by `solverforge-ui`.
161
+ β”‚ └── app/
162
+ β”‚ β”œβ”€β”€ main.mjs
163
+ β”‚ β”‚ Browser boot and wiring.
164
+ β”‚ β”œβ”€β”€ shell/
165
+ β”‚ β”‚ App shell, state, solver controls, and panels.
166
+ β”‚ β”œβ”€β”€ schedule/
167
+ β”‚ β”‚ Hospital-specific grouping, presentation, and rail rendering.
168
+ β”‚ └── views/registry.mjs
169
+ β”‚ Named view registration.
170
+ └── tests/
171
+ β”œβ”€β”€ frontend/
172
+ β”‚ Browserless frontend tests.
173
+ └── support/
174
+ Fake DOM support used by the frontend tests.
175
+ ```
176
+
177
+ ## Why The Model Looks This Way
178
+
179
+ This app is intentionally narrow.
180
+
181
+ - There is one planning entity type: `Shift`.
182
+ - There is one scalar planning variable: `employee_idx`.
183
+ - Nearby search is attached directly to that scalar variable.
184
+ - The solver does not juggle multiple variable types, chained assignments, or
185
+ list planning.
186
+
187
+ That makes the example easier to learn because the optimization problem stays
188
+ visible:
189
+
190
+ - facts live in `Plan.employees`
191
+ - decisions live in `Plan.shifts[*].employee_idx`
192
+ - score rules read those two things and judge the assignment
193
+
194
+ ## Why The Demo Data Is Structured
195
+
196
+ The demo-data generator is not filler.
197
+
198
+ It is designed to give beginners a problem that is:
199
+
200
+ - deterministic
201
+ - feasible
202
+ - interesting enough that local search still has work to do
203
+ - stable enough that tests and comparisons stay meaningful
204
+
205
+ One important design trick is the hidden witness roster in
206
+ `src/data/data_seed/witness.rs`.
207
+
208
+ That internal roster gives the generator a known feasible staffing pattern.
209
+ The published dataset is then shaped around that witness, while the solver only
210
+ sees the final public problem. This lets the repo ship a realistic-feeling
211
+ demo without random feasibility failures.
212
+
213
+ ## Why The Runtime Uses REST And SSE
214
+
215
+ The solve is long-lived compared with a normal request/response handler, so the
216
+ backend splits the contract into two parts:
217
+
218
+ - REST for control and snapshots
219
+ - SSE for live progress
220
+
221
+ That separation keeps the frontend simple:
222
+
223
+ - create a job
224
+ - poll or fetch details when needed
225
+ - subscribe once to the live event stream
226
+
227
+ It also mirrors how a real retained SolverForge app behaves in production.
228
+
229
+ One small but important detail: `GET /jobs/{id}` and `GET /jobs/{id}/status`
230
+ return the same summary payload. The second route exists as a stock-compatible
231
+ alias for clients that expect the explicit `/status` URL shape.
232
+
233
+ ## Solver Policy
234
+
235
+ `solver.toml` is embedded by `Plan` and is therefore part of the actual model,
236
+ not a side document.
237
+
238
+ The shipped search policy is deliberately conservative:
239
+
240
+ - `cheapest_insertion` builds a feasible first assignment
241
+ - local search stays in the nearby scalar neighborhood
242
+ - `late_acceptance` and `accepted_count` keep the search moving without blowing
243
+ up step cost
244
+
245
+ That narrow configuration is intentional for this demo. It is the balanced
246
+ 30-second policy that currently performs best on the hospital dataset.
247
+
248
+ ## Frontend Design
249
+
250
+ The frontend is intentionally thin.
251
+
252
+ This repo is not trying to teach a custom framework. It is showing how to take
253
+ stock `solverforge-ui` pieces and adapt them to one concrete planning problem.
254
+
255
+ - `shell/` owns app lifecycle, backend wiring, and side panels
256
+ - `schedule/` owns the hospital-specific transformation from domain data to
257
+ visual rails
258
+ - tests in `tests/frontend/` lock that presentation behavior down without a
259
+ full browser
260
+
261
+ ## Validation Surfaces
262
+
263
+ When you change this repo, think in four separate layers:
264
+
265
+ 1. Domain and constraint logic:
266
+ `make test-rust`
267
+ 2. Slow end-to-end solve quality:
268
+ `make test-slow`
269
+ 3. Frontend module correctness:
270
+ `make test-frontend-syntax`
271
+ 4. Frontend behavior:
272
+ `make test-frontend`
273
+ 5. Local deploy readiness for the Docker-based Space target:
274
+ `make ci-local`
275
+
276
+ If you keep those five layers green, the repo remains teachable and usable.