refactor: use token-based fuzzy matching for flexible search
Some checks failed
CI / build-and-test (push) Has been cancelled

This commit is contained in:
2026-01-31 01:03:14 -05:00
parent d4036095f1
commit 6f91361966

View File

@@ -1785,45 +1785,39 @@ public class JellyfinController : ControllerBase
var artist = artistField(item) ?? ""; var artist = artistField(item) ?? "";
var album = albumField(item) ?? ""; var album = albumField(item) ?? "";
var scores = new List<int>(); // Token-based fuzzy matching: split query and fields into words
var queryTokens = query.ToLower()
.Split(new[] { ' ', '-', '_' }, StringSplitOptions.RemoveEmptyEntries)
.ToList();
// Individual field scores var fieldText = $"{title} {artist} {album}".ToLower();
scores.Add(FuzzyMatcher.CalculateSimilarity(query, title)); var fieldTokens = fieldText
if (!string.IsNullOrEmpty(artist)) .Split(new[] { ' ', '-', '_' }, StringSplitOptions.RemoveEmptyEntries)
scores.Add(FuzzyMatcher.CalculateSimilarity(query, artist)); .ToList();
if (!string.IsNullOrEmpty(album))
scores.Add(FuzzyMatcher.CalculateSimilarity(query, album));
// Two-field combinations if (queryTokens.Count == 0) return (item, 0);
if (!string.IsNullOrEmpty(artist))
// Count how many query tokens match field tokens (with fuzzy tolerance)
var matchedTokens = 0;
foreach (var queryToken in queryTokens)
{ {
scores.Add(FuzzyMatcher.CalculateSimilarity(query, $"{title} {artist}")); // Check if any field token matches this query token
scores.Add(FuzzyMatcher.CalculateSimilarity(query, $"{artist} {title}")); var hasMatch = fieldTokens.Any(fieldToken =>
}
if (!string.IsNullOrEmpty(album))
{ {
scores.Add(FuzzyMatcher.CalculateSimilarity(query, $"{title} {album}")); // Exact match or substring match
scores.Add(FuzzyMatcher.CalculateSimilarity(query, $"{album} {title}")); if (fieldToken.Contains(queryToken) || queryToken.Contains(fieldToken))
} return true;
if (!string.IsNullOrEmpty(artist) && !string.IsNullOrEmpty(album))
{ // Fuzzy match with Levenshtein distance
scores.Add(FuzzyMatcher.CalculateSimilarity(query, $"{artist} {album}")); var similarity = FuzzyMatcher.CalculateSimilarity(queryToken, fieldToken);
scores.Add(FuzzyMatcher.CalculateSimilarity(query, $"{album} {artist}")); return similarity >= 70; // 70% similarity threshold for individual words
});
if (hasMatch) matchedTokens++;
} }
// Three-field combinations (all permutations) // Score = percentage of query tokens that matched
if (!string.IsNullOrEmpty(artist) && !string.IsNullOrEmpty(album)) var baseScore = (matchedTokens * 100) / queryTokens.Count;
{
scores.Add(FuzzyMatcher.CalculateSimilarity(query, $"{title} {artist} {album}"));
scores.Add(FuzzyMatcher.CalculateSimilarity(query, $"{title} {album} {artist}"));
scores.Add(FuzzyMatcher.CalculateSimilarity(query, $"{artist} {title} {album}"));
scores.Add(FuzzyMatcher.CalculateSimilarity(query, $"{artist} {album} {title}"));
scores.Add(FuzzyMatcher.CalculateSimilarity(query, $"{album} {title} {artist}"));
scores.Add(FuzzyMatcher.CalculateSimilarity(query, $"{album} {artist} {title}"));
}
// Use the best score from all attempts
var baseScore = scores.Max();
// Give external results a small boost (+5 points) to prioritize the larger catalog // Give external results a small boost (+5 points) to prioritize the larger catalog
var finalScore = isExternal ? Math.Min(100, baseScore + 5) : baseScore; var finalScore = isExternal ? Math.Min(100, baseScore + 5) : baseScore;