feat(taste): household co-play similarity — #1533
Milestone #160 Opt 5. A collaborative candidate arm: tracks by artists co-played across the instance with the seed's artist. Minstrel is a single shared-library, multi-user server (no per-user library ACL — verified: no owner/share/group model), so the "household" is the whole instance's user set; the rule #47 scoping is satisfied by the shared-library boundary. Single-user servers produce no edges. - No migration: source='user_cooccurrence' was pre-whitelisted in the 0009 similarity CHECK from day one. - internal/db/queries/coplay.sql: Delete + Insert artist co-play edges. Score = Jaccard of the two artists' distinct-player sets (controls for globally-popular artists); >= 2 co-players AND Jaccard >= floor kept (the floor also self-limits hub artists). Completed plays, 365d window. - internal/coplay: periodic worker (6h) that atomic-replaces the user_cooccurrence edge set from play_events — pure local SQL, no external calls. Wired in main.go alongside the similarity worker. - LoadRadioCandidatesV2: new coplay_artists arm (source='user_cooccurrence', seed-artist based, 0.5 damp like similar_artists) + $11 limit; CandidateSourceLimits.UserCoplay (default 20, For-You 40). - Integration tests: perfect-overlap Jaccard=1.0 edge + single-user empty-set gate. Device axis and AcousticBrainz (Opt 4) are separately tracked; this closes the milestone-#160 sequential options. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,10 @@ type CandidateSourceLimits struct {
|
||||
// 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
|
||||
// with the seed's artist (source='user_cooccurrence'). Empty on
|
||||
// single-user servers, so it contributes nothing there.
|
||||
UserCoplay int
|
||||
}
|
||||
|
||||
// DefaultCandidateSourceLimits returns the v1 hardcoded constants per spec.
|
||||
@@ -93,6 +97,7 @@ func DefaultCandidateSourceLimits() CandidateSourceLimits {
|
||||
LikesOverlap: 20,
|
||||
RandomFill: 30,
|
||||
TasteOverlap: 20,
|
||||
UserCoplay: 20,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +130,7 @@ func LoadCandidatesFromSimilarity(
|
||||
Limit_4: int32(limits.LikesOverlap),
|
||||
Limit_5: int32(limits.RandomFill),
|
||||
Limit_6: int32(limits.TasteOverlap),
|
||||
Limit_7: int32(limits.UserCoplay),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in New Issue
Block a user