feat(taste): fold enriched folksonomy tags into the profile (#1490 Step 3)
The taste recompute's tag facet now unions the cached track_tags (MusicBrainz/Last.fm folksonomy tags) alongside raw ID3 genre, so a coarse "Rock" gains "post-punk / shoegaze / melancholic". - taste_profile.sql: ListPlayEngagementInputsForUser + ListLikedTrackTasteInputsForUser now return track_id to key the enriched-tag lookup. - accumulate(): for each play, fold its track's enriched tags weighted by engagement × tag.weight × EnrichedTagScale; for each liked track, by the tag-like bonus × tag.weight × scale. A track with no cached tags contributes genre only (graceful). - New Config.EnrichedTagScale (default 0.5) — enriched tags augment the ID3 signal without swamping it; 0 = genre-only. Flows through recsettings.TasteConfig() (starts from DefaultConfig). Promoting it into the admin tuning lab is a small follow-up. Unit-tested the pure foldEnrichedTags helper (overlap accumulation + scale=0 disable). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,10 +4,39 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
||||
)
|
||||
|
||||
func strLess(a, b string) bool { return a < b }
|
||||
|
||||
func TestFoldEnrichedTags(t *testing.T) {
|
||||
tagW := map[string]float64{"rock": 1.0} // pre-existing ID3 genre weight
|
||||
tags := []dbq.TrackTag{
|
||||
{Tag: "shoegaze", Weight: 1.0},
|
||||
{Tag: "melancholic", Weight: 0.5},
|
||||
{Tag: "rock", Weight: 0.4}, // overlaps genre → accumulates
|
||||
}
|
||||
// base=2 (engagement), scale=0.5 → contribution = 2 * weight * 0.5 = weight.
|
||||
foldEnrichedTags(tagW, tags, 2.0, 0.5)
|
||||
if got := tagW["shoegaze"]; got != 1.0 {
|
||||
t.Errorf("shoegaze = %v, want 1.0", got)
|
||||
}
|
||||
if got := tagW["melancholic"]; got != 0.5 {
|
||||
t.Errorf("melancholic = %v, want 0.5", got)
|
||||
}
|
||||
if got := tagW["rock"]; got != 1.4 { // 1.0 genre + 2*0.4*0.5
|
||||
t.Errorf("rock = %v, want 1.4", got)
|
||||
}
|
||||
|
||||
// scale=0 disables the enriched contribution entirely.
|
||||
base := map[string]float64{"rock": 1.0}
|
||||
foldEnrichedTags(base, tags, 2.0, 0)
|
||||
if len(base) != 1 || base["rock"] != 1.0 {
|
||||
t.Errorf("scale=0 should leave tagW untouched; got %v", base)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRankWeighted_DropsEpsilonAndSortsDesc(t *testing.T) {
|
||||
m := map[string]float64{"a": 3.0, "b": 1.0, "tiny": 0.01, "d": -2.0}
|
||||
got := rankWeighted(m, 0.05, 10, 10, strLess)
|
||||
|
||||
Reference in New Issue
Block a user