feat: personalized search ranking — library > similar > neither

Migration 15: add in_library and is_similar INTEGER columns to
explore_index. Backfills in_library from existing library MBIDs.

Search index FTS5 query now includes personalization in scoring:
  ORDER BY bm25(...) - (ln(pop+1) * 1.5)
           - (in_library * 3.0) - (is_similar * 1.5)

For equal text+popularity scores:
  - Library artist beats unrelated by 3.0 points
  - Similar artist beats unrelated by 1.5 points
  - Library > Similar > Neither

Tier 3 (library) entries get in_library=1 via markInLibrary.
Tier 4 (similar) entries get is_similar=1 via markSimilar.

MB result reranking (boostWithIndexPopularity) adds a 10M
popularity bonus for library artists, ensuring they always
rank above non-library artists with equal text relevance.
This commit is contained in:
2026-03-28 12:50:57 -04:00
parent 15a0b94348
commit 7f0c6d362a
3 changed files with 153 additions and 7 deletions
+16 -2
View File
@@ -737,10 +737,18 @@ const (
// is ready.
func (e *Service) boostWithIndexPopularity(result *MBSearchResult) {
// Look up popularity for all artist MBIDs.
// Give a large bonus to library artists so they rank first.
artistPop := make(map[string]int, len(result.Artists))
for _, a := range result.Artists {
if pop := e.index.GetPopularity(a.MBID); pop > 0 {
pop := e.index.GetPopularity(a.MBID)
// Library artists get a massive popularity bonus.
if e.index.IsInLibrary(a.MBID) {
pop += 10_000_000 //nolint:mnd
}
if pop > 0 {
artistPop[a.MBID] = pop
}
}
@@ -751,7 +759,13 @@ func (e *Service) boostWithIndexPopularity(result *MBSearchResult) {
rgPop := make(map[string]int, len(result.ReleaseGroups))
for _, rg := range result.ReleaseGroups {
if pop := e.index.GetPopularity(rg.MBID); pop > 0 {
pop := e.index.GetPopularity(rg.MBID)
if e.index.IsInLibrary(rg.MBID) {
pop += 10_000_000 //nolint:mnd
}
if pop > 0 {
rgPop[rg.MBID] = pop
}
}