01
01 — Ringkasan
Dua lapis, dipisah sengaja berdasarkan biaya
Layer 1 (CV klasik) menangani semua tracking dasar secara terus-menerus dan murah. Layer 2 (VLM) cuma dipanggil batch, pada sampel yang relevan. Identitas konsumen tidak pernah di-Re-ID — sesi dilabel ulang ke identitas asli hanya pada titik transaksi/loyalty scan.
Kenapa bukan Re-ID biometrik persisten: bukan cuma soal kepatuhan UU PDP — Re-ID butuh resolusi kamera tinggi, model embedding yang jauh lebih berat, storage yang terus tumbuh, dan matching yang melambat seiring skala. Skema sesi-sekali-pakai + relabel-saat-transaksi lebih murah dan lebih patuh sekaligus. Detail lengkap trade-off ada di README §1 & §20.
02
02 — Pipeline
Dari kamera sampai insight
Enam tahap, dua di antaranya batch (bukan streaming).
kamera
Buffer lokal
Rekam terus, klip 5–15 menit atau per event masuk/keluar frame. Tidak ada yang dikirim ke model apa pun di tahap ini.
↓
layer 1 · terus-menerus
Deteksi + tracking
YOLOv8n/11n + ByteTrack. Hitung dwell time, deteksi marker warna/ArUco untuk staf, estimasi head-pose kasar untuk proxy viewing time. Tulis ke track_event, zone_dwell_segment, staff_zone_presence.
↓
layer 2 · batch 15–30 mnt
VLM sampling
Cuma track yang lolos threshold (dwell tinggi / ada interaksi tangan-objek) dikirim ke VLM murah untuk label semantik. Tulis ke track_enrichment.
↓
batch harian
Join ke POS
Cocokkan track_event ke transaksi lewat window waktu + counter_id. Hasilkan track_transaction_match (closing/non-closing) dan, kalau ada loyalty_id, backtrack identitas ke member.
↓
batch harian
Scoring & sinyal turunan
Skor komposit staf (staff_score_daily), sinyal minat sekunder (secondary_interest_signal), flag anomali.
↓
output
Dashboard & trigger
Laporan untuk manager/marketing/owner, broadcast WhatsApp otomatis (personalized_offer_log).
03
03 — Data Model
Video & POS (Layer 1 + 2 + join)
Tabel inti — skema lengkap dengan komentar ada di README §10. Ringkasan kolom kunci per tabel:
track_eventLayer 1 · per sesi kunjungan
| track_id | pk | sekali pakai per sesi |
| store_id, counter_id | | lokasi |
| detected_marker | | null kalau konsumen |
| entered_at / exited_at | | rentang sesi |
| dwell_seconds, gaze_seconds | | total sesi |
| has_hand_object_interaction | | trigger Layer 2 |
track_enrichmentLayer 2 · hasil VLM
| track_id | fk | → track_event |
| interaction_type | | lihat / pegang-coba / ngobrol |
| product_hint | | model produk terinfer |
| model_used | | audit biaya/akurasi |
zone_dwell_segmentLayer 1 · per zona
| track_id, zone_id | fk | breakdown per area |
| entered_at / exited_at | | per kunjungan ke zona |
| dwell_seconds | | SUM boleh > total sesi (bolak-balik) |
staff_zone_presenceLayer 1 · coverage staf
| cashier_id, zone_id | | dari marker (§5.6) |
| entered_at / exited_at | | dasar response time, idle time, coverage gap |
track_transaction_matchJoin layer
| track_id, transaksi_id | fk | null transaksi_id = non-closing |
| is_closing | | boolean hasil matching |
| match_confidence | | exact_counter / store_level_fallback |
cashier_marker_mapInput manual
| marker_code | pk | warna/ArUco id |
| cashier_id | | diupdate manual tiap rotasi staf |
-- matching logic, dijalankan sebagai batch job harian setelah POS sync
SELECT t.track_id, tx.transaksi_id
FROM track_event t
JOIN transaksi tx
ON tx.counter_id = t.counter_id
AND tx.created_at BETWEEN t.exited_at AND t.exited_at + INTERVAL '5 minutes'
WHERE t.store_id = tx.store_id;
04
04 — Presisi Etalase
Deteksi SKU-level tanpa VLM
README §26. Kamera/sensor kecil per etalase mendeteksi slot kosong/terisi (frame differencing sederhana, bukan klasifikasi visual) — dicocokkan ke track_id lewat timestamp + zona, pola yang sama persis dengan matching di §pipeline.
etalase_slot_definitionMapping manual
| slot_id, sku | pk | 1 slot = 1 SKU pasti, dari inventori |
| zone_id | | → zone_definition |
slot_eventMentah, dari sensor
| event_type | | 'lifted' | 'returned' |
| detected_at | | timestamp presisi |
slot_hold_sessionlifted+returned dipasangkan
| lifted_at, returned_at | | rentang pegang |
| hold_seconds | | dihitung otomatis, bukan manual |
slot_track_matchJoin ke orang
| slot_hold_session_id, track_id | fk | via zona+waktu, §5.1 |
| match_confidence | | single_person_in_zone paling andal |
Kenapa lebih murah dari Layer 2 VLM, bukan lebih mahal: kamera etalase cuma jawab "slot ini kosong atau tidak" — bukan "ini model apa". SKU sudah pasti dari etalase_slot_definition, jadi product_hint jadi ground truth, menggantikan sebagian kebutuhan VLM (§4) yang sebelumnya menebak dari gambar. Alternatif presisi lebih tinggi: RFID/NFC per unit jam, tanpa CV sama sekali — lihat README §26.4.
Prasyarat (§27): akurasi etalase_slot_definition di atas bergantung penuh pada data inventori toko yang sudah jadi 1 sumber kebenaran (stock_ledger, section berikutnya) — kalau data SKU per toko masih berantakan/manual, presisi di sini ikut runtuh duluan.
05
05 — Supply Chain
Toko sebagai warehouse terdistribusi
README §27. Terinspirasi dari riset Zappos/Diapers.com/Chewy — manfaatkan toko yang sudah tersebar di seluruh Indonesia jadi warehouse node, minimal 1 per pulau, tanpa capex gudang baru.
warehouse_nodeToko yang jadi hub
| warehouse_id, store_id | pk | toko tetap bisa jual langsung |
| pulau | | minimal 1 node per pulau |
stock_ledgerSumber kebenaran tunggal
| warehouse_id, sku | fk | 1 baris = stok SKU di 1 node |
| source_event | | sale / transfer / restock — audit trail |
inter_warehouse_transferTransfer antar pulau
| from/to_warehouse_id | fk | rute transfer stok |
| status | | requested → in_transit → received |
stock_sync_queueEvent-triggered, bukan streaming
| payload, retry_count | | buffer + retry kalau koneksi toko putus |
| synced_at | | null = masih di-retry |
Kenapa bukan real-time penuh: konsisten dengan prinsip non-realtime/batch di seluruh sistem ini (§1) — live replication mahal & rentan gagal di toko dengan koneksi kurang stabil. Event-triggered + antrean retry terasa "instan" ke operasional tapi jauh lebih murah & robust dibangun.
06
06 — Data Model
Gamifikasi (konsumen & staf)
Pelengkap §19 README — 13 tabel total, di sini yang paling sering dipakai. points_ledger adalah satu-satunya sumber kebenaran saldo poin (dihitung dari SUM, bukan kolom terpisah).
memberRegistrasi WhatsApp-first
| loyalty_id | pk | sama dgn backtrack §5.3 |
| no_hp, bulan_lahir | | data minimal (UU PDP) |
| deleted_at | | soft delete → right to erasure |
points_ledgerAppend-only
| event_type | | earn / redeem / expire / referral |
| points_delta | | +/- , saldo = SUM |
| source_ref | | audit trail |
secondary_interest_signalBatch harian
| loyalty_id, track_id | fk | hanya sesi closing + scan member |
| zone_id vs purchased_zone_id | | minat vs yang dibeli |
staff_score_dailySnapshot historikal
| cashier_id, score_date | unique | 1 baris per staf per hari |
| composite_score | | weighted: conversion + basket + approach + diversity |
Belum ada di skema — sengaja ditunda: visit_companion_group (deteksi kunjungan berpasangan) butuh data posisi kontinu, bukan cuma entry/exit per zona seperti sekarang — lihat README §21.2 sebelum diimplementasikan.
07
07 — Program Loyalitas
Siklus servis 6-bulan, poin, tier, trade-in
README §23–§25. Servis baterai gratis dijadwalkan proaktif tiap 6 bulan (bukan menunggu baterai habis) — ini jadi jantung seluruh sistem poin & tier, plus program trade-in/refurbish yang terpisah secara ekonomi.
battery_service_logSiklus 6 bulan
| loyalty_id, serviced_at | fk | tiap kunjungan servis |
| next_due_at | | = serviced_at + 6 bulan |
| was_on_time | | basis streak, §25.2 |
battery_reminder_logTrigger reaktivasi
| due_at | | = next_due_at yg memicu |
| sent_at | | WA terkirim, biasanya H-7 |
| responded_at | | null = belum datang lagi |
trade_in_appraisalPenilaian tukar-tambah
| watch_model_traded, grade | | A / B / C / reject |
| trade_in_credit | | ≤ target_cogs per grade |
| photos_ref | | audit konsistensi grading |
refurb_inventoryUnit siap jual
| source_appraisal_id | fk | → trade_in_appraisal |
| listed_price | | bervariasi per grade, bukan flat |
| non_exchangeable | | default true — cegah siklus tak berujung |
Aturan poin (basis: 1 poin = Rp10.000 spend)
| Aktivitas | Poin | event_type |
| Servis baterai 6-bulanan | +50 flat | earn_battery_service |
| Streak on-time (kunjungan ke-2 berturut) | +25, naik bertahap (cap) | earn_mission (on_time_streak) |
| Trade-in diproses | +200 flat | earn_trade_in |
| Personalisasi/gravir | +30 | earn_mission |
| Referral berhasil | +100 / +50 | referral_bonus |
Tier — total poin earned, rolling 12 bulan
| Tier | Threshold | Kira-kira setara |
| Dasar | 0+ | Member baru |
| Menengah | 500+ | ~2 servis on-time (1 thn) + 1 pembelian menengah |
| Utama | 1.500+ | Pembelian rutin + servis konsisten + 1 trade-in |
| Kolektor | 4.000+ | Riwayat multi-pembelian/trade-in |
Margin refurbish — cek dulu sebelum set harga: pada margin toko ~3x (COGS ≈33% harga jual), diskon flat 50% menyisakan trade-in credit yang terlalu kecil untuk menarik partisipasi. Pakai diskon bertingkat per grade (15–20% / 25–30% / 35–40%) dengan formula trade_in_credit + refurb_cost ≤ target_cogs(grade) — detail perhitungan di README §23.3.
08
08 — Tech Stack
Pilihan komponen, MVP 1 toko
| Komponen | Pilihan | Catatan |
| Person detection + tracking | ultralytics YOLOv8n/11n + ByteTrack | CPU cukup untuk 1–2 kamera |
| Marker staf | HSV segmentation atau cv2.aruco | nol biometrik, robust resolusi rendah |
| VLM Layer 2 | Gemini Flash / GPT-5-mini / Claude Haiku, atau SmolVLM self-host | model-agnostic, tinggal ganti endpoint |
| Database | SQLite (MVP) → Postgres/Supabase (scale) | upgrade saat multi-toko |
| Scheduler batch | Cron / worker sederhana, interval 15–30 mnt | tidak perlu infra streaming |
| Broadcast konsumen | WhatsApp Business API + Flows | bukan app proprietary — lihat §keputusan |
09
09 — Fase Implementasi
Urutan build, dari yang paling murah
1–2 mgu
Tier 1 — Traffic & antrean
- Counting line di pintu + polygon zona kasir
- Log ke SQLite lokal, rekap harian via cron
- Validasi akurasi vs hitungan manual sebelum dipercaya
3–4 mgu
Tier 2 — Coverage staf + zone engagement
- Rollout marker, kalibrasi HSV di kondisi cahaya toko sebenarnya
- Petakan zona toko manual ke koordinat kamera
- Aktifkan Layer 2 untuk label produk per zona
30 hari
MVP gamifikasi konsumen (opsional, keputusan terpisah)
- WhatsApp registration flow, validasi struk manual
member, consent_log, points_ledger, redemption_log saja dulu
bertahap
Gamifikasi staf — 3 fase trust-building
- Observasi (manager saja) → non-finansial (badge) → insentif (dengan jalur banding)
- Jangan lompat langsung ke fase insentif — README §17.5
10
10 — Keputusan Desain Kunci
Kenapa dipilih begini, bukan yang lain
Batch, bukan realtime
VLM dipanggil per 15–30 menit pada sampel yang lolos threshold, bukan tiap frame — inti dari kenapa biayanya di bawah Rp150rb/toko/bulan. Lihat README §2–§3.
Sesi sekali-pakai, relabel saat transaksi — bukan Re-ID persisten
Lebih murah (tidak perlu model embedding, storage database wajah yang terus tumbuh) dan lebih patuh UU PDP sekaligus — bukan trade-off. README §1, §5.3, §20.
Marker fisik untuk staf, bukan face recognition
Staf bisa diminta pakai marker (konsumen tidak bisa) — HSV/ArUco jauh lebih murah dan lebih robust di resolusi CCTV standar daripada face embedding. README §5.6.
WhatsApp-first, bukan app proprietary
~31% konsumen enggan unduh app baru (riset chatgpt-1.md). WhatsApp Business API + Flows menghilangkan friksi ini sepenuhnya. README §16-B.
Reward selalu deterministik, tidak pernah random
Mekanik random (lucky wheel dst.) butuh izin Kemensos (Permensos UGB No. 3/2024) + hibah 10% + pajak 25% dari nilai hadiah. Guaranteed reward menghindari proses perizinan ini sejak desain. README §6, §11.
11
11 — Checklist Integrasi
Yang dibutuhkan dari sisi client sebelum mulai
- Akses data POS — format & metode (API / export harian / replikasi DB) untuk membangun
track_transaction_match
- POS sudah catat
cashier_id per transaksi? Kalau belum, ini prasyarat sebelum breakdown performa per kasir bisa jalan
- Kamera per-counter atau general per-toko? Menentukan resolusi analisis (per kasir vs per toko/shift)
- Program loyalty existing — sudah catat identifier (no. HP/kartu) di transaksi POS?
- Toko pilot pertama untuk validasi sebelum scale ke multi-toko