File size: 8,299 Bytes
e7a9f02
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# FlowTwin — what to fix next

Written at the end of the build, from what actually broke. Ordered by value per
hour, not by size. Everything here is a known gap, not a wish list.

---

## P0 — before the final submission

### 1. The optimizer stops separating once the queue exists

**The bug.** Intervene at T+450 s and the winner is clearly better (peak density
2.45 → 1.74, queue 2,185 → 1,534). Intervene at T+900 s and *every* strategy
returns identical peak density and critical time; the winner is chosen only
because staggering reroutes nobody and so scores lowest on the reroute-cost term.

**Why.** Once a 4,000-person queue exists it drains at the gate rate (380/min)
no matter where new arrivals are sent. Peak density over the window is already
locked in, so `J` cannot distinguish the candidates. This is physically correct
and it only appeared after the queue-extent fix landed.

**Fix.** Add an end-of-window term to `J` — a strategy that leaves the queue
smaller at T+horizon is better even when the peak is identical. Concretely, in
`strategy/counterfactual.py` the metrics already carry `final_density` and
`final_queue`; add them to `OBJECTIVES` in `optimizer.py` with weights around
0.10–0.15 and re-run `scripts/run_benchmarks.py` to confirm the early case does
not regress. Also consider scoring the *integral* of density over the window
rather than its maximum.

**Test to add.** Assert separation at both an early and a late intervention
time, so this cannot silently return.

### 2. Verify the Hugging Face path against real weights

Nothing in the perception chain has ever run against downloaded weights — this
sandbox had no access to `huggingface.co`. On a networked machine:

```bash
pip install -r backend/requirements.txt
python scripts/fetch_hf_model.py
```

Expect the CSRNet candidate to be the risky one: the repo may ship a full module
rather than a `state_dict`, or use different layer names. `perception/huggingface.py`
already handles both shapes and falls through to YOLOv8n and then to YOLOS-tiny,
but **the fallthrough has not been exercised**. Budget an hour. If CSRNet
resists, take the detector path and say so in the pitch — the architecture is
the point, not which checkpoint won.

Then drop two or three crowd photographs into `data/perception/` with an
`index.json` giving each one a `zone_id` and `zone_area_m2`, so the Perception
panel has something to click rather than requiring an upload.

### 3. Commit and push

```bash
git init && git add -A && git commit -m "FlowTwin: AI crowd digital twin for F1 venues"
git branch -M main && git remote add origin <url> && git push -u origin main
```

`data/fallback/*.json` is gitignored (16 MB of generated frames). Anyone cloning
regenerates it with `python scripts/record_fallback.py`. The trained model *is*
committed (3.8 MB) so a fresh clone predicts properly without retraining. If the
repo must stay under a size limit, move the model to a release asset and have
`DensityPredictor` fall back to the analytic baseline — it already does that
cleanly.

---

## P1 — makes the submission materially stronger

### 4. Safety mode (compression risk) as a visible mode

The compression-risk proxy is **already implemented** — density growth, velocity
drop, queue growth and opposing flow all feed the risk score, and
`risk_contributions()` exposes the per-term breakdown. What is missing is the
proposal's in-event scenario and the framing.

Cheapest useful version: a third scenario on `circuit_alpha` where the crowd is
already inside and a large fraction converges on one zone (a fan-zone stage).
That is a new JSON in `data/scenarios/` plus demand groups pointing at
`CONC_NORTH` — no engine changes. Add a "SAFETY" toggle to the header that
switches the alert panel to rank by compression risk instead of flow risk.

Keep the Astroworld framing exactly as the proposal states it: a stress-test
reference for the *type* of scenario, never a claim of prevention.

### 5. Personnel dispatch

The largest genuinely-missing feature from the master proposal. Scope it small:

- A `resources` block in the venue JSON: id, type (security/medical), position, response speed.
- `DispatchScore = expected_risk_reduction − λ·response_time − µ·cost`, computed against the primary bottleneck.
- A map layer for unit positions and a dashed line to the recommended assignment.
- A `dispatch` intervention family so it appears in the strategy table alongside the flow levers.

Half a day. High pitch value because it answers "who should act", which no
crowd-monitoring product does.

### 6. Ablation study

The proposal asks for it and it is nearly free — the benchmark harness already
supports arms. Add: routing-only, prediction + routing, and the full
prediction + counterfactual loop. This is the cleanest possible answer to "which
part is actually doing the work", and right now that question has no data behind it.

### 7. Barcelona's static baseline is identical to shortest path

Both baselines produce byte-identical results because most origin–destination
pairs in the reconstructed topology have exactly one sensible route. That is a
legitimate finding and the README says so, but the benchmark column looks broken
at a glance. Either add a second rail approach so route choice exists, or
annotate the table in-place explaining why the two columns match.

---

## P2 — polish, if time remains

- **Pitch deck.** `docs/DEMO.md` has the narrative and every real number; it needs to become slides. Slide 7 (results) should be a screenshot of the strategy table, not retyped figures.
- **Deploy.** One box running `uvicorn` behind a reverse proxy is enough. Do not containerise for the sake of it; the demo runs from one command today.
- **Record a screen capture** of both demos as the true last-resort fallback, above the replay recordings.
- **Agent groups.** Families walk together and comply together; a `group_id` already exists in the schema but nothing uses it.
- **Uncertainty on predictions.** Quantile regression would give the alert a confidence band, which is honest and cheap with the existing training script.
- **Mobile/tablet layout.** Breakpoints exist down to 900 px but have not been exercised on a real device.

---

## Things not to change

Learned the hard way; each of these was a bug once.

1. **Do not average density over a whole corridor.** Per-cell evaluation is why a queue at a gate does not freeze people 200 m back. Reverting collapses throughput to about a tenth.
2. **Do not let a link accept people until it is physically full.** The backward-wave receiving function is what makes congestion spill back instead of a corridor silently absorbing an impossible crowd.
3. **Do not measure the queue only at the stop line.** Measuring it from everyone who has stopped is what lets a gate discharge at its real rate.
4. **Do not let routes transit grandstands.** They are seating bowls. Allowing it deadlocked Barcelona with 18,000 people stranded.
5. **Do not remove the U-turn guard or the penalty decay.** Without them, repeated interventions leave a residue bouncing between two nodes forever.
6. **Do not put a language model in the decision path.** It is the answer to "where is the AI" that judges respect, and the whole explainability story depends on the reasoning being the same arithmetic that produced the score.
7. **Do not let panels re-render every frame.** Frames arrive 5×/second; rebuilding a card restarts its entry animation and the panel goes invisible. This bug shipped once already.
8. **Do not unpin the thread limits** in `flowtwin/__init__.py`. A 66-row inference takes 1,000 ms on two threads and 9 ms on one.

---

## Pre-demo checklist

```bash
python scripts/build_venues.py            # venues regenerate cleanly
cd backend && python -m pytest -q         # 70 tests
cd .. && python scripts/ui_check.py       # full acceptance path in a browser
python scripts/record_fallback.py         # refresh replay insurance
```

Then, in the dashboard: run Simulation 1, wait for the prediction to read
"critical in N seconds" (roughly **T+07:30–T+10:00**), and press *Simulate
strategies* **then** — not after the alert has been red for minutes. See P0-1 for
why that timing matters until the optimizer is fixed.