Skip to content

Latest commit

 

History

History
309 lines (252 loc) · 7.96 KB

File metadata and controls

309 lines (252 loc) · 7.96 KB

Store Intelligence — Purplle Tech Challenge

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.


Quick Start (5 commands)

git clone <your-repo-url>
cd store-intelligence
docker compose up --build
# API available at http://localhost:8000
# Dashboard at  http://localhost:8000/dashboard

Detection 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/ingest

Project Structure

store-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

API Endpoints

Ingest Events

POST /events/ingest
Content-Type: application/json

{
  "events": [ {...event...}, ... ]   // up to 500 events per batch
}

Response:
{
  "accepted": 10,
  "rejected": 0,
  "errors": [],
  "duplicate_skipped": 2
}

Store Metrics

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
}

Funnel

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}
  ]
}

Heatmap

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},
    ...
  ]
}

Anomalies

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)

Health

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


Running the Detection Pipeline

Prerequisites

pip install ultralytics opencv-python numpy httpx

Single camera

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 \
  --conf 0.35 \
  --skip-frames 2

Real-time mode (for live dashboard — Part E)

Add --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 \
  --realtime

All cameras (Windows)

cd pipeline && run.bat

All cameras (Linux/macOS)

cd pipeline && bash run.sh

Camera types

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


Dashboard

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

Development Setup (without Docker)

cd store-intelligence
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000

Run tests

pytest tests/ -v

Run a specific test module

pytest tests/test_metrics.py -v
pytest tests/test_anomalies.py -v
pytest tests/test_pipeline.py -v

Environment Variables

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

Event Schema Reference

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}

Design & Architecture

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.