fix(playlists): Songs-like mixes no longer vanish after a quiet week
PickSeedArtists had a hard 7-day window with no fallback: a week without listening emptied the seed pool, produceSeedMixes returned zero playlists, and the daily atomic-replace build deleted every existing "Songs like X" mix until the user played something again (#1255). The query now falls back through widening engagement windows — 7d → 30d → all-time → liked artists — the same tiered shape that fixed the identical vanish for For You's seeds (PickTopPlayedTracksForUser). Like-boost scoring is preserved in every tier. All returned rows share the winning tier, and produceSeedMixes maps it onto the rule-#131 pick-kind ladder (7d = tier1 exact, 30d = tier2, all-time/liked = tier3) and stamps the built tracks — the #1270 provenance pipeline then attributes plays and skips to seed freshness, so the metrics card can say whether stale-seeded mixes actually perform worse. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TsF3cNoKrqCYsU78cXC8U6
This commit is contained in:
@@ -147,8 +147,28 @@ const (
|
||||
pickKindDormant = "dormant"
|
||||
pickKindCrossUser = "cross_user"
|
||||
pickKindRandom = "random"
|
||||
pickKindTier1 = "tier1"
|
||||
pickKindTier2 = "tier2"
|
||||
pickKindTier3 = "tier3"
|
||||
)
|
||||
|
||||
// pickKindForSeedTier maps PickSeedArtists' seed-pool fallback tier
|
||||
// onto the rule-#131 pick-kind ladder: fresh 7-day engagement pins the
|
||||
// mix's exact desire (tier1), the 30-day window steps back a little
|
||||
// (tier2), and all-time / liked-only seeds are the far fallback
|
||||
// (tier3). Stamped mix-wide — seed staleness is a property of the
|
||||
// whole build, and it's what the metrics breakdown attributes skips to.
|
||||
func pickKindForSeedTier(tier int32) string {
|
||||
switch tier {
|
||||
case 0:
|
||||
return pickKindTier1
|
||||
case 1:
|
||||
return pickKindTier2
|
||||
default:
|
||||
return pickKindTier3
|
||||
}
|
||||
}
|
||||
|
||||
const systemMixLength = 25
|
||||
|
||||
// systemMixWeights are the fixed scoring weights used by the cron worker.
|
||||
@@ -389,8 +409,11 @@ func produceForYou(
|
||||
}
|
||||
|
||||
// produceSeedMixes: up to 3 "Songs like {artist}" mixes. Seed
|
||||
// artists rotate daily-deterministically. The base seed-artist
|
||||
// query failing is fatal; per-artist failures are logged + skipped.
|
||||
// artists rotate daily-deterministically; the seed query falls back
|
||||
// through widening engagement windows (#1255) and every returned row
|
||||
// shares the winning tier, stamped onto the built tracks as their
|
||||
// pick_kind. The base seed-artist query failing is fatal; per-artist
|
||||
// failures are logged + skipped.
|
||||
func produceSeedMixes(
|
||||
ctx context.Context, q *dbq.Queries, logger *slog.Logger,
|
||||
userID pgtype.UUID, dateStr string, now time.Time,
|
||||
@@ -399,6 +422,10 @@ func produceSeedMixes(
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pick seed artists: %w", err)
|
||||
}
|
||||
seedTierKind := ""
|
||||
if len(seedRows) > 0 {
|
||||
seedTierKind = pickKindForSeedTier(seedRows[0].Tier)
|
||||
}
|
||||
seedRowsLocal := make([]seedArtistRow, 0, len(seedRows))
|
||||
for _, r := range seedRows {
|
||||
seedRowsLocal = append(seedRowsLocal, seedArtistRow{
|
||||
@@ -443,6 +470,9 @@ func produceSeedMixes(
|
||||
if len(tracks) == 0 {
|
||||
continue
|
||||
}
|
||||
for i := range tracks {
|
||||
tracks[i].PickKind = seedTierKind
|
||||
}
|
||||
out = append(out, builtPlaylist{
|
||||
Name: fmt.Sprintf("Songs like %s", artistRow.Name),
|
||||
Variant: "songs_like_artist",
|
||||
|
||||
Reference in New Issue
Block a user