feat(taste): era/decade taste facet — #1530
Milestone #160 Opt 2 (era half). A third taste facet alongside artists + genre tags: signed weights over decade buckets ("1990s") derived from albums.release_date, rebuilt daily and scored into the taste match. - Migration 0045: taste_profile_eras table (mirrors taste_profile_tags) + taste_tuning.era_scale column (DEFAULT 0.5). - Build side (internal/taste): Config.EraScale ([0,1] damper, mirrors EnrichedTagScale), accumulate folds each play/like's decade at base*EraScale, persist atomic-replaces the era rows. - Scorer (internal/recommendation): TasteProfile gains an era term (own tanh scale + additive 0.15 share so it never weakens the existing artist/tag signal when a track is undated); candidate queries return album release_date; decadeOf mirrors the builder helper. - Tuning lab: era_scale threaded through recsettings + admin API + web card (auto-renders the new row) + Go/web tests. Mood facet deferred to #1534 (partial enrichment coverage + needs candidate-side enriched-tag loading). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -585,8 +585,10 @@ SELECT
|
||||
(l.user_id IS NOT NULL)::bool AS is_liked,
|
||||
pe.last_played_at::timestamptz AS last_played_at,
|
||||
pe.play_count,
|
||||
pe.skip_count
|
||||
pe.skip_count,
|
||||
al.release_date AS release_date
|
||||
FROM tracks t
|
||||
JOIN albums al ON al.id = t.album_id
|
||||
LEFT JOIN general_likes l ON l.user_id = $1 AND l.track_id = t.id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
@@ -620,6 +622,7 @@ type LoadRadioCandidatesRow struct {
|
||||
LastPlayedAt pgtype.Timestamptz
|
||||
PlayCount int64
|
||||
SkipCount int64
|
||||
ReleaseDate pgtype.Date
|
||||
}
|
||||
|
||||
// Returns all tracks except the seed and any played by the user within
|
||||
@@ -660,6 +663,7 @@ func (q *Queries) LoadRadioCandidates(ctx context.Context, arg LoadRadioCandidat
|
||||
&i.LastPlayedAt,
|
||||
&i.PlayCount,
|
||||
&i.SkipCount,
|
||||
&i.ReleaseDate,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -775,6 +779,7 @@ SELECT
|
||||
pe.last_played_at::timestamptz AS last_played_at,
|
||||
pe.play_count,
|
||||
pe.skip_count,
|
||||
al.release_date AS release_date,
|
||||
COALESCE(max(u.sim_score), 0.0) AS similarity_score
|
||||
FROM (
|
||||
SELECT track_id, sim_score FROM lb_similar
|
||||
@@ -785,6 +790,7 @@ FROM (
|
||||
UNION ALL SELECT track_id, sim_score FROM random_fill
|
||||
) u
|
||||
JOIN tracks t ON t.id = u.track_id
|
||||
JOIN albums al ON al.id = t.album_id
|
||||
LEFT JOIN general_likes l ON l.user_id = $1 AND l.track_id = t.id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT max(started_at) AS last_played_at,
|
||||
@@ -796,7 +802,8 @@ LEFT JOIN LATERAL (
|
||||
GROUP BY t.id, t.title, t.album_id, t.artist_id, t.duration_ms, t.file_path,
|
||||
t.file_format, t.file_size, t.bitrate, t.track_number, t.disc_number,
|
||||
t.mbid, t.genre, t.added_at, t.updated_at,
|
||||
l.user_id, pe.last_played_at, pe.play_count, pe.skip_count
|
||||
l.user_id, pe.last_played_at, pe.play_count, pe.skip_count,
|
||||
al.release_date
|
||||
`
|
||||
|
||||
type LoadRadioCandidatesV2Params struct {
|
||||
@@ -818,6 +825,7 @@ type LoadRadioCandidatesV2Row struct {
|
||||
LastPlayedAt pgtype.Timestamptz
|
||||
PlayCount int64
|
||||
SkipCount int64
|
||||
ReleaseDate pgtype.Date
|
||||
SimilarityScore interface{}
|
||||
}
|
||||
|
||||
@@ -874,6 +882,7 @@ func (q *Queries) LoadRadioCandidatesV2(ctx context.Context, arg LoadRadioCandid
|
||||
&i.LastPlayedAt,
|
||||
&i.PlayCount,
|
||||
&i.SkipCount,
|
||||
&i.ReleaseDate,
|
||||
&i.SimilarityScore,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in New Issue
Block a user