From 8aba91a2dcc1db14815f9ef0e25f18026f4abea0 Mon Sep 17 00:00:00 2001 From: Fuwn Date: Sun, 1 Mar 2026 13:09:43 -0800 Subject: feat(match): skip ambiguous subtitle matches via score margin --- src/lib/Media/Anime/Airing/Subtitled/match.ts | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/lib/Media/Anime/Airing') diff --git a/src/lib/Media/Anime/Airing/Subtitled/match.ts b/src/lib/Media/Anime/Airing/Subtitled/match.ts index dc8dbff9..661d0d23 100644 --- a/src/lib/Media/Anime/Airing/Subtitled/match.ts +++ b/src/lib/Media/Anime/Airing/Subtitled/match.ts @@ -67,6 +67,7 @@ const isMeaningfulToken = (token: string): boolean => const MIN_MATCH_SCORE = 0.3; const MIN_TOKEN_OVERLAP = 2; +const MIN_MATCH_MARGIN = 0.08; interface SimilarityAnalysis { score: number; @@ -124,6 +125,7 @@ export const findClosestMatch = (times: Time[], anime: Media): Time | null => { })); let bestMatch: Time | null = null; let bestScore = 0; + let secondBestScore = 0; let bestTokenOverlap = 0; let bestNumericTokenOverlap = 0; const searchTitles = [anime.title.romaji, anime.title.english, ...anime.synonyms].filter(Boolean); @@ -137,15 +139,19 @@ export const findClosestMatch = (times: Time[], anime: Media): Time | null => { const similarity = calculateWeightedSimilarity(normalizedSearchTitle, normalized); if (similarity.score > bestScore) { + secondBestScore = bestScore; bestScore = similarity.score; bestTokenOverlap = similarity.tokenOverlap; bestNumericTokenOverlap = similarity.numericTokenOverlap; bestMatch = time; + } else if (similarity.score > secondBestScore) { + secondBestScore = similarity.score; } } } if (bestScore < MIN_MATCH_SCORE) return null; + if (bestScore - secondBestScore < MIN_MATCH_MARGIN) return null; if (bestNumericTokenOverlap === 0 && bestTokenOverlap < MIN_TOKEN_OVERLAP) return null; return bestMatch; -- cgit v1.2.3