fix(taste): drop unused now param + tighten test (golangci-lint revive)
golangci-lint v2 (CI-only; local is v1) flagged two unused-parameter issues: - BuildTasteProfile's `now` was genuinely dead — decay/windowing are computed DB-side via now(), so no Go-side timestamp is threaded. Removed it (a phase-3 context model that needs a pinned reference time would re-add it); updated the scheduler call site. - the degenerate-params engagement test ignored t; reworked it to assert the result stays in [-1,1], which also strengthens the test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -227,7 +227,7 @@ func (s *Scheduler) runStartupCatchUp(ctx context.Context, users []dbq.ListActiv
|
|||||||
// build can read a fresh profile (phase-2 consumption); both steps are
|
// build can read a fresh profile (phase-2 consumption); both steps are
|
||||||
// best-effort and a failure in one is logged without blocking the other.
|
// best-effort and a failure in one is logged without blocking the other.
|
||||||
func (s *Scheduler) rebuildUserDaily(ctx context.Context, userID pgtype.UUID, now time.Time) {
|
func (s *Scheduler) rebuildUserDaily(ctx context.Context, userID pgtype.UUID, now time.Time) {
|
||||||
if err := taste.BuildTasteProfile(ctx, s.pool, s.logger, userID, now, taste.DefaultConfig()); err != nil {
|
if err := taste.BuildTasteProfile(ctx, s.pool, s.logger, userID, taste.DefaultConfig()); err != nil {
|
||||||
s.logger.Warn("scheduler: taste profile rebuild failed",
|
s.logger.Warn("scheduler: taste profile rebuild failed",
|
||||||
"user_id", uuidStringPL(userID), "err", err)
|
"user_id", uuidStringPL(userID), "err", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,11 +44,15 @@ func TestEngagement_MonotonicNonDecreasing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEngagement_DegenerateParamsNoPanic(t *testing.T) {
|
func TestEngagement_DegenerateParamsStayInRange(t *testing.T) {
|
||||||
// Non-increasing thresholds must collapse ramps, not divide by zero.
|
// Non-increasing thresholds must collapse ramps (no divide-by-zero) and
|
||||||
|
// still produce a value in [-1, 1].
|
||||||
p := EngagementParams{HardSkip: 0.5, NeutralCompletion: 0.5, FullCompletion: 0.5}
|
p := EngagementParams{HardSkip: 0.5, NeutralCompletion: 0.5, FullCompletion: 0.5}
|
||||||
for _, c := range []float64{0, 0.25, 0.5, 0.75, 1} {
|
for _, c := range []float64{0, 0.25, 0.5, 0.75, 1} {
|
||||||
_ = Engagement(c, p)
|
got := Engagement(c, p)
|
||||||
|
if got < -1 || got > 1 {
|
||||||
|
t.Errorf("Engagement(%.2f) = %.3f out of [-1,1]", c, got)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
@@ -81,9 +80,13 @@ func DefaultConfig() Config {
|
|||||||
// Self-contained transaction for the replace. Returns an error only on a
|
// Self-contained transaction for the replace. Returns an error only on a
|
||||||
// fatal DB failure; the daily caller logs and proceeds to playlist building
|
// fatal DB failure; the daily caller logs and proceeds to playlist building
|
||||||
// regardless (best-effort).
|
// regardless (best-effort).
|
||||||
|
//
|
||||||
|
// The decay reference time is the database clock (age is computed in SQL via
|
||||||
|
// now()), so no Go-side timestamp is threaded here; a phase-3 context model
|
||||||
|
// that needs a pinned reference time would add one.
|
||||||
func BuildTasteProfile(
|
func BuildTasteProfile(
|
||||||
ctx context.Context, pool *pgxpool.Pool, logger *slog.Logger,
|
ctx context.Context, pool *pgxpool.Pool, logger *slog.Logger,
|
||||||
userID pgtype.UUID, now time.Time, cfg Config,
|
userID pgtype.UUID, cfg Config,
|
||||||
) error {
|
) error {
|
||||||
q := dbq.New(pool)
|
q := dbq.New(pool)
|
||||||
|
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ func TestBuildTasteProfile_PositiveVsNegative(t *testing.T) {
|
|||||||
seedPlay(t, pool, u.ID, skippedTrack.ID, now.Add(-time.Duration(i+1)*time.Hour), 0.02)
|
seedPlay(t, pool, u.ID, skippedTrack.ID, now.Add(-time.Duration(i+1)*time.Hour), 0.02)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := BuildTasteProfile(context.Background(), pool, discardLogger(), u.ID, now, DefaultConfig()); err != nil {
|
if err := BuildTasteProfile(context.Background(), pool, discardLogger(), u.ID, DefaultConfig()); err != nil {
|
||||||
t.Fatalf("build: %v", err)
|
t.Fatalf("build: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ func TestBuildTasteProfile_AggregationProtectsArtist(t *testing.T) {
|
|||||||
skipTrack := seedTrack(t, pool, fav.ID, "Indie")
|
skipTrack := seedTrack(t, pool, fav.ID, "Indie")
|
||||||
seedPlay(t, pool, u.ID, skipTrack.ID, now.Add(-2*time.Hour), 0.02)
|
seedPlay(t, pool, u.ID, skipTrack.ID, now.Add(-2*time.Hour), 0.02)
|
||||||
|
|
||||||
if err := BuildTasteProfile(context.Background(), pool, discardLogger(), u.ID, now, DefaultConfig()); err != nil {
|
if err := BuildTasteProfile(context.Background(), pool, discardLogger(), u.ID, DefaultConfig()); err != nil {
|
||||||
t.Fatalf("build: %v", err)
|
t.Fatalf("build: %v", err)
|
||||||
}
|
}
|
||||||
w, ok := artistWeight(t, pool, u.ID, fav.ID)
|
w, ok := artistWeight(t, pool, u.ID, fav.ID)
|
||||||
@@ -203,7 +203,6 @@ func TestBuildTasteProfile_AggregationProtectsArtist(t *testing.T) {
|
|||||||
// plays still earns a positive weight from the like bonus.
|
// plays still earns a positive weight from the like bonus.
|
||||||
func TestBuildTasteProfile_ArtistLikeBonus(t *testing.T) {
|
func TestBuildTasteProfile_ArtistLikeBonus(t *testing.T) {
|
||||||
pool := newPool(t)
|
pool := newPool(t)
|
||||||
now := time.Now().UTC()
|
|
||||||
u := seedUser(t, pool, "tp3")
|
u := seedUser(t, pool, "tp3")
|
||||||
liked := seedArtist(t, pool, "LikedOnly")
|
liked := seedArtist(t, pool, "LikedOnly")
|
||||||
if _, err := pool.Exec(context.Background(),
|
if _, err := pool.Exec(context.Background(),
|
||||||
@@ -212,7 +211,7 @@ func TestBuildTasteProfile_ArtistLikeBonus(t *testing.T) {
|
|||||||
t.Fatalf("seed artist like: %v", err)
|
t.Fatalf("seed artist like: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := BuildTasteProfile(context.Background(), pool, discardLogger(), u.ID, now, DefaultConfig()); err != nil {
|
if err := BuildTasteProfile(context.Background(), pool, discardLogger(), u.ID, DefaultConfig()); err != nil {
|
||||||
t.Fatalf("build: %v", err)
|
t.Fatalf("build: %v", err)
|
||||||
}
|
}
|
||||||
w, ok := artistWeight(t, pool, u.ID, liked.ID)
|
w, ok := artistWeight(t, pool, u.ID, liked.ID)
|
||||||
@@ -240,7 +239,7 @@ func TestBuildTasteProfile_AtomicReplace(t *testing.T) {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; i < 2; i++ {
|
||||||
if err := BuildTasteProfile(ctx, pool, discardLogger(), u.ID, now, DefaultConfig()); err != nil {
|
if err := BuildTasteProfile(ctx, pool, discardLogger(), u.ID, DefaultConfig()); err != nil {
|
||||||
t.Fatalf("build %d: %v", i, err)
|
t.Fatalf("build %d: %v", i, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user