diff --git a/shanty-playlist/src/scoring.rs b/shanty-playlist/src/scoring.rs index 41aa5e8..4cf8119 100644 --- a/shanty-playlist/src/scoring.rs +++ b/shanty-playlist/src/scoring.rs @@ -59,29 +59,46 @@ pub fn score_tracks( for track in local_tracks { let title_lower = track.title.as_ref().map(|t| t.to_lowercase()); - // Match by: exact title, MBID, or prefix/contains (for parenthetical suffixes) - let playcount = title_lower + // Match by: exact title, MBID, and prefix — take the MAXIMUM playcount + // across all methods so a popular base track isn't hidden by a less + // popular variant that happens to match exactly. + let mut best_playcount: Option = None; + let mut consider = |pc: u64| { + best_playcount = Some(best_playcount.map_or(pc, |cur: u64| cur.max(pc))); + }; + + // Exact title match + if let Some(pc) = title_lower .as_ref() .and_then(|t| playcount_by_name.get(t).copied()) - .or_else(|| { - track - .musicbrainz_id - .as_ref() - .and_then(|id| playcount_by_mbid.get(id).copied()) - }) - .or_else(|| { - // Fuzzy: local title starts with a top track name, or vice versa - title_lower.as_ref().and_then(|local| { - playcount_by_name - .iter() - .filter(|(top_name, _)| { - local.starts_with(top_name.as_str()) - || top_name.starts_with(local.as_str()) - }) - .max_by_key(|&(_, &pc)| pc) - .map(|(_, &pc)| pc) + { + consider(pc); + } + + // MBID match + if let Some(pc) = track + .musicbrainz_id + .as_ref() + .and_then(|id| playcount_by_mbid.get(id).copied()) + { + consider(pc); + } + + // Prefix match: local title starts with a top track name, or vice versa + if let Some(local) = title_lower.as_ref() { + if let Some((_, &pc)) = playcount_by_name + .iter() + .filter(|(top_name, _)| { + local.starts_with(top_name.as_str()) + || top_name.starts_with(local.as_str()) }) - }); + .max_by_key(|&(_, &pc)| pc) + { + consider(pc); + } + } + + let playcount = best_playcount; // If we have popularity data, use it; unmatched tracks get a low base score let (popularity, similarity, score) = if !playcount_by_name.is_empty() {