fix(home): use hybrid approach for Continue Listening to show recent plays immediately

Instead of relying solely on pre-computed scores (trained on login),
now merge the 4 most recent plays with scored items. This ensures
newly played tracks appear at the top without waiting for retraining.
This commit is contained in:
vicrodh committed 2026-01-11 02:17:37 -06:00
1 parent e864e3272e
commit 5499add37d
1 file changed
+51 -4
+51 -4
View File
@@ -150,16 +150,27 @@ pub async fn reco_get_home_ml(
let db = state.db.lock().await; let db = state.db.lock().await;
let has_scores = db.has_scores("all")?; let has_scores = db.has_scores("all")?;
// HYBRID APPROACH: Always get fresh recent plays first, then supplement with scored
// This ensures newly played tracks appear immediately without waiting for score retraining
// Get truly recent plays (last few) to ensure freshness
let recent_fresh_albums = db.get_recent_album_ids(4)?;
let recent_fresh_tracks = db.get_recent_track_ids(4)?;
let mut recently_played_album_ids = if has_scores { let mut recently_played_album_ids = if has_scores {
db.get_scored_album_ids("all", limit_recent_albums)? let scored = db.get_scored_album_ids("all", limit_recent_albums + 4)?;
merge_unique_preserve_order(recent_fresh_albums, scored, limit_recent_albums as usize)
} else { } else {
Vec::new() db.get_recent_album_ids(limit_recent_albums)?
}; };
let mut continue_listening_track_ids = if has_scores { let mut continue_listening_track_ids = if has_scores {
db.get_scored_track_ids("all", limit_continue_tracks)? let scored = db.get_scored_track_ids("all", limit_continue_tracks + 4)?;
merge_unique_preserve_order(recent_fresh_tracks, scored, limit_continue_tracks as usize)
} else { } else {
Vec::new() db.get_recent_track_ids(limit_continue_tracks)?
}; };
let mut top_artist_ids = if has_scores { let mut top_artist_ids = if has_scores {
db.get_scored_artist_scores("all", limit_top_artists)? db.get_scored_artist_scores("all", limit_top_artists)?
.into_iter() .into_iter()
@@ -171,17 +182,20 @@ pub async fn reco_get_home_ml(
} else { } else {
Vec::new() Vec::new()
}; };
let mut favorite_album_ids = if has_scores { let mut favorite_album_ids = if has_scores {
db.get_scored_album_ids("favorite", limit_favorites)? db.get_scored_album_ids("favorite", limit_favorites)?
} else { } else {
Vec::new() Vec::new()
}; };
let mut favorite_track_ids = if has_scores { let mut favorite_track_ids = if has_scores {
db.get_scored_track_ids("favorite", limit_favorites)? db.get_scored_track_ids("favorite", limit_favorites)?
} else { } else {
Vec::new() Vec::new()
}; };
// Fallbacks for empty results
if recently_played_album_ids.is_empty() { if recently_played_album_ids.is_empty() {
recently_played_album_ids = db.get_recent_album_ids(limit_recent_albums)?; recently_played_album_ids = db.get_recent_album_ids(limit_recent_albums)?;
} }
@@ -207,6 +221,39 @@ pub async fn reco_get_home_ml(
}) })
} }
/// Merge two lists preserving order: fresh items first, then scored items (excluding duplicates)
fn merge_unique_preserve_order<T: Eq + std::hash::Hash + Clone>(
fresh: Vec<T>,
scored: Vec<T>,
limit: usize,
) -> Vec<T> {
use std::collections::HashSet;
let mut seen: HashSet<T> = HashSet::new();
let mut result = Vec::with_capacity(limit);
// Add fresh items first
for item in fresh {
if seen.insert(item.clone()) {
result.push(item);
if result.len() >= limit {
return result;
}
}
}
// Add scored items (excluding already seen)
for item in scored {
if seen.insert(item.clone()) {
result.push(item);
if result.len() >= limit {
return result;
}
}
}
result
}
struct ScoreMaps { struct ScoreMaps {
tracks: HashMap<u64, f64>, tracks: HashMap<u64, f64>,
albums: HashMap<String, f64>, albums: HashMap<String, f64>,