Files
minstrel/internal/db/queries/system_mixes.sql
T
bvandeusen 2be07ef271
test-go / test (push) Successful in 29s
test-go / integration (push) Successful in 4m36s
fix(mixes): close three intent gaps found in the system-playlists audit
Three discovery-mix defects from the intent audit (Scribe note #1254),
all sharing the same root pattern — skips treated as non-events:

- Deep Cuts (#1257): eligibility counted only unskipped plays, so a
  track skipped twice with zero completed listens read as "barely
  heard" and kept being re-offered. Tracks with >=2 skips no longer
  qualify; a single accidental skip doesn't banish.

- Rediscover (#1258): a skip on a rediscover-sourced play — the user
  explicitly declining the resurfacing invitation — changed nothing,
  so declined tracks re-qualified the next day forever. Such tracks
  now sit out 90 days.

- On This Day (#1256): day-of-year distance used plain ABS, so
  Dec 28 vs Jan 3 read as 359 days apart and the window silently
  gutted itself for ~3 weeks around every New Year. Now circular
  (LEAST(d, 365-d)), anchored on the build-date parameter instead of
  now() so it's testable and consistent with the mix's daily
  determinism.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TsF3cNoKrqCYsU78cXC8U6
2026-07-03 08:47:01 -04:00

195 lines
7.6 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).
-- Tracks the user has skipped twice or more don't qualify (#1257):
-- eligibility used to count only unskipped plays, so the most actively
-- rejected tracks read as "barely heard" and kept being re-offered.
-- Threshold 2 so a single accidental skip doesn't banish a track;
-- passive-signal only (no dislike UI, rule #101).
-- $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
),
skip_counts AS (
SELECT track_id, COUNT(*) AS c
FROM play_events
WHERE user_id = $1 AND was_skipped = true
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
LEFT JOIN skip_counts sc ON sc.track_id = t.id
WHERE COALESCE(pc.c, 0) <= 2
AND COALESCE(sc.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 but has drifted away
-- from. One blended pool (issue #1246: the old two-tier UNION was
-- all-or-nothing — a single ">=6 months cold" row suppressed the whole
-- ">=30 days" tier, which is how a one-song playlist shipped; the 6mo
-- tier was also a strict subset of the 30d tier). Eligibility is >=3
-- non-skip plays and >=30 days cold; the bar sits at 3 rather than 5
-- so a weeks-old library still fields a pool — on young histories the
-- >=5-play tracks are precisely the ones still in rotation. Ordering
-- prefers true rediscoveries (>=6 months cold), then strong affection
-- (>=5 plays), then raw play count; the producer's minimum floor
-- decides whether the pool is big enough to ship at all. $1 user_id.
WITH stats AS (
SELECT pe.track_id, COUNT(*) AS c, MAX(pe.started_at) AS last_at
FROM play_events pe
WHERE pe.user_id = $1 AND pe.was_skipped = false
GROUP BY pe.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 >= 3
AND s.last_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
)
-- Declined-resurfacing cooldown (#1258): a skip on a
-- rediscover-sourced play is the user explicitly saying "I've moved
-- on" to the exact invitation this mix extends — such tracks sit
-- out ~90 days instead of re-qualifying the next day. Passive
-- signal only (rule #101).
AND NOT EXISTS (
SELECT 1 FROM play_events rp
WHERE rp.user_id = $1
AND rp.track_id = t.id
AND rp.source = 'rediscover'
AND rp.was_skipped = true
AND rp.started_at > now() - interval '90 days'
)
ORDER BY (s.last_at <= now() - interval '6 months') DESC,
(s.c >= 5) DESC,
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
-- (±10 day-of-year) in the past, excluding the very recent (>30 days
-- ago) so it's nostalgic, not just "last week". Weighted by how much
-- they were played in those windows. Floor relaxed from 60→30 days
-- and window ±7→±10 so it surfaces on a months-old library instead
-- of needing a full year of history; still skips cleanly (no rows →
-- no playlist) when there's no qualifying history yet.
-- Day-of-year distance is circular (#1256): plain ABS made Dec 28 vs
-- Jan 3 read as 359 days apart, silently gutting the window for ~3
-- weeks around every New Year — precisely when holiday nostalgia is
-- the point. LEAST(d, 365-d) wraps the boundary; leap-year drift of
-- ±1 is absorbed by the ±10 window. The build date ($2, already here
-- for the md5 rotation) anchors "today" instead of now() so the
-- window is testable and consistent with the mix's daily determinism.
-- $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 '30 days'
AND LEAST(
ABS(EXTRACT(DOY FROM started_at) - EXTRACT(DOY FROM $2::date)),
365 - ABS(EXTRACT(DOY FROM started_at) - EXTRACT(DOY FROM $2::date))
) <= 10
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;