2a re-ranks the existing pool by TasteMatch; this ensures taste-relevant tracks
ARE in the pool. Adds a 6th arm to LoadRadioCandidatesV2: in-library tracks by
the user's top positively-weighted taste-profile artists ($10 K, weight > 0,
deterministic weight-DESC,id order so it doesn't reintroduce same-day
nondeterminism). Pool-inclusion only (sim_score 0) — TasteMatch already scores
the fit. Empty for cold-start users (no profile).
- CandidateSourceLimits.TasteOverlap; default 20 (radio), 80 for For-You via
systemForYouSourceLimits.
- You-might-like deliberately sets TasteOverlap=0: it surfaces NOT-actively-
engaged artists, so flooding its pool with top-taste (mostly already-played)
artists would just feed the read-time dedup.
- Test: positive-weight artist's track enters via the arm; negative-weight one
is excluded (weight > 0). Existing pool tests unaffected (no profile seeded).
Deferred within 2b: profile-seeded For-You — marginal given the arm + TasteMatch
already inject taste broadly (top-played seed ≈ top-taste artist).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The profile built in phase 1 now changes what gets surfaced. Adds a TasteMatch
term to the weighted-shuffle score so candidates are re-ranked by their fit to
the user's learned taste (positive draws toward it; negative reflects passive
avoidance; 0 at cold start).
- recommendation/score.go: ScoringInputs.TasteMatchScore ([-1,+1]) +
ScoringWeights.TasteWeight + the term in Score.
- recommendation/taste.go: LoadTasteProfile reads the taste_profile_* tables;
TasteProfile.Match blends the candidate's artist weight (0.7) and avg genre-tag
weight (0.3), each tanh-squashed by a fixed scale so one outlier artist can't
compress the rest. Unknown artist/tags and empty profiles → 0 (neutral).
- candidates.go: both candidate loaders set TasteMatchScore per candidate, so
every Score caller (system playlists incl. You-might-like, radio) becomes
taste-aware automatically.
- weights: systemMixWeights.TasteWeight = 1.5 (daily mixes are the primary
taste surface); config.RecommendationConfig gains taste_weight (default 1.0,
lighter — radio is seed-directed) wired into the radio handler.
- tests: pure (Match curve incl. saturation/clamp/empty-neutral, Score term
add+subtract) + DB round-trip (seed taste rows → Match positive). All green
vs real Postgres; existing playlist/radio tests unaffected (empty profile →
zero taste effect).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a 6th argument (currentVector SessionVector) to LoadCandidates,
bulk-fetches the user's active contextual_likes in one query, and sets
ContextualMatchScore on each candidate's ScoringInputs via max-similarity
over that candidate's like vectors. Adds helper + 6 integration tests
covering no-likes, single match, multi-like max, soft-delete filtering,
seed-like filtering, and seed-current short-circuit. Adds contextual_likes
to the TRUNCATE list in testPool to prevent cross-test leakage.
internal/api/radio.go is intentionally left broken (Task 6 fixes it).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>