radio/similar resolved ids one get_by_id at a time (N round-trips). Add
TrackRepository.get_many and use it (plus ArtistRepository.get_many) to
hydrate in a single query, preserving requested order.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
POST /radio + /radio/next (stateless infinite feed: seed track / from-likes,
exploration mix, client-passed exclude_ids) and GET /tracks|artists/{id}/similar,
replacing the stubs. Recommender port abstracts the (future) ML service —
NullRecommender is wired now so RecommendationService always uses its metadata
heuristics (genre/artist similarity, random exploration filler), never a hard ML
dependency. Adds TrackRepository.list_similar/sample_playable + Artist.list_similar,
reason codes for the client, RemoteRecommender skeleton (TODO: ML contract).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>