feat(recommendation): SuggestArtists service for M5c

Add per-user artist-suggestion service ranking out-of-library MBIDs by
signal x similarity. Single-CTE SQL collects user likes (5x weight) and
recency-decayed plays, joins against artist_similarity_unmatched, and
filters in-library candidates plus non-terminal lidarr_requests. The
service resolves top-3 attribution seeds to artist names in a batched
GetArtistsByIDs call so the UI can render "because you liked X" reasons.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-01 06:20:02 -04:00
parent 5e73f590a9
commit 277898a49a
6 changed files with 619 additions and 0 deletions
+33
View File
@@ -69,6 +69,39 @@ func (q *Queries) GetArtistByName(ctx context.Context, name string) (Artist, err
return i, err
}
const getArtistsByIDs = `-- name: GetArtistsByIDs :many
SELECT id, name, sort_name, mbid, created_at, updated_at FROM artists WHERE id = ANY($1::uuid[])
`
// Batched lookup used by M5c suggestion attribution to resolve top-3
// contributing seed UUIDs back to artist names in one round-trip.
func (q *Queries) GetArtistsByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]Artist, error) {
rows, err := q.db.Query(ctx, getArtistsByIDs, dollar_1)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Artist
for rows.Next() {
var i Artist
if err := rows.Scan(
&i.ID,
&i.Name,
&i.SortName,
&i.Mbid,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listArtists = `-- name: ListArtists :many
SELECT id, name, sort_name, mbid, created_at, updated_at FROM artists ORDER BY sort_name
`