Skip to content

ranjitcj/store-intelligence

Repository files navigation

Store Intelligence

Real-time retail analytics from CCTV footage. A computer-vision pipeline detects visitors, zone dwell, and billing-queue activity from store video; a FastAPI service ingests events and exposes metrics, funnels, heatmaps, and anomalies; a Streamlit dashboard visualizes live store performance.

Reference store: ST1008 (Brigade Bangalore) — see store-layout.json.


What it does

Layer Responsibility
Detection pipeline YOLOv8 + ByteTrack on MP4 clips → JSONL event stream
Intelligence API Idempotent event ingestion, session derivation, analytics endpoints
Dashboard Polls the API for metrics, funnel, heatmap, queue, and anomalies

Analytics surfaced

  • Unique visitors, conversion rate, average dwell
  • Entry → zone → billing → purchase funnel
  • Zone heatmap (visit frequency, normalized 0–100)
  • Billing queue depth and wait patterns
  • Rule-based anomalies (queue spike, conversion drop, dead zones, stale feed)

Architecture

flowchart LR
  subgraph ingest [Ingestion]
    CCTV[CCTV MP4 clips]
    POS[POS CSV]
  end

  CCTV --> DET[pipeline/detect.py]
  DET --> JSONL[output/events.jsonl]
  JSONL --> EVT[pipeline/ingest_events.py]
  POS --> POSING[pipeline/ingest_pos.py]
  EVT --> API[FastAPI POST /events/ingest]
  POSING --> API
  API --> DB[(SQLite)]
  DB --> EP[GET metrics / funnel / heatmap / anomalies / queue]
  EP --> DASH[Streamlit dashboard :8501]
Loading

Design choices

  • Events are the source of truth; visitor_sessions and queue_events are derived on ingest.
  • Duplicate event_id values return 409 Conflict (idempotent ingestion).
  • Zones use static pixel regions (pipeline/zones.py + store-layout.json), not VLMs.
  • Staff are heuristically excluded (tracks visible for many sampled frames).

Full design notes: Desgin.MD · Engineering rationale: CHOICES.md


Project structure

store-intelligence/
├── app/                    # FastAPI application
│   ├── api/routes/         # HTTP endpoints
│   ├── core/               # Settings (pydantic-settings)
│   ├── db/                 # SQLAlchemy models and database
│   ├── schemas/            # Pydantic request/response models
│   └── services/           # Business logic (metrics, funnel, anomalies, …)
├── pipeline/               # Offline CV pipeline and ingest scripts
│   ├── detect.py           # Main orchestrator (python -m pipeline.detect)
│   ├── tracker.py          # YOLO + ByteTrack wrapper
│   ├── zones.py            # Zone polygons per camera
│   ├── emit.py             # Event builders and JSONL merge
│   ├── ingest_events.py    # POST JSONL to API
│   ├── ingest_pos.py       # CSV → PURCHASE events
│   └── run.sh              # detect → ingest events → ingest POS
├── dashboard/              # Streamlit UI (app.py)
├── tests/                  # Pytest API tests
├── scripts/                # Utilities (e.g. seed_demo_data.py)
├── data/                   # POS CSV and optional video inputs
├── Dataset/                # Default dataset root for pipeline
├── output/                 # Pipeline JSONL output (created at runtime)
├── store-layout.json       # Store, camera, and zone metadata
├── docker-compose.yml      # API + dashboard services
├── Dockerfile              # API image (includes ML deps)
├── Dockerfile.dashboard    # Dashboard image
├── requirements*.txt       # Python dependencies (split by layer)
├── setup.sh                # Create venv and install deps on host
├── Desgin.MD               # Architecture documentation
└── CHOICES.md              # Engineering decision log

Prerequisites

  • Docker and Docker Compose (recommended for API + dashboard)
  • Python 3.11+ on the host for running the detection pipeline (heavy ML deps; not required inside the API container for normal API-only use)
  • curl (optional, for API examples)

Quick start

1. Start API and dashboard

git clone <repo-url> store-intelligence
cd store-intelligence
docker compose up --build
Service URL
API http://localhost:8000
API docs (Swagger) http://localhost:8000/docs
Dashboard http://localhost:8501
Health http://localhost:8000/health

The API persists data in a Docker volume (db_data) at sqlite:////app/db/store_intelligence.db.

2. Run the detection pipeline (host)

The pipeline needs GPU/CPU ML dependencies on the host (or a machine with the project venv), not only inside the API container:

bash setup.sh          # creates venv/ and installs requirements.txt
bash pipeline/run.sh   # detect → ingest events → ingest POS

setup.sh installs requirements.txt (API + ML stacks). pipeline/run.sh uses venv/bin/python when present.

3. Seed demo data (optional)

If you have no video clips, populate the API with sample events:

# API must be running
python scripts/seed_demo_data.py
# API_URL=http://localhost:8000  STORE_ID=ST1008  (defaults)

Detection pipeline

End-to-end flow

On the host, bash pipeline/run.sh works without the API: ingest falls back to local SQLite (store_intelligence.db). To use the HTTP API (required for the Docker dashboard DB volume), start it first:

docker compose up --build

Then on the host:

bash setup.sh
bash pipeline/run.sh

Or step by step:

python -m pipeline.detect --dataset Dataset --output output
python pipeline/ingest_events.py output/events.jsonl
python pipeline/ingest_pos.py data/Brigade_Bangalore.csv

pipeline/run.sh defaults: DATASET_DIR=Dataset, OUTPUT_DIR=output. Override with environment variables:

DATASET_DIR=data/videos OUTPUT_DIR=output bash pipeline/run.sh
PYTHON_BIN=.venv/bin/python bash pipeline/run.sh

Video layout

Place .mp4 files under a dataset root. The pipeline recursively discovers clips whose filename contains one of:

Substring in filename Role Typical use
entry ENTRY Door threshold (CAM 3)
zone ZONE Floor cameras — dwell by zone
billing BILLING Cash counter queue (CAM 5)

Store folders: paths like Store 1/ or Store 2/ map to store IDs (ST1076, ST1008). Other folder names are normalized to ST#### via pattern matching or hashing.

Example layout (bundled dataset style):

Dataset/
├── Store 1/
│   ├── entry_cam3.mp4
│   ├── zone_floor1.mp4
│   └── billing_cam5.mp4
└── Store 2/
    └── ...

Alternative layout (README-style naming):

data/videos/
├── CAM 1.mp4    # Floor — zone dwell
├── CAM 2.mp4
├── CAM 3.mp4    # Entry/exit (filename must include entry/zone/billing role keyword)
├── CAM 4.mp4
└── CAM 5.mp4    # Billing

Clips must include entry, zone, or billing in the filename for discovery. Rename or symlink if your files use only CAM N.mp4 naming.

POS data

Place transaction CSV at one of:

  • data/Brigade_Bangalore.csv (preferred by run.sh)
  • data/pos_transactions.csv

Sample POS files also exist under Dataset/.

Pipeline environment variables

Variable Default Description
VID_STRIDE 6 Process every Nth frame (~6× faster)
MAX_FRAMES 0 Cap frames per clip (0 = unlimited)
YOLO_MODEL yolov8s.pt Ultralytics weights (see pipeline/tracker.py)
IMG_SIZE 320 Inference image size
API_URL http://localhost:8000 Target for ingest_events.py and ingest_pos.py
DATASET_DIR Dataset Used by run.sh
OUTPUT_DIR output JSONL output directory

Output: per-camera events_*.jsonl and merged output/events.jsonl.

Event types (ingest schema)

ENTRY, EXIT, ZONE_ENTER, ZONE_EXIT, ZONE_DWELL, BILLING_QUEUE_JOIN, BILLING_QUEUE_ABANDON, REENTRY, PURCHASE


API reference

Health and root

Method Path Description
GET / Service version
GET /health DB status, last event time, stale_feed if no event in 10 minutes

Events

Method Path Description
POST /events/ingest Ingest one event (409 if event_id already exists)

Stores

Method Path Description
GET /stores/ List stores with recent activity
GET /stores/{store_id} Store metadata from events

Analytics (per store_id)

Method Path Description
GET /stores/{store_id}/metrics Visitors, conversion, dwell
GET /stores/{store_id}/funnel Entry → zone → billing → purchase
GET /stores/{store_id}/heatmap Zone visit frequency (0–100)
GET /stores/{store_id}/anomalies Active anomalies with severity
GET /stores/{store_id}/queue Billing queue statistics

Examples

curl http://localhost:8000/health
curl http://localhost:8000/stores/ST1008/metrics
curl http://localhost:8000/stores/ST1008/funnel

Ingest an event:

curl -X POST http://localhost:8000/events/ingest \
  -H "Content-Type: application/json" \
  -d '{
    "event_id": "550e8400-e29b-41d4-a716-446655440000",
    "store_id": "ST1008",
    "camera_id": "CAM_3",
    "visitor_id": "VIS_abc123",
    "event_type": "ENTRY",
    "timestamp": "2026-04-10T16:00:00Z",
    "dwell_ms": 0,
    "is_staff": false,
    "confidence": 0.91,
    "metadata": {}
  }'

Store layout (ST1008)

Configured in store-layout.json:

Zone Camera Notes
ENTRY CAM_3 Glass door threshold
SKINCARE CAM_1 DermDoc, Minimalist, Aqualogica, Pilgrim
HAIRCARE CAM_1 EB, TFS, GV, D&K
MAKEUP CAM_2 Maybelline, Faces, Lakme, Swiss+, Mars+NyBae
PREMIUM CAM_2 Alps, L'Oreal, Beauty
FRAGRANCE CAM_1 Specialty counter
FOH CAM_1 Front of House / Makeup Unit
BILLING CAM_5 Cash counter
Camera Type
CAM_1, CAM_2 Floor (zone dwell)
CAM_3 Entry / exit
CAM_4 Warehouse (excluded from customer metrics)
CAM_5 Billing queue

Configuration

Copy environment template and adjust as needed:

cp .env.example .env
Variable Default (example) Notes
DATABASE_URL sqlite:///./store_intelligence.db Compose uses sqlite:////app/db/store_intelligence.db in the API container
APP_ENV development production in Docker Compose
LOG_LEVEL INFO Structured request logging in app/main.py

.env.example includes PostgreSQL variables for a future migration; the shipped Compose stack uses SQLite only.

Dashboard: API_URL (default http://localhost:8000; http://api:8000 inside Compose).


Running without Docker

API only (from project root with venv):

python3 -m venv venv && source venv/bin/activate
pip install -r requirements-base.txt
export DATABASE_URL=sqlite:///./store_intelligence.db
uvicorn app.main:app --reload --port 8000

Dashboard (separate terminal):

pip install -r requirements.dashboard.txt
API_URL=http://localhost:8000 streamlit run dashboard/app.py

Full pipeline requires requirements.txt (includes ML stack).


Tests

# API tests (default; ML tests ignored via pytest.ini)
pytest tests/ -v --tb=short
Test module Coverage
tests/test_pipeline.py Ingest idempotency, health, funnel, staff exclusion, re-entry
tests/test_metrics.py Metrics, conversion, multi-store isolation
tests/test_anomalies.py Anomaly types and severity

ML/integration tests (test_yolo.py, test_tracker.py) are excluded from the default run.


Dependencies

File Contents
requirements-base.txt FastAPI, SQLAlchemy, pytest, httpx
requirements-ml.txt Ultralytics, PyTorch, OpenCV
requirements.txt Base + ML (pipeline and API Docker image)
requirements.dashboard.txt Streamlit, Pandas, Plotly

Troubleshooting

Issue What to check
Pipeline: No supported camera clips found Filenames must contain entry, zone, or billing; verify --dataset path
Pipeline: ultralytics is not installed Run bash setup.sh and use the same Python as run.sh
Dashboard empty Run pipeline or scripts/seed_demo_data.py; confirm API health
/health shows STALE_FEED No events in the last 10 minutes — re-run ingest or pipeline
Ingest 409 Expected for duplicate event_id (idempotent retry)
Connection refused on /events/ingest API not running. pipeline/run.sh falls back to direct SQLite ingest (store_intelligence.db). For HTTP ingest, start the API first: docker compose up --build or uvicorn app.main:app --port 8000

About

Real-time retail analytics from CCTV footage using YOLOv8, ByteTrack, FastAPI, and Streamlit for visitor tracking, dwell analysis, queue monitoring, funnels, heatmaps, and anomaly detection.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors