feat(scrobble): add pure Qualifies threshold function

This commit is contained in:
2026-04-28 09:10:39 -04:00
parent 5359a16091
commit 767e246b73
2 changed files with 57 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
// Package scrobble owns the outbound-scrobble pipeline: threshold detection,
// the queue write path, and the worker goroutine. The HTTP client is in
// internal/scrobble/listenbrainz; this package consumes it.
package scrobble
// Qualifies returns true if a closed play_event meets ListenBrainz's
// recommended scrobble threshold: ≥240 seconds played OR ≥50% of the
// track length played.
//
// The two thresholds are deliberately separate from M2's skip-detection
// thresholds (which serve a different purpose — internal engine signal
// for the scoring formula's skip penalty).
func Qualifies(durationPlayedMs int, completionRatio float64) bool {
return durationPlayedMs >= 240_000 || completionRatio >= 0.5
}
+42
View File
@@ -0,0 +1,42 @@
package scrobble
import "testing"
func TestQualifies_NeverPlayed(t *testing.T) {
if Qualifies(0, 0) {
t.Error("0/0 should not qualify")
}
}
func TestQualifies_AtLeast240Seconds(t *testing.T) {
if !Qualifies(240_000, 0.0) {
t.Error("240_000 ms played should qualify regardless of ratio")
}
}
func TestQualifies_AtLeastHalfRatio(t *testing.T) {
if !Qualifies(0, 0.5) {
t.Error("ratio=0.5 should qualify regardless of duration")
}
}
func TestQualifies_BelowBoth(t *testing.T) {
if Qualifies(120_000, 0.49) {
t.Error("120s + 49% should not qualify")
}
}
func TestQualifies_BoundaryJustUnder(t *testing.T) {
if Qualifies(239_999, 0.4999) {
t.Error("just-under both thresholds should not qualify")
}
}
func TestQualifies_BoundaryExactly(t *testing.T) {
if !Qualifies(240_000, 0.4999) {
t.Error("exactly 240s should qualify")
}
if !Qualifies(239_999, 0.5) {
t.Error("exactly 50% should qualify")
}
}