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:
@@ -120,6 +120,9 @@ public partial class MainViewModel : ViewModelBase
|
||||
? "LlamaCasty"
|
||||
: $"{StreamTitle} — LlamaCasty";
|
||||
StreamStatus = StreamStatus.Streaming;
|
||||
// TASK 3·20: the session report window starts here. Derived on demand from YouTube, never
|
||||
// persisted, and reset when the session ends.
|
||||
_sessionStartedAtUtc = DateTime.UtcNow;
|
||||
ResetHealth(StreamStatus.Streaming);
|
||||
_audioMixer.StartLive(EncoderOptions.DefaultAudioPipeName);
|
||||
_liveElapsed = TimeSpan.Zero;
|
||||
@@ -371,6 +374,7 @@ public partial class MainViewModel : ViewModelBase
|
||||
var wasRecording = IsRecording;
|
||||
|
||||
StreamStatus = StreamStatus.Offline;
|
||||
_sessionStartedAtUtc = null;
|
||||
IsRecording = false;
|
||||
LiveScene = null;
|
||||
EditingActive = true;
|
||||
|
||||
@@ -416,6 +416,10 @@ public partial class MainViewModel : ViewModelBase
|
||||
set => SetProperty(ref _previewGlowThickness, value);
|
||||
}
|
||||
|
||||
/// <summary>When the current/last streaming session started — the window a session report covers.
|
||||
/// Null when no session has run since launch.</summary>
|
||||
private DateTime? _sessionStartedAtUtc;
|
||||
|
||||
public string LiveElapsedText
|
||||
{
|
||||
get => _liveElapsedText;
|
||||
|
||||
@@ -23,7 +23,10 @@ public partial class MainViewModel
|
||||
() => _layoutStore,
|
||||
() => ChannelStatsFactoryOverride?.Invoke() ?? new ChannelStatsService(_youtubeAuth),
|
||||
() => IsConnected,
|
||||
() => _youtubeAuth.CurrentChannel?.DisplayName);
|
||||
() => _youtubeAuth.CurrentChannel?.DisplayName,
|
||||
rewardEvents: () => new SuperChatEventService(_youtubeAuth),
|
||||
channelId: () => _youtubeAuth.CurrentChannel?.ChannelId,
|
||||
sessionStartedAtUtc: () => _sessionStartedAtUtc);
|
||||
|
||||
TestSession = new TestSessionViewModel(
|
||||
_youtubeStream,
|
||||
|
||||
Reference in New Issue
Block a user