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.
Dari kamera sampai insight
Enam tahap, dua di antaranya batch (bukan streaming).
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.
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.
VLM sampling
Cuma track yang lolos threshold (dwell tinggi / ada interaksi tangan-objek) dikirim ke VLM murah untuk label semantik. Tulis ke track_enrichment.
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.
Scoring & sinyal turunan
Skor komposit staf (staff_score_daily), sinyal minat sekunder (secondary_interest_signal), flag anomali.
Dashboard & trigger
Laporan untuk manager/marketing/owner, broadcast WhatsApp otomatis (personalized_offer_log).
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;
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.
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.
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.
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 |
Urutan build, dari yang paling murah
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
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
MVP gamifikasi konsumen (opsional, keputusan terpisah)
- WhatsApp registration flow, validasi struk manual
member,consent_log,points_ledger,redemption_logsaja dulu
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
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.
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_idper 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