feat: soft tier bonuses + library bonus on slow path
Replaced hard tier boundaries with additive score adjustments:
Artist tiers: exact +12, starts-with +6, substring +0, none -10
Album tiers: credit-exact +12, credit-contains +8,
title-exact +4, title-contains +0, none -5
A sufficiently popular lower-tier result can now overcome an
unpopular exact match. The effective gap between tier 0 and tier 1
is 6 points on a 0-100 scale, requiring roughly a 4-5x popularity
difference to overcome — matching the intuition that 'slightly more
popular near-match loses to exact, much more popular near-match wins.'
Also added library bonus (+10M) to the slow path (boostWithPopularity)
so library artists rank highly regardless of which reranking path
is used. Previously only the index fast path applied this bonus.
This commit is contained in:
+55
-24
@@ -902,6 +902,31 @@ const (
|
|||||||
minBlendedScore = 25
|
minBlendedScore = 25
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// tierBonus maps artist name-match tiers to additive score adjustments.
|
||||||
|
// These are soft bonuses — a sufficiently popular lower-tier result can
|
||||||
|
// overcome the tier advantage. The effective gap between adjacent tiers
|
||||||
|
// (~6 points on a 0–100 scale) requires roughly a 4–5× popularity
|
||||||
|
// difference to overcome.
|
||||||
|
//
|
||||||
|
//nolint:gochecknoglobals
|
||||||
|
var tierBonus = map[int]int{
|
||||||
|
0: 12, // exact match: "shannon" == "shannon"
|
||||||
|
1: 6, // starts with: "shannon" in "shannon and the clams"
|
||||||
|
2: 0, // substring: "shannon" in "del shannon"
|
||||||
|
3: -10, // no substring match: only individual words matched
|
||||||
|
}
|
||||||
|
|
||||||
|
// rgTierBonus maps release group match tiers to additive score adjustments.
|
||||||
|
//
|
||||||
|
//nolint:gochecknoglobals
|
||||||
|
var rgTierBonus = map[int]int{
|
||||||
|
0: 12, // artist credit exact match
|
||||||
|
1: 8, // artist credit contains query
|
||||||
|
2: 4, // title exact match
|
||||||
|
3: 0, // title contains query
|
||||||
|
4: -5, // no match in either field
|
||||||
|
}
|
||||||
|
|
||||||
// mbSpecialPurposeArtists is a set of MusicBrainz Special Purpose
|
// mbSpecialPurposeArtists is a set of MusicBrainz Special Purpose
|
||||||
// Artist MBIDs that should be excluded from search results. These
|
// Artist MBIDs that should be excluded from search results. These
|
||||||
// are placeholder entries (e.g. [unknown], [anonymous]) that
|
// are placeholder entries (e.g. [unknown], [anonymous]) that
|
||||||
@@ -1057,6 +1082,18 @@ func (e *Service) boostWithPopularity(result *MBSearchResult) {
|
|||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
|
// Add library bonus to artist popularity — same bonus as the
|
||||||
|
// fast path (boostWithIndexPopularity via GetPopularityBatch).
|
||||||
|
if artistPop != nil {
|
||||||
|
libraryMBIDs := e.libMBID.CheckMBIDs(artistMBIDs)
|
||||||
|
|
||||||
|
for mbid, entityType := range libraryMBIDs {
|
||||||
|
if entityType == "artist" {
|
||||||
|
artistPop[mbid] += 10_000_000 //nolint:mnd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Rerank each entity type.
|
// Rerank each entity type.
|
||||||
rerankArtists(result.Artists, artistPop)
|
rerankArtists(result.Artists, artistPop)
|
||||||
rerankRecordings(result.Recordings, recordingPop)
|
rerankRecordings(result.Recordings, recordingPop)
|
||||||
@@ -1078,42 +1115,36 @@ func (e *Service) boostNameMatches(query string, result *MBSearchResult) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Boost artists whose name contains the full query.
|
// Apply tier bonus/penalty to artist scores. This replaces
|
||||||
|
// the hard tier sort — tiers are now additive adjustments to
|
||||||
|
// the blended score, so a sufficiently popular near-match can
|
||||||
|
// overcome an unpopular exact match.
|
||||||
if len(result.Artists) > 1 {
|
if len(result.Artists) > 1 {
|
||||||
sort.SliceStable(result.Artists, func(i, j int) bool {
|
for i := range result.Artists {
|
||||||
iMatch := nameMatchTier(q, strings.ToLower(result.Artists[i].Name))
|
tier := nameMatchTier(q, strings.ToLower(result.Artists[i].Name))
|
||||||
jMatch := nameMatchTier(q, strings.ToLower(result.Artists[j].Name))
|
result.Artists[i].Score += tierBonus[tier]
|
||||||
|
}
|
||||||
|
|
||||||
return iMatch < jMatch
|
sort.SliceStable(result.Artists, func(i, j int) bool {
|
||||||
|
return result.Artists[i].Score > result.Artists[j].Score
|
||||||
})
|
})
|
||||||
|
|
||||||
// For same-named artists in tier 0, resolve ordering via
|
// For same-named artists in tier 0, resolve ordering via
|
||||||
// a targeted LB popularity lookup. This handles the case
|
// a targeted LB popularity lookup.
|
||||||
// where multiple artists share a name (e.g. "The Teenagers"
|
|
||||||
// US vs FR) and the index has no popularity for either.
|
|
||||||
e.disambiguateSameNameArtists(q, result.Artists)
|
e.disambiguateSameNameArtists(q, result.Artists)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Boost release groups whose title or artist credit contains the query.
|
// Apply tier bonus/penalty to release group scores.
|
||||||
if len(result.ReleaseGroups) > 1 {
|
if len(result.ReleaseGroups) > 1 {
|
||||||
sort.SliceStable(result.ReleaseGroups, func(i, j int) bool {
|
for i := range result.ReleaseGroups {
|
||||||
iMatch := rgMatchTier(q,
|
tier := rgMatchTier(q,
|
||||||
strings.ToLower(result.ReleaseGroups[i].Title),
|
strings.ToLower(result.ReleaseGroups[i].Title),
|
||||||
strings.ToLower(result.ReleaseGroups[i].ArtistCredit))
|
strings.ToLower(result.ReleaseGroups[i].ArtistCredit))
|
||||||
jMatch := rgMatchTier(q,
|
result.ReleaseGroups[i].Score += rgTierBonus[tier]
|
||||||
strings.ToLower(result.ReleaseGroups[j].Title),
|
}
|
||||||
strings.ToLower(result.ReleaseGroups[j].ArtistCredit))
|
|
||||||
|
|
||||||
if iMatch != jMatch {
|
sort.SliceStable(result.ReleaseGroups, func(i, j int) bool {
|
||||||
return iMatch < jMatch
|
return result.ReleaseGroups[i].Score > result.ReleaseGroups[j].Score
|
||||||
}
|
|
||||||
|
|
||||||
// Within same tier, prefer higher blended score.
|
|
||||||
if result.ReleaseGroups[i].Score != result.ReleaseGroups[j].Score {
|
|
||||||
return result.ReleaseGroups[i].Score > result.ReleaseGroups[j].Score
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user