Files
qbz/crates/qbz-external-reco/Cargo.toml
T
vicrodh 408b3c8308 feat(reco): external-recommendations engine (Last.fm + ListenBrainz -> Qobuz)
New frontend-agnostic crate qbz-external-reco: blends Last.fm + ListenBrainz
recommendation signals into the Discover "External Recommendations" carousels,
validating every candidate against the Qobuz catalog (ISRC direct -> MusicBrainz
inc=isrcs bridge -> fuzzy text scorer, gated) before display. Four carousels
(C1 similar artists / C2 similar tracks you haven't heard, C3 listened-but-not-
recently, C4 deep cuts from known artists) capped at 20 with daily rotation over
a larger cache-backed pool, plus a Qobuz-editorial cold-start fallback (top
albums + artists) when no external source is connected. Round-robin blend across
sources. Resolution cache (per-user SQLite, 30d hits / 7d misses, WAL).

Supporting public read methods added to qbz-integrations:
- Last.fm: get_top_artists / get_top_tracks / get_loved_tracks /
  get_recent_tracks / get_similar_tracks (public reads via the Cloudflare proxy).
- ListenBrainz: get_cf_recommendations / get_recent_listens /
  get_metadata_recordings (204/404 -> empty, never error).
- MusicBrainz: get_recording_isrcs (the MBID -> ISRC bridge).

Engine is headless and unit-tested (matching + cache); no qbz-ui dependency.

cc
2026-06-27 02:37:32 -06:00

26 lines
660 B
TOML

[package]
name = "qbz-external-reco"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "Frontend-agnostic external-recommendations engine (Last.fm + ListenBrainz -> Qobuz) for Discover"
[dependencies]
qbz-integrations = { path = "../qbz-integrations" }
qbz-models = { path = "../qbz-models" }
# Async
tokio = { workspace = true }
async-trait = { workspace = true }
futures-util = { workspace = true }
# Serialization
serde = { workspace = true }
serde_json = { workspace = true }
# Cache (per-user SQLite, WAL per ADR-002)
rusqlite = { version = "0.31", features = ["bundled"] }
# Logging
log = { workspace = true }