feat(reco): order For You favorites by trained taste score
Reorder the resolved favorite albums so the reco-scored favorites lead (taste order from the trained favorite_album scores), keeping unscored albums in their original relative order. When reco is cold/disabled the row keeps the original Qobuz order, so a fresh store never regresses. The reorder flows into the Favorites, Rediscover, Radio and more-from-library seed (all share fav_albums). cc
This commit is contained in:
2 files changed
+35
No files matched your search
@@ -444,6 +444,19 @@ fn build_rediscover(
|
||||
/// Favorite Albums — the user's favorited albums, capped at 18, in favorite
|
||||
/// order (matches Tauri's home-resolved favoriteAlbums sliced 18; unfiltered,
|
||||
/// unlike Rediscover).
|
||||
/// Reorder resolved albums so the reco-scored favorites lead (taste order),
|
||||
/// keeping unscored albums in their original relative order. `scored` is the
|
||||
/// reco favorite-album id order; `None`/empty leaves the input untouched, so a
|
||||
/// cold reco store never reorders (no regression).
|
||||
fn order_by_score(mut albums: Vec<Album>, scored: Option<&[String]>) -> Vec<Album> {
|
||||
if let Some(order) = scored {
|
||||
if !order.is_empty() {
|
||||
albums.sort_by_key(|a| order.iter().position(|id| id == &a.id).unwrap_or(usize::MAX));
|
||||
}
|
||||
}
|
||||
albums
|
||||
}
|
||||
|
||||
fn build_favorite_albums(fav_albums: &[Album]) -> Vec<AlbumCard> {
|
||||
fav_albums.iter().take(18).cloned().map(map_album).collect()
|
||||
}
|
||||
@@ -863,6 +876,10 @@ pub fn spawn_for_you<A>(
|
||||
let recent_ids = recent_ids.clone();
|
||||
Box::pin(async move {
|
||||
let fav_albums = fetch_fav_albums(&runtime).await;
|
||||
// reco: lead with the highest-scored favorites (trained taste
|
||||
// order) when the store is warm; cold -> original Qobuz order.
|
||||
let scored_fav = crate::reco::scored_favorite_album_ids(80);
|
||||
let fav_albums = order_by_score(fav_albums, scored_fav.as_deref());
|
||||
apply_favorite_albums(&weak, &cache, build_favorite_albums(&fav_albums));
|
||||
// reco: backfill genres for the resolved favorite albums so the
|
||||
// engine's top-genres has data (plays alone carry no genre).
|
||||
|
||||
@@ -194,6 +194,24 @@ pub fn forgotten_favorite_album_ids(limit: u32, recency_days: u32) -> Option<Vec
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// The reco-scored favorite album ids in taste order (highest first) when the
|
||||
/// store is warm (trained); `None` when reco is cold/disabled so the caller
|
||||
/// keeps its original ordering. Bounded by `limit`.
|
||||
pub fn scored_favorite_album_ids(limit: u32) -> Option<Vec<String>> {
|
||||
let limits = HomeSeedLimits {
|
||||
recent_albums: 0,
|
||||
continue_tracks: 0,
|
||||
top_artists: 0,
|
||||
favorites: limit,
|
||||
};
|
||||
let seeds = home_seeds(limits)?;
|
||||
if seeds.favorite_album_ids.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(seeds.favorite_album_ids)
|
||||
}
|
||||
}
|
||||
|
||||
/// Backfill genres `(album_id, genre_id, genre_name)` onto reco events +
|
||||
/// album-meta once albums are resolved. Best-effort, blocking SQLite — call
|
||||
/// from `spawn_blocking`. Plays carry no genre, so this is what feeds
|
||||
|
||||
Reference in new issue
Block a user