diff --git a/README.md b/README.md index c03e484..0a1b3cc 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,17 @@ design decision and visual evidence. All people, companies, vehicles, bookings and documents are synthetic. The workflows, validation, integrations, audit logging and access boundaries are intended to be real. +## Demo + +The demo presents itself as **Northstar Mobility**, a fictitious Belgian camper/van +rental company — the login screen, a permanent "Synthetische demo" indicator, an in-app +guided tour (Demo Guide), a curated `/scenarios` overview, and an "Over deze demo" page +all make the fictional context, synthetic-data status, and real-vs-simulated boundaries +explicit without any verbal explanation. See `docs/demo-release/` for the full demo +concept, the five named scenarios, the seed/date-anchoring strategy, the guided-tour +design, and the operational runbook (5-minute and 10-minute demo flows, reset, redeploy, +rollback). + ## Scope The PoC implements: @@ -77,6 +88,9 @@ and `artifacts/final-acceptance/summary.md` for the original M0–M7 acceptance - `artifacts/design-validation/` — baseline audit, Stitch direction references and implemented responsive captures. - `docs/functional-completion/` — the functional-completion audit and pre-work server baseline. - `artifacts/functional-completion/` — functional-completion acceptance evidence. +- `docs/demo-release/` — demo concept, scenarios, seed/date-anchoring strategy, guided + tour, and runbook. +- `artifacts/demo-release/` — demo-productization acceptance evidence. ## Quickstart @@ -100,9 +114,9 @@ All defaults are configurable via `.env` (see `.env.example`). ## Quality gates ```bash -make test # backend: pytest (117 tests) +make test # backend: pytest (127 tests) make lint # backend: ruff + mypy (strict, zero errors) -make e2e # frontend: Playwright end-to-end (37 tests, live stack required) +make e2e # frontend: Playwright end-to-end (56 tests, live stack required) ``` Frontend build/typecheck: `cd frontend && npm run build` (`tsc -b && vite build`). diff --git a/docs/17-runbook.md b/docs/17-runbook.md index 8a56b8c..8314167 100644 --- a/docs/17-runbook.md +++ b/docs/17-runbook.md @@ -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 diff --git a/docs/demo-release/demo-concept.md b/docs/demo-release/demo-concept.md new file mode 100644 index 0000000..a46e52b --- /dev/null +++ b/docs/demo-release/demo-concept.md @@ -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. diff --git a/docs/demo-release/demo-data.md b/docs/demo-release/demo-data.md new file mode 100644 index 0000000..c454478 --- /dev/null +++ b/docs/demo-release/demo-data.md @@ -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. diff --git a/docs/demo-release/demo-guide.md b/docs/demo-release/demo-guide.md new file mode 100644 index 0000000..39d355f --- /dev/null +++ b/docs/demo-release/demo-guide.md @@ -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. diff --git a/docs/demo-release/demo-runbook.md b/docs/demo-release/demo-runbook.md new file mode 100644 index 0000000..deff09a --- /dev/null +++ b/docs/demo-release/demo-runbook.md @@ -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 1–3: 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-.tar.gz +ssh unraid "cd /mnt/user/appdata/mobilityops \ + && tar -xzf .deploy/source-.tar.gz \ + && echo > .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-.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. diff --git a/docs/demo-release/demo-scenarios.md b/docs/demo-release/demo-scenarios.md new file mode 100644 index 0000000..02cff0e --- /dev/null +++ b/docs/demo-release/demo-scenarios.md @@ -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.