fix(recommendation): artist-level arms take one track per related artist in turn (#5297)
The similar_artists and coplay_artists arms ordered by artist score, so the closest related artist's catalogue filled the whole LIMIT. On the operator's library the 30-row similar_artists arm held exactly one artist for all 17 seeds measured (#3879), though each seed had 7-33 similar artists in the library. Both arms now rank tracks within each artist and take every artist's first track, best artist first, before anyone's second. Both also skip missing tracks: the outer select already dropped them, but only after they had taken places in the arm's LIMIT. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -828,13 +828,23 @@ lb_similar AS (
|
||||
LIMIT $5
|
||||
),
|
||||
similar_artists AS (
|
||||
SELECT t.id AS track_id, asim.score * 0.5 AS sim_score
|
||||
FROM artist_similarity asim
|
||||
JOIN tracks t ON t.artist_id = asim.artist_b_id
|
||||
JOIN seed_artist sa ON asim.artist_a_id = sa.artist_id
|
||||
WHERE asim.source = 'listenbrainz'
|
||||
AND t.id NOT IN (SELECT id FROM excluded_ids)
|
||||
ORDER BY asim.score DESC, md5(t.id::text || $12::text)
|
||||
-- Round-robin across the similar artists (#5297): each artist's first
|
||||
-- track, best-scoring artist first, then each one's second, and so on.
|
||||
-- Ordering by artist score alone let the closest similar artist's
|
||||
-- catalogue fill the whole LIMIT; on the operator's library this arm held
|
||||
-- exactly one artist for all 17 seeds measured (#3879).
|
||||
SELECT track_id, sim_score
|
||||
FROM (
|
||||
SELECT t.id AS track_id, asim.score * 0.5 AS sim_score, asim.score AS artist_score,
|
||||
row_number() OVER (PARTITION BY asim.artist_b_id
|
||||
ORDER BY md5(t.id::text || $12::text)) AS artist_turn
|
||||
FROM artist_similarity asim
|
||||
JOIN tracks t ON t.artist_id = asim.artist_b_id AND t.missing_since IS NULL
|
||||
JOIN seed_artist sa ON asim.artist_a_id = sa.artist_id
|
||||
WHERE asim.source = 'listenbrainz'
|
||||
AND t.id NOT IN (SELECT id FROM excluded_ids)
|
||||
) ranked
|
||||
ORDER BY artist_turn, artist_score DESC, md5(track_id::text || $12::text)
|
||||
LIMIT $6
|
||||
),
|
||||
tag_overlap AS (
|
||||
@@ -882,14 +892,20 @@ coplay_artists AS (
|
||||
-- the coplay worker). Mirrors similar_artists but from local co-occurrence
|
||||
-- instead of ListenBrainz; empty on single-user servers. Same 0.5 damp as
|
||||
-- similar_artists since it's artist-level.
|
||||
SELECT t.id AS track_id, asim.score * 0.5 AS sim_score
|
||||
FROM artist_similarity asim
|
||||
JOIN tracks t ON t.artist_id = asim.artist_b_id
|
||||
JOIN seed_artist sa ON asim.artist_a_id = sa.artist_id
|
||||
WHERE asim.source = 'user_cooccurrence'
|
||||
AND t.id NOT IN (SELECT id FROM excluded_ids)
|
||||
AND t.id <> $2
|
||||
ORDER BY asim.score DESC, md5(t.id::text || $12::text)
|
||||
-- Round-robin across artists for the same reason as similar_artists.
|
||||
SELECT track_id, sim_score
|
||||
FROM (
|
||||
SELECT t.id AS track_id, asim.score * 0.5 AS sim_score, asim.score AS artist_score,
|
||||
row_number() OVER (PARTITION BY asim.artist_b_id
|
||||
ORDER BY md5(t.id::text || $12::text)) AS artist_turn
|
||||
FROM artist_similarity asim
|
||||
JOIN tracks t ON t.artist_id = asim.artist_b_id AND t.missing_since IS NULL
|
||||
JOIN seed_artist sa ON asim.artist_a_id = sa.artist_id
|
||||
WHERE asim.source = 'user_cooccurrence'
|
||||
AND t.id NOT IN (SELECT id FROM excluded_ids)
|
||||
AND t.id <> $2
|
||||
) ranked
|
||||
ORDER BY artist_turn, artist_score DESC, md5(track_id::text || $12::text)
|
||||
LIMIT $11
|
||||
),
|
||||
random_fill AS (
|
||||
|
||||
@@ -96,13 +96,23 @@ lb_similar AS (
|
||||
LIMIT $5
|
||||
),
|
||||
similar_artists AS (
|
||||
SELECT t.id AS track_id, asim.score * 0.5 AS sim_score
|
||||
FROM artist_similarity asim
|
||||
JOIN tracks t ON t.artist_id = asim.artist_b_id
|
||||
JOIN seed_artist sa ON asim.artist_a_id = sa.artist_id
|
||||
WHERE asim.source = 'listenbrainz'
|
||||
AND t.id NOT IN (SELECT id FROM excluded_ids)
|
||||
ORDER BY asim.score DESC, md5(t.id::text || $12::text)
|
||||
-- Round-robin across the similar artists (#5297): each artist's first
|
||||
-- track, best-scoring artist first, then each one's second, and so on.
|
||||
-- Ordering by artist score alone let the closest similar artist's
|
||||
-- catalogue fill the whole LIMIT; on the operator's library this arm held
|
||||
-- exactly one artist for all 17 seeds measured (#3879).
|
||||
SELECT track_id, sim_score
|
||||
FROM (
|
||||
SELECT t.id AS track_id, asim.score * 0.5 AS sim_score, asim.score AS artist_score,
|
||||
row_number() OVER (PARTITION BY asim.artist_b_id
|
||||
ORDER BY md5(t.id::text || $12::text)) AS artist_turn
|
||||
FROM artist_similarity asim
|
||||
JOIN tracks t ON t.artist_id = asim.artist_b_id AND t.missing_since IS NULL
|
||||
JOIN seed_artist sa ON asim.artist_a_id = sa.artist_id
|
||||
WHERE asim.source = 'listenbrainz'
|
||||
AND t.id NOT IN (SELECT id FROM excluded_ids)
|
||||
) ranked
|
||||
ORDER BY artist_turn, artist_score DESC, md5(track_id::text || $12::text)
|
||||
LIMIT $6
|
||||
),
|
||||
tag_overlap AS (
|
||||
@@ -150,14 +160,20 @@ coplay_artists AS (
|
||||
-- the coplay worker). Mirrors similar_artists but from local co-occurrence
|
||||
-- instead of ListenBrainz; empty on single-user servers. Same 0.5 damp as
|
||||
-- similar_artists since it's artist-level.
|
||||
SELECT t.id AS track_id, asim.score * 0.5 AS sim_score
|
||||
FROM artist_similarity asim
|
||||
JOIN tracks t ON t.artist_id = asim.artist_b_id
|
||||
JOIN seed_artist sa ON asim.artist_a_id = sa.artist_id
|
||||
WHERE asim.source = 'user_cooccurrence'
|
||||
AND t.id NOT IN (SELECT id FROM excluded_ids)
|
||||
AND t.id <> $2
|
||||
ORDER BY asim.score DESC, md5(t.id::text || $12::text)
|
||||
-- Round-robin across artists for the same reason as similar_artists.
|
||||
SELECT track_id, sim_score
|
||||
FROM (
|
||||
SELECT t.id AS track_id, asim.score * 0.5 AS sim_score, asim.score AS artist_score,
|
||||
row_number() OVER (PARTITION BY asim.artist_b_id
|
||||
ORDER BY md5(t.id::text || $12::text)) AS artist_turn
|
||||
FROM artist_similarity asim
|
||||
JOIN tracks t ON t.artist_id = asim.artist_b_id AND t.missing_since IS NULL
|
||||
JOIN seed_artist sa ON asim.artist_a_id = sa.artist_id
|
||||
WHERE asim.source = 'user_cooccurrence'
|
||||
AND t.id NOT IN (SELECT id FROM excluded_ids)
|
||||
AND t.id <> $2
|
||||
) ranked
|
||||
ORDER BY artist_turn, artist_score DESC, md5(track_id::text || $12::text)
|
||||
LIMIT $11
|
||||
),
|
||||
random_fill AS (
|
||||
|
||||
Reference in New Issue
Block a user