feat(discover): taste-targeted novelty bucket + rebalance toward discovery
The 2-week metrics review found Discover beating the manual baseline on skip rate — which for a discovery surface means it plays it safe. On a single-user server it's effectively dormant + crude-random, and the per-user taste profile (taste_profile_tags, #796) went unused (#1254 gap). Add a fourth Discover candidate bucket, ListTasteUnheardTracksForDiscover: unheard / non-liked / non-quarantined tracks ranked by summed taste-tag weight over tracks.genre (split like the radio tag_overlap arm), md5 tiebreak. Its picks are stamped pick_kind = 'taste_unheard' (migration 0041 widens the CHECK on playlist_tracks + play_events, rule #36). Rebalance the slot allocation 40/30/30 → taste_unheard 35 / dormant 30 / cross_user 20 / random 15, and lead the interleave with taste_unheard so a track shared with another bucket keeps the taste stamp and the targeted-novelty arm stays measurable. Metrics label "Taste-matched" + order entry added to the single server-side pickKindLabels map, so web and Android surface the new breakdown row with no client change. Cold start (empty taste_profile_tags) yields an empty taste bucket that redistributes to the survivors, so Discover still fills. Scribe #1488. Companion review outcomes: Songs-like starvation already fixed (#1255); For You v2 ratified as-is (fresh-injection cost disproven). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -130,17 +130,18 @@ type rankedCandidate struct {
|
||||
// Pick kinds, persisted on playlist_tracks.pick_kind so plays can
|
||||
// attribute skips to the population that sourced the track — For You's
|
||||
// "taste engine missing" vs "freshness tax", Discover's three buckets
|
||||
// (#1270). Values mirror the CHECK in migration 0039; the tier1-3
|
||||
// values there land with the tiered-mix rebuilds.
|
||||
// (#1270). Values mirror the CHECK in migration 0041; taste_unheard is
|
||||
// Discover's taste-targeted-novelty arm (#1488).
|
||||
const (
|
||||
pickKindTaste = "taste"
|
||||
pickKindFresh = "fresh"
|
||||
pickKindDormant = "dormant"
|
||||
pickKindCrossUser = "cross_user"
|
||||
pickKindRandom = "random"
|
||||
pickKindTier1 = "tier1"
|
||||
pickKindTier2 = "tier2"
|
||||
pickKindTier3 = "tier3"
|
||||
pickKindTaste = "taste"
|
||||
pickKindFresh = "fresh"
|
||||
pickKindDormant = "dormant"
|
||||
pickKindTasteUnheard = "taste_unheard"
|
||||
pickKindCrossUser = "cross_user"
|
||||
pickKindRandom = "random"
|
||||
pickKindTier1 = "tier1"
|
||||
pickKindTier2 = "tier2"
|
||||
pickKindTier3 = "tier3"
|
||||
)
|
||||
|
||||
// pickKindForSeedTier maps PickSeedArtists' seed-pool fallback tier
|
||||
|
||||
Reference in New Issue
Block a user