Recommender¶
Recommendation engine internals from musicseed-core (musicseed.recommender.*): candidate
generation, scoring, playlist selection, and recommender-population helpers.
Scoring¶
musicseed.recommender.scoring
¶
Scoring primitives for MusicSeed recommendations.
Weights
¶
Bases: BaseModel
Normalized recommendation weights.
Popularity means proximity to the seed popularity, not an absolute boost. Artist diversity is enforced as a selection constraint, not as a score component. Weights are normalized by their sum at scoring time, so absolute values only control relative importance.
SeedProfile
¶
Bases: BaseModel
Aggregated recommendation signals from one or more seed tracks.
ScoreBreakdown
¶
Bases: BaseModel
Component-level score details for explainable CLI output.
SonicCoverage
¶
Bases: BaseModel
How many scored candidates actually had a Plex sonic vector.
A candidate without a vector scores a neutral 0.5 on the sonic
dimension, indistinguishable from a genuine mid-similarity match. Surfacing
this count lets a caller tell a flattened dimension from real coverage.
cosine_similarity(a, b)
¶
Return cosine similarity normalized from [-1, 1] into [0, 1].
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Sequence[float] | ndarray | None
|
first vector; None or empty means "unknown". |
required |
b
|
Sequence[float] | ndarray | None
|
second vector; None or empty means "unknown". |
required |
Returns:
| Type | Description |
|---|---|
float
|
The normalized similarity, or the neutral |
float
|
is missing or has zero norm. |
jaccard(left, right)
¶
Return the Jaccard similarity of two tag sets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
left
|
set[str]
|
first tag set (e.g. seed styles). |
required |
right
|
set[str]
|
second tag set (e.g. candidate styles). |
required |
Returns:
| Type | Description |
|---|---|
float
|
|
float
|
when both sets are empty (no information either way) and |
float
|
when exactly one side is empty (known mismatch). |
average_or_none(values)
¶
Return the mean of the non-None values, or None when there are none.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
Iterable[float | int | None]
|
numbers, any of which may be None. |
required |
Returns:
| Type | Description |
|---|---|
float | None
|
The arithmetic mean over the concrete values, or None when every |
float | None
|
value is None. |
track_popularity_value(track)
¶
Return the best available popularity value on a 0-100 scale.
build_seed_profile(seed_tracks, vectors)
¶
Aggregate one or more seed tracks into a single recommendation profile.
The sonic embedding is the element-wise mean of the seed vectors (None when no seed has a vector); styles and genres are the union across seeds; year and popularity are averaged over the seeds that have them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seed_tracks
|
Sequence[Track]
|
the resolved seed tracks. |
required |
vectors
|
SonicVectors
|
the query-time Plex sonic vector store. |
required |
Returns:
| Type | Description |
|---|---|
SeedProfile
|
The aggregated seed profile used for candidate generation and |
SeedProfile
|
scoring. |
popularity_proximity(seed_popularity, candidate_popularity)
¶
Score how close a candidate's popularity is to the seed's.
Popularity is a proximity signal, not an absolute boost: a candidate at the seed's popularity scores 1.0, and the score decays linearly to 0.0 at 100 popularity points of distance (the full 0-100 scale).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seed_popularity
|
float | None
|
seed popularity on a 0-100 scale, or None. |
required |
candidate_popularity
|
float | None
|
candidate popularity on a 0-100 scale, or None. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Proximity in [0, 1], or the neutral |
float
|
unknown. |
era_proximity(seed_year, candidate_year)
¶
Score how close a candidate's release year is to the seed's.
Same-year releases score 1.0; the score decays linearly to 0.0 at 50 years of distance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seed_year
|
int | None
|
seed release year, or None. |
required |
candidate_year
|
int | None
|
candidate release year, or None. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Proximity in [0, 1], or the neutral |
float
|
unknown. |
novelty_score(play_count)
¶
Score a candidate's novelty from its local play count.
Never-played tracks score 1.0; the score decays as 1 / (1 + 0.2 *
plays), so 5 plays yields ~0.5 and 20 plays ~0.2.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
play_count
|
int | None
|
local play count; None is treated as 0. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Novelty in (0, 1]. |
calculate_score(candidate, seed, weights, vectors)
¶
Score one candidate against a seed profile on all six signals.
Each signal produces a component in [0, 1]; signals with missing data
(no sonic vector, unknown popularity/year, empty tag sets on both sides)
contribute the neutral 0.5 rather than zero, so missing data neither
rewards nor punishes a candidate. The total is the weighted mean of the
components — weights are normalized by their sum, so absolute weight
values only control relative importance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
candidate
|
Track
|
the track to score. |
required |
seed
|
SeedProfile
|
the aggregated seed profile. |
required |
weights
|
Weights
|
per-signal weights. |
required |
vectors
|
SonicVectors
|
the query-time Plex sonic vector store. |
required |
Returns:
| Type | Description |
|---|---|
ScoreBreakdown
|
The total score plus every component score for explainability. |
Candidates¶
musicseed.recommender.candidates
¶
Candidate generation for multi-signal recommendations.
CandidatePool
¶
Bases: BaseModel
Merged candidate IDs and the signal sources that produced each candidate.
track_ids
property
¶
All distinct candidate track ids in the pool.
add(track_id, source, seed_ids)
¶
Record that source produced track_id as a candidate.
Seed tracks are silently skipped — seeds are never their own candidates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
track_id
|
int
|
local id of the candidate track. |
required |
source
|
str
|
name of the signal source (e.g. |
required |
seed_ids
|
set[int]
|
ids of the seed tracks to exclude. |
required |
add_many(track_ids, source, seed_ids)
¶
Record several candidate ids from one source (see add).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
track_ids
|
list[int]
|
local ids of the candidate tracks. |
required |
source
|
str
|
name of the signal source that produced them. |
required |
seed_ids
|
set[int]
|
ids of the seed tracks to exclude. |
required |
sources_for(track_id)
¶
Return the sorted source names that produced a candidate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
track_id
|
int
|
local id of the candidate track. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
Sorted source names, or an empty list for an unknown id. |
build_candidate_pool(session, seed, vectors, *, limit, year_min=None, year_max=None)
¶
Build a candidate pool from all available recommendation signals.
Each signal contributes its own bounded query (capped at
max(limit * 4, 50) ids) so the pool is a generous superset that the
scorer later trims — sources are only included when the seed profile has
data for them:
sonic— nearest neighbors of the seed embedding in Plex's vectors (ranked in memory; the year window is still applied in SQL),genre/style— tracks sharing any seed genre/style,era— tracks closest to the seed year,popularity— tracks closest to the seed popularity,novelty— least-played tracks first (always included).
The year window filters every source before its limit is applied, so a
narrow window searches within the window rather than truncating after
the fact. Seed tracks are excluded by CandidatePool.add.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
open database session. |
required |
seed
|
SeedProfile
|
the aggregated seed profile. |
required |
vectors
|
SonicVectors
|
the query-time Plex sonic vector store. |
required |
limit
|
int
|
requested number of final recommendations; per-source queries are bounded to a multiple of this. |
required |
year_min
|
int | None
|
only consider tracks released in this year or later. |
None
|
year_max
|
int | None
|
only consider tracks released in this year or earlier. |
None
|
Returns:
| Type | Description |
|---|---|
CandidatePool
|
The merged candidate pool, recording which sources produced each |
CandidatePool
|
candidate. |
Playlist¶
musicseed.recommender.playlist
¶
Playlist recommendation orchestration.
Recommendation
¶
Bases: BaseModel
One recommended track with its score breakdown and candidate sources.
resolve_seed_tracks(session, *, seed_texts=None, seed_ids=None)
¶
Resolve seed IDs and seed text queries into loaded Track objects.
Text seeds accept "Artist - Title" or a bare title; exact
case-insensitive matches win, otherwise a substring search must narrow to
exactly one track. Results are deduplicated, preserving input order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
open database session. |
required |
seed_texts
|
Sequence[str] | None
|
seed tracks as text queries. |
None
|
seed_ids
|
Sequence[int] | None
|
seed tracks by local database id. |
None
|
Returns:
| Type | Description |
|---|---|
list[Track]
|
The resolved seed tracks with artist, album, tags, and stats eagerly |
list[Track]
|
loaded. |
Raises:
| Type | Description |
|---|---|
ValueError
|
when a seed id or text matches no track, a text seed is ambiguous (multiple matches), or no seeds were given at all. |
recommend_tracks(session, *, seed_texts=None, seed_ids=None, limit=50, weights=None, year_min=None, year_max=None, max_tracks_per_artist=3, min_score=None)
¶
Generate recommendations using multi-source candidates and constrained selection.
Pipeline: resolve the seeds, aggregate them into a SeedProfile, build
a multi-source candidate pool, score every candidate against the profile,
then select greedily in descending total score. Selection enforces the
artist diversity constraint — at most max_tracks_per_artist tracks
per artist — and stops at limit tracks or at the first candidate
below min_score. Seed tracks are never recommended.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
open database session. |
required |
seed_texts
|
Sequence[str] | None
|
seed tracks as text queries (see |
None
|
seed_ids
|
Sequence[int] | None
|
seed tracks by local database id. |
None
|
limit
|
int
|
maximum number of recommendations to select. |
50
|
weights
|
Weights | None
|
signal weights; defaults to |
None
|
year_min
|
int | None
|
only recommend tracks released in this year or later. |
None
|
year_max
|
int | None
|
only recommend tracks released in this year or earlier. |
None
|
max_tracks_per_artist
|
int
|
artist diversity cap applied during selection. |
3
|
min_score
|
float | None
|
drop recommendations with a total score below this value. |
None
|
Returns:
| Type | Description |
|---|---|
list[Track]
|
|
list[Recommendation]
|
reports how many candidate tracks had a real Plex sonic vector. |
Raises:
| Type | Description |
|---|---|
ValueError
|
if |
Populate¶
musicseed.recommender.populate
¶
Recommendation strategies for populating an existing playlist.
PopulateMethod = Literal['average', 'frequency']
module-attribute
¶
Playlist populate strategies: "average" scores against the playlist's
mean profile; "frequency" aggregates per-track votes.
populate_average(session, playlist_track_ids, *, limit, weights=None, year_min=None, year_max=None, max_tracks_per_artist=3, min_score=None)
¶
Recommend tracks against the mean sonic/metadata profile of a playlist.
Tracks already in the playlist are excluded automatically because they are
the seed set, and recommend_tracks never returns seed tracks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
open database session. |
required |
playlist_track_ids
|
list[int]
|
local ids of the playlist's tracks, used as seeds. |
required |
limit
|
int
|
maximum number of recommendations to return. |
required |
weights
|
Weights | None
|
signal weights; defaults to |
None
|
year_min
|
int | None
|
only recommend tracks released in this year or later. |
None
|
year_max
|
int | None
|
only recommend tracks released in this year or earlier. |
None
|
max_tracks_per_artist
|
int
|
artist diversity cap applied during selection. |
3
|
min_score
|
float | None
|
drop recommendations with a total score below this value. |
None
|
Returns:
| Type | Description |
|---|---|
list[Recommendation]
|
Scored recommendations, best first. |
populate_frequency(session, playlist_track_ids, *, limit, per_seed_limit=30, weights=None, year_min=None, year_max=None, max_tracks_per_artist=3, min_score=None)
¶
Recommend tracks voted for by multiple individual playlist tracks.
Each playlist track is used as its own single-track seed to gather candidates. A candidate's score is the average of its per-seed scores across every seed that recommended it (its "votes"); results are ranked by that average score, with vote count as a tiebreaker, so --limit cuts at the highest-scoring candidates. This avoids a literal set-intersection across seeds, which collapses to empty once a playlist has more than a handful of tracks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
open database session. |
required |
playlist_track_ids
|
list[int]
|
local ids of the playlist's tracks, each used as an individual seed. |
required |
limit
|
int
|
maximum number of recommendations to return. |
required |
per_seed_limit
|
int
|
candidates gathered per playlist track. |
30
|
weights
|
Weights | None
|
signal weights; defaults to |
None
|
year_min
|
int | None
|
only recommend tracks released in this year or later. |
None
|
year_max
|
int | None
|
only recommend tracks released in this year or earlier. |
None
|
max_tracks_per_artist
|
int
|
artist diversity cap applied during selection. |
3
|
min_score
|
float | None
|
drop recommendations with a total score below this value. |
None
|
Returns:
| Type | Description |
|---|---|
list[Recommendation]
|
Aggregated recommendations, best first; each recommendation's |
list[Recommendation]
|
|
populate_playlist_recommendations(session, playlist_track_ids, *, method='average', limit=10, per_seed_limit=30, weights=None, year_min=None, year_max=None, max_tracks_per_artist=3, min_score=None)
¶
Dispatch to the requested populate strategy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
Session
|
open database session. |
required |
playlist_track_ids
|
list[int]
|
local ids of the playlist's tracks. |
required |
method
|
PopulateMethod
|
|
'average'
|
limit
|
int
|
maximum number of recommendations to return. |
10
|
per_seed_limit
|
int
|
candidates gathered per playlist track ("frequency" method only). |
30
|
weights
|
Weights | None
|
signal weights; defaults to |
None
|
year_min
|
int | None
|
only recommend tracks released in this year or later. |
None
|
year_max
|
int | None
|
only recommend tracks released in this year or earlier. |
None
|
max_tracks_per_artist
|
int
|
artist diversity cap applied during selection. |
3
|
min_score
|
float | None
|
drop recommendations with a total score below this value. |
None
|
Returns:
| Type | Description |
|---|---|
list[Recommendation]
|
Scored recommendations, best first. |
Raises:
| Type | Description |
|---|---|
ValueError
|
if |