fix: revert to minBlendedScore=25, add separate zero-popularity filter

minBlendedScore=50 was too aggressive on the fast path where
non-indexed MB results get zero popularity (blended score ~35).
This killed all MB results that weren't in the explore index,
leaving only library/index artists.

New approach: two-tier filtering in filterAndCap:
1. minBlendedScore=25 — baseline filter for all artists
2. minZeroPopScore=50 — stricter filter for artists with NO LB
   popularity data (HasPopularity=false)

HasPopularity is set by both reranking paths when an artist has
any listen count in the index or LB API. Shannon Hale (zero
listens, score 37) gets filtered by the zero-pop threshold.
Regular MB results that happen to not be in the index but do have
LB popularity pass the normal threshold.
This commit is contained in:
2026-03-30 03:06:09 -04:00
parent 071e0cb490
commit fc2a50e853
2 changed files with 36 additions and 7 deletions
+35 -7
View File
@@ -827,14 +827,28 @@ func scalePopularity(listens int) int {
// filterAndCap removes low-scoring results, special-purpose
// MusicBrainz artists, and limits each entity slice to maxResults.
func filterAndCap(result *MBSearchResult) {
// Filter artists by minimum blended score and remove SPAs.
// Filter artists: remove SPAs and zero-popularity artists
// that aren't exact name matches.
if len(result.Artists) > 0 {
filtered := result.Artists[:0]
for _, a := range result.Artists {
if a.Score >= minBlendedScore && !mbSpecialPurposeArtists[a.MBID] {
filtered = append(filtered, a)
if mbSpecialPurposeArtists[a.MBID] {
continue
}
if a.Score < minBlendedScore {
continue
}
// Keep artists with popularity data. Also keep artists
// without popularity if they have a high enough score
// (likely exact or close name matches).
if !a.HasPopularity && a.Score < minZeroPopScore {
continue
}
filtered = append(filtered, a)
}
result.Artists = filtered
@@ -899,9 +913,13 @@ const (
// minBlendedScore is the floor for artists and recordings
// after popularity reranking and tier adjustment (0100 scale).
// At 50, artists with zero LB popularity and partial name
// matches are filtered out.
minBlendedScore = 50
minBlendedScore = 25
// minZeroPopScore is the floor for artists with zero LB
// popularity data. Higher than minBlendedScore so that
// obscure artists without any listening history are filtered
// unless they're a very strong name match (exact or near-exact).
minZeroPopScore = 50
)
// tierBonus maps artist name-match tiers to percentage score multipliers.
@@ -984,9 +1002,10 @@ func (e *Service) boostWithIndexPopularity(result *MBSearchResult) {
// Build per-entity maps from the batch result.
artistPop := make(map[string]int, len(result.Artists))
for _, a := range result.Artists {
for i, a := range result.Artists {
if pop, ok := popMap[a.MBID]; ok {
artistPop[a.MBID] = pop
result.Artists[i].HasPopularity = true
}
}
@@ -1095,6 +1114,15 @@ func (e *Service) boostWithPopularity(result *MBSearchResult) {
}
}
// Mark artists that have popularity data.
if artistPop != nil {
for i := range result.Artists {
if _, ok := artistPop[result.Artists[i].MBID]; ok {
result.Artists[i].HasPopularity = true
}
}
}
// Rerank each entity type.
rerankArtists(result.Artists, artistPop)
rerankRecordings(result.Recordings, recordingPop)
+1
View File
@@ -26,6 +26,7 @@ type MBArtist struct {
Disambiguation string `json:"disambiguation"`
Score int `json:"score"`
OriginalScore int `json:"-"` // MB search relevance, preserved across reranking
HasPopularity bool `json:"-"` // true if LB/index had listen data for this artist
}
// MBReleaseGroup is a Wails-friendly projection of a MusicBrainz