feat(ypp): session report derived from YouTube — no ledger, money off by default (TASK 3·20)

The task asked for a `RewardEvents` table plus a backfill. That would have contradicted the published
promise that no viewer, member or donor information is retained after the session ends, and it would
have meant owning a ledger that YouTube already keeps.

So the report is DERIVED on demand instead:

- `Services/SuperChatEventService.cs` — `superChatEvents.list`, parsed for both event types (a sticker
  must not be dropped), authorised by the existing `auth/youtube` scope, so no re-consent.
- `Models/SessionRewardSummary.cs` — rolls events up for the session window with **per-currency totals
  that are never summed together** (a $5 and a €5 are not $10). Nothing is written down.
- The YPP drawer gains a **THIS SESSION** section: counts are neutral and always shown when there were
  events; money appears only when the creator asks for it. `Show monetary metrics` is a drawer toggle,
  persisted, **off by default** — a creator who never monetized is never shown a $0.00.

Also: money is labelled as estimates (removed/refunded events are excluded and totals can be restated
for weeks, not days).

Tests: `SessionRewardReportTests` — both reward types parse, the window makes it a session rather than
a channel report, currencies stay separate, an empty session shows nothing, a sticker-only session
still counts. `BackOnAirTests` moves into the serialised `MainViewModelLayoutPath` collection: it builds
a MainViewModel directly and was flaking against parallel tests that swap the static layout path.

FAST gate: 557 passed, 0 failed, 0 warnings.
This commit is contained in:
2026-11-05 15:38:32 -08:00
parent 27f655c8de
commit 0fade9702b
13 changed files with 488 additions and 7 deletions
+12
View File
@@ -298,6 +298,18 @@ comments removed in Q1 2026 alone, child safety the #2 reason at 124.6M). **No f
— chat history, moderation logs, analytics, crash payloads — may quietly break that
assumption.** If one is ever proposed, the 11.12 position must be re-argued first.
### Monetization: derived, never a ledger (2026-11-05)
YouTube is the **sole authority** for monetization progress and qualification; the API exposes **no**
eligibility field (`channels.list` has no monetization part; `auditDetails` needs an MCN scope). So the app
**asks YouTube on demand** (`superChatEvents.list` for the session report; Analytics for the YPP bars in
TASK 39·2) and **stores nothing**. That keeps the promise above literally true — there is no reward ledger
to retain — and it means the app never has to model YouTube's moving thresholds as our own data.
**Money is off by default** ("Show monetary metrics", in the YPP drawer, persisted): counts are neutral,
and a creator who never monetized is never shown a zero. Amounts are estimates that can be restated for
weeks, and are labelled as such wherever they appear.
### Minors
The **operator is 13+ by construction**, not by promise: LlamaCasty requires a YouTube