Files
minstrel/internal/recommendation/sessionvector.go
T
bvandeusenandClaude Opus 5.5 fcf5b7e5a8 docs: wanted, dbtest, recommendation and playlists comments say what the code does (#6277)
The same rewrite as d191a282, for four packages whose comments carried the
most history: tuning stories, quotes from the operator, single-install
measurements in library_scale.go, and 'used to' accounts of bugs. The
constraint each story protected stays as a present-tense reason (why tier 3
is reduced not removed, why the randomised arms take an order seed, why the
seed artist is eligible in Songs-like). Comments naming system_cron.go and
config.RecommendationConfig, neither of which holds what they claimed, now
name the scheduler and internal/recsettings.

Checked: non-comment tokens identical to HEAD; gofmt clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 12:19:15 -04:00

70 lines
1.9 KiB
Go

package recommendation
import (
"strings"
"github.com/jackc/pgx/v5/pgtype"
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
)
type SessionVector struct {
Seed bool `json:"seed"`
Artists []string `json:"artists"`
Tags map[string]int `json:"tags"`
RecentTrackIDs []string `json:"recent_track_ids"`
// DeviceClass is the current request's device, set by the radio
// handler from the user's latest play; drives the device dimension of the
// context-affinity term. Empty (omitted) for the daily mixes and for the
// snapshot stored on play_events, so it never narrows those.
DeviceClass string `json:"device_class,omitempty"`
}
func BuildSessionVector(priorTracks []dbq.Track) SessionVector {
v := SessionVector{
Seed: len(priorTracks) < 3,
Artists: []string{},
Tags: map[string]int{},
RecentTrackIDs: []string{},
}
seen := map[string]bool{}
for _, t := range priorTracks {
artistID := uuidString(t.ArtistID)
if !seen[artistID] {
seen[artistID] = true
v.Artists = append(v.Artists, artistID)
}
if t.Genre != nil && *t.Genre != "" {
for _, g := range splitGenres(*t.Genre) {
v.Tags[g]++
}
}
v.RecentTrackIDs = append(v.RecentTrackIDs, uuidString(t.ID))
}
return v
}
func uuidString(u pgtype.UUID) string {
if !u.Valid {
return ""
}
return u.String()
}
// splitGenres splits a track's denormalized genre string on the common
// multi-genre delimiters (`;`, `,`) used by various tag editors. Trims
// whitespace; drops empty fragments. Strings with no delimiter come back
// as a single-element slice.
func splitGenres(s string) []string {
parts := strings.FieldsFunc(s, func(r rune) bool {
return r == ';' || r == ','
})
out := make([]string, 0, len(parts))
for _, p := range parts {
if p = strings.TrimSpace(p); p != "" {
out = append(out, p)
}
}
return out
}