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
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:
@@ -69,8 +69,9 @@ func TestLoadCandidatesFromSimilarity_LBSimilarSourceContributes(t *testing.T) {
|
||||
if found == nil {
|
||||
t.Fatal("LB-similar target missing from candidates")
|
||||
}
|
||||
if found.Inputs.SimilarityScore < 0.84 || found.Inputs.SimilarityScore > 0.86 {
|
||||
t.Errorf("LB-similar SimilarityScore = %v, want ~0.85", found.Inputs.SimilarityScore)
|
||||
// Tier 1's band (#6177): 0.5 + 0.5 × 0.85.
|
||||
if found.Inputs.SimilarityScore < 0.92 || found.Inputs.SimilarityScore > 0.93 {
|
||||
t.Errorf("LB-similar SimilarityScore = %v, want ~0.925 (0.5 + 0.5 × 0.85)", found.Inputs.SimilarityScore)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,9 +118,9 @@ func TestLoadCandidatesFromSimilarity_TagOverlapContributes(t *testing.T) {
|
||||
}
|
||||
for _, c := range got {
|
||||
if c.Track.ID == target.ID {
|
||||
// Seed has 2 tags; target shares 1 → jaccard 1/2 = 0.5.
|
||||
if c.Inputs.SimilarityScore < 0.49 || c.Inputs.SimilarityScore > 0.51 {
|
||||
t.Errorf("tag-overlap SimilarityScore = %v, want ~0.5", c.Inputs.SimilarityScore)
|
||||
// Seed has 2 tags; target shares 1 → 1/2, in tier 2's band → 0.25.
|
||||
if c.Inputs.SimilarityScore < 0.24 || c.Inputs.SimilarityScore > 0.26 {
|
||||
t.Errorf("tag-overlap SimilarityScore = %v, want ~0.25 (0.5 × 1/2)", c.Inputs.SimilarityScore)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -144,10 +145,10 @@ func TestLoadCandidatesFromSimilarity_LikesOverlapContributes(t *testing.T) {
|
||||
}
|
||||
for _, c := range got {
|
||||
if c.Track.ID == liked.ID {
|
||||
// Both tracks tagged "Rock" → jaccard 1/1 = 1.0 from tag-overlap.
|
||||
// likes-overlap = 0.6. Max wins = 1.0.
|
||||
if c.Inputs.SimilarityScore < 0.59 {
|
||||
t.Errorf("likes-overlap candidate SimilarityScore = %v, want ≥ 0.6", c.Inputs.SimilarityScore)
|
||||
// Both tracks tagged "Rock" → 0.5 × 1/1 = 0.5 from tag-overlap.
|
||||
// likes-overlap = 0.3. Max wins = 0.5.
|
||||
if c.Inputs.SimilarityScore < 0.29 {
|
||||
t.Errorf("likes-overlap candidate SimilarityScore = %v, want ≥ 0.3", c.Inputs.SimilarityScore)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -243,8 +244,8 @@ func TestLoadCandidatesFromSimilarity_DedupTakesMaxScore(t *testing.T) {
|
||||
seed := f.tracks[0]
|
||||
target := f.tracks[1]
|
||||
helperSetTrackGenre(t, f, seed.ID, "Rock")
|
||||
helperSetTrackGenre(t, f, target.ID, "Rock") // jaccard 1/1 = 1.0 from tag-overlap
|
||||
helperLBSimilarity(t, f, seed.ID, target.ID, 0.5) // weaker LB signal
|
||||
helperSetTrackGenre(t, f, target.ID, "Rock") // 0.5 × 1/1 = 0.5 from tag-overlap
|
||||
helperLBSimilarity(t, f, seed.ID, target.ID, 0.1) // a weak LB edge → 0.5 + 0.05
|
||||
got, err := LoadCandidatesFromSimilarity(
|
||||
context.Background(), f.q, f.user, seed.ID, 1, SessionVector{Seed: true}, nil, defaultLimits(), "test-seed",
|
||||
)
|
||||
@@ -255,9 +256,10 @@ func TestLoadCandidatesFromSimilarity_DedupTakesMaxScore(t *testing.T) {
|
||||
for _, c := range got {
|
||||
if c.Track.ID == target.ID {
|
||||
count++
|
||||
// tag-overlap (1.0) wins over LB (0.5) per max() — expect ≥ 0.99
|
||||
if c.Inputs.SimilarityScore < 0.99 {
|
||||
t.Errorf("dedup max SimilarityScore = %v, want ≥ 0.99 (tag-overlap should win)", c.Inputs.SimilarityScore)
|
||||
// max() keeps LB's 0.55. Even a weak ListenBrainz edge is tier 1 and
|
||||
// outranks a full tag match (#6177).
|
||||
if c.Inputs.SimilarityScore < 0.54 || c.Inputs.SimilarityScore > 0.56 {
|
||||
t.Errorf("dedup max SimilarityScore = %v, want ~0.55 (the LB edge's band should win)", c.Inputs.SimilarityScore)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user