feat(server): "You might like" album/artist Home rows (#790)
Surface in-library albums/artists the listener doesn't actively spin but is predicted to enjoy, derived from the same similarity + like-weighted candidate engine that powers For-You — rolled up from track scores to album/artist granularity. Built in the daily 3am BuildSystemPlaylists pass, atomic-replaced alongside the system playlists, and read back by /api/home (+ /api/home/index). Cold-start gate: skips generation entirely below 20 distinct unskipped tracks AND 5 distinct artists, so a thin profile ships empty rows rather than near-random tiles. - migration 0034: you_might_like_albums / you_might_like_artists (id+rank, CASCADE, per-user rank index). - playlists/you_might_like.go: cold-start gate + similarity roll-up (sum-of-top-3 aggregation, per-artist album cap, daily-rotating via the same userIDHash jitter as For-You) + atomic-replace persist in the tx. - recommendation/home.go: two new HomePayload sections with read-time cross-section dedup vs Most Played / Rediscover / Last Played, trimmed to 10 each. - api: you_might_like_albums / you_might_like_artists on /api/home and /api/home/index, reusing albumRefFrom / artistRefFromCovered. - tests: pure roll-up/aggregation/cap unit tests + DB-backed gate, sufficiency, and atomic-replace tests (all green vs real Postgres). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -497,6 +497,13 @@ func BuildSystemPlaylists(ctx context.Context, pool *pgxpool.Pool, logger *slog.
|
||||
built = append(built, out...)
|
||||
}
|
||||
|
||||
// "You might like" Home rows reuse the same similarity engine but
|
||||
// persist to dedicated album/artist tables (not playlist_tracks).
|
||||
// Computed here (reads only) and atomic-replaced inside the tx below,
|
||||
// alongside the system playlists. A failed/gated computation is
|
||||
// handled by yml.built (leave prior rows vs. clear) — never fatal.
|
||||
yml := buildYouMightLike(ctx, q, logger, userID, dateStr, now)
|
||||
|
||||
// Atomic replace inside a transaction.
|
||||
tx, err := pool.Begin(ctx)
|
||||
if err != nil {
|
||||
@@ -530,6 +537,13 @@ func BuildSystemPlaylists(ctx context.Context, pool *pgxpool.Pool, logger *slog.
|
||||
createdIDs = append(createdIDs, id)
|
||||
}
|
||||
|
||||
if yml.built {
|
||||
if err := persistYouMightLike(ctx, qtx, userID, yml); err != nil {
|
||||
buildErr = fmt.Errorf("persist you-might-like: %w", err)
|
||||
return buildErr
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
buildErr = fmt.Errorf("commit tx: %w", err)
|
||||
return buildErr
|
||||
|
||||
Reference in New Issue
Block a user