feat(metrics): provenance as standard — pick_kind for all system mixes
The #1249 mechanism (stamp WHY a track is in the snapshot at build time, freeze it onto the play at ingestion, break it down in metrics) generalizes from a For You one-off to the standard for every system mix (#1270): - Migration 0039 widens both pick_kind CHECKs (drop + re-add in the same change) to taste/fresh + Discover's dormant/cross_user/random + tier1-3 for the rule-#131 eligibility ladders. - GetForYouPickKindForTrack becomes GetSystemPickKindForTrack (user, variant, track); ingestion stamps any systemPlaylistSources play from its own variant's live snapshot, live + offline paths. - Discover stamps its candidate bucket on discoverTrack before the interleave, making the 40/30/30 allocation measurable; dedup keeps the taking bucket's stamp. - Metrics replace the for_you special-case with one pick-kind vocabulary — any family with attributed plays gets a breakdown, future stamping mixes need no metrics change. - Web: breakdown sub-rows are now toggled per surface (collapsed by default) so eight stamping mixes don't swamp the card. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TsF3cNoKrqCYsU78cXC8U6
This commit is contained in:
@@ -31,33 +31,6 @@ func (q *Queries) GetCurrentSessionVectorForUser(ctx context.Context, userID pgt
|
||||
return session_vector_at_play, err
|
||||
}
|
||||
|
||||
const getForYouPickKindForTrack = `-- name: GetForYouPickKindForTrack :one
|
||||
SELECT pt.pick_kind
|
||||
FROM playlist_tracks pt
|
||||
JOIN playlists p ON p.id = pt.playlist_id
|
||||
WHERE p.user_id = $1
|
||||
AND p.system_variant = 'for_you'
|
||||
AND pt.track_id = $2
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
type GetForYouPickKindForTrackParams struct {
|
||||
UserID pgtype.UUID
|
||||
TrackID pgtype.UUID
|
||||
}
|
||||
|
||||
// Looks a track up in the user's CURRENT For You snapshot and returns
|
||||
// its pick_kind ('taste'/'fresh'). Used at play-ingestion time to
|
||||
// freeze exploration attribution onto the play_event — the snapshot is
|
||||
// rebuilt daily, so attribution can't be reconstructed at read time
|
||||
// (#1249). No row = track not in today's snapshot (caller stores NULL).
|
||||
func (q *Queries) GetForYouPickKindForTrack(ctx context.Context, arg GetForYouPickKindForTrackParams) (*string, error) {
|
||||
row := q.db.QueryRow(ctx, getForYouPickKindForTrack, arg.UserID, arg.TrackID)
|
||||
var pick_kind *string
|
||||
err := row.Scan(&pick_kind)
|
||||
return pick_kind, err
|
||||
}
|
||||
|
||||
const getMostRecentPlaySessionForUser = `-- name: GetMostRecentPlaySessionForUser :one
|
||||
SELECT id, user_id, started_at, ended_at, last_event_at, track_count, client_id FROM play_sessions
|
||||
WHERE user_id = $1
|
||||
@@ -137,6 +110,38 @@ func (q *Queries) GetPlayEventByID(ctx context.Context, id pgtype.UUID) (PlayEve
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getSystemPickKindForTrack = `-- name: GetSystemPickKindForTrack :one
|
||||
SELECT pt.pick_kind
|
||||
FROM playlist_tracks pt
|
||||
JOIN playlists p ON p.id = pt.playlist_id
|
||||
WHERE p.user_id = $1
|
||||
AND p.system_variant = $2
|
||||
AND pt.track_id = $3
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
type GetSystemPickKindForTrackParams struct {
|
||||
UserID pgtype.UUID
|
||||
SystemVariant *string
|
||||
TrackID pgtype.UUID
|
||||
}
|
||||
|
||||
// Looks a track up in the user's CURRENT snapshot of the given system
|
||||
// variant and returns its pick_kind. Used at play-ingestion time to
|
||||
// freeze provenance onto the play_event — snapshots rebuild daily, so
|
||||
// attribution can't be reconstructed at read time (#1249/#1270). No
|
||||
// row = track not in today's snapshot (caller stores NULL); a row with
|
||||
// NULL pick_kind = the variant doesn't stamp (yet). songs_like_artist
|
||||
// is non-singleton (up to 3 mixes/user); a track in two of them takes
|
||||
// whichever LIMIT 1 hits — acceptable, tier stamps there describe the
|
||||
// same eligibility ladder.
|
||||
func (q *Queries) GetSystemPickKindForTrack(ctx context.Context, arg GetSystemPickKindForTrackParams) (*string, error) {
|
||||
row := q.db.QueryRow(ctx, getSystemPickKindForTrack, arg.UserID, arg.SystemVariant, arg.TrackID)
|
||||
var pick_kind *string
|
||||
err := row.Scan(&pick_kind)
|
||||
return pick_kind, err
|
||||
}
|
||||
|
||||
const insertPlayEvent = `-- name: InsertPlayEvent :one
|
||||
INSERT INTO play_events (
|
||||
user_id, track_id, session_id, started_at, client_id, source, pick_kind
|
||||
@@ -154,8 +159,9 @@ type InsertPlayEventParams struct {
|
||||
PickKind *string
|
||||
}
|
||||
|
||||
// pick_kind is non-NULL only for source='for_you' plays whose track was
|
||||
// found in the user's live For You snapshot at ingestion time (#1249).
|
||||
// pick_kind is non-NULL only for system-playlist plays whose track was
|
||||
// found (with a stamped kind) in the user's live snapshot for that
|
||||
// variant at ingestion time (#1249, generalized in #1270).
|
||||
func (q *Queries) InsertPlayEvent(ctx context.Context, arg InsertPlayEventParams) (PlayEvent, error) {
|
||||
row := q.db.QueryRow(ctx, insertPlayEvent,
|
||||
arg.UserID,
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
-- Rows stamped with the widened vocabulary must be cleared before the
|
||||
-- narrow CHECK can be re-added; NULL reads as "unattributed", which is
|
||||
-- the honest downgrade.
|
||||
UPDATE play_events SET pick_kind = NULL
|
||||
WHERE pick_kind NOT IN ('taste', 'fresh');
|
||||
UPDATE playlist_tracks SET pick_kind = NULL
|
||||
WHERE pick_kind NOT IN ('taste', 'fresh');
|
||||
|
||||
ALTER TABLE play_events
|
||||
DROP CONSTRAINT play_events_pick_kind_check;
|
||||
ALTER TABLE play_events
|
||||
ADD CONSTRAINT play_events_pick_kind_check
|
||||
CHECK (pick_kind IN ('taste', 'fresh'));
|
||||
|
||||
ALTER TABLE playlist_tracks
|
||||
DROP CONSTRAINT playlist_tracks_pick_kind_check;
|
||||
ALTER TABLE playlist_tracks
|
||||
ADD CONSTRAINT playlist_tracks_pick_kind_check
|
||||
CHECK (pick_kind IN ('taste', 'fresh'));
|
||||
@@ -0,0 +1,41 @@
|
||||
-- Provenance as standard (milestone #127, Scribe #1270).
|
||||
--
|
||||
-- #1249 introduced pick_kind as a For You one-off ('taste'/'fresh').
|
||||
-- The mechanism — stamp WHY a track is in the snapshot at build time,
|
||||
-- freeze it onto the play at ingestion, break it down in metrics — is
|
||||
-- now the standard for every system mix, so the CHECK vocabulary
|
||||
-- widens to cover:
|
||||
--
|
||||
-- taste / fresh For You head vs freshness-injection tail
|
||||
-- dormant / cross_user / random
|
||||
-- Discover's three candidate buckets — makes
|
||||
-- the 40/30/30 allocation measurable instead
|
||||
-- of a guess
|
||||
-- tier1 / tier2 / tier3 the tiered-eligibility ladder (project rule
|
||||
-- #131): tier 1 pins the mix's exact desire,
|
||||
-- each higher tier steps back a little. Tier
|
||||
-- provenance is how we measure what the
|
||||
-- step-back trades away.
|
||||
--
|
||||
-- Postgres CHECK whitelists can't be altered in place: DROP + re-ADD
|
||||
-- with the expanded list, in the same migration.
|
||||
|
||||
ALTER TABLE playlist_tracks
|
||||
DROP CONSTRAINT playlist_tracks_pick_kind_check;
|
||||
ALTER TABLE playlist_tracks
|
||||
ADD CONSTRAINT playlist_tracks_pick_kind_check
|
||||
CHECK (pick_kind IN (
|
||||
'taste', 'fresh',
|
||||
'dormant', 'cross_user', 'random',
|
||||
'tier1', 'tier2', 'tier3'
|
||||
));
|
||||
|
||||
ALTER TABLE play_events
|
||||
DROP CONSTRAINT play_events_pick_kind_check;
|
||||
ALTER TABLE play_events
|
||||
ADD CONSTRAINT play_events_pick_kind_check
|
||||
CHECK (pick_kind IN (
|
||||
'taste', 'fresh',
|
||||
'dormant', 'cross_user', 'random',
|
||||
'tier1', 'tier2', 'tier3'
|
||||
));
|
||||
@@ -24,25 +24,30 @@ ORDER BY started_at DESC
|
||||
LIMIT 1;
|
||||
|
||||
-- name: InsertPlayEvent :one
|
||||
-- pick_kind is non-NULL only for source='for_you' plays whose track was
|
||||
-- found in the user's live For You snapshot at ingestion time (#1249).
|
||||
-- pick_kind is non-NULL only for system-playlist plays whose track was
|
||||
-- found (with a stamped kind) in the user's live snapshot for that
|
||||
-- variant at ingestion time (#1249, generalized in #1270).
|
||||
INSERT INTO play_events (
|
||||
user_id, track_id, session_id, started_at, client_id, source, pick_kind
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, sqlc.narg(pick_kind)::text)
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetForYouPickKindForTrack :one
|
||||
-- Looks a track up in the user's CURRENT For You snapshot and returns
|
||||
-- its pick_kind ('taste'/'fresh'). Used at play-ingestion time to
|
||||
-- freeze exploration attribution onto the play_event — the snapshot is
|
||||
-- rebuilt daily, so attribution can't be reconstructed at read time
|
||||
-- (#1249). No row = track not in today's snapshot (caller stores NULL).
|
||||
-- name: GetSystemPickKindForTrack :one
|
||||
-- Looks a track up in the user's CURRENT snapshot of the given system
|
||||
-- variant and returns its pick_kind. Used at play-ingestion time to
|
||||
-- freeze provenance onto the play_event — snapshots rebuild daily, so
|
||||
-- attribution can't be reconstructed at read time (#1249/#1270). No
|
||||
-- row = track not in today's snapshot (caller stores NULL); a row with
|
||||
-- NULL pick_kind = the variant doesn't stamp (yet). songs_like_artist
|
||||
-- is non-singleton (up to 3 mixes/user); a track in two of them takes
|
||||
-- whichever LIMIT 1 hits — acceptable, tier stamps there describe the
|
||||
-- same eligibility ladder.
|
||||
SELECT pt.pick_kind
|
||||
FROM playlist_tracks pt
|
||||
JOIN playlists p ON p.id = pt.playlist_id
|
||||
WHERE p.user_id = $1
|
||||
AND p.system_variant = 'for_you'
|
||||
AND pt.track_id = $2
|
||||
AND p.system_variant = $2
|
||||
AND pt.track_id = $3
|
||||
LIMIT 1;
|
||||
|
||||
-- name: UpdatePlayEventEnded :one
|
||||
|
||||
Reference in New Issue
Block a user