Real-time retail analytics platform using computer vision and a REST API to track visitor behaviour, zone engagement, queue dynamics, and conversion for Purplle offline stores.
git clone <your-repo-url>
cd store-intelligence
docker compose up --build
# API available at http://localhost:8000
# Dashboard at http://localhost:8000/dashboardDetection pipeline (run separately, requires GPU-optional Python env):
pip install ultralytics opencv-python numpy httpx
cd pipeline
python detect.py --store ST1008 --video "../../Store 1/CAM 3 - entry.mp4" --camera CAM3 --camera-type entry --api-url http://localhost:8000/events/ingeststore-intelligence/
├── pipeline/ # Video processing & event emission
│ ├── detect.py # Main detection + tracking pipeline
│ ├── tracker.py # Lightweight IoU tracker with Re-ID
│ ├── emit.py # Event schemas and emission (JSONL + API)
│ ├── run.sh # Process all cameras (Linux/macOS)
│ └── run.bat # Process all cameras (Windows)
├── app/ # FastAPI backend
│ ├── main.py # App entry point, middleware, startup
│ ├── models.py # Pydantic schemas
│ ├── database.py # SQLAlchemy models
│ ├── ingestion.py # POST /events/ingest
│ ├── metrics.py # GET /stores/{id}/metrics + /heatmap
│ ├── funnel.py # GET /stores/{id}/funnel
│ ├── anomalies.py # GET /stores/{id}/anomalies
│ └── health.py # GET /health
├── dashboard/ # Web dashboard
│ ├── index.html
│ └── app.js
├── tests/ # pytest test suite
├── data/ # store_layout.json + pos_transactions.csv
├── docs/ # DESIGN.md + CHOICES.md
├── Dockerfile
├── docker-compose.yml
└── requirements.txt
POST /events/ingest
Content-Type: application/json
{
"events": [ {...event...}, ... ] // up to 500 events per batch
}
Response:
{
"accepted": 10,
"rejected": 0,
"errors": [],
"duplicate_skipped": 2
}
GET /stores/{store_id}/metrics
Response:
{
"store_id": "ST1008",
"as_of": "2026-04-10T15:00:00+00:00",
"unique_visitors": 42,
"conversion_rate": 0.238,
"avg_dwell_per_zone": {"PURPLLE_ST1008_Z01": 87.3, ...},
"current_queue_depth": 3,
"abandonment_rate": 0.12,
"total_transactions": 10,
"revenue_today": 8524.50
}
GET /stores/{store_id}/funnel
Response:
{
"store_id": "ST1008",
"as_of": "...",
"stages": [
{"stage": "ENTRY", "count": 42, "drop_off_pct": 0.0},
{"stage": "ZONE_VISIT", "count": 35, "drop_off_pct": 16.7},
{"stage": "BILLING_QUEUE", "count": 15, "drop_off_pct": 57.1},
{"stage": "PURCHASE", "count": 10, "drop_off_pct": 33.3}
]
}
GET /stores/{store_id}/heatmap
Response:
{
"store_id": "ST1008",
"zones": [
{"zone_id": "...", "zone_name": "Makeup Unit", "visit_count": 28,
"avg_dwell_seconds": 145.2, "heat_score": 100.0},
...
]
}
GET /stores/{store_id}/anomalies
Response:
{
"store_id": "ST1008",
"anomalies": [
{
"anomaly_type": "BILLING_QUEUE_SPIKE",
"severity": "CRITICAL",
"description": "Billing queue depth 12 exceeds 2x 7-day average (3.2)",
"suggested_action": "Open additional billing counter..."
}
],
"total": 1
}
Anomaly types:
BILLING_QUEUE_SPIKE— queue > 2× average (WARN/CRITICAL)CONVERSION_DROP— today < 7-day avg by >20% (WARN/CRITICAL)DEAD_ZONE— no revenue zone visits in 30 min (INFO)LOW_TRAFFIC— <5 visitors in last hour (INFO/WARN)
GET /health
Response:
{
"status": "ok",
"uptime_seconds": 3600.1,
"db_status": "ok",
"feeds": [
{"store_id": "ST1008", "last_event_timestamp": "...", "lag_seconds": 45.2, "status": "OK"},
{"store_id": "ST1009", "last_event_timestamp": null, "lag_seconds": null, "status": "NO_DATA"}
]
}
Feed status: OK | STALE (>10 min lag) | NO_DATA
pip install ultralytics opencv-python numpy httpxcd pipeline
python detect.py \
--store ST1008 \
--video "../../Store 1/CAM 3 - entry.mp4" \
--camera CAM3 \
--camera-type entry \
--api-url http://localhost:8000/events/ingest \
--conf 0.35 \
--skip-frames 2Add --realtime flag to pace processing at video FPS and flush every event to the API immediately. Open the dashboard at http://localhost:8000/dashboard and watch metrics update live:
cd pipeline
python detect.py \
--store ST1008 \
--video "../../Store 1/CAM 3 - entry.mp4" \
--camera CAM3 \
--camera-type entry \
--api-url http://localhost:8000/events/ingest \
--realtimecd pipeline && run.batcd pipeline && bash run.sh--camera-type |
Logic |
|---|---|
entry |
Line-crossing detection → ENTRY / EXIT events |
zone |
Polygon hit-test → ZONE_ENTERED / ZONE_EXITED events |
billing |
Polygon hit-test + queue management → QUEUE events |
Events are also written to data/events/{store_id}_{camera_id}.jsonl.
Open http://localhost:8000/dashboard in your browser.
Features:
- Store selector (ST1008 / ST1009)
- Live KPI cards: visitors, conversion rate, queue depth, abandonment rate, revenue, transactions
- Zone dwell bar chart (Chart.js)
- Funnel visualisation with drop-off percentages
- Anomaly alerts banner with severity colour-coding
- Auto-refresh every 5 seconds
cd store-intelligence
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000pytest tests/ -vpytest tests/test_metrics.py -v
pytest tests/test_anomalies.py -v
pytest tests/test_pipeline.py -v| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
sqlite:///./data/store_intelligence.db |
SQLAlchemy DB URL |
LOG_LEVEL |
INFO |
Python logging level |
API_URL (pipeline) |
— | API ingest endpoint for pipeline |
SKIP_FRAMES (pipeline) |
2 |
Process every N-th frame |
All event types match the sample_eventsbe42122.jsonl schema:
// entry / exit
{"event_type":"entry","id_token":"ID_00001","store_code":"store_1008",
"camera_id":"CAM3","event_timestamp":"2026-04-10T12:00:00+00:00",
"is_staff":false,"gender_pred":"F","age_pred":28,"age_bucket":"25-34",
"is_face_hidden":false,"group_id":null,"group_size":null,"confidence":0.87}
// zone_entered / zone_exited
{"event_type":"zone_entered","track_id":101,"store_id":"ST1008",
"camera_id":"CAM1","zone_id":"PURPLLE_ST1008_Z01","zone_name":"Left Shelf",
"zone_type":"SHELF","is_revenue_zone":"Yes",
"event_time":"2026-04-10T12:05:00+00:00",
"zone_hotspot_x":412.6,"zone_hotspot_y":238.4,
"gender":"F","age":28,"age_bucket":"25-34"}
// queue_completed / queue_abandoned
{"queue_event_id":"uuid","event_type":"queue_completed","track_id":101,
"store_id":"ST1008","camera_id":"CAM5",
"zone_id":"PURPLLE_ST1008_Z_BILLING","zone_name":"Billing Counter Queue",
"zone_type":"BILLING","is_revenue_zone":"Yes",
"queue_join_ts":"...","queue_served_ts":"...","queue_exit_ts":"...",
"wait_seconds":45.0,"queue_position_at_join":2,"abandoned":false}See docs/DESIGN.md for the full system architecture, data flow diagram, and AI-assisted design decisions.
See docs/CHOICES.md for the three key technical decisions and their rationale.