# 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.