feat(recommendation): add BuildSessionVector pure function
Pure aggregation per spec §6: Seed flag (true when prior < 3), deduplicated Artists ordered by first appearance, Tags bag-of-counts from tracks.genre (empty genres skipped), RecentTrackIDs preserving input order. JSON round-trip verified.
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
package recommendation
|
||||
|
||||
import (
|
||||
"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"`
|
||||
}
|
||||
|
||||
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 != "" {
|
||||
v.Tags[*t.Genre]++
|
||||
}
|
||||
v.RecentTrackIDs = append(v.RecentTrackIDs, uuidString(t.ID))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func uuidString(u pgtype.UUID) string {
|
||||
if !u.Valid {
|
||||
return ""
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
Reference in New Issue
Block a user