2e7b81fdfe
For-You silently vanished after ~7 days of not listening (seed query required a non-skip play in the last 7 days) and capped at ~40 on self-hosted libraries with no ListenBrainz similarity data. - PickTopPlayedTracksForUser: tiered seed — last 30d top plays, else all-time top plays, else liked tracks. For-You only disappears now if the account has zero plays AND zero likes. - produceForYou uses deeper candidate source limits (raised random/ tag/similar K) so it reaches ~100 even with empty lb_similar / similar_artists; richer when LB enrichment is present. - Rediscover: tiered — 6-month-dormant, else ≥5-play 30-day-dormant. - On This Day: floor 60→30 days, window ±7→±10 doy; still skips cleanly (no rows → no playlist) on insufficient history. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
171 lines
6.0 KiB
SQL
171 lines
6.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
|
|
-- has drifted away from. Tiered so a young library still gets a mix:
|
|
-- tier 0 not played in the last 6 months (true rediscovery)
|
|
-- tier 1 (only if tier 0 empty) not played in the last 30 days
|
|
-- Ordered by historical affection. $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
|
|
),
|
|
deep AS (
|
|
SELECT t.id, t.album_id, t.artist_id, s.c, 0 AS tier
|
|
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
|
|
)
|
|
),
|
|
shallow AS (
|
|
SELECT t.id, t.album_id, t.artist_id, s.c, 1 AS tier
|
|
FROM tracks t
|
|
JOIN stats s ON s.track_id = t.id
|
|
WHERE s.c >= 5
|
|
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
|
|
)
|
|
)
|
|
SELECT id, album_id, artist_id
|
|
FROM (
|
|
SELECT id, album_id, artist_id, c, tier FROM deep
|
|
UNION ALL
|
|
SELECT id, album_id, artist_id, c, tier FROM shallow
|
|
WHERE NOT EXISTS (SELECT 1 FROM deep)
|
|
) u
|
|
ORDER BY tier, c DESC, 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.
|
|
-- $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 ABS(EXTRACT(DOY FROM started_at) - EXTRACT(DOY FROM now())) <= 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;
|