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 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
scores.Add(FuzzyMatcher.CalculateSimilarity(query, title));
if (!string.IsNullOrEmpty(artist))
scores.Add(FuzzyMatcher.CalculateSimilarity(query, artist));
if (!string.IsNullOrEmpty(album))
scores.Add(FuzzyMatcher.CalculateSimilarity(query, album));
var fieldText = $"{title} {artist} {album}".ToLower();
var fieldTokens = fieldText
.Split(new[] { ' ', '-', '_' }, StringSplitOptions.RemoveEmptyEntries)
.ToList();
// Two-field combinations
if (!string.IsNullOrEmpty(artist))
if (queryTokens.Count == 0) return (item, 0);
// 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}"));
scores.Add(FuzzyMatcher.CalculateSimilarity(query, $"{artist} {title}"));
}
if (!string.IsNullOrEmpty(album))
// Check if any field token matches this query token
var hasMatch = fieldTokens.Any(fieldToken =>
{
scores.Add(FuzzyMatcher.CalculateSimilarity(query, $"{title} {album}"));
scores.Add(FuzzyMatcher.CalculateSimilarity(query, $"{album} {title}"));
}
if (!string.IsNullOrEmpty(artist) && !string.IsNullOrEmpty(album))
{
scores.Add(FuzzyMatcher.CalculateSimilarity(query, $"{artist} {album}"));
scores.Add(FuzzyMatcher.CalculateSimilarity(query, $"{album} {artist}"));
// Exact match or substring match
if (fieldToken.Contains(queryToken) || queryToken.Contains(fieldToken))
return true;
// Fuzzy match with Levenshtein distance
var similarity = FuzzyMatcher.CalculateSimilarity(queryToken, fieldToken);
return similarity >= 70; // 70% similarity threshold for individual words
});
if (hasMatch) matchedTokens++;
}
// Three-field combinations (all permutations)
if (!string.IsNullOrEmpty(artist) && !string.IsNullOrEmpty(album))
{
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();
// Score = percentage of query tokens that matched
var baseScore = (matchedTokens * 100) / queryTokens.Count;
// Give external results a small boost (+5 points) to prioritize the larger catalog
var finalScore = isExternal ? Math.Min(100, baseScore + 5) : baseScore;