feat(taste): era/decade taste facet — #1530
test-go / test (push) Successful in 34s
test-web / test (push) Successful in 41s
test-go / integration (push) Successful in 4m40s

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:
2026-07-14 09:01:00 -04:00
parent 40056d2e9a
commit 40384cc05e
20 changed files with 376 additions and 65 deletions
+11 -4
View File
@@ -10,7 +10,7 @@ import (
)
const getTasteTuning = `-- name: GetTasteTuning :one
SELECT singleton, half_life_days, engagement_hard_skip, engagement_neutral, engagement_full, updated_at, enriched_tag_scale FROM taste_tuning WHERE singleton = true
SELECT singleton, half_life_days, engagement_hard_skip, engagement_neutral, engagement_full, updated_at, enriched_tag_scale, era_scale FROM taste_tuning WHERE singleton = true
`
func (q *Queries) GetTasteTuning(ctx context.Context) (TasteTuning, error) {
@@ -24,6 +24,7 @@ func (q *Queries) GetTasteTuning(ctx context.Context) (TasteTuning, error) {
&i.EngagementFull,
&i.UpdatedAt,
&i.EnrichedTagScale,
&i.EraScale,
)
return i, err
}
@@ -122,9 +123,10 @@ UPDATE taste_tuning
engagement_neutral = $3,
engagement_full = $4,
enriched_tag_scale = $5,
era_scale = $6,
updated_at = now()
WHERE singleton = true
RETURNING singleton, half_life_days, engagement_hard_skip, engagement_neutral, engagement_full, updated_at, enriched_tag_scale
RETURNING singleton, half_life_days, engagement_hard_skip, engagement_neutral, engagement_full, updated_at, enriched_tag_scale, era_scale
`
type UpdateTasteTuningParams struct {
@@ -133,6 +135,7 @@ type UpdateTasteTuningParams struct {
EngagementNeutral float64
EngagementFull float64
EnrichedTagScale float64
EraScale float64
}
func (q *Queries) UpdateTasteTuning(ctx context.Context, arg UpdateTasteTuningParams) (TasteTuning, error) {
@@ -142,6 +145,7 @@ func (q *Queries) UpdateTasteTuning(ctx context.Context, arg UpdateTasteTuningPa
arg.EngagementNeutral,
arg.EngagementFull,
arg.EnrichedTagScale,
arg.EraScale,
)
var i TasteTuning
err := row.Scan(
@@ -152,6 +156,7 @@ func (q *Queries) UpdateTasteTuning(ctx context.Context, arg UpdateTasteTuningPa
&i.EngagementFull,
&i.UpdatedAt,
&i.EnrichedTagScale,
&i.EraScale,
)
return i, err
}
@@ -214,8 +219,8 @@ func (q *Queries) UpdateWeightProfile(ctx context.Context, arg UpdateWeightProfi
const upsertTasteTuningDefaults = `-- name: UpsertTasteTuningDefaults :exec
INSERT INTO taste_tuning (
singleton, half_life_days, engagement_hard_skip,
engagement_neutral, engagement_full, enriched_tag_scale
) VALUES (true, $1, $2, $3, $4, $5)
engagement_neutral, engagement_full, enriched_tag_scale, era_scale
) VALUES (true, $1, $2, $3, $4, $5, $6)
ON CONFLICT (singleton) DO NOTHING
`
@@ -225,6 +230,7 @@ type UpsertTasteTuningDefaultsParams struct {
EngagementNeutral float64
EngagementFull float64
EnrichedTagScale float64
EraScale float64
}
func (q *Queries) UpsertTasteTuningDefaults(ctx context.Context, arg UpsertTasteTuningDefaultsParams) error {
@@ -234,6 +240,7 @@ func (q *Queries) UpsertTasteTuningDefaults(ctx context.Context, arg UpsertTaste
arg.EngagementNeutral,
arg.EngagementFull,
arg.EnrichedTagScale,
arg.EraScale,
)
return err
}