Fix case-sensitive comparison in compare_strings fuzzy matching (#3151)
authorDavid Bishop <teancom@users.noreply.github.com>
Fri, 13 Feb 2026 09:33:02 +0000 (01:33 -0800)
committerGitHub <noreply@github.com>
Fri, 13 Feb 2026 09:33:02 +0000 (10:33 +0100)
In non-strict mode, compare_strings lowercases str1 but passes the
original-case str2 to SequenceMatcher on line 564. This causes fuzzy
matching to penalize case differences, making it fail to match strings
like "Track Feat. John" vs "TRACK FT. JOHN" that should be considered
equivalent.

Also fix the elif branch (line 559) to replace on str2_lower instead
of str2, so the result is consistently lowered.

Co-authored-by: David Bishop <git@gnuconsulting.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
music_assistant/helpers/compare.py
tests/core/test_compare.py

index 81b050296d76af987aacb235f30434a9caa2bfb8..c03d064860af34fa5333f72f7bd7a42bd300206c 100644 (file)
@@ -556,12 +556,12 @@ def compare_strings(str1: str, str2: str, strict: bool = True) -> bool:
     if " & " in str1_lower and " and " in str2_lower:
         str2 = str2_lower.replace(" and ", " & ")
     elif " and " in str1_lower and " & " in str2:
-        str2 = str2.replace(" & ", " and ")
+        str2 = str2_lower.replace(" & ", " and ")
     if create_safe_string(str1) == create_safe_string(str2):
         return True
     # last resort: use difflib to compare strings
     required_accuracy = 0.9 if (len(str1) + len(str2)) > 18 else 0.8
-    return SequenceMatcher(a=str1_lower, b=str2).ratio() > required_accuracy
+    return SequenceMatcher(a=str1_lower, b=str2_lower).ratio() > required_accuracy
 
 
 def compare_version(base_version: str, compare_version: str) -> bool:
index f80711ce6d9b50522ef42680f9f8944a97e70754..578efbe81538a7ddec2ef53f242a2ebbc4ddc994 100644 (file)
@@ -369,3 +369,10 @@ def test_compare_track() -> None:  # noqa: PLR0915
         (ExternalID.MB_RECORDING, "abcd"),
     }
     assert compare.compare_track(track_a, track_b) is False
+
+
+def test_compare_strings_case_insensitive_fuzzy() -> None:
+    """Test that non-strict fuzzy matching is fully case-insensitive."""
+    # These differ slightly ("Feat." vs "FT.") so create_safe_string won't match,
+    # falling through to SequenceMatcher which must compare both strings lowered.
+    assert compare.compare_strings("Track Feat. John", "TRACK FT. JOHN", strict=False) is True