Files
minstrel/internal/similarity/worker.go
T

67 lines
1.8 KiB
Go

// Package similarity owns the inbound ListenBrainz similarity ingest
// pipeline. A periodic worker queries LB's /explore/similar-recordings
// and /explore/similar-artists endpoints for tracks the user has played,
// filters returned MBIDs to the local library, and stores the top-K
// edges in track_similarity / artist_similarity for M4c's radio
// candidate-pool builder.
package similarity
import (
"context"
"log/slog"
"time"
"github.com/jackc/pgx/v5/pgxpool"
"git.fabledsword.com/bvandeusen/minstrel/internal/scrobble/listenbrainz"
)
// Worker drains played-tracks-and-artists needing similarity and POSTs
// the results into track_similarity / artist_similarity. Failures are
// passively retried via the timer (no durable queue table — losing one
// tick's worth of refresh attempts is "1 hour of staleness," fine).
type Worker struct {
pool *pgxpool.Pool
client *listenbrainz.Client
logger *slog.Logger
tick time.Duration
batch int32
topK int
}
// NewWorker constructs a worker with production defaults: 1h tick,
// batch=5, topK=20.
func NewWorker(pool *pgxpool.Pool, client *listenbrainz.Client, logger *slog.Logger) *Worker {
return &Worker{
pool: pool,
client: client,
logger: logger,
tick: 1 * time.Hour,
batch: 5,
topK: 20,
}
}
// Run blocks until ctx is cancelled, ticking every w.tick.
func (w *Worker) Run(ctx context.Context) {
t := time.NewTicker(w.tick)
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
if err := w.tickOnce(ctx); err != nil {
w.logger.Error("similarity: tick failed", "err", err)
}
}
}
}
// tickOnce drains one batch of tracks and one batch of artists. Stub —
// implementation lands in Task 6 along with the integration tests that
// drive it.
func (w *Worker) tickOnce(_ context.Context) error {
return nil
}