The Kenmei manga entry.
The AniList manga entry.
Optional partial match engine configuration.
An object containing confidence, isExactMatch, and matchedField.
export function scoreMatch(
kenmeiManga: KenmeiManga,
anilistManga: AniListManga,
config: Partial<MatchEngineConfig> = {},
): { confidence: number; isExactMatch: boolean; matchedField: string } {
const matchConfig = { ...DEFAULT_MATCH_CONFIG, ...config };
const {
caseSensitive,
preferEnglishTitles,
preferRomajiTitles,
useAlternativeTitles,
} = matchConfig;
// Normalize the Kenmei title
const kenmeiTitle = normalizeString(kenmeiManga.title, caseSensitive);
// Skip extremely short titles (likely errors)
if (kenmeiTitle.length < matchConfig.minTitleLength) {
return { confidence: 0, isExactMatch: false, matchedField: "none" };
}
// Array to store all similarity scores with their sources
const scores: Array<{ field: string; score: number }> = [];
// Check primary titles based on preferences
// We'll calculate similarity for all titles but weight them differently later
if (anilistManga.title.english) {
const englishScore = calculateSimilarity(
kenmeiTitle,
anilistManga.title.english,
matchConfig,
);
scores.push({ field: "english", score: englishScore });
// Exact match check
if (englishScore === 100) {
return { confidence: 100, isExactMatch: true, matchedField: "english" };
}
}
if (anilistManga.title.romaji) {
const romajiScore = calculateSimilarity(
kenmeiTitle,
anilistManga.title.romaji,
matchConfig,
);
scores.push({ field: "romaji", score: romajiScore });
// Exact match check
if (romajiScore === 100) {
return { confidence: 100, isExactMatch: true, matchedField: "romaji" };
}
}
if (anilistManga.title.native) {
const nativeScore = calculateSimilarity(
kenmeiTitle,
anilistManga.title.native,
matchConfig,
);
scores.push({ field: "native", score: nativeScore });
// Exact match check
if (nativeScore === 100) {
return { confidence: 100, isExactMatch: true, matchedField: "native" };
}
}
// Check alternative titles if enabled
if (
useAlternativeTitles &&
anilistManga.synonyms &&
anilistManga.synonyms.length > 0
) {
for (const synonym of anilistManga.synonyms) {
if (!synonym) continue;
const synonymScore = calculateSimilarity(
kenmeiTitle,
synonym,
matchConfig,
);
scores.push({ field: "synonym", score: synonymScore });
// Exact match check
if (synonymScore === 100) {
return { confidence: 100, isExactMatch: true, matchedField: "synonym" };
}
}
}
// Check Kenmei alternative titles against AniList titles
if (
useAlternativeTitles &&
kenmeiManga.alternative_titles &&
kenmeiManga.alternative_titles.length > 0
) {
for (const altTitle of kenmeiManga.alternative_titles) {
if (!altTitle) continue;
const normalizedAltTitle = normalizeString(altTitle, caseSensitive);
// Skip very short alternative titles
if (normalizedAltTitle.length < matchConfig.minTitleLength) continue;
// Check against each AniList title field
if (anilistManga.title.english) {
const altEnglishScore = calculateSimilarity(
normalizedAltTitle,
anilistManga.title.english,
matchConfig,
);
scores.push({ field: "alt_to_english", score: altEnglishScore });
if (altEnglishScore === 100) {
return {
confidence: 95,
isExactMatch: true,
matchedField: "alt_to_english",
};
}
}
if (anilistManga.title.romaji) {
const altRomajiScore = calculateSimilarity(
normalizedAltTitle,
anilistManga.title.romaji,
matchConfig,
);
scores.push({ field: "alt_to_romaji", score: altRomajiScore });
if (altRomajiScore === 100) {
return {
confidence: 95,
isExactMatch: true,
matchedField: "alt_to_romaji",
};
}
}
}
}
// If we have no scores, return zero confidence
if (scores.length === 0) {
return { confidence: 0, isExactMatch: false, matchedField: "none" };
}
// Get the highest score and its field
scores.sort((a, b) => b.score - a.score);
const topScore = scores[0];
// Apply title preference weighting
let adjustedScore = topScore.score;
if (topScore.field === "english" && preferEnglishTitles) {
adjustedScore = Math.min(100, adjustedScore * 1.05);
} else if (topScore.field === "romaji" && preferRomajiTitles) {
adjustedScore = Math.min(100, adjustedScore * 1.05);
}
// Consider an "exact match" if the confidence is very high
const isExactMatch = adjustedScore >= 95;
return {
confidence: Math.round(adjustedScore),
isExactMatch,
matchedField: topScore.field,
};
}
Scores a match between a Kenmei manga and an AniList manga entry. Returns a score between 0-100 and information about the match.