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>
Pure aggregation per spec §6: Seed flag (true when prior < 3),
deduplicated Artists ordered by first appearance, Tags bag-of-counts
from tracks.genre (empty genres skipped), RecentTrackIDs preserving
input order. JSON round-trip verified.
ListRecentSessionTracks + UpdatePlayEventVector for the vector capture
path inside RecordPlayStarted. LikeTrack switches to :execrows so the
contextual-likes capture can detect insert vs already-exists. Existing
callers updated to ignore the count for now; later tasks consume it.
Composes Score over a candidate slice, sorts descending, truncates.
Pure — no IO. Liked-rank-higher and high-skip-ranks-last behaviors
are stable across RNG seeds (jitter band is smaller than LikeBoost
and SkipPenalty).
Implements spec §6 weighted-shuffle scoring without the
contextual_match_score term (sub-plan #3 adds it). Pure Go, no DB
dependency; injectable RNG for deterministic tests. Coverage 100%
on score.go via the boundary tests.