feat(reco): radio + similar with metadata fallback (§6.5)
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>
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
"""Radio + similarity response schemas (§6.5)."""
|
||||
|
||||
import uuid
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.api.schemas.artist import ArtistOut
|
||||
from app.api.schemas.track import TrackOut
|
||||
|
||||
|
||||
class RadioRequest(BaseModel):
|
||||
"""Start or continue a radio. ``seed_track_id`` seeds from a track;
|
||||
``from_likes`` seeds from the caller's likes. ``exclude_ids`` are already-
|
||||
queued tracks to skip (the client drives the infinite feed). ``exploration``
|
||||
biases familiar↔new."""
|
||||
|
||||
seed_track_id: uuid.UUID | None = None
|
||||
from_likes: bool = False
|
||||
exploration: float = Field(default=0.25, ge=0.0, le=1.0)
|
||||
count: int = Field(default=20, ge=1, le=50)
|
||||
exclude_ids: list[uuid.UUID] = Field(default_factory=list)
|
||||
|
||||
|
||||
class RadioTrackOut(BaseModel):
|
||||
track: TrackOut
|
||||
# Short code the client localizes: ml | similar | from_likes | discover.
|
||||
reason: str
|
||||
|
||||
|
||||
class RadioResponse(BaseModel):
|
||||
# Where the picks came from: "ml" or "metadata" (fallback).
|
||||
source: str
|
||||
tracks: list[RadioTrackOut]
|
||||
|
||||
|
||||
class SimilarTracksOut(BaseModel):
|
||||
source: str
|
||||
tracks: list[TrackOut]
|
||||
|
||||
|
||||
class SimilarArtistsOut(BaseModel):
|
||||
source: str
|
||||
artists: list[ArtistOut]
|
||||
Reference in New Issue
Block a user