// Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.31.1 // source: system_mixes.sql package dbq import ( "context" "github.com/jackc/pgx/v5/pgtype" ) const listDeepCutsTracks = `-- name: ListDeepCutsTracks :many 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 ` type ListDeepCutsTracksParams struct { UserID pgtype.UUID Column2 string } type ListDeepCutsTracksRow struct { ID pgtype.UUID AlbumID pgtype.UUID ArtistID pgtype.UUID } // 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. // #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. func (q *Queries) ListDeepCutsTracks(ctx context.Context, arg ListDeepCutsTracksParams) ([]ListDeepCutsTracksRow, error) { rows, err := q.db.Query(ctx, listDeepCutsTracks, arg.UserID, arg.Column2) if err != nil { return nil, err } defer rows.Close() var items []ListDeepCutsTracksRow for rows.Next() { var i ListDeepCutsTracksRow if err := rows.Scan(&i.ID, &i.AlbumID, &i.ArtistID); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listFirstListensTracks = `-- name: ListFirstListensTracks :many WITH attempted AS ( SELECT DISTINCT pe.track_id FROM play_events pe WHERE pe.user_id = $1 AND pe.duration_played_ms >= 30000 ), album_attempts AS ( SELECT t.album_id, COUNT(a.track_id) AS attempted_count, COUNT(*) AS track_count FROM tracks t LEFT JOIN attempted a ON a.track_id = t.id GROUP BY t.album_id ), attempted_artists AS ( SELECT DISTINCT t.artist_id FROM attempted a JOIN tracks t ON t.id = a.track_id ), albums_tiered AS ( SELECT aa.album_id, CASE WHEN aa.attempted_count = 0 THEN 1 WHEN aa.attempted_count::float / GREATEST(aa.track_count, 1) <= 0.25 THEN 2 END AS tier FROM album_attempts aa ) SELECT t.id, t.album_id, t.artist_id, alt.tier::int AS tier FROM tracks t JOIN albums al ON al.id = t.album_id JOIN albums_tiered alt ON alt.album_id = al.id WHERE alt.tier IS NOT NULL AND NOT EXISTS (SELECT 1 FROM attempted a WHERE a.track_id = t.id) AND NOT EXISTS ( SELECT 1 FROM lidarr_quarantine q WHERE q.user_id = $1 AND q.track_id = t.id ) ORDER BY alt.tier, (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 attempted_artists) THEN 1 ELSE 2 END), al.id, t.disc_number NULLS FIRST, t.track_number NULLS FIRST LIMIT 300 ` type ListFirstListensTracksRow struct { ID pgtype.UUID AlbumID pgtype.UUID ArtistID pgtype.UUID Tier int32 } // #423 First Listens: songs the user has never heard or even // attempted. "Attempted" is track-level with a >=30s listen threshold // (#1268, operator decision 2026-07-03) — the old version disqualified // a whole album on ANY play_event, so a 2-second accidental brush // banished it permanently. // // tier 1 the exact desire: albums with ZERO attempted tracks // tier 2 step back: barely-attempted albums (<=25% of tracks // reached 30s) — brushed, never explored. The already- // attempted tracks themselves are excluded; they're not // first listens. // // Within each tier, affinity-ordered as before: liked artist, then // attempted-played artist (>=30s — skip-only contact isn't trust), // then the rest; album-coherent. The tier lands on // playlist_tracks.pick_kind via the producer. $1 user_id. func (q *Queries) ListFirstListensTracks(ctx context.Context, userID pgtype.UUID) ([]ListFirstListensTracksRow, error) { rows, err := q.db.Query(ctx, listFirstListensTracks, userID) if err != nil { return nil, err } defer rows.Close() var items []ListFirstListensTracksRow for rows.Next() { var i ListFirstListensTracksRow if err := rows.Scan( &i.ID, &i.AlbumID, &i.ArtistID, &i.Tier, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listNewForYouTracks = `-- name: ListNewForYouTracks :many WITH attempted AS ( SELECT DISTINCT pe.track_id FROM play_events pe WHERE pe.user_id = $1 AND pe.duration_played_ms >= 30000 ), consumed_albums AS ( SELECT DISTINCT t.album_id FROM attempted a JOIN tracks t ON t.id = a.track_id ), affinity_artists AS ( SELECT gla.artist_id FROM general_likes_artists gla WHERE gla.user_id = $1 UNION SELECT t.artist_id FROM attempted a JOIN tracks t ON t.id = a.track_id GROUP BY t.artist_id HAVING COUNT(*) >= 3 ), albums_tiered AS ( SELECT al.id AS album_id, al.created_at, CASE WHEN al.created_at >= now() - interval '30 days' AND al.artist_id IN (SELECT artist_id FROM affinity_artists) THEN 1 WHEN al.artist_id IN (SELECT artist_id FROM affinity_artists) THEN 2 ELSE 3 END AS tier FROM albums al WHERE al.created_at >= now() - interval '90 days' AND al.id NOT IN (SELECT album_id FROM consumed_albums) ) SELECT t.id, t.album_id, t.artist_id, alt.tier::int AS tier FROM tracks t JOIN albums_tiered alt ON alt.album_id = t.album_id WHERE NOT EXISTS ( SELECT 1 FROM lidarr_quarantine q WHERE q.user_id = $1 AND q.track_id = t.id ) ORDER BY alt.tier, alt.created_at DESC, t.album_id, t.disc_number NULLS FIRST, t.track_number NULLS FIRST LIMIT 300 ` type ListNewForYouTracksRow struct { ID pgtype.UUID AlbumID pgtype.UUID ArtistID pgtype.UUID Tier int32 } // #421 New for you, tiered per project rule #131 (#1267). The old // single hard rule (added <30d AND direct affinity) had two failure // modes with one root: no notion of consumption — the album you // devoured in week one crowded the mix for three more weeks, and // after a quiet month nothing qualified at all. // // "Consumed" = any track attempted for >=30s (the First Listens // threshold, #1268): once you've meaningfully engaged, the album is // no longer news. Applies to every tier — played albums leave the // mix the next build. // // tier 1 the exact desire: unconsumed albums added <30d by // direct-affinity artists (liked, or >=3 attempted plays) // tier 2 step back a little: unconsumed affinity albums from the // wider 30-90d window — added while you weren't looking // tier 3 step back more: any unconsumed album added <90d // regardless of affinity, newest first // // Album-coherent within tiers (newest album first, then disc/track); // the producer fills top-down and rotates within tier blocks. The // tier lands on playlist_tracks.pick_kind so metrics can price what // each step-back trades away. $1 user_id. func (q *Queries) ListNewForYouTracks(ctx context.Context, userID pgtype.UUID) ([]ListNewForYouTracksRow, error) { rows, err := q.db.Query(ctx, listNewForYouTracks, userID) if err != nil { return nil, err } defer rows.Close() var items []ListNewForYouTracksRow for rows.Next() { var i ListNewForYouTracksRow if err := rows.Scan( &i.ID, &i.AlbumID, &i.ArtistID, &i.Tier, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listOnThisDayTracks = `-- name: ListOnThisDayTracks :many 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 ` type ListOnThisDayTracksParams struct { UserID pgtype.UUID Column2 string } type ListOnThisDayTracksRow struct { ID pgtype.UUID AlbumID pgtype.UUID ArtistID pgtype.UUID } // #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. func (q *Queries) ListOnThisDayTracks(ctx context.Context, arg ListOnThisDayTracksParams) ([]ListOnThisDayTracksRow, error) { rows, err := q.db.Query(ctx, listOnThisDayTracks, arg.UserID, arg.Column2) if err != nil { return nil, err } defer rows.Close() var items []ListOnThisDayTracksRow for rows.Next() { var i ListOnThisDayTracksRow if err := rows.Scan(&i.ID, &i.AlbumID, &i.ArtistID); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listRediscoverTracks = `-- name: ListRediscoverTracks :many 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 ` type ListRediscoverTracksRow struct { ID pgtype.UUID AlbumID pgtype.UUID ArtistID pgtype.UUID } // #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. func (q *Queries) ListRediscoverTracks(ctx context.Context, userID pgtype.UUID) ([]ListRediscoverTracksRow, error) { rows, err := q.db.Query(ctx, listRediscoverTracks, userID) if err != nil { return nil, err } defer rows.Close() var items []ListRediscoverTracksRow for rows.Next() { var i ListRediscoverTracksRow if err := rows.Scan(&i.ID, &i.AlbumID, &i.ArtistID); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil }