/** * Normalize a team/participant name for fuzzy matching across data sources. * Folds accents (so "Stéfanos Tsitsipás" matches a plain "Stefanos Tsitsipas"), * lowercases, trims, and collapses whitespace. */ export function normalizeTeamName(name: string): string { return name .normalize("NFKD") .replace(/[̀-ͯ]/g, "") // strip combining diacritical marks .toLowerCase() .trim() .replace(/\s+/g, " "); } /** * Try to match `apiName` to one of the participant names. * Returns the matched participant name, or null if no match found. * * Strategy: * 1. Exact normalized match (case-insensitive) * 2. Substring: one contains the other (handles "Golden State" vs "Golden State Warriors") * * Note: City abbreviations like "LA" ↔ "Los Angeles" are NOT handled here — the * NBA/NHL APIs typically return full city names so the admin should enter full names * in the participant list to ensure reliable matching. */ export function findMatchingTeamName( apiName: string, participantNames: string[] ): string | null { const normalizedApi = normalizeTeamName(apiName); // Guard: empty API name should never match anything if (!normalizedApi) return null; // 1. Exact match for (const name of participantNames) { if (normalizeTeamName(name) === normalizedApi) return name; } // 2. Substring match (only when the shorter string is at least 4 chars to avoid false positives) for (const name of participantNames) { const normalizedParticipant = normalizeTeamName(name); if ( normalizedApi.length >= 4 && normalizedParticipant.length >= 4 && (normalizedApi.includes(normalizedParticipant) || normalizedParticipant.includes(normalizedApi)) ) { return name; } } return null; }