0bfd51a149
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>