feat(recommendation): fall back to liked entities when You-might-like is thin (#790)
The taste roll-up surfaces top-similar albums/artists, which for a heavy listener are mostly ones they already play — so the read-time dedup (vs Most Played + Rediscover + Last Played) can strip the section down to a single tile (reported on the artists row). The code was sound; the section was just starved. Adds a read-time fallback: when a You-might-like row comes up short after dedup, top it up from the user's LIKED artists/albums — a far larger pool than the 12-entity similarity roll-up, so the same exclusions still leave plenty. Reuses the existing Rediscover-fallback queries (no new SQL), applies the same exclusions (already-shown + Rediscover + Most/Last Played) so it never duplicates a tile or suggests an actively-played entity, and is best-effort (a query error leaves the section as-is). Takes effect immediately — no rebuild. A cold-start user with no likes gets nothing from the fallback, so the new-user-empty behaviour is preserved (test still passes). Test: 20 liked artists, none played → Rediscover fills 10, You-might-like fallback fills the other 10, disjoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ package recommendation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -10,6 +11,43 @@ import (
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
||||
)
|
||||
|
||||
// TestHomeData_YouMightLike_FallbackFillsFromLikedArtists: when the taste
|
||||
// roll-up is empty/thin (no you_might_like_artists rows here), the section
|
||||
// fills from the user's liked artists. Rediscover takes its share first; the
|
||||
// You-might-like fallback fills the rest disjointly so no artist is shown
|
||||
// twice. 20 liked artists, none played → Rediscover 10 + You-might-like 10.
|
||||
func TestHomeData_YouMightLike_FallbackFillsFromLikedArtists(t *testing.T) {
|
||||
pool := newPool(t)
|
||||
user := seedUser(t, pool, "home-yml-fallback")
|
||||
liked := make(map[pgtype.UUID]bool, 20)
|
||||
for i := 0; i < 20; i++ {
|
||||
a := seedArtist(t, pool, fmt.Sprintf("YmlArtist%02d", i), "")
|
||||
likeArtist(t, pool, user.ID, a.ID)
|
||||
liked[a.ID] = true
|
||||
}
|
||||
|
||||
got, err := HomeData(context.Background(), pool, user.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("HomeData: %v", err)
|
||||
}
|
||||
if len(got.YouMightLikeArtists) != HomeYouMightLikeLimit {
|
||||
t.Fatalf("you-might-like artists = %d, want %d (filled by fallback)",
|
||||
len(got.YouMightLikeArtists), HomeYouMightLikeLimit)
|
||||
}
|
||||
inRediscover := map[pgtype.UUID]bool{}
|
||||
for _, r := range got.RediscoverArtists {
|
||||
inRediscover[r.Artist.ID] = true
|
||||
}
|
||||
for _, r := range got.YouMightLikeArtists {
|
||||
if !liked[r.Artist.ID] {
|
||||
t.Errorf("you-might-like artist %s not from the liked pool", r.Artist.Name)
|
||||
}
|
||||
if inRediscover[r.Artist.ID] {
|
||||
t.Errorf("artist %s shown in both Rediscover and You-might-like", r.Artist.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHomeData_NewUser_AllSectionsEmpty(t *testing.T) {
|
||||
pool := newPool(t)
|
||||
user := seedUser(t, pool, "home-newuser")
|
||||
|
||||
Reference in New Issue
Block a user