Recommendation batch: You-might-like fallback + taste 2b + observability #96

Merged
bvandeusen merged 3 commits from dev into main 2026-06-12 01:10:10 -04:00
Owner

Server + web. Three CI-green commits since v2026.06.11.

You-might-like fallback broadened (#790)

The artists row showed only a couple of tiles because the fallback drew from
explicit artist-likes only. Broaden both fallbacks to entities you've shown
affinity for — artist fallback = explicit artist-likes ∪ artists of liked
albums ∪ artists of liked tracks; album fallback = liked albums ∪ albums of
liked tracks. Read-side, fills immediately.

Taste phase 2b — taste_overlap candidate arm (#796)

A 6th arm to the candidate union: in-library tracks by the user's top
positively-weighted taste-profile artists enter the pool (weight > 0,
deterministic order, pool-inclusion only — TasteMatch scores the fit). For-You
/ radio use it; You-might-like sets it to 0 (it wants NOT-engaged artists).

Taste phase 4 — recommendation observability (#796)

GET /api/me/recommendation-metrics: per-source plays / skips / skip-rate /
avg-completion so you can see which surfaces land and tune the taste weights. A
"Recommendation metrics" card on the Settings page renders it. (For-You /
Discover / mixes; You-might-like plays aren't source-tagged yet.)

Deferred: phase 3 (context) — design-heavy, payoff concentrated in radio; do it
later if the metrics show radio needs help.

🤖 Generated with Claude Code

Server + web. Three CI-green commits since v2026.06.11. ## You-might-like fallback broadened (#790) The artists row showed only a couple of tiles because the fallback drew from explicit artist-likes only. Broaden both fallbacks to entities you've shown affinity for — artist fallback = explicit artist-likes ∪ artists of liked albums ∪ artists of liked tracks; album fallback = liked albums ∪ albums of liked tracks. Read-side, fills immediately. ## Taste phase 2b — taste_overlap candidate arm (#796) A 6th arm to the candidate union: in-library tracks by the user's top positively-weighted taste-profile artists enter the pool (weight > 0, deterministic order, pool-inclusion only — TasteMatch scores the fit). For-You / radio use it; You-might-like sets it to 0 (it wants NOT-engaged artists). ## Taste phase 4 — recommendation observability (#796) GET /api/me/recommendation-metrics: per-source plays / skips / skip-rate / avg-completion so you can see which surfaces land and tune the taste weights. A "Recommendation metrics" card on the Settings page renders it. (For-You / Discover / mixes; You-might-like plays aren't source-tagged yet.) Deferred: phase 3 (context) — design-heavy, payoff concentrated in radio; do it later if the metrics show radio needs help. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bvandeusen added 3 commits 2026-06-12 01:10:00 -04:00
fix(recommendation): broaden You-might-like fallback to liked album/track artists (#790)
test-go / test (push) Successful in 29s
test-go / integration (push) Successful in 4m36s
c7adf2c87a
The fallback pulled artists only from explicit artist-likes (general_likes_artists),
but most users like albums and tracks far more than artists — so the artists row
still came up thin (a couple of tiles) even with a rich library, while the albums
row filled fine.

Broaden both fallbacks to "entities you've shown affinity for":
- artist fallback = explicit artist-likes ∪ artists of liked albums ∪ artists of
  liked tracks.
- album fallback = explicit album-likes ∪ albums of liked tracks.
New dedicated queries (ListYouMightLike{Artist,Album}FallbackForUser) replace the
narrow Rediscover-fallback reuse; same projection so the Go layer still converts
directly. (Aliased + fully-qualified the UNION arms — sqlc merges UNION scopes,
so unqualified user_id was ambiguous across the three like tables.)

Test: 12 liked TRACKS by distinct artists, no artist-likes → the artist row now
fills from their artists (was empty before).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(taste): phase 2b — taste_overlap candidate arm (#796)
test-go / test (push) Successful in 37s
test-go / integration (push) Successful in 4m41s
6c26ba807e
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>
feat(taste): phase 4 — recommendation observability (#796)
test-go / test (push) Successful in 33s
test-web / test (push) Successful in 41s
test-go / integration (push) Successful in 4m24s
1a7515e6ea
Per-source play outcomes so the operator can see whether each recommendation
surface is landing and tune the now-operator-tunable taste weights.

Server:
- query RecommendationSourceMetricsForUser: groups the user's play_events by
  source (system-playlist surface), reporting plays / skips / avg completion
  over a window; NULL-source (library/radio) plays excluded.
- GET /api/me/recommendation-metrics?days=30 (default 30, capped 365) →
  {window_days, sources:[{source, plays, skips, skip_rate, avg_completion}]}.
- handler test: 401 unauth; per-source aggregation + NULL-source exclusion +
  skip_rate / avg_completion math.

Web:
- lib/api/metrics.ts: query + friendly source labels.
- settings page gains a "Recommendation metrics" card (table of surface / plays
  / skip rate / avg completion), with loading/error/empty states.
- settings tests mock the new query (manual subscribe-store, hoisting-safe).

Note: You-might-like plays aren't source-tagged (it's a Home row, not a system
playlist), so this covers For-You / Discover / the mixes. Tagging YML plays
would be a client follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bvandeusen merged commit db393bbe65 into main 2026-06-12 01:10:10 -04:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bvandeusen/minstrel#96