docs(demo): add demo concept, scenarios, data, guide and runbook docs

Documents the demo-productization work from this branch: the Northstar
Mobility fictional concept and scope, the five named scenarios and their
fixed records, the seed/date-anchoring strategy (including the real bug
it fixed), the in-app Demo Guide's design and the English-suggested-
questions decision, and an operational runbook covering 5/10-minute demo
flows, reset, Unraid redeploy and rollback. Updates README with current
test counts and pointers to the new docs.
This commit is contained in:
NuklearRabbit
2026-08-03 15:24:35 +02:00
parent f04a81f6c7
commit 38f654b97a
7 changed files with 419 additions and 2 deletions
+4
View File
@@ -1,5 +1,9 @@
# PoC runbook
For the demo-specific 5-minute/10-minute walkthroughs, reset behaviour, Unraid
redeploy/rollback steps and troubleshooting, see `docs/demo-release/demo-runbook.md`.
This document covers general environment bootstrap and n8n setup.
## Bootstrap (clean checkout)
```bash
+77
View File
@@ -0,0 +1,77 @@
# Demo concept
## The fictional problem
**Northstar Mobility** is a fictional Belgian camper/van rental company with roughly 50
vehicles, one main location, a rental team, an operations manager, and a small workshop.
Northstar Mobility does not exist — this name and every customer, vehicle, booking and
procedure in the demo are synthetic. Before MobilityOps, Northstar Mobility's bookings,
returns, customer records and maintenance history lived in spreadsheets and verbal
hand-offs, so problems (duplicate customers, incorrect odometer readings, double-booked
vehicles) only surfaced late, after they had already caused friction. MobilityOps shows
how one connected system detects these problems early and lets an Operations Manager
resolve them under audit, with automation handling the routine follow-up.
## Target audience
Anyone evaluating how MobilityOps approaches operational data-quality and hand-off
problems for a small rental fleet: Operations Managers, Rental Employees, and reviewers
assessing the approach. No account setup or backend knowledge is required to start —
the login screen explains the fictional context and offers a guided path.
## Demo scope
This is a focused proof of concept, not a full ERP. In scope: vehicle and booking
management, return processing with server-side evaluation, five data-quality detection
rules each with one bounded resolution flow, a full audit trail, and orchestration via a
real n8n instance. Explicitly out of scope: accounting, payments, public reservations, a
generic CRM, inventory, HR, a second RAG stack, or autonomous write actions from any
external tool.
## What's really implemented
All of the following is functional code, not a mockup or hardcoded screen:
- Role-based authentication and server-verified sessions (Operations Manager, Rental
Employee), enforced both server-side and in the frontend's navigation/route guards.
- Vehicle and booking management, with return preview/commit sharing one authoritative
evaluation function so they can never drift.
- Five data-quality rules (possible duplicate customer, missing required field, odometer
regression, booking overlap, vehicle status conflict), each with exactly one bounded,
audited resolution endpoint.
- A full audit trail with before/after state, actor, correlation IDs, and a
"view related events" link reusing that correlation.
- Reliable outbox-based delivery to a real n8n instance, with bounded retries and
stale-delivery recovery.
- Docker Compose deployment and an automated test suite (backend pytest, Ruff, mypy,
and a full Playwright end-to-end suite covering the demo experience itself).
- A demo manifest (`GET /api/v1/demo/manifest`) as the single source of truth for the
demo's identity, synthetic-data status, reset state, scenario readiness, and honest
per-integration labels — the frontend never hardcodes what mode it's in.
## What's synthetic
The organisation, all customers, vehicles, bookings, maintenance history, the knowledge
base's procedures, and the five prepared demo scenarios are entirely fictional. No data
refers to a real person, vehicle, or company. Every seeded email uses the `.test`
reserved domain. See [`demo-data.md`](demo-data.md) for exactly how this data is
generated and kept fresh across resets.
## What's not yet live-connected
- **RAGcore**: not connected. The knowledge assistant uses a local, deterministic
keyword-matching "demo knowledge base" built from five procedure documents, honestly
labelled as such everywhere in the UI (never presented as RAGcore). A
`RAGcoreKnowledgeProvider` HTTP adapter exists and is unit-tested, ready to take over
the same interface once a real RAGcore backend is available — swapping providers is a
configuration change (`KNOWLEDGE_PROVIDER`), not a UI change.
- **ITWorx MCP Hub**: not connected. Registration is disabled by default
(`MCP_HUB_REGISTRATION_ENABLED=false`) and the UI always shows "Not connected" —
never a fabricated successful registration.
## Where to go next
- [`demo-scenarios.md`](demo-scenarios.md) — the five guided scenarios and their fixed records.
- [`demo-data.md`](demo-data.md) — seed strategy and date-anchoring.
- [`demo-guide.md`](demo-guide.md) — the in-app 8-step guided tour.
- [`demo-runbook.md`](demo-runbook.md) — how to run, reset, and redeploy the demo.
+83
View File
@@ -0,0 +1,83 @@
# Demo data strategy
## Three separate concepts
It's important to keep these distinct — they solve different problems:
1. **Deterministic records** — every seeded entity has a stable public reference
(`BK-DEMO-RETURN`, `DQ-DEMO-DUPLICATE`, `MO-024`, `CUS-0012`, ...). These references
never change between seed generations or resets; they are what the five demo
scenarios, the Demo Guide, and `docs/13-seed-and-demo-scenarios.md` all link against.
2. **Date anchoring** — the mechanism that keeps "today"/"near-future"/"currently
overlapping" scenarios true to whenever the environment was actually last reset,
described below.
3. **The reset date** — the real wall-clock moment a reset actually happened. This
changes every time someone resets; it's the input to date anchoring, not a fixed
record.
## Generating the seed dataset
```bash
python seed/generate_seed.py --anchor 2026-08-01 --seed 20260801
```
This produces the committed CSVs in `seed/*.csv` with **absolute ISO timestamps**
authored relative to a fixed anchor date (`2026-08-01`). Target scale: 50 vehicles, 180
customers (including three duplicate pairs), ~245 historical/current/future bookings,
realistic inspections and maintenance history, and the fixed quality/workflow scenarios
described in [`demo-scenarios.md`](demo-scenarios.md). Names, towns and vehicle makes are
drawn from believable Flemish/Kempen-region pools; all emails use the `.test` domain.
## Date anchoring
`backend/app/seed_loader.py` defines:
```python
SEED_AUTHORED_ANCHOR = date(2026, 8, 1) # matches generate_seed.py's --anchor
```
Every seed/reset computes `shift = today - SEED_AUTHORED_ANCHOR` and applies it to every
seeded booking, inspection, maintenance and outbox timestamp before insertion. Public
references and entity relationships are never touched by the shift — only datetime
columns move. This means:
- `BK-DEMO-RETURN` always ends "today" (or very close to it) relative to whenever you
actually reset, not relative to the frozen 2026-08-01 authoring date.
- `BK-DEMO-NEXT` and the overlap-scenario bookings always read as "near future".
- The shift is recomputed fresh on every reset, so scenarios never decay as real time
passes between resets — this was a real, confirmed bug before this fix (see
`docs/demo-release/current-demo-gap-audit.md`, gap #3): the environment would drift
further out of sync with every day it wasn't reset, and a reset didn't fix it because
nothing re-anchored the underlying stored dates.
`load_seed()` returns the resolved `anchor_date` (real today) and `seeded_at` timestamp,
and records a `demo_data_seeded` audit event carrying both the resolved anchor and the
original authoring anchor, so the shift applied on any given reset stays traceable via
the audit trail.
`dashboard.py::_today()` uses real wall-clock UTC date (not a frozen setting) to filter
"today's movements", consistent with the shifted data.
## Reset
`POST /api/v1/demo/reset` (Operations Manager only, and only if `DEMO_ALLOW_RESET=true`)
clears all MobilityOps tables, reloads the seed with a fresh date shift, re-runs the
data-quality scan, and runs a server-side **scenario-integrity check**
(`scenario_integrity_report()` in `backend/app/services/demo_manifest.py`) confirming all
five named scenarios are actually present and ready — recorded in both the response body
and the `demo_reset` audit event's metadata. Reset only ever affects MobilityOps's own
tables; it never touches shared n8n, RAGcore, or MCP data, other containers, or volumes.
## Seed-validation tests
`backend/tests/test_seed.py` proves, after every reset:
- S1 (`BK-DEMO-RETURN`/`MO-024`) is active with no end odometer recorded yet.
- S2 (`CUS-0012`/`CUS-0178`/`DQ-DEMO-DUPLICATE`) is open with matching evidence.
- S4 (`MO-016`/`BK-DEMO-OVERLAP-A`/`-B`/`DQ-DEMO-OVERLAP`) genuinely overlaps in time.
- S5 (the seeded failed outbox event) is durably `failed` immediately after reset, not
silently auto-healed by the background dispatcher (which only claims `pending` rows).
- The date-anchoring shift and the `demo_data_seeded` audit marker are both correct.
`backend/tests/test_demo_manifest.py` additionally proves that all five manifest
scenarios report `ready: true` with no `blocked_reason` right after a fresh reset.
+71
View File
@@ -0,0 +1,71 @@
# The in-app Demo Guide
## What it is (and isn't)
The Demo Guide is a compact, reusable side panel (a bottom sheet on mobile) that walks an
Operations Manager through eight fixed steps covering the demo's core functionality. It is
**not** a generic tutorial engine and **not** a source of business logic — every action it
prompts is a real action against the real API; the guide only narrates, links, and tracks
progress. It duplicates no business logic: routes for the two scenario-backed steps
(return, duplicate-merge) are resolved from the live `GET /api/v1/demo/manifest` response
rather than hardcoded, so they can never point at a stale or missing record.
## Where it lives
- `frontend/src/data/demoGuideSteps.ts` — the eight step definitions (title, what you'll
see, why it matters, the exact start action, the expected outcome, and a `route()`
function).
- `frontend/src/context/DemoGuideContext.tsx` — open/close state and step progress,
persisted to `sessionStorage` only (browser-local, never touches auth or business
state; a "Demo opnieuw voorbereiden" restart or a new browser session starts fresh).
- `frontend/src/components/DemoGuide.tsx` — the panel itself and its topbar trigger
(`DemoGuideTrigger`, Operations-Manager-only, since all eight steps require that role).
## The eight steps
1. **Understand the operational state** — the dashboard's readiness and attention queue.
2. **Open the booking needing attention**`BK-DEMO-RETURN`, resolved from the manifest.
3. **Process the return with an odometer anomaly** — same booking; the return form is
pre-filled with the suspicious reading (see [`demo-scenarios.md`](demo-scenarios.md)
scenario 1).
4. **Handle the created data-quality issue** — the fresh odometer-regression issue that
step 3 just created.
5. **Review and merge the possible duplicate customer**`DQ-DEMO-DUPLICATE`.
6. **Ask the procedure assistant a question** — one of the suggested questions.
7. **Check automation and the audit trail**.
8. **Review what's real, simulated, or not yet connected** — the About page.
## How progression works
- **"Ga naar deze stap"** navigates to the step's resolved route without marking it done.
- **"Volgende"** marks the current step complete and advances the index (used for steps
with no dedicated in-page continuation, like the knowledge-assistant step).
- Several real actions (a successful return, a resolved data-quality issue) show their
own **"Ga verder met de demo"** button that both completes the current step and
navigates straight to the next one — this is how the guide chains through steps 3→4→5
without a detour back through the panel's own controls.
- The step list on the panel lets you jump directly to any step.
- **"Demo opnieuw voorbereiden"** calls the real reset endpoint, resets the guide's own
progress, and returns to the login screen — mirroring the existing sidebar reset
control, not a separate implementation.
## A known, deliberate limitation: suggested questions stay in English
The demo knowledge base's five procedure documents (`knowledge/procedures/*.md`) are
written in English. Verified empirically while building step 6: an equivalent Dutch
question returned `insufficient` evidence against the demo provider, while the English
original returned `grounded`. Rather than mistranslate the demo's centerpiece "grounded
answer" feature into something that silently answers incorrectly, the suggested questions
on `/knowledge` and this guide's step 6 instructions stay in English, with the guide
explicitly explaining why in Dutch. Retranslating the procedure documents themselves was
judged out of scope for a demo-productization pass (see the language-split note in
`docs/demo-release/current-demo-gap-audit.md`, gap #11).
## Layout note: the panel reserves space, it doesn't overlap content
On desktop the panel is a fixed 400px-wide right-side overlay. `Layout.tsx` adds a
`guide-open` class to the main workspace while the guide is open, which reserves
`padding-right` so page content reflows aside instead of sitting underneath the panel —
this was a real bug found and fixed while building the full guided-demo Playwright test
(see `PROJECT_STATE.md`, Batch 6): without it, the return form's "Review return" button
was unclickable while the guide was open at ordinary desktop widths.
+97
View File
@@ -0,0 +1,97 @@
# Demo runbook
## Starting the demo (any environment)
Open the deployed URL (Unraid review: `http://192.168.10.150:1236`; local:
`http://localhost:1228`). The login screen names the fictional organisation, states that
all data is synthetic and all workflows are really implemented, and offers:
- **Start begeleide demo** — logs in as Operations Manager and opens the Demo Guide at
step 1.
- **Verken als Operations Manager** / **Verken als Rental Employee** — free exploration,
no guide.
No password is shown or required for either path.
## Five-minute demo (guided)
1. Click **Start begeleide demo**.
2. Follow steps 13: dashboard → the booking needing attention → confirm the pre-filled
odometer-anomaly return.
3. Step 4: resolve the data-quality issue the return just created (any decision is fine
for a quick pass — "Retain canonical" is the fastest).
4. Step 6: ask a suggested knowledge question and show the cited source.
5. Step 7: show the automation/audit trail link-through.
6. Close with step 8, the About page's honest real/synthetic/not-connected breakdown.
## Ten-minute demo (guided + one extra scenario)
Do the five-minute path above, then from `/scenarios`:
- Run **scenario 2** (duplicate customer merge) if not already done via the guide's own
step 5.
- Run **scenario 3** (booking overlap) — `/data-quality`, resolve `DQ-DEMO-OVERLAP`.
- Run **scenario 4** (failed automation retry) — `/automation`, filter to failed, retry.
All five scenarios can be run in any order and are independent of each other.
## Resetting the environment
Any Operations Manager can reset from: the sidebar ("Reset demo data"), the Demo Guide
panel ("Demo opnieuw voorbereiden"), or the About page (points to the sidebar control).
Reset requires confirmation, rebuilds the deterministic dataset with a fresh date anchor,
runs a server-side scenario-integrity check, and signs the acting session out (the server
invalidates the session as part of reset). It only ever touches MobilityOps's own tables
— never shared n8n, RAGcore, or MCP data, other containers, or volumes. It can be
disabled entirely via `DEMO_ALLOW_RESET=false` if an environment must not be rebuildable.
## Redeploying to Unraid
```bash
# From a clean local checkout on the target branch/commit:
git archive --format=tar.gz -o /tmp/mobilityops-source.tar.gz HEAD
scp /tmp/mobilityops-source.tar.gz unraid:/mnt/user/appdata/mobilityops/.deploy/source-<short-sha>.tar.gz
ssh unraid "cd /mnt/user/appdata/mobilityops \
&& tar -xzf .deploy/source-<short-sha>.tar.gz \
&& echo <full-sha> > .deploy/source-revision"
# Rebuild only what changed (api and/or web); db is never rebuilt:
ssh unraid "cd /mnt/user/appdata/mobilityops \
&& docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml up --build -d api web"
# Confirm migrations and reseed:
ssh unraid "cd /mnt/user/appdata/mobilityops \
&& docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml exec -T api python -m alembic current \
&& docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml exec -T api python -m app.cli seed --reset"
```
Extraction preserves the server's existing `.env` and named volumes (Postgres data, n8n
data) — the tarball never contains `.env` since it's gitignored. Never edit source
directly on the server; never deploy uncommitted changes.
## Rollback
`.deploy/source-revision` on the server records exactly which commit is live. Prior
source tarballs remain in `.deploy/` for rollback: extract an earlier
`source-<short-sha>.tar.gz`, update `source-revision` to match, and re-run the rebuild
step above. Database rollback is out of scope for this demo (migrations are additive;
there has been no destructive migration on this branch).
## Server safety (Unraid)
Only touch the `mobilityops` Compose project's own `api`/`web` services (and `db` only
via migrations, never manually). Never stop other containers, run `docker system prune`,
delete unrecognised images/networks/volumes, delete the MobilityOps database, overwrite
the server `.env`, print secrets, start a second permanent n8n instance, or activate
guessed RAGcore/MCP URLs. PostgreSQL is never exposed externally.
## Troubleshooting
- **A scenario shows "Niet beschikbaar" on `/scenarios`**: it has already been resolved
since the last reset (expected once you've worked through it) — reset to restore it.
- **Knowledge question returns "insufficient evidence"**: check the question is in
English and close to one of the suggested phrasings — the demo knowledge base is
English-only (see `demo-guide.md`).
- **n8n shows a failed delivery that isn't the seeded demo one**: check `/automation`'s
filter and retry — bounded retries mean it should self-heal within
`n8n_max_attempts` attempts, or can be retried manually by an Operations Manager.
+71
View File
@@ -0,0 +1,71 @@
# Demo scenarios
Five fixed scenarios are surfaced at `/scenarios` and via `GET /api/v1/demo/manifest`'s
`scenarios` array, which derives each scenario's `ready`/`blocked_reason` from the actual
underlying records — never hardcoded. All five are re-findable, using the same public
references, after every demo reset (see [`demo-data.md`](demo-data.md)).
## 1 — Odometer regression on return
- **Fixed records**: booking `BK-DEMO-RETURN` on vehicle `MO-024`.
- **Role**: Rental Employee or Operations Manager.
- **Problem demonstrated**: a return is submitted with an odometer reading lower than the
vehicle's canonical reading — usually a data-entry mistake or a return logged against
the wrong vehicle.
- **Flow**: open the booking. The return form is pre-filled with a suspicious reading
below `MO-024`'s canonical odometer (the demo never asks a visitor to invent one), with
a callout explaining why. Review the server-evaluated preview, then confirm. The
canonical odometer is never silently lowered; a data-quality issue is opened
automatically and the outcome is queued for delivery to n8n.
- **Continue**: the result panel links to the vehicle, automation status, and audit
trail, plus (inside the guided tour) a "Ga verder met de demo" button.
## 2 — Possible duplicate customer
- **Fixed records**: customers `CUS-0012` and `CUS-0178` (same email/phone), issue
`DQ-DEMO-DUPLICATE`.
- **Role**: Operations Manager (merge is a bounded, audited action).
- **Problem demonstrated**: two customer profiles that are very likely the same person,
registered twice — a risk for split booking history and duplicate contact.
- **Flow**: open the issue, compare both profiles field by field, choose which survives,
and merge. The losing profile becomes a tombstone linked to the survivor; its bookings
are rewired. The result is recorded in the audit trail.
## 3 — Overlapping bookings
- **Fixed records**: vehicle `MO-016`, bookings `BK-DEMO-OVERLAP-A`/`BK-DEMO-OVERLAP-B`,
issue `DQ-DEMO-OVERLAP`.
- **Role**: Operations Manager.
- **Problem demonstrated**: the same vehicle committed to two overlapping reservations —
a legacy-import-style conflict that a normal booking command would reject outright.
- **Flow**: open the issue, choose which of the two bookings to block; the other keeps
its current status.
## 4 — Failed automation, retried
- **Fixed record**: a seeded outbox event (`00000000-0000-4000-8000-000000000020`,
`BK-H-0020`) that is durably `failed` immediately after every reset — a safe, simulated
connection error, not a deliberately destabilised n8n configuration.
- **Role**: Operations Manager.
- **Problem demonstrated**: bounded retries and visible failure/recovery state for
workflow delivery, rather than a silent drop.
- **Flow**: open Automation, filter to failed deliveries, retry the event; it moves out
of the failed filter once delivered.
## 5 — Grounded procedure question
- **Role**: Rental Employee or Operations Manager.
- **Problem demonstrated**: an operational question gets an answer with a citation from
the demo knowledge base — or an honest "insufficient evidence" if nothing indexed
answers it — never an invented answer.
- **Flow**: open Knowledge, click one of the suggested questions (English, matching the
indexed procedure content — see the note in [`demo-guide.md`](demo-guide.md) about why
suggested questions stay in English even though the rest of the demo narrates in
Dutch), and inspect the cited source and evidence state.
## A sixth scenario exists but isn't one of the five requested
`docs/13-seed-and-demo-scenarios.md`'s **S3** ("missing inspection before next booking",
vehicle `MO-031`) is also seeded and shows up in the attention queue; it isn't surfaced
on `/scenarios` because the brief names five specific scenarios, but it remains available
for anyone exploring the attention queue directly.