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:
@@ -97,25 +97,26 @@ var systemPlaylistSources = map[string]bool{
|
||||
"songs_like_artist": true,
|
||||
}
|
||||
|
||||
// sourceForYou is the one source whose plays carry exploration
|
||||
// attribution (taste vs fresh pick, #1249).
|
||||
const sourceForYou = "for_you"
|
||||
|
||||
// lookupForYouPickKind resolves a For-You play's pick_kind ('taste' or
|
||||
// 'fresh') from the user's live snapshot at ingestion time. Attribution
|
||||
// must be frozen now — the snapshot rebuilds daily, so it can't be
|
||||
// reconstructed at metrics-read time. Returns nil (unattributed) when
|
||||
// the track isn't in the current snapshot: plays predating the feature,
|
||||
// or an offline replay arriving after the track rotated out. Real DB
|
||||
// lookupSystemPickKind resolves a system-playlist play's pick_kind
|
||||
// (taste/fresh, Discover's bucket, or a rule-#131 tier) from the user's
|
||||
// live snapshot of that variant at ingestion time. Attribution must be
|
||||
// frozen now — snapshots rebuild daily, so it can't be reconstructed at
|
||||
// metrics-read time (#1249, generalized in #1270). Source strings
|
||||
// double as system_variant values (see systemPlaylistSources). Returns
|
||||
// nil (unattributed) when the track isn't in the current snapshot —
|
||||
// plays predating the feature, or an offline replay arriving after the
|
||||
// track rotated out — and when the variant doesn't stamp yet. Real DB
|
||||
// errors propagate and fail the caller's transaction.
|
||||
func lookupForYouPickKind(
|
||||
func lookupSystemPickKind(
|
||||
ctx context.Context,
|
||||
q *dbq.Queries,
|
||||
userID, trackID pgtype.UUID,
|
||||
variant string,
|
||||
) (*string, error) {
|
||||
kind, err := q.GetForYouPickKindForTrack(ctx, dbq.GetForYouPickKindForTrackParams{
|
||||
UserID: userID,
|
||||
TrackID: trackID,
|
||||
kind, err := q.GetSystemPickKindForTrack(ctx, dbq.GetSystemPickKindForTrackParams{
|
||||
UserID: userID,
|
||||
SystemVariant: &variant,
|
||||
TrackID: trackID,
|
||||
})
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, nil
|
||||
@@ -157,8 +158,8 @@ func (w *Writer) RecordPlayStartedWithSource(
|
||||
sourcePtr = &source
|
||||
}
|
||||
var pickKind *string
|
||||
if source == sourceForYou {
|
||||
pickKind, err = lookupForYouPickKind(ctx, q, userID, trackID)
|
||||
if systemPlaylistSources[source] {
|
||||
pickKind, err = lookupSystemPickKind(ctx, q, userID, trackID, source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -433,8 +434,8 @@ func (w *Writer) RecordOfflinePlay(
|
||||
sourcePtr = &source
|
||||
}
|
||||
var pickKind *string
|
||||
if source == sourceForYou {
|
||||
pickKind, err = lookupForYouPickKind(ctx, q, userID, trackID)
|
||||
if systemPlaylistSources[source] {
|
||||
pickKind, err = lookupSystemPickKind(ctx, q, userID, trackID, source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -489,14 +489,13 @@ func TestRecordPlayStarted_NoRotationWithoutSource(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// seedForYouSnapshot creates a system For You playlist containing the
|
||||
// fixture track with the given pick_kind, mimicking what the builder
|
||||
// persists (#1249).
|
||||
func seedForYouSnapshot(t *testing.T, f fixture, pickKind string) {
|
||||
// seedSystemSnapshot creates a system playlist of the given variant
|
||||
// containing the fixture track with the given pick_kind, mimicking
|
||||
// what the builder persists (#1249, generalized #1270).
|
||||
func seedSystemSnapshot(t *testing.T, f fixture, variant, pickKind string) {
|
||||
t.Helper()
|
||||
variant := "for_you"
|
||||
pl, err := f.q.CreateSystemPlaylist(context.Background(), dbq.CreateSystemPlaylistParams{
|
||||
UserID: f.user, Name: "For You", SystemVariant: &variant,
|
||||
UserID: f.user, Name: variant, SystemVariant: &variant,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("CreateSystemPlaylist: %v", err)
|
||||
@@ -510,7 +509,7 @@ func seedForYouSnapshot(t *testing.T, f fixture, pickKind string) {
|
||||
|
||||
func TestRecordPlayStartedWithSource_StampsForYouPickKind(t *testing.T) {
|
||||
f := newFixture(t, 200_000)
|
||||
seedForYouSnapshot(t, f, "fresh")
|
||||
seedSystemSnapshot(t, f, "for_you", "fresh")
|
||||
res, err := f.w.RecordPlayStartedWithSource(
|
||||
context.Background(), f.user, f.track, "c", "for_you", time.Now().UTC(),
|
||||
)
|
||||
@@ -545,11 +544,13 @@ func TestRecordPlayStartedWithSource_NoSnapshotMatch_PickKindNull(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordPlayStartedWithSource_NonForYouSource_PickKindNull(t *testing.T) {
|
||||
// Even with a For You snapshot containing the track, a play from a
|
||||
// different surface must not pick up For You attribution.
|
||||
func TestRecordPlayStartedWithSource_CrossVariant_PickKindNull(t *testing.T) {
|
||||
// Attribution is variant-scoped: even with a For You snapshot
|
||||
// containing the track, a play from a different surface must not
|
||||
// pick up For You's stamp — it reads its OWN variant's snapshot,
|
||||
// which here doesn't exist.
|
||||
f := newFixture(t, 200_000)
|
||||
seedForYouSnapshot(t, f, "taste")
|
||||
seedSystemSnapshot(t, f, "for_you", "taste")
|
||||
res, err := f.w.RecordPlayStartedWithSource(
|
||||
context.Background(), f.user, f.track, "c", "discover", time.Now().UTC(),
|
||||
)
|
||||
@@ -561,7 +562,27 @@ func TestRecordPlayStartedWithSource_NonForYouSource_PickKindNull(t *testing.T)
|
||||
t.Fatalf("get: %v", err)
|
||||
}
|
||||
if got.PickKind != nil {
|
||||
t.Errorf("pick_kind = %q, want NULL for non-for_you source", *got.PickKind)
|
||||
t.Errorf("pick_kind = %q, want NULL for a cross-variant play", *got.PickKind)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordPlayStartedWithSource_StampsDiscoverBucket(t *testing.T) {
|
||||
// Discover stamps its candidate bucket (#1270); a discover play whose
|
||||
// track is in the live snapshot freezes that bucket onto the play.
|
||||
f := newFixture(t, 200_000)
|
||||
seedSystemSnapshot(t, f, "discover", "dormant")
|
||||
res, err := f.w.RecordPlayStartedWithSource(
|
||||
context.Background(), f.user, f.track, "c", "discover", time.Now().UTC(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("RecordPlayStartedWithSource: %v", err)
|
||||
}
|
||||
got, err := f.q.GetPlayEventByID(context.Background(), res.PlayEventID)
|
||||
if err != nil {
|
||||
t.Fatalf("get: %v", err)
|
||||
}
|
||||
if got.PickKind == nil || *got.PickKind != "dormant" {
|
||||
t.Errorf("pick_kind = %v, want dormant (frozen from live snapshot)", got.PickKind)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,7 +590,7 @@ func TestRecordOfflinePlay_StampsForYouPickKind(t *testing.T) {
|
||||
// The offline replay path threads source the same way; a replayed
|
||||
// for_you play whose track is still in the snapshot gets attributed.
|
||||
f := newFixture(t, 200_000)
|
||||
seedForYouSnapshot(t, f, "taste")
|
||||
seedSystemSnapshot(t, f, "for_you", "taste")
|
||||
at := time.Now().UTC().Add(-time.Hour)
|
||||
if err := f.w.RecordOfflinePlay(
|
||||
context.Background(), f.user, f.track, "c", "for_you", at, 180_000,
|
||||
|
||||
Reference in New Issue
Block a user