# Specification audit Every requirement from the two project documents and the build brief, checked against what is actually in the repository. Where something is partial or deliberately not built, it says so. Legend: **✓** built and verified · **◐** partial, scoped deliberately · **✗** not built --- ## Core loop — SEE → PREDICT → SIMULATE → ACT | Requirement | Status | Where | |---|---|---| | Observe current crowd state | ✓ | `crowd/state.py` — occupancy, density, inflow, outflow, velocity, utilisation, growth, conflict, risk | | Predict near-term state | ✓ | `prediction/` — trained model + physics baseline, +30/60/90/120 s | | Simulate candidate interventions | ✓ | `strategy/counterfactual.py` — each candidate on an identical clone | | Act: recommend and apply | ✓ | `strategy/optimizer.py`, `strategy/engine.py`; operator applies from the UI | | Observe again — closed loop | ✓ | applying feeds the same simulation; benchmark arm re-reviews on a cycle | ## Simulation | Requirement | Status | Notes | |---|---|---| | Individual agents | ✓ | Structure-of-arrays; 40,000 in the showcase run, 78,000 in Barcelona | | Origins, destinations, movement, velocity | ✓ | Per-agent speed factor from a clipped normal | | Routes and route preferences | ✓ | Next-hop tables per policy; three policies | | Reroute compliance | ✓ | Per agent, sampled per scenario; instructions are refused by some | | Congestion | ✓ | Weidmann speed–density, evaluated per ~12 m cell | | Capacity constraints | ✓ | Node service rate and edge throughput, both with fractional carry | | Changing infrastructure capacity | ✓ | Scripted timeline events; retunable from the What-If panel | | Event phases | ✓ | Venue phases drive the phase label; scenarios carry a phase timeline | | Rerouting | ✓ | Adaptive policy adoption with per-agent compliance | | Visible bottlenecks | ✓ | Verified in the browser and asserted in tests | | Deterministic seeds | ✓ | RNG state travels with the snapshot; asserted in tests | | Accelerated execution | ✓ | 1× … 40× | | 10,000–40,000 agents | ✓ | ~2–4 ms per step at 40,000 | ## Crowd State Engine Occupancy, density, inflow, outflow, velocity, capacity utilisation, density growth, queue growth, risk score — **✓** all present, plus opposing-flow conflict and peak local density. Per-term risk contributions are exposed so alerts can explain themselves. ## Prediction | Requirement | Status | Notes | |---|---|---| | Genuine prediction layer | ✓ | Gradient boosting, one model per horizon | | Uses actual simulation state | ✓ | Features come from the Crowd State Engine only | | "Where will congestion develop in 30/60/90 s" | ✓ | Plus 120 s | | Strong baseline first, model swappable | ✓ | Analytic mass-balance projection; model used only if it beats it on held-out seeds | | No faked ML | ✓ | Validation on disjoint seeds, reported in the UI | ## Hugging Face | Requirement | Status | Notes | |---|---|---| | Genuine integration | ✓ | `perception/huggingface.py`, real inference path | | Camera → HF model → observation → crowd state | ✓ | Shared observation schema | | Both modes converge | ✓ | `observation_to_zone_state` | | Documented model choice | ✓ | Candidate chain with the two specification models first; manifest written on load | | Never fabricates a count | ✓ | Reports the real error instead; asserted in tests | **Caveat, stated plainly:** the build environment had no network access to `huggingface.co`, so the chain could not be exercised against live weights here. The code path, the CSRNet architecture, the manifest and the failure reporting are all implemented and the endpoint is verified to fail honestly when no model loads. Run `scripts/fetch_hf_model.py` on a networked machine to download, select and verify with a real inference. ## Strategy engine | Requirement | Status | |---|---| | Generates candidate interventions | ✓ | | No action / reroute % / gate stagger / alternate exit / destination split | ✓ | | Combined interventions | ✓ | | Set depends on venue topology | ✓ — a reroute needs an alternative path; an alternate exit needs measured spare capacity | ## Counterfactual simulation Capture state → clone → apply A → simulate → reset → apply B → … → compare → select. **✓** Implemented exactly, with tests asserting that two branches of one state produce identical results and that evaluation does not advance the live run. ## Optimization Peak density, critical duration, average travel time, queue, throughput, aggregate risk, unnecessary rerouting — **✓** all seven in `J`, weights configurable by environment variable, contributions exposed per strategy. Explanation generated from the same normalised terms that produced the score. ## Dynamic routing | Requirement | Status | |---|---| | Not static shortest path | ✓ | | Cost responds to distance, travel time, congestion, density, capacity, risk | ✓ | | Oscillation prevention | ✓ — hysteresis, policy stickiness, cycle-break, asserted in tests | | Rerouting produces observable change | ✓ — verified in the browser and in tests | ## Simulation 1 — F1 Circuit Stress Test Fictional venue with 4 gates, 6 spectator zones, 8 concourse corridors, 4 exits, 3 concessions, 2 transport hubs — **✓**. Large post-race crowd, simultaneous egress, reduced exit capacity — **✓**. The full arc (normal flow → bottleneck → prediction → strategy evaluation → recommendation → rerouting → recovery) runs end to end without manual intervention and is verified by `scripts/ui_check.py`. ## Simulation 2 — Barcelona 2022 | Requirement | Status | |---|---| | Simplified digital twin of the spectator/transport network | ✓ | | Circuit, spectator zones, gates, pedestrian routes, exits, parking, transport | ✓ | | Historical facts separated from assumptions | ✓ — two labelled lists, on screen, each fact with a source | | Explicit counterfactual disclaimer | ✓ — in the venue data, the briefing and the UI | | No claim of reproducing telemetry | ✓ — asserted in tests | ## Frontend Race Control dashboard with event, crowd state, venue map, density, predicted bottlenecks, alerts, flow direction, simulation status and recommendation — **✓**. Crowds move; congested zones change colour; routes animate; predicted congestion is drawn distinctly from current congestion. Strategy simulator with measured outcomes and a recommended row — **✓**. Explainability panel using actual calculated values — **✓**. ## Backend & real-time FastAPI, modular packages (simulation / crowd / prediction / routing / strategy / perception / api), WebSocket streaming with no per-frame polling, graceful validation failures — **✓** all present, with API tests covering the failure modes. ## Evaluation Baseline shortest-path, baseline static routing and FlowTwin compared across multiple seeds with mean ± standard deviation, generated automatically — **✓** `scripts/run_benchmarks.py` → `benchmarks/BENCHMARKS.md`. No number is entered by hand. ## Demo reliability Precomputed recordings replay through the same interface — **✓** `scripts/record_fallback.py`, `ReplaySession`. The live path is always primary. --- ## Deliberately not built These come from the wider master proposal rather than the P0 list in the build brief, and were left out rather than half-built: | Item | Why | |---|---| | **Safety / compression mode as a separate mode** | ◐ The compression-risk proxy itself *is* implemented — the risk score combines density, density growth, velocity drop, queue growth and opposing flow exactly as the proposal specifies, and alerts surface those causes. What is not built is a separate in-event concert scenario and a distinct "safety mode" UI. | | **Personnel dispatch engine** | ✗ Security/medical resources, dispatch scoring and their map layer are not implemented. It is P1 in the proposal and absent from the brief's P0 list. | | **Natural-language assistant** | ✗ P2, and explicitly excluded from the decision loop by design. | | **Redis / PostgreSQL** | ✗ Deliberate. See `ARCHITECTURE.md` §10. | | **Next.js frontend** | ◐ Traded for a zero-build single-page console served by the backend. Rationale in `ARCHITECTURE.md` §8. | | **Multi-camera fusion, venue editor, city-scale transport** | ✗ P2. | ## Defects found and fixed during this audit Recorded because they are the difference between a demo that looks right and a model that is right. | Defect | Symptom | Fix | |---|---|---| | Density averaged over a whole corridor | A queue at one gate slowed everyone in the corridor, including people 200 m back with clear space; throughput collapsed to a tenth of the real value | Density and speed evaluated per ~12 m cell | | Links accepted people at capacity until physically full | Corridors silently absorbed impossible numbers instead of pushing congestion upstream | Backward-wave receiving function — a link stops accepting before it is full, so congestion spills back | | Queue extent measured only at the stop line | The standing queue occupied almost no length, so people had to walk *through* a near-jammed corridor to reach it, throttling a 500/min gate to under 200/min | Queue extent measured from everyone who has actually stopped | | Routes cut straight through grandstands | Shortest paths used seating bowls as shortcuts, deadlocking against the people trying to leave them; the Barcelona venue gridlocked with 18,000 people stranded | A route may start or end at a stand, never transit one | | Agents U-turning in corridors | After repeated interventions a residue bounced between two nodes and never arrived | Reversing onto the corridor just walked is refused unless it is the only option | | Intervention penalties compounded without limit | Repeated operator action permanently distorted the cost surface | Penalties are capped and relax back towards neutral each refresh | | What-If capacity slider sent an empty override | A control that appeared to work and did nothing | The slider now retunes the scripted timeline event itself | | Alert cards rebuilt on every frame | Entry animation restarted 5×/second, leaving the alert panel permanently mid-fade and effectively invisible | Cards keyed on structure; live values written in place | | Orphaned sessions kept simulating | A refreshed browser tab starved the event loop and new runs appeared to hang | Sessions with no subscribers idle and are reaped | | Model inference pinned to two threads | A 66-row inference took 1,000 ms instead of 9 ms | BLAS/OpenMP thread pools pinned at import | ## Verification performed - `backend/tests/` — 70 tests across simulation, intelligence and API - `scripts/ui_check.py` — drives the full acceptance path in a real browser, fails on any console error or failed request - `scripts/run_benchmarks.py` — multi-seed quantitative evaluation - `scripts/train_predictor.py` — held-out validation of the predictor