feat(stats): flip home Plays card live (Phase 16.5)

Add TotalPlays + UniqueListeners to HomeStatsDto, composed at
StatsController from IEventService (no migration). Card reads via
existing persistent-state-bridged round-trip.
This commit is contained in:
daniel-c-harvey
2026-06-19 15:26:07 -04:00
parent cfcc2693f2
commit be1a55fd37
7 changed files with 123 additions and 13 deletions
+16 -2
View File
@@ -4,8 +4,8 @@ namespace DeepDrftModels.DTOs;
/// <summary>
/// Aggregate figures behind the public home hero stat row (NowPlayingStats). A single read returns
/// everything the three cards need so the client makes one round-trip. All counts exclude soft-deleted
/// rows. The Plays card is a static placeholder and has no field here.
/// everything the three cards need so the client makes one round-trip. The track-domain counts exclude
/// soft-deleted rows; the play-domain figures (Phase 16) come from the event domain.
/// </summary>
public class HomeStatsDto
{
@@ -27,6 +27,20 @@ public class HomeStatsDto
/// duration (not yet backfilled) contribute 0. The Mixes card's secondary figure, rendered hh:mm.
/// </summary>
public double MixRuntimeSeconds { get; set; }
/// <summary>
/// Site-wide total plays across all tracks — the sum of every play_counter's bucket columns
/// (partial + sampled + complete), all-time (Phase 16 §5). The Plays card's primary odometer figure.
/// Reads zero until the play-telemetry migration is applied; that is expected, not an error.
/// </summary>
public long TotalPlays { get; set; }
/// <summary>
/// Site-wide distinct anonymous listeners — distinct non-null anon_id across all play events,
/// all-time (Phase 16 §3 / D7). The Plays card's secondary line ("N listeners"). Over-counts by
/// design (one token per browser-install, honestly labelled "listeners").
/// </summary>
public int UniqueListeners { get; set; }
}
/// <summary>One row of the Cut release-type breakdown: a ReleaseType and how many Cut releases have it.</summary>