New, fully-additive module — the heart of the 'expand Because You Listen To into a real
listening-driven block' plan. Two pure functions, no DB/network/config:
- rank_recommended_artists(seeds, similars_by_seed, owned): consensus-ranked artists you'd love
but don't own. Score = Σ over endorsing seeds of (play_weight × similarity) — rewards consensus,
play weight and similarity strength in one sum. Excludes owned + seeds; min_seed_count is the
adventurousness dial's lever; exposes seed_count + which seeds ('because you like A, B, C').
- aggregate_candidate_tracks(recs, top_tracks_by_artist, owned): per-artist-capped, deduped,
rank-ordered candidate list for the generated playlist; exclude_owned toggles discovery vs replay.
11 tests (consensus vs single, play-weight, similarity, owned/seed exclusion, min_seed_count,
case-insensitive dedup, per-artist cap, owned exclusion, total limit, empty-artist skip). Nothing
existing touched — wiring into the watchlist scan + playlist sync comes next.