solverforge-hospital / WIREFRAME.md
github-actions[bot]
chore: sync uc-hospital Space
7596726

solverforge-hospital WIREFRAME

This file is the architectural map for beginners.

If README.md tells you how to run the app, this document tells you how the pieces fit together and in which order to read them.

Documentation Roles

The docs in this repo are meant to work together rather than compete:

  • README.md Quick start, concepts, and user-facing orientation.
  • WIREFRAME.md Architecture, execution flow, and file-map walkthrough.
  • docs/api-and-solver-policy.md REST routes, lifecycle semantics, payload shape, and solver policy notes.
  • AGENTS.md Rules for keeping code, comments, tests, and docs aligned in future changes.
  • Makefile The shared developer command surface, including the local Space validation pipeline.
  • docs/screenshot.png Current browser screenshot embedded by the README.

What This Repo Is Teaching

solverforge-hospital is a complete SolverForge example, not just a scoring snippet.

It shows how to build a planning app where:

  • the domain model is small and explicit
  • the score rules are readable one file at a time
  • the dataset is deterministic and intentionally shaped
  • the solver runs as a retained background job
  • the browser UI watches the solve through REST and SSE

The planning question is:

"For each hospital shift, which employee should be assigned?"

SolverForge Concepts In Plain Language

  • Employee Input data. The solver reads it, but does not move it.
  • Shift The thing the solver is allowed to assign.
  • employee_idx The one real decision variable in this app. It points from a shift to one employee inside Plan.employees.
  • hard score Rules that must not be broken, such as missing skills or overlapping shifts.
  • soft score Preferences and quality goals, such as honoring desired days or balancing workload.
  • retained job A solve that keeps living in memory after it starts, so the UI can poll it, pause it, resume it, stop it through runtime cancel, or inspect snapshots. Delete is terminal cleanup before the next fresh Solve, not the Stop action.

Read Order

If you are new to this repo, read files in this order:

  1. src/domain/employee.rs Learn the input facts first.
  2. src/domain/care_hub.rs Learn the hospital service-line grouping used by nearby search.
  3. src/domain/mod.rs See the planning_model! manifest that lists and exports the model modules.
  4. src/domain/plan.rs Learn the planning entity, planning variable, nearby meters, and derived fields.
  5. src/constraints/mod.rs See the full score model at a glance.
  6. src/constraints/*.rs Read one scheduling rule per file.
  7. src/data/data_seed/entrypoints.rs See the public demo-data surface.
  8. src/data/data_seed/large.rs See how the published dataset is assembled.
  9. src/solver/service.rs See how a domain solve becomes a retained runtime job.
  10. src/api/routes.rs and src/api/sse.rs See the HTTP contract.
  11. static/app/main.mjs See the browser boot sequence.
  12. static/app/shell/ and static/app/schedule/ See how stock solverforge-ui components are adapted to this hospital demo.

Runtime Flow

The shortest way to understand the app is to follow one request all the way through:

  1. The browser loads static/index.html.
  2. static/app/main.mjs loads config, the generated UI model, and the LARGE demo dataset.
  3. The frontend turns the returned PlanDto into schedule rails and side-panel summaries.
  4. When the user clicks Solve, the frontend sends the current plan to POST /jobs.
  5. src/api/routes.rs converts that HTTP request into a PlanDto.
  6. PlanDto::to_domain() rebuilds the in-memory Plan, including derived helper fields the solver expects.
  7. SolverService starts a retained solve through SolverManager<Plan>.
  8. The solver emits lifecycle, phase, telemetry, best-solution, and analysis events.
  9. src/solver/service.rs converts those runtime events into the JSON event shapes expected by the UI.
  10. The browser consumes those events over /jobs/{id}/events and updates the visible status, timeline, and analysis panels.

The browser shell also contains a visible REST API guide. That makes static/app/shell/api-guide.mjs part of the documentation surface, not just a UI helper.

File Map

.
β”œβ”€β”€ Cargo.toml
β”‚   Rust crate metadata and registry dependency requests.
β”œβ”€β”€ solver.toml
β”‚   Embedded solver policy. This is the runtime source of truth for search.
β”œβ”€β”€ solverforge.app.toml
β”‚   App metadata used by the surrounding SolverForge tooling.
β”œβ”€β”€ Dockerfile
β”‚   Container build for running the app outside the dev checkout.
β”œβ”€β”€ Makefile
β”‚   Local build, validation, and Docker/Space workflow wrapper.
β”œβ”€β”€ README.md
β”‚   Beginner run guide and learning path.
β”œβ”€β”€ docs/api-and-solver-policy.md
β”‚   REST, payload, lifecycle, telemetry, and solver-policy reference.
β”œβ”€β”€ WIREFRAME.md
β”‚   This architectural walkthrough.
β”œβ”€β”€ AGENTS.md
β”‚   Repo-specific contributor and documentation rules.
β”œβ”€β”€ docs/screenshot.png
β”‚   Current browser screenshot used by the README.
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ lib.rs
β”‚   β”‚   Crate root and public module surface.
β”‚   β”œβ”€β”€ main.rs
β”‚   β”‚   Axum server bootstrap, CORS, static serving, and route composition.
β”‚   β”œβ”€β”€ domain/
β”‚   β”‚   `planning_model!` manifest plus problem model modules.
β”‚   β”œβ”€β”€ constraints/
β”‚   β”‚   One scheduling rule per file plus the assembler in `mod.rs`.
β”‚   β”œβ”€β”€ data/
β”‚   β”‚   Deterministic demo-data generator and published entrypoints.
β”‚   β”œβ”€β”€ solver/
β”‚   β”‚   Retained-job facade over the SolverForge runtime.
β”‚   └── api/
β”‚       DTOs, REST routes, and SSE streaming.
β”œβ”€β”€ static/
β”‚   β”œβ”€β”€ index.html
β”‚   β”‚   Browser entrypoint.
β”‚   β”œβ”€β”€ sf-config.json
β”‚   β”‚   Runtime UI config for the stock frontend shell.
β”‚   β”œβ”€β”€ generated/ui-model.json
β”‚   β”‚   Generated view metadata used by `solverforge-ui`.
β”‚   └── app/
β”‚       β”œβ”€β”€ main.mjs
β”‚       β”‚   Browser boot and wiring.
β”‚       β”œβ”€β”€ shell/
β”‚       β”‚   App shell, state, solver controls, and panels.
β”‚       β”œβ”€β”€ schedule/
β”‚       β”‚   Hospital-specific grouping, presentation, and rail rendering.
β”‚       └── views/registry.mjs
β”‚           Named view registration.
└── tests/
    β”œβ”€β”€ frontend/
    β”‚   Browserless frontend tests.
    β”œβ”€β”€ e2e/
    β”‚   Playwright browser tests for the served app.
    └── support/
        Fake DOM support used by the frontend tests.

Why The Model Looks This Way

This app is intentionally narrow.

  • There is one planning entity type: Shift.
  • There is one scalar planning variable: employee_idx.
  • Nearby search is attached directly to that scalar variable.
  • The solver does not juggle multiple variable types, chained assignments, or list planning.

That makes the example easier to learn because the optimization problem stays visible:

  • facts live in Plan.employees
  • decisions live in Plan.shifts[*].employee_idx
  • score rules read those two things and judge the assignment

Why The Demo Data Is Structured

The demo-data generator is not filler.

It is designed to give beginners a problem that is:

  • deterministic
  • feasible
  • interesting enough that local search still has work to do
  • stable enough that tests and comparisons stay meaningful

One important design trick is the hidden witness roster in src/data/data_seed/witness.rs.

That internal roster gives the generator a known feasible staffing pattern. The published dataset is then shaped around that witness, while the solver only sees the final public problem. This lets the repo ship a realistic-feeling demo without random feasibility failures.

Why The Runtime Uses REST And SSE

The solve is long-lived compared with a normal request/response handler, so the backend splits the contract into two parts:

  • REST for control and snapshots
  • SSE for live progress

That separation keeps the frontend simple:

  • create a job
  • poll or fetch details when needed
  • subscribe once to the live event stream

It also mirrors how a real retained SolverForge app behaves in production.

One small but important detail: GET /jobs/{id} and GET /jobs/{id}/status return the same summary payload. The second route exists as a stock-compatible alias for clients that expect the explicit /status URL shape.

Solver Policy

solver.toml is embedded by Plan and is therefore part of the actual model, not a side document.

The shipped search policy is deliberately conservative:

  • cheapest_insertion builds a feasible first assignment
  • local search stays in the nearby scalar neighborhood
  • late_acceptance and accepted_count keep the search moving without blowing up step cost

That narrow configuration is intentional for this demo. It is the balanced 30-second policy that currently performs best on the hospital dataset.

Frontend Design

The frontend is intentionally thin.

This repo is not trying to teach a custom framework. It is showing how to take stock solverforge-ui pieces and adapt them to one concrete planning problem.

  • shell/ owns app lifecycle, backend wiring, and side panels
  • schedule/ owns the hospital-specific transformation from domain data to visual rails
  • tests in tests/frontend/ lock browserless presentation behavior down
  • tests in tests/e2e/ verify the served browser app with Playwright

Validation Surfaces

When you change this repo, think in six separate layers:

  1. Domain and constraint logic: make test-rust
  2. Slow end-to-end solve quality: make test-slow
  3. Frontend module correctness: make test-frontend-syntax
  4. Frontend module behavior: make test-frontend
  5. Served browser behavior: make test-e2e
  6. Local deploy readiness for the Docker-based Space target: make ci-local

If you keep those six layers green, the repo remains teachable and usable.