fix: ListenBrainz similarity is stored relative to the seed's best, and the candidate arms keep tier order (#6177)
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m23s
release / go (push) Successful in 1m36s
release / integration (push) Successful in 5m4s
release / android (push) Successful in 5m5s
release / Build signed APK (releases and dev) (push) Successful in 5m18s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 30s
release / Verify release artifacts (tag releases only) (push) Skipped

LB's similarity scores are counts (16 to 1445 for recordings, to 10,875
for artists), and they reached a scorer that reads similarity as 0 to 1.
One edge added 64 to 5,800 points under Songs-like, so likes, skips and
recency stopped mattering for any candidate with an edge.

- ResolveListenBrainzTrackEdges divides each edge by the seed's best
  score in its whole cached answer. The cache keeps the raw answer.
- The worker stores matched and unmatched artist rows over the seed's
  top artist.
- Migration 0096 rescales the rows already stored.
- The candidate arms hold sim_score bands so tier 1 stays above tier 2:
  lb_similar 0.5 + 0.5 x edge, tag_overlap halved, likes_overlap 0.3.
  Otherwise a seed's weaker LB edges fell below a shared genre tag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-21 10:56:26 -04:00
co-authored by Claude Opus 5.5
parent 46e1dd6c9c
commit e44025435e
11 changed files with 216 additions and 45 deletions
+18 -2
View File
@@ -68,13 +68,29 @@ ON CONFLICT (artist_id) DO UPDATE SET fetched_at = now(), returned = EXCLUDED.re
-- pool would offer the same song twice. No cap per seed: LB's answer is already
-- at most 50.
--
-- The score is RELATIVE: each edge's LB score over the seed's best score in its
-- whole cached answer, so 1.0 is the seed's closest recording and the rest fall
-- below it (#6177). LB's raw scores are counts (16 to 1445 on the operator's
-- library) and reached the scorer, which reads similarity as 0 to 1, so one
-- edge outweighed likes, skips and recency together. The cache keeps the raw
-- answer; only the edge is scaled.
--
-- Only edges of seeds the cache owns are removed, those with a fetch row.
-- Edges written before the cache existed, or copied onto a survivor by a fold
-- (merge.sql), stay until their seed is fetched.
WITH want AS (
WITH best AS (
SELECT seed_track_id, max(score) AS score
FROM listenbrainz_similar_recordings
WHERE sqlc.narg(seed_ids)::uuid[] IS NULL
OR seed_track_id = ANY(sqlc.narg(seed_ids)::uuid[])
GROUP BY seed_track_id
),
want AS (
SELECT DISTINCT ON (c.seed_track_id, c.recording_mbid)
c.seed_track_id AS track_a_id, t.id AS track_b_id, c.score
c.seed_track_id AS track_a_id, t.id AS track_b_id,
CASE WHEN b.score > 0 THEN c.score / b.score ELSE 0 END AS score
FROM listenbrainz_similar_recordings c
JOIN best b ON b.seed_track_id = c.seed_track_id
JOIN tracks t ON t.mbid = c.recording_mbid
AND t.missing_since IS NULL
AND t.id <> c.seed_track_id