Hackathon MVP — AI early-warning system that turns reactive crowd monitoring into predictive public safety.
Crowd telemetry is generated by a 3D simulation, ingested by a FastAPI backend, stored & secured in Supabase, visualized on a live admin dashboard, and pushed as alerts to a citizen mobile app.
CrowdShield continuously monitors crowd density, movement speed, surges, and bottlenecks across zones of a venue, computes a 0–100 risk score, and surfaces level-based responses (SAFE → WARNING → HIGH → CRITICAL) to both security operators and citizens.
One line: Simulate → Analyze → Alert → Act.
- 🎮 3D crowd simulation (React Three Fiber) generating realistic telemetry
- 📊 Real-time crowd monitoring (live metrics, history, heatmaps)
⚠️ AI risk engine (0–100 score, 4 levels, explainable reasons)- 🧠 Recommendation engine (level-gated operational actions)
- 🗺️ Admin dashboard (React + Vite) with live map + camera feed, alerts, telemetry panel, analytics
- 📱 Citizen mobile app (Expo) with alerts, safe routes, incident reporting
- 🔔 Push notifications via Expo Notifications
- 🔐 Auth & RLS via Supabase (roles: citizen / authority / admin)
flowchart TD
subgraph group_simulation["Telemetry sources"]
node_sim_app["Digital-twin simulator<br/>React simulation<br/>[App.jsx]"]
node_crowd_engine["Crowd engine<br/>simulation engine<br/>[CrowdEngine.js]"]
node_telemetry_sync["Telemetry sync<br/>API producer<br/>[TelemetrySync.js]"]
node_cv_pipeline["Camera CV prototype<br/>planned producer<br/>[detect.py]"]
end
subgraph group_backend["FastAPI service"]
node_api["FastAPI entrypoint<br/>Python API<br/>[main.py]"]
node_crowd_router["Crowd ingestion<br/>crowd router<br/>[crowd.py]"]
node_live_store["Latest metrics store<br/>process-local state<br/>[shared_data.py]"]
node_risk_router["Risk API<br/>risk router<br/>[risk.py]"]
node_risk_engine["Risk engine<br/>decision engine<br/>[risk_engine.py]"]
node_recommendations["Recommendation engine<br/>decision engine"]
node_db_adapter["Persistence adapter<br/>database adapter<br/>[db.py]"]
end
subgraph group_data["Data and identity"]
node_supabase[("Supabase<br/>Postgres, auth, RLS")]
node_schema["Domain schema<br/>SQL migrations<br/>[0001_db_init.sql]"]
end
subgraph group_clients["Operator and citizen clients"]
node_web_app["Operator web console<br/>Vite React app<br/>[main.jsx]"]
node_web_routes["Web routes and auth<br/>route composition<br/>[AppRoutes.jsx]"]
node_web_live_data["Web live polling<br/>API client hook<br/>[useLiveData.js]"]
node_mobile_app["Citizen mobile app<br/>Expo Router app<br/>[_layout.tsx]"]
node_mobile_api["Mobile live-risk client<br/>API client<br/>[api.ts]"]
node_notifications["Risk notifications<br/>notification provider"]
end
node_expo_notifications{{"Expo notification services<br/>external service"}}
node_sim_app -->|"runs"| node_crowd_engine
node_crowd_engine -->|"zone state"| node_telemetry_sync
node_telemetry_sync -->|"POST metrics"| node_crowd_router
node_cv_pipeline -.->|"planned metrics source"| node_crowd_router
node_api -->|"mounts"| node_crowd_router
node_api -->|"mounts"| node_risk_router
node_crowd_router -->|"updates"| node_live_store
node_crowd_router -->|"persists metrics"| node_db_adapter
node_crowd_router -->|"scores metrics"| node_risk_engine
node_risk_engine -->|"creates risk events"| node_db_adapter
node_risk_router -->|"calculates and reads events"| node_risk_engine
node_risk_router -->|"generates"| node_recommendations
node_db_adapter -->|"durable persistence"| node_supabase
node_schema -->|"defines tables"| node_supabase
node_web_app -->|"composes"| node_web_routes
node_web_routes -->|"auth and roles"| node_supabase
node_web_app -->|"uses"| node_web_live_data
node_web_live_data -->|"polls telemetry and events"| node_api
node_mobile_app -->|"auth and domain data"| node_supabase
node_mobile_app -->|"uses"| node_mobile_api
node_mobile_api -->|"polls live risk"| node_api
node_mobile_app -->|"provides alerts"| node_notifications
node_notifications -->|"delivers notifications"| node_expo_notifications
click node_sim_app "https://github.com/chandrakantamandal/crowdshield/blob/main/Simulation/src/App.jsx"
click node_crowd_engine "https://github.com/chandrakantamandal/crowdshield/blob/main/Simulation/src/engine/CrowdEngine.js"
click node_telemetry_sync "https://github.com/chandrakantamandal/crowdshield/blob/main/Simulation/src/engine/TelemetrySync.js"
click node_cv_pipeline "https://github.com/chandrakantamandal/crowdshield/blob/main/Ai/detect.py"
click node_api "https://github.com/chandrakantamandal/crowdshield/blob/main/Server/main.py"
click node_crowd_router "https://github.com/chandrakantamandal/crowdshield/blob/main/Server/routers/crowd.py"
click node_live_store "https://github.com/chandrakantamandal/crowdshield/blob/main/Server/shared_data.py"
click node_risk_router "https://github.com/chandrakantamandal/crowdshield/blob/main/Server/routers/risk.py"
click node_risk_engine "https://github.com/chandrakantamandal/crowdshield/blob/main/Server/services/risk_engine.py"
click node_recommendations "https://github.com/chandrakantamandal/crowdshield/blob/main/Server/services/recommendation_engine.py"
click node_db_adapter "https://github.com/chandrakantamandal/crowdshield/blob/main/Server/db.py"
click node_schema "https://github.com/chandrakantamandal/crowdshield/blob/main/App/db/migrations/0001_db_init.sql"
click node_web_app "https://github.com/chandrakantamandal/crowdshield/blob/main/Web/src/main.jsx"
click node_web_routes "https://github.com/chandrakantamandal/crowdshield/blob/main/Web/src/router/AppRoutes.jsx"
click node_web_live_data "https://github.com/chandrakantamandal/crowdshield/blob/main/Web/src/lib/useLiveData.js"
click node_mobile_app "https://github.com/chandrakantamandal/crowdshield/blob/main/App/app/_layout.tsx"
click node_mobile_api "https://github.com/chandrakantamandal/crowdshield/blob/main/App/lib/api.ts"
click node_notifications "https://github.com/chandrakantamandal/crowdshield/blob/main/App/components/RiskNotificationProvider.tsx"
classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_sim_app,node_crowd_engine,node_telemetry_sync,node_cv_pipeline toneBlue
class node_api,node_crowd_router,node_live_store,node_risk_router,node_risk_engine,node_recommendations,node_db_adapter toneAmber
class node_supabase,node_schema toneMint
class node_web_app,node_web_routes,node_web_live_data,node_mobile_app,node_mobile_api,node_notifications toneRose
class node_expo_notifications toneNeutral
Simulation tick (1/s)
↓
Per-zone metrics
↓
POST /api/crowd/metrics (5 zones)
↓
FastAPI validates (Pydantic)
↓
Upsert in-memory latest_metrics
↓
Insert into Supabase crowd_data (if migrations applied)
Risk Engine
(density / speed / surge / bottleneck / flow_conflict)
↓
0–100 score + level + reasons
↓
Insert into Supabase risk_events (on level change only)
Dashboard polls (2s)
↓
GET /api/crowd/metrics + /api/risk/events
↓
Live cards + map + camera feed + alert feed + telemetry
| Layer | Technology |
|---|---|
| Simulation | React 18 + Vite 5 + React Three Fiber / drei / Three.js |
| Backend API | FastAPI + Uvicorn (Python 3.10+) |
| Data / Auth | Supabase (Postgres, Auth, Row Level Security) |
| Admin Dashboard | React 19 + Vite 8 + Tailwind CSS v4 + zustand + React Three Fiber |
| Mobile App | React Native + Expo SDK 54 + Expo Router + NativeWind |
| Notifications | Expo Notifications |
| Computer Vision (planned) | YOLO + OpenCV |
| Path | Purpose |
|---|---|
Server/ |
FastAPI backend: API, risk engine, recommendations, Supabase persistence |
Simulation/ |
3D crowd simulation that streams telemetry to the Server |
Web/ |
Admin dashboard (operator console) |
App/ |
Citizen mobile app (Expo) |
App/db/migrations/ |
Supabase SQL migrations (0001_db_init.sql, 0002_backend_persistence.sql) |
Defined in Server/services/risk_engine.py.
| Signal | Weight | Trigger |
|---|---|---|
| High Crowd Density | 35 | density > 0.00005 |
| High Movement Speed | 20 | average_speed > 1.5 |
| Crowd Surge | 15 | surge_detected == true |
| Bottleneck | 10 | bottleneck == true |
| Flow Conflict | 10 | flow_conflict == true (opposing movement in a zone) |
| Level | Score | Operator Response (recommendation_engine.py) |
|---|---|---|
| SAFE | 0–30 | Situation normal, continue monitoring |
| WARNING | 31–60 | Increase CCTV monitoring, alert nearby security |
| HIGH | 61–80 | Deploy security team, control entry gate, broadcast warning |
| CRITICAL | 81–100 | Close gate G3, open exit E2, redirect crowd, call emergency team |
Input keys: density, average_speed, surge_detected, bottleneck, flow_conflict
Output:
{
"risk_score": int,
"risk_level": "SAFE"|"WARNING"|"HIGH"|"CRITICAL",
"reasons": string[]
}On the dashboard, risk levels map to green (SAFE) / yellow (WARNING) / orange (HIGH) / red (CRITICAL).
| Method | Endpoint | Description | Params / Body |
|---|---|---|---|
| GET | / |
Health message | — |
| GET | /status |
Service status | — |
| GET | /api/crowd/metrics |
Latest in-memory metrics | — |
| GET | /api/crowd/history |
Crowd history from Supabase | limit=50, zone_id= |
| POST | /api/crowd/metrics |
Ingest telemetry (persists to crowd_data) |
Metrics JSON |
| GET | /api/risk/events |
Latest risk events | limit=50 |
| POST | /api/risk/calculate |
Compute risk score + persist event | Metrics JSON |
| POST | /api/recommendations |
Compute risk + get level-gated actions | Metrics JSON |
Pydantic model — Server/models.py
{
"camera_id": "SIM_ZONE_A",
"zone_id": "ZONE_A",
"people_count": 84,
"density": 0.0003,
"speed": 0.4,
"surge_detected": false,
"bottleneck": false,
"flow_conflict": false,
"direction": "NORTH",
"timestamp": "2026-08-09T12:34:56.789Z"
}Note:
POST /api/crowd/metricsacceptsspeed(alias) and internally maps it toaverage_speed(ConfigDict(populate_by_name=True)+validation_alias="speed"), so the API also emitsaverage_speed.
Run migrations in order in the Supabase SQL Editor:
App/db/migrations/0001_db_init.sql— schema + RLS + auto-profile triggerApp/db/migrations/0002_backend_persistence.sql— addssurge_detected/bottleneckcolumns and anon INSERT policies for backend persistence
Tables:
profilesvenuescrowd_datarisk_eventsincidentsalerts
Note: The anon key cannot run DDL — applying
0002is required for live inserts to work from the Server (see the Blocked note in Work State).
Known gap:
flow_conflictis consumed by the risk engine and posted by the simulation, but it is not yet persisted tocrowd_data(the insert inServer/db.pyomits it). A0003migration adding the column + aninsert_crowd_dataupdate would close this gap.
- Python 3.10+
- Node.js 18+ (npm)
- A Supabase project (free tier) with URL + anon key
All three services (Server, Web, Simulation) run as Vite/Vitest-style dev containers — code is mounted fresh at build time, so no hot-reload bind-mount is configured. Requires Docker Desktop running.
# from repo root — build images (first time, a few minutes)
docker compose build
# start everything: server :8000, web :5173, simulation :3000
docker compose up
# background mode (Ctrl+C won't stop it)
docker compose up -d
# tail logs / stop / full reset
docker compose logs -f
docker compose down
docker compose down -v # also drops container volumesNotes:
Serverreads credentials fromServer/.envviaenv_file. No Supabase credentials? The container still boots — persistence just degrades gracefully.Web/SimulationreceiveVITE_API_URLfrom your shell (VITE_API_URL=http://localhost:8000default). Override without editing files:VITE_API_URL=http://<your-lan-ip>:8000 docker compose up web.- Healthcheck:
servermust be healthy beforeweb/simulationstart (depends_on: condition: service_healthy). - Everything here is the dev path; the built/non-dev path is out of scope
cd Server
pip install -r requirements.txt
# create Server/.env
# SUPABASE_URL=https://<project>.supabase.co
# SUPABASE_ANON_KEY=<anon key>
uvicorn main:app --reload --port 8000Swagger UI:
http://localhost:8000/docs
No
SUPABASE_URL/key? The server still boots and the in-memory metrics + risk engine work — only DB persistence degrades gracefully.
cd Web
npm install
# create Web/.env
# VITE_SUPABASE_URL=https://<project>.supabase.co
# VITE_SUPABASE_ANON_KEY=<anon key>
# VITE_API_URL=http://localhost:8000
npm run dev
npm run build
npm run lintDashboard:
http://localhost:5173
cd Simulation
npm install
npm run devSimulation:
http://localhost:3000
Streaming to the server is off by default (TelemetrySync.js). Enable it in the simulation UI (toggle) so it starts posting each zone's telemetry to:
POST /api/crowd/metrics
The sim streams 5 zones (ZONE_A…ZONE_E) once per tick, each with:
densityspeedsurge_detectedbottleneckflow_conflict
If the server is offline it retries with a 15-second backoff so the console stays clean.
cd App
npm install
# create App/.env
# EXPO_PUBLIC_API_URL=http://<your-lan-ip>:8000
# EXPO_PUBLIC_SUPABASE_URL=...
# EXPO_PUBLIC_SUPABASE_ANON_KEY=...
npx expo startScan the QR code with Expo Go.
Expo SDK 54 — consult the versioned docs at
https://docs.expo.dev/versions/v54.0.0/before changing Expo code.
| Variable | Component | Required |
|---|---|---|
SUPABASE_URL |
Server | Yes (falls back to default project) |
SUPABASE_ANON_KEY |
Server | Yes for persistence |
VITE_SUPABASE_URL |
Web | Yes |
VITE_SUPABASE_ANON_KEY |
Web | Yes |
VITE_API_URL |
Web | Yes (http://localhost:8000) |
EXPO_PUBLIC_API_URL |
App | Yes |
EXPO_PUBLIC_SUPABASE_URL / EXPO_PUBLIC_SUPABASE_ANON_KEY |
App | Yes for auth |
1. Launch Server (uvicorn :8000)
2. Launch Simulation (:3000)
→ toggle streaming ON
3. Launch Dashboard (:5173)
→ login (Supabase auth)
→ dashboard
4. Simulation drives crowds in 5 zones
→ per-zone telemetry POSTed every second
5. Dashboard polls live metrics + risk events
→ cards/map/camera feed/alert feed update
6. Trigger a surge/bottleneck/flow-conflict in the simulation
→ risk_score climbs
→ CRITICAL alert (red) appears in dashboard + app
7. App user reports an incident / receives a push notification
- Real-time: replace 2s polling with WebSocket/SSE + push
- Ingestion: decouple via message bus (Kafka / Redis Streams / SQS)
- Time-series: TimescaleDB/InfluxDB for
crowd_data(aggregation + retention) - Config: move venue/zones to a DB config table (currently hardcoded)
- Vision: wire YOLO + OpenCV camera pipeline as a telemetry source
- Scaling: N camera ingestors → bus → risk-engine workers → events + aggregates
- Digital twin simulation
- AI crowd prediction (ML on historical
crowd_data) - Voice dashboard
- Multilingual assistant
This project is licensed under the MIT License.
See the LICENSE file for the full license text.
Built for hackathon innovation 🚀
Focus: AI + Real-time systems + Public Safety