-- Recommendation observability (#796 phase 4). Per-source play outcomes so the -- operator can see whether each recommendation surface is landing and tune the -- taste weights. Source is stamped on play_events when a play is launched from -- a recommendation surface; NULL means the user picked the track manually — -- those rows are INCLUDED here as the baseline control group the surfaces are -- judged against (milestone #127: delta-vs-baseline is what makes the numbers -- actionable). Raw source strings are bucketed into stable surface families in -- the Go handler; completion_n is carried so family merges can weight -- avg_completion correctly. -- name: RecommendationSourceMetricsForUser :many -- $1 user_id, $2 window_days. plays/skips are counts; avg_completion is the -- mean completion ratio over the completion_n plays that recorded one. -- pick_kind splits For You plays into taste/fresh/unattributed (#1249); -- it is NULL for every other source, so those still group to one row. SELECT pe.source, pe.pick_kind, count(*)::bigint AS plays, count(*) FILTER (WHERE pe.was_skipped)::bigint AS skips, count(pe.completion_ratio)::bigint AS completion_n, COALESCE(avg(pe.completion_ratio), 0)::float8 AS avg_completion FROM play_events pe WHERE pe.user_id = $1 AND pe.started_at > now() - ($2::float8 * INTERVAL '1 day') GROUP BY pe.source, pe.pick_kind ORDER BY plays DESC;