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:
+29
-3
@@ -1,14 +1,20 @@
|
||||
"""Artist endpoints."""
|
||||
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Query
|
||||
|
||||
from app.api.deps import AlbumRepoDep, ArtistRepoDep, CurrentUser, TrackRepoDep
|
||||
from app.api.deps import (
|
||||
AlbumRepoDep,
|
||||
ArtistRepoDep,
|
||||
CurrentUser,
|
||||
RecommendationServiceDep,
|
||||
TrackRepoDep,
|
||||
)
|
||||
from app.api.schemas.album import AlbumOut
|
||||
from app.api.schemas.artist import ArtistOut
|
||||
from app.api.schemas.pagination import PagedResponse
|
||||
from app.api.schemas.radio import SimilarArtistsOut
|
||||
from app.api.schemas.track import TrackOut
|
||||
from app.api.v1.albums import _build_album_out
|
||||
from app.api.v1.tracks import _build_track_out
|
||||
@@ -124,4 +130,24 @@ async def get_artist_tracks(
|
||||
|
||||
|
||||
@router.get("/{artist_id}/similar")
|
||||
async def get_similar_artists(artist_id: uuid.UUID, _: CurrentUser) -> Any: ...
|
||||
async def get_similar_artists(
|
||||
artist_id: uuid.UUID,
|
||||
service: RecommendationServiceDep,
|
||||
artist_repo: ArtistRepoDep,
|
||||
_: CurrentUser,
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
) -> SimilarArtistsOut:
|
||||
"""Artists similar to this one (§6.5). ML when configured, else a shared-
|
||||
genre metadata heuristic."""
|
||||
source, artists = await service.similar_artists(artist_id, limit=limit)
|
||||
items = [
|
||||
ArtistOut(
|
||||
id=a.id,
|
||||
name=a.name,
|
||||
album_count=await artist_repo.album_count(a.id),
|
||||
track_count=await artist_repo.track_count(a.id),
|
||||
created_at=a.created_at,
|
||||
)
|
||||
for a in artists
|
||||
]
|
||||
return SimilarArtistsOut(source=source, artists=items)
|
||||
|
||||
Reference in New Issue
Block a user