Discover request surface — taste-aware, rotating, snoozable, tag-targeted (milestone #268) #116
@@ -1022,22 +1022,6 @@ func (q *Queries) LoadRadioCandidatesV2(ctx context.Context, arg LoadRadioCandid
|
||||
}
|
||||
|
||||
const suggestArtistsForUser = `-- name: SuggestArtistsForUser :many
|
||||
-- Per-user artist suggestions ranked by taste signal x similarity, projected
|
||||
-- through artist_similarity_unmatched (out-of-library candidates only).
|
||||
--
|
||||
-- Seeds are TIERED (rule #131) so the surface never empties:
|
||||
-- tier 1 - taste_profile_artists.weight: engagement-graded, time-decayed and
|
||||
-- SIGNED by internal/taste, so an artist the user has drifted away
|
||||
-- from stops contributing instead of accumulating forever.
|
||||
-- tier 2 - likes + completed plays, used ONLY when the profile has no rows
|
||||
-- (new account, or before the first daily recompute).
|
||||
--
|
||||
-- The signal is log-damped: contribution is signal x similarity, and the old
|
||||
-- undamped sum let one heavily-played artist's neighbours take every slot --
|
||||
-- entrenching harder the MORE the user listened (issue #2367 mechanism 2).
|
||||
--
|
||||
-- Candidates already in the library, or already requested and not terminal,
|
||||
-- are excluded. $1=user_id, $2=half_life_days (tier 2 decay), $3=limit.
|
||||
WITH artist_plays AS (
|
||||
-- Completed plays only. The previous seed query counted every play_event,
|
||||
-- so skipping an artist repeatedly INCREASED its signal and pushed more of
|
||||
@@ -1128,13 +1112,23 @@ type SuggestArtistsForUserRow struct {
|
||||
TopPlayCounts []int64
|
||||
}
|
||||
|
||||
// M5c: per-user artist suggestions ranked by signal x similarity. The
|
||||
// seeds CTE collects the user's likes (x5) plus recency-decayed plays
|
||||
// (exp(-age_days / $2)). The contributions CTE joins those seeds against
|
||||
// artist_similarity_unmatched and filters out candidates already in
|
||||
// library or already in a non-terminal lidarr_request. The outer SELECT
|
||||
// aggregates per candidate, returning the top-3 contributing seeds for
|
||||
// attribution. $1=user_id, $2=half_life_days, $3=limit.
|
||||
// Per-user artist suggestions ranked by taste signal x similarity, projected
|
||||
// through artist_similarity_unmatched (out-of-library candidates only).
|
||||
//
|
||||
// Seeds are TIERED (rule #131) so the surface never empties:
|
||||
//
|
||||
// tier 1 - taste_profile_artists.weight: engagement-graded, time-decayed and
|
||||
// SIGNED by internal/taste, so an artist the user has drifted away
|
||||
// from stops contributing instead of accumulating forever.
|
||||
// tier 2 - likes + completed plays, used ONLY when the profile has no rows
|
||||
// (new account, or before the first daily recompute).
|
||||
//
|
||||
// The signal is log-damped: contribution is signal x similarity, and the old
|
||||
// undamped sum let one heavily-played artist's neighbours take every slot --
|
||||
// entrenching harder the MORE the user listened (issue #2367 mechanism 2).
|
||||
//
|
||||
// Candidates already in the library, or already requested and not terminal,
|
||||
// are excluded. $1=user_id, $2=half_life_days (tier 2 decay), $3=limit.
|
||||
func (q *Queries) SuggestArtistsForUser(ctx context.Context, arg SuggestArtistsForUserParams) ([]SuggestArtistsForUserRow, error) {
|
||||
rows, err := q.db.Query(ctx, suggestArtistsForUser, arg.UserID, arg.Column2, arg.Limit)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user