# dooh-platform — Design Doc (v1, 2026-08-27)

Last updated Aug 27, 2026 · Page: https://freebeeads.com/docs/repo/design-doc · Markdown: https://freebeeads.com/docs/md/repo/design-doc.md · All docs: https://freebeeads.com/llms-full.txt

## Locked premises

- **P1** One mall first. Everything is single-operator, multi-venue-ready but not multi-tenant SaaS.
- **P2** Web before mobile. The advertiser portal is responsive web; a Flutter app is phase 3+.
- **P3** Measurement is aggregates-only. No personal data anywhere in the system (see CLAUDE.md rule 3).
- **P4** The play ledger is the invoice. Append-only, exportable, auditable (CLAUDE.md rule 1).
- **P5** Simulator before hardware. The full cloud + portal stack is built and demoed against
  simulated screens; real RK3588 players replace them without any API change.
- **P6** No payment rails in v1. Bookings track payment status; money moves offline (UPI/bank) and
  ops marks it paid. Razorpay when demand proves out.
- **P7** Loop model: each screen runs a fixed loop (default 8 slots × 10 s = 80 s). A booking buys
  one or more slots on one or more screens for a date range. Sold-out = all 8 slots booked that day.
- **P8** Gate: before real capex, the pilot must show ≥50% loop fill at rate card on the first mall
  (mirrors the entry-memo month-4 gate).

## System overview

Three surfaces, one backend:

1. **Advertiser portal** (web `/`): signup/login → business profile → upload creative (image/video)
   → automatic validation → pick screens + dates + slots → book (payment pending) → live dashboard
   (plays, watchers, attention, demographics, proof gallery) → CSV export.
2. **Ops portal** (web `/ops`, role-gated): creative approval queue · screen & venue registry ·
   device pairing (enter code shown on screen) · fleet health (last_seen, sync lag, uptime) ·
   bookings/payments admin · all-campaign metrics.
3. **Devices** (simulator now, player later): pair once → poll `GET /device/schedule` → play loop →
   batch-upload plays + audience summaries → server writes ledger.

## Device pairing flow (TV-style)

1. Player boots unpaired → generates `device_uid` + secret, calls `POST /device/pairing/start`
   → gets 6-char code, shows it on screen. Polls `POST /device/pairing/poll`.
2. Ops user enters code in ops portal → `POST /ops/devices/claim {code, screen_id}` binds device
   to a screen (venue/zone/position already registered).
3. Poll returns a **device token** (opaque, hashed at rest, revocable). All later calls use
   `Authorization: Device <token>`.
4. Revocation: ops portal → device gets 401 → shows pairing screen again.

## Data model (core tables)

- `users` (email, password_hash, role: advertiser|ops), `advertiser_profiles` (business name, GSTIN?)
- `venues` → `zones` → `screens` (loop_slots=8, slot_seconds=10, rate_per_slot_day, orientation)
- `devices` (device_uid, token_hash, screen_id nullable until claimed, last_seen_at, status)
- `creatives` (owner, kind: image|video, file, duration_s, status: pending|approved|rejected,
  validation json, reject_reason)
- `campaigns` (advertiser, name) → `bookings` (campaign, screen, slot_count, start_date, end_date,
  price_total, payment_status: pending|paid|waived, status: draft|confirmed|cancelled)
- `schedule_entries` (screen, date, slot_index, booking, creative) — materialized on booking confirm
- **Ledger:** `plays` (device, screen, creative, booking, started_at, duration_ms, batch_id) and
  `audience_summaries` (play_id, ots, watchers, attention_ms_total, age band counts json,
  gender counts json) — both append-only.
- `ingest_batches` (device, batch_seq, received_at, state: recorded|duplicate|rejected, reason)
- `rollups_daily` (booking, screen, date, plays, ots, watchers, attention_ms, demographics json)

## Metrics shown (advertiser dashboard)

Live: now playing per screen + watchers now (from latest events). Campaign: plays delivered vs
booked · OTS → watchers → avg attention funnel · attention rate · age band / gender split ·
hour×day heatmap · per-creative comparison · per-screen breakdown · proof gallery (screenshots,
phase 2) · CSV export reproducing the ledger.

## Phases

1. **Foundation (this build):** backend + advertiser portal + ops portal + simulator; money-path
   E2E green with simulated screens.
2. **Edge:** RK3588 player + vision service replacing simulator; bench accuracy study.
3. **Pilot install:** 2–3 screens in mall, ground-truth study, first advertisers.
4. Later: Razorpay, Flutter app, geofenced vehicle SKU, programmatic export, landlord footfall view.
