Two improvements to the system playlist builder:
1. Per-artist (<=3) and per-album (<=2) caps applied to the
pickTopN truncation step, using the same numeric caps Discover
already enforces. Both For-You and Songs-like-X benefit. Same
skewed candidate pool no longer collapses to "10 tracks from
the same artist" — the playlist always carries at least 9
distinct artists in 25 slots.
2. New pickHeadAndTail function for For-You: 20 top-similarity
tracks + 5 sampled from the tail (positions 2*headN onward of
the score-sorted, cap-applied pool). Tail sampling uses
tieBreakHash for daily determinism — same user same day still
sees the same playlist, but the daily refresh feels less
stuck-in-a-rut. Tail tracks are still similarity-related
(they passed the similarity candidate filter) so the user
should enjoy them, just from artists they wouldn't have surfaced
via strict top-N ranking.
Songs-like-X keeps the simple pickTopN call — the seed-artist
context already provides the "you'll like this" framing without
needing a tail injection.
Refactors pickTopN internals: now sorts candidates first via
scoreAndSortCandidates, applies the cap on []Candidate via
capCandidatesByAlbumAndArtist, and truncates. Removes the now-
dead stableSortByScoreThenHash helper (only used in old pickTopN).
The cap helper mirrors capByAlbumAndArtist in discover.go but
operates on recommendation.Candidate so it sees Track.AlbumID /
Track.ArtistID directly.
Tests cover the cap helper truth table, head+tail split with
small/large pools, buffer-zone exclusion, daily determinism, and
cross-day tail variance.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>