docs(plan): add Phase 16 spec — anonymous play & share tracking
Design spec for the telemetry layer behind the home-hero Plays card: completion-bucketed plays, shares, optional anonymous unique listeners under a no-PII constraint. Seven open decisions flagged for Daniel.
This commit is contained in:
@@ -239,6 +239,27 @@ Sequenced as **eight waves**; the critical path is `11.A → 11.B → 11.C → 1
|
||||
|
||||
---
|
||||
|
||||
## Phase 16 — Anonymous Play & Share Tracking
|
||||
|
||||
The phase deferred behind the home-hero **Plays** stat card (`NowPlayingStats.razor`'s third card, today a static `XXX / Plays (Coming Soon)` odometer placeholder). Adds a **privacy-light, anonymous** telemetry layer to the public site: counting **plays** (bucketed by completion) and **shares**, tied to individual **tracks and releases**, plus an optional **unique-listener** "plus" metric. Hard constraint: **no accounts, no PII, anonymous identification only** — the unique-listener metric in particular is solved within that constraint, not around it. Full design, metric definitions, instrumentation seam, anonymity-mechanism options, storage model, the card payoff, and wave decomposition: `product-notes/phase-16-play-share-tracking.md`.
|
||||
|
||||
**Architectural spine.** Plays are instrumented at **one seam** — the `StreamingAudioPlayerService` playback lifecycle (not the UI, not the HTTP/media-client layer, which fires multiple times per play via seek-beyond-buffer). A small play-session tracker opens on playback-start, advances a high-water position on the existing progress callback, and closes (classifying the §1 completion bucket) on track-switch / stop / organic-end / page-unload. Shares instrument at the **real** share surface (`SharePopover`'s Copy-link / Copy-embed actions — clipboard writes that record nothing today). Events ship **fire-and-forget via `sendBeacon`** to new `POST api/event/{play,share}` endpoints (proxied through `DeepDrftPublic`, same hop as `api/track/*`), land in an **append-only SQL event log + incremental counter rollup** in `DeepDrftData`, and the home card reads the total through the **existing** `GET api/stats/home` / `HomeStatsDto` / `IStatsDataService` path (the same single persistent-state-bridged round-trip the other two cards use). Release attribution is **resolved server-side** from the track→release join (client sends only the track key); release plays are **derived** (sum of their tracks' plays). All SQL — the FileDatabase vault is not involved.
|
||||
|
||||
**Completion buckets (agreed thresholds).** `partial` < 30%, `complete` > 80%; the 30–80% middle band is proposed as its own `sampled` bucket so the three are exhaustive and non-overlapping (headline plays = sum of all three) — see spec §1a / decision **D1**.
|
||||
|
||||
**Sequenced as four waves.** `16.A → 16.B`, `16.A → 16.C`, `16.A → 16.D`. 16.A is the only cold-start wave.
|
||||
|
||||
- **16.A — Play & share counters (core).** Player-service play tracker (three-bucket classification + engagement floor), share tracker in `SharePopover`, `sendBeacon` interop + `POST api/event/{play,share}` (rate-limited), `play_event`/`share_event` log + incremental `play_counter` rollup, server-side release resolution. **No `anonId` yet.** Free-floating cold-start wave.
|
||||
- **16.B — Home Plays-card payoff.** Extend `HomeStatsDto` + `GetHomeStatsAsync` with `TotalPlays` (+ a secondary line, D7); flip the third card from placeholder to live. **Depends on 16.A.** The visible win — sequence immediately after 16.A.
|
||||
- **16.C — Unique listeners (stretch / "plus").** The anonymity mechanism (recommend a client-minted random first-party `localStorage` id; alt: server-derived salted daily token; fingerprinting rejected — see §3 / **D5**): thread an `anonId` onto event payloads, count distinct server-side, expose per-target and/or as the card's secondary line. **Depends on 16.A. Explicitly lower priority** — defers indefinitely without stranding 16.A/16.B.
|
||||
- **16.D — Per-target stats surfaces.** `[speculative]` — detail-page play/share/listener display + CMS analytics views (bucket/channel splits, leaderboards). Not in the agreed scope; the event log already supports it. Build when a surface wants it.
|
||||
|
||||
**Open product decisions (D1–D7, spec §10) — unresolved, awaiting Daniel.** Headline is **D5** (unique-listener anonymity mechanism). Others: D1 (middle-band bucket), D2 (engagement floor before a play counts), D3 (unique-listener window), D4 (release plays derived vs. counted), D6 (rollup strategy), D7 (card secondary line). Resolve before 16.A is decomposed; recommendations are carried in the spec.
|
||||
|
||||
**Adjacency to deferred Identity / accounts (the un-phased backlog item above).** This phase is the deliberate **anonymous** answer to "how many plays" — it does **not** need the accounts/identity work and must not be entangled with it. If identity ever lands, per-user listening history is an additive layer above this anonymous substrate, not a replacement.
|
||||
|
||||
---
|
||||
|
||||
## Working with this file
|
||||
|
||||
- **Add items by extending an existing phase first**; only create a new phase when the addition genuinely doesn't fit any of 1–5. Phase numbers are organisational, not sequencing.
|
||||
|
||||
Reference in New Issue
Block a user