Files
minstrel/internal/db/queries/system_mixes.sql
T
bvandeusen c7ee0871a5 feat(playlists): #411 R3 — five discovery mixes (#419-423)
Each is one candidate query + one registry entry; zero client work
(R2 made tiles/refresh/shuffle generic, all are singleton kinds so
web's server `refreshable` flag and Flutter's derived getter both
light up automatically).

- deep_cuts (#419): <=2-play tracks from liked / heavily-played
  artists; diversity-capped.
- rediscover (#420): >=5-play tracks not heard in 6 months, by
  historical affection.
- new_for_you (#421): tracks from albums added <=30d whose artist
  the user likes/plays; album-coherent (no cap).
- on_this_day (#422): tracks played within ±7 day-of-year in prior
  windows (>60d ago), weighted by play count.
- first_listens (#423): never-played albums, tiered liked-artist →
  played-artist → rest; album-coherent.

system_mixes.go producers mirror the Discover model (SQL gives the
ranking; finishMix caps+truncates to 100 to match For-You/Discover
shuffle depth; album-coherent mixes skip the cap). Builder query
failure is non-fatal (logged, yields no playlist) like Discover.
Existing system_test existence checks are unaffected.

Closes the #411 system-playlists-v2 umbrella's new-types thread.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 13:55:27 -04:00

146 lines
5.0 KiB
SQL

-- Candidate queries for the discovery system-playlist mixes
-- (#419-423). Each returns (id, album_id, artist_id) rows; the Go
-- producer caps + truncates and converts to rankedCandidate. Daily-
-- deterministic md5(id||dateStr) ordering (where used) keeps a mix
-- stable within a day while rotating across days; shuffle-on-play
-- gives per-play variety on top.
-- name: ListDeepCutsTracks :many
-- #419 Deep Cuts: low-play tracks (<=2 plays) from artists the user
-- has liked OR played heavily (>=5 non-skip plays across the artist).
-- $1 user_id, $2 date string.
WITH affinity_artists AS (
SELECT artist_id FROM general_likes_artists WHERE user_id = $1
UNION
SELECT t.artist_id
FROM play_events pe JOIN tracks t ON t.id = pe.track_id
WHERE pe.user_id = $1 AND pe.was_skipped = false
GROUP BY t.artist_id
HAVING COUNT(*) >= 5
),
play_counts AS (
SELECT track_id, COUNT(*) AS c
FROM play_events
WHERE user_id = $1 AND was_skipped = false
GROUP BY track_id
)
SELECT t.id, t.album_id, t.artist_id
FROM tracks t
JOIN affinity_artists aa ON aa.artist_id = t.artist_id
LEFT JOIN play_counts pc ON pc.track_id = t.id
WHERE COALESCE(pc.c, 0) <= 2
AND NOT EXISTS (
SELECT 1 FROM lidarr_quarantine q
WHERE q.user_id = $1 AND q.track_id = t.id
)
ORDER BY md5(t.id::text || $2::text)
LIMIT 200;
-- name: ListRediscoverTracks :many
-- #420 Rediscover: tracks the user played a lot (>=5 non-skip) but
-- not in the last 6 months. Ordered by historical affection.
-- $1 user_id.
WITH stats AS (
SELECT track_id, COUNT(*) AS c, MAX(started_at) AS last_at
FROM play_events
WHERE user_id = $1 AND was_skipped = false
GROUP BY track_id
)
SELECT t.id, t.album_id, t.artist_id
FROM tracks t
JOIN stats s ON s.track_id = t.id
WHERE s.c >= 5
AND s.last_at <= now() - interval '6 months'
AND NOT EXISTS (
SELECT 1 FROM lidarr_quarantine q
WHERE q.user_id = $1 AND q.track_id = t.id
)
ORDER BY s.c DESC, t.id
LIMIT 200;
-- name: ListNewForYouTracks :many
-- #421 New for you: tracks from albums added in the last 30 days
-- whose artist the user has liked OR played (>=3 non-skip). Album-
-- coherent (newest album first, then disc/track) — the producer does
-- NOT diversity-cap these; they're meant as whole-album discovery.
-- $1 user_id.
WITH affinity_artists AS (
SELECT artist_id FROM general_likes_artists WHERE user_id = $1
UNION
SELECT t.artist_id
FROM play_events pe JOIN tracks t ON t.id = pe.track_id
WHERE pe.user_id = $1 AND pe.was_skipped = false
GROUP BY t.artist_id
HAVING COUNT(*) >= 3
)
SELECT t.id, t.album_id, t.artist_id
FROM tracks t
JOIN albums al ON al.id = t.album_id
JOIN affinity_artists aa ON aa.artist_id = al.artist_id
WHERE al.created_at >= now() - interval '30 days'
AND NOT EXISTS (
SELECT 1 FROM lidarr_quarantine q
WHERE q.user_id = $1 AND q.track_id = t.id
)
ORDER BY al.created_at DESC, t.disc_number NULLS FIRST, t.track_number NULLS FIRST
LIMIT 200;
-- name: ListOnThisDayTracks :many
-- #422 On This Day: tracks the user played around this calendar date
-- (±7 day-of-year) in the past, excluding the very recent (>60 days
-- ago) so it's nostalgic, not just "last week". Weighted by how much
-- they were played in those windows.
-- $1 user_id, $2 date string.
WITH windowed AS (
SELECT track_id, COUNT(*) AS c
FROM play_events
WHERE user_id = $1 AND was_skipped = false
AND started_at < now() - interval '60 days'
AND ABS(EXTRACT(DOY FROM started_at) - EXTRACT(DOY FROM now())) <= 7
GROUP BY track_id
)
SELECT t.id, t.album_id, t.artist_id
FROM tracks t
JOIN windowed w ON w.track_id = t.id
WHERE NOT EXISTS (
SELECT 1 FROM lidarr_quarantine q
WHERE q.user_id = $1 AND q.track_id = t.id
)
ORDER BY w.c DESC, md5(t.id::text || $2::text)
LIMIT 200;
-- name: ListFirstListensTracks :many
-- #423 First Listens: albums the user has never played any track of.
-- Tiered: liked-artist albums first, then played-artist albums, then
-- the rest — album-coherent within each tier. Not diversity-capped
-- (whole-album discovery). $1 user_id.
WITH heard_albums AS (
SELECT DISTINCT t.album_id
FROM play_events pe JOIN tracks t ON t.id = pe.track_id
WHERE pe.user_id = $1
),
played_artists AS (
SELECT DISTINCT t.artist_id
FROM play_events pe JOIN tracks t ON t.id = pe.track_id
WHERE pe.user_id = $1
)
SELECT t.id, t.album_id, t.artist_id
FROM tracks t
JOIN albums al ON al.id = t.album_id
WHERE NOT EXISTS (SELECT 1 FROM heard_albums h WHERE h.album_id = al.id)
AND NOT EXISTS (
SELECT 1 FROM lidarr_quarantine q
WHERE q.user_id = $1 AND q.track_id = t.id
)
ORDER BY
(CASE
WHEN EXISTS (
SELECT 1 FROM general_likes_artists gla
WHERE gla.user_id = $1 AND gla.artist_id = al.artist_id
) THEN 0
WHEN al.artist_id IN (SELECT artist_id FROM played_artists) THEN 1
ELSE 2
END),
al.id, t.disc_number NULLS FIRST, t.track_number NULLS FIRST
LIMIT 300;