fix: switch tier bonuses from additive to percentage-based
Additive bonuses (+12 fixed points) didn't scale with the blended
score range. Log-compressed popularity puts most scores in a narrow
80-92 band, making +12 disproportionately large.
Percentage multipliers scale naturally:
Artist: exact +15%, starts-with +8%, substring 0%, none -15%
Album: credit-exact +15%, credit-contains +10%, title-exact +5%,
title-contains 0%, none -10%
A tier-0 exact match with blended score 86 gets 86×1.15=99.
A tier-1 starts-with with blended score 92 gets 92×1.08=99.
The 4× popularity gap exactly offsets the 7% tier advantage —
proportional behavior where the boost scales with the artist's
existing score rather than being a fixed number.
This commit is contained in:
+23
-24
@@ -902,29 +902,28 @@ const (
|
|||||||
minBlendedScore = 25
|
minBlendedScore = 25
|
||||||
)
|
)
|
||||||
|
|
||||||
// tierBonus maps artist name-match tiers to additive score adjustments.
|
// tierBonus maps artist name-match tiers to percentage score multipliers.
|
||||||
// These are soft bonuses — a sufficiently popular lower-tier result can
|
// Applied as: score = score * (1 + multiplier). A popular lower-tier
|
||||||
// overcome the tier advantage. The effective gap between adjacent tiers
|
// result can overcome the tier advantage when the popularity gap is
|
||||||
// (~6 points on a 0–100 scale) requires roughly a 4–5× popularity
|
// proportionally larger than the tier difference.
|
||||||
// difference to overcome.
|
|
||||||
//
|
//
|
||||||
//nolint:gochecknoglobals
|
//nolint:gochecknoglobals
|
||||||
var tierBonus = map[int]int{
|
var tierBonus = map[int]float64{
|
||||||
0: 12, // exact match: "shannon" == "shannon"
|
0: 0.15, // exact match: +15%
|
||||||
1: 6, // starts with: "shannon" in "shannon and the clams"
|
1: 0.08, // starts with: +8%
|
||||||
2: 0, // substring: "shannon" in "del shannon"
|
2: 0.0, // substring: no change
|
||||||
3: -10, // no substring match: only individual words matched
|
3: -0.15, // no substring match: -15%
|
||||||
}
|
}
|
||||||
|
|
||||||
// rgTierBonus maps release group match tiers to additive score adjustments.
|
// rgTierBonus maps release group match tiers to percentage multipliers.
|
||||||
//
|
//
|
||||||
//nolint:gochecknoglobals
|
//nolint:gochecknoglobals
|
||||||
var rgTierBonus = map[int]int{
|
var rgTierBonus = map[int]float64{
|
||||||
0: 12, // artist credit exact match
|
0: 0.15, // artist credit exact match: +15%
|
||||||
1: 8, // artist credit contains query
|
1: 0.10, // artist credit contains query: +10%
|
||||||
2: 4, // title exact match
|
2: 0.05, // title exact match: +5%
|
||||||
3: 0, // title contains query
|
3: 0.0, // title contains query: no change
|
||||||
4: -5, // no match in either field
|
4: -0.10, // no match: -10%
|
||||||
}
|
}
|
||||||
|
|
||||||
// mbSpecialPurposeArtists is a set of MusicBrainz Special Purpose
|
// mbSpecialPurposeArtists is a set of MusicBrainz Special Purpose
|
||||||
@@ -1115,14 +1114,14 @@ func (e *Service) boostNameMatches(query string, result *MBSearchResult) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply tier bonus/penalty to artist scores. This replaces
|
// Apply tier multiplier to artist scores. Percentage-based so the
|
||||||
// the hard tier sort — tiers are now additive adjustments to
|
// boost scales with the artist's existing score — a popular
|
||||||
// the blended score, so a sufficiently popular near-match can
|
// near-match can overcome an unpopular exact match when the
|
||||||
// overcome an unpopular exact match.
|
// popularity gap is proportionally larger than the tier difference.
|
||||||
if len(result.Artists) > 1 {
|
if len(result.Artists) > 1 {
|
||||||
for i := range result.Artists {
|
for i := range result.Artists {
|
||||||
tier := nameMatchTier(q, strings.ToLower(result.Artists[i].Name))
|
tier := nameMatchTier(q, strings.ToLower(result.Artists[i].Name))
|
||||||
result.Artists[i].Score += tierBonus[tier]
|
result.Artists[i].Score = int(float64(result.Artists[i].Score) * (1.0 + tierBonus[tier]))
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.SliceStable(result.Artists, func(i, j int) bool {
|
sort.SliceStable(result.Artists, func(i, j int) bool {
|
||||||
@@ -1134,13 +1133,13 @@ func (e *Service) boostNameMatches(query string, result *MBSearchResult) {
|
|||||||
e.disambiguateSameNameArtists(q, result.Artists)
|
e.disambiguateSameNameArtists(q, result.Artists)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply tier bonus/penalty to release group scores.
|
// Apply tier multiplier to release group scores.
|
||||||
if len(result.ReleaseGroups) > 1 {
|
if len(result.ReleaseGroups) > 1 {
|
||||||
for i := range result.ReleaseGroups {
|
for i := range result.ReleaseGroups {
|
||||||
tier := 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))
|
||||||
result.ReleaseGroups[i].Score += rgTierBonus[tier]
|
result.ReleaseGroups[i].Score = int(float64(result.ReleaseGroups[i].Score) * (1.0 + rgTierBonus[tier]))
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.SliceStable(result.ReleaseGroups, func(i, j int) bool {
|
sort.SliceStable(result.ReleaseGroups, func(i, j int) bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user