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:
@@ -0,0 +1,3 @@
|
||||
-- Reverse 0034_you_might_like.up.sql.
|
||||
DROP TABLE IF EXISTS you_might_like_artists;
|
||||
DROP TABLE IF EXISTS you_might_like_albums;
|
||||
@@ -0,0 +1,36 @@
|
||||
-- 0034_you_might_like.up.sql — "You might like" Home rows (#790).
|
||||
--
|
||||
-- Per-user ranked lists of 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). Built in the daily
|
||||
-- 3am BuildSystemPlaylists pass and atomic-replaced, exactly like the
|
||||
-- system playlists; read back by /api/home.
|
||||
--
|
||||
-- Two thin tables (id + rank) rather than denormalized snapshots: the
|
||||
-- Home read path hydrates album/artist refs through the existing
|
||||
-- albums/artists joins, so a later metadata edit is reflected without
|
||||
-- a rebuild. CASCADE on user/album/artist deletes keeps them clean.
|
||||
|
||||
CREATE TABLE you_might_like_albums (
|
||||
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
album_id uuid NOT NULL REFERENCES albums(id) ON DELETE CASCADE,
|
||||
rank integer NOT NULL,
|
||||
built_at timestamptz NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (user_id, album_id)
|
||||
);
|
||||
|
||||
-- Read path orders by rank within a user; the index serves it directly.
|
||||
CREATE INDEX you_might_like_albums_user_rank_idx
|
||||
ON you_might_like_albums (user_id, rank);
|
||||
|
||||
CREATE TABLE you_might_like_artists (
|
||||
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
artist_id uuid NOT NULL REFERENCES artists(id) ON DELETE CASCADE,
|
||||
rank integer NOT NULL,
|
||||
built_at timestamptz NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (user_id, artist_id)
|
||||
);
|
||||
|
||||
CREATE INDEX you_might_like_artists_user_rank_idx
|
||||
ON you_might_like_artists (user_id, rank);
|
||||
Reference in New Issue
Block a user