docs: wanted, dbtest, recommendation and playlists comments say what the code does (#6277)
The same rewrite as d191a282, for four packages whose comments carried the
most history: tuning stories, quotes from the operator, single-install
measurements in library_scale.go, and 'used to' accounts of bugs. The
constraint each story protected stays as a present-tense reason (why tier 3
is reduced not removed, why the randomised arms take an order seed, why the
seed artist is eligible in Songs-like). Comments naming system_cron.go and
config.RecommendationConfig, neither of which holds what they claimed, now
name the scheduler and internal/recsettings.
Checked: non-comment tokens identical to HEAD; gofmt clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -82,11 +82,11 @@ type CandidateSourceLimits struct {
|
||||
TagOverlap int
|
||||
LikesOverlap int
|
||||
RandomFill int
|
||||
// TasteOverlap (#796 phase 2b): tracks by the user's top positively-
|
||||
// TasteOverlap: tracks by the user's top positively-
|
||||
// weighted taste-profile artists. 0 disables the arm (e.g. cold-start
|
||||
// users have an empty profile, so it contributes nothing anyway).
|
||||
TasteOverlap int
|
||||
// UserCoplay (#1533): tracks by artists co-played across the instance
|
||||
// UserCoplay: tracks by artists co-played across the instance
|
||||
// with the seed's artist (source='user_cooccurrence'). Empty on
|
||||
// single-user servers, so it contributes nothing there.
|
||||
UserCoplay int
|
||||
@@ -105,21 +105,18 @@ func DefaultCandidateSourceLimits() CandidateSourceLimits {
|
||||
}
|
||||
}
|
||||
|
||||
// SongsLikeCandidateSourceLimits is the pool shape for "Songs like {X}"
|
||||
// (#3881). Same total size as the default (~170) — the composition is what
|
||||
// changes, shifted hard toward arms that actually measure distance from the
|
||||
// seed.
|
||||
// SongsLikeCandidateSourceLimits is the pool shape for "Songs like {X}".
|
||||
// Same total size as the default (~170) — the composition is what changes,
|
||||
// shifted hard toward arms that actually measure distance from the seed.
|
||||
//
|
||||
// The surface answers "what sounds like THIS", and it shared the default
|
||||
// pool with For-You, which answers the much broader "what will they enjoy
|
||||
// today". Under the default, 50 of ~170 candidates carried sim_score = 0 by
|
||||
// construction — `taste_overlap` (tracks by the user's top taste artists) and
|
||||
// `random_fill` (literally any track not already in the pool), both of which
|
||||
// are seed-INDEPENDENT. Nearly a third of the pool had no relationship to the
|
||||
// seed at all, and the operator saw it: "I was getting a seeming wide variety
|
||||
// of music from each one when I was hoping to stay in a certain neighborhood."
|
||||
// The surface answers "what sounds like THIS", where For-You answers the much
|
||||
// broader "what will they enjoy today". In the default pool, 50 of ~170
|
||||
// candidates carry sim_score = 0 by construction — `taste_overlap` (tracks by
|
||||
// the user's top taste artists) and `random_fill` (any track not already in
|
||||
// the pool), both seed-INDEPENDENT — so nearly a third of it has no
|
||||
// relationship to the seed.
|
||||
//
|
||||
// TIERED, per rule 131 — a system mix degrades, it never vanishes:
|
||||
// TIERED — a system mix degrades, it never vanishes:
|
||||
//
|
||||
// tier 1 lb_similar real track-level similarity. The exact promise.
|
||||
// tier 2 similar_artist, tag_overlap, coplay, likes_overlap
|
||||
@@ -129,43 +126,23 @@ func DefaultCandidateSourceLimits() CandidateSourceLimits {
|
||||
//
|
||||
// The tiering is enforced by SCORE rather than by a fallback ladder: each tier
|
||||
// holds its own sim_score band (recommendation.sql; tier 1 above 0.5, tier 2
|
||||
// at or below it, #6177), and tier-3 arms carry sim_score 0, so under SongsLike weights (SimilarityWeight 4.0,
|
||||
// everything seed-independent demoted) they rank below any real match and
|
||||
// surface only when tiers 1–2 cannot fill the mix. That is the rule's
|
||||
// "fill from tier 1 first, reach down only when a tier cannot fill".
|
||||
// at or below it), and tier-3 arms carry sim_score 0, so under the SongsLike
|
||||
// weights (SimilarityWeight 4.0, everything seed-independent demoted) they
|
||||
// rank below any real match and surface only when tiers 1–2 cannot fill the
|
||||
// mix: fill from tier 1 first, reach down only when a tier cannot fill.
|
||||
//
|
||||
// Which is exactly why tier 3 is REDUCED rather than removed. Zeroing those
|
||||
// two arms was the first instinct and it is the vanish-or-nothing shape rule
|
||||
// 131 exists to forbid: a seed whose artist has thin ListenBrainz coverage
|
||||
// would produce a short mix or none at all, and "no playlist" is a worse
|
||||
// answer than "a few tracks further from the seed than we would like".
|
||||
// Tier 3 is REDUCED, never removed. Zeroing those two arms would leave a seed
|
||||
// whose artist has thin ListenBrainz coverage with a short mix or none at
|
||||
// all, and "no playlist" is a worse answer than "a few tracks further from
|
||||
// the seed than we would like". The trim is safe because those arms order by
|
||||
// md5(id || seed): a smaller limit takes a smaller but REPRODUCIBLE slice, so
|
||||
// same-day rebuilds still agree.
|
||||
//
|
||||
// The seed-independent arms are trimmed hardest, because on this surface they
|
||||
// are noise: `taste_overlap` (tracks by the user's top taste artists) and
|
||||
// `random_fill` (any track not already in the pool) both carry
|
||||
// `0.0::float8 AS sim_score`, so nearly a third of the default pool had no
|
||||
// relationship to the seed at all.
|
||||
//
|
||||
// THESE TRIMS WERE BLOCKED UNTIL #3889. `likes_overlap` and `random_fill`
|
||||
// used to end in a bare `ORDER BY random()`, which made their output a stable
|
||||
// SET only while the limit exceeded the eligible rows — so SHRINKING them
|
||||
// changed pool membership between same-day rebuilds and broke daily
|
||||
// determinism. Those arms now order by md5(id || seed), so a smaller limit
|
||||
// takes a smaller but REPRODUCIBLE slice, and the trim is safe.
|
||||
//
|
||||
// Reduced, never removed. Rule 131: the two seed-independent arms are the
|
||||
// tier-3 floor, and zeroing them would leave a seed with thin ListenBrainz
|
||||
// coverage producing a short mix or none at all. The weights (SimilarityWeight
|
||||
// 4.0, everything seed-independent demoted) keep them ranked last, so they
|
||||
// surface only when the closer tiers cannot fill the mix.
|
||||
//
|
||||
// likes_overlap is cut hardest of the tier-2 arms for a specific reason: its
|
||||
// SQL assigns a FLAT 0.3 sim_score (recommendation.sql) rather than measuring
|
||||
// anything. It is a collaborative signal wearing similarity's clothes, and a
|
||||
// raised SimilarityWeight amplifies it. The tier bands keep it below every
|
||||
// ListenBrainz match (#6177); halved pending the fill-rate measurement in
|
||||
// #3879, and the honest fix is still to stop it claiming a similarity score
|
||||
// it never computed.
|
||||
// likes_overlap is cut hardest of the tier-2 arms: its SQL assigns a FLAT 0.3
|
||||
// sim_score (recommendation.sql) rather than measuring anything. It is a
|
||||
// collaborative signal wearing similarity's clothes, and a raised
|
||||
// SimilarityWeight amplifies it. The tier bands keep it below every
|
||||
// ListenBrainz match.
|
||||
func SongsLikeCandidateSourceLimits() CandidateSourceLimits {
|
||||
return CandidateSourceLimits{
|
||||
LBSimilar: 60, // tier 1 — doubled; the only arm that measures the seed
|
||||
@@ -178,13 +155,13 @@ func SongsLikeCandidateSourceLimits() CandidateSourceLimits {
|
||||
}
|
||||
}
|
||||
|
||||
// LoadCandidatesFromSimilarity is M4c's primary candidate-pool loader.
|
||||
// LoadCandidatesFromSimilarity is the primary candidate-pool loader.
|
||||
// 5-way SQL UNION (LB-similar / similar-artist tracks / MB-tag overlap /
|
||||
// likes-overlap / random fill) + dedup-by-max sim_score. Returns
|
||||
// []Candidate (same shape as LoadCandidates) so Shuffle is unchanged.
|
||||
//
|
||||
// orderSeed decides whether the randomised arms repeat their draw — see
|
||||
// Column12 below and #3889. Pass a stable per-(user, day) value where the
|
||||
// Column12 below. Pass a stable per-(user, day) value where the
|
||||
// selection must be reproducible, and a varying one where it should not be.
|
||||
//
|
||||
// Caller (radio handler) falls back to LoadCandidates on error.
|
||||
@@ -213,10 +190,8 @@ func LoadCandidatesFromSimilarity(
|
||||
Limit_5: int32(limits.RandomFill),
|
||||
Limit_6: int32(limits.TasteOverlap),
|
||||
Limit_7: int32(limits.UserCoplay),
|
||||
// #3889. Four arms used to end in a bare ORDER BY random(), which made
|
||||
// their output a stable SET only while the limit exceeded the eligible
|
||||
// rows. They now order by md5(id || this), so the caller decides
|
||||
// whether the draw repeats: a per-(user, day) seed for the system
|
||||
// The randomised arms order by md5(id || this) rather than random(),
|
||||
// so the caller decides whether the draw repeats: a per-(user, day) seed for the system
|
||||
// mixes that promise daily determinism, a fresh one per radio request.
|
||||
Column12: orderSeed,
|
||||
})
|
||||
@@ -282,7 +257,7 @@ func LoadCandidatesFromSimilarity(
|
||||
}
|
||||
|
||||
// loadCandidateMoods fetches the enriched tags for the given candidate tracks
|
||||
// and reduces each to its canonical mood buckets (internal/mood, #1534), so the
|
||||
// and reduces each to its canonical mood buckets (internal/mood), so the
|
||||
// scorer can apply the mood facet per candidate. Tracks with no mood-word tags
|
||||
// are absent from the map (→ no mood signal). Empty input short-circuits.
|
||||
func loadCandidateMoods(
|
||||
|
||||
Reference in New Issue
Block a user