UNPKG

name-match

Version:

A robust name matching library combining multiple algorithms for optimal performance

130 lines (107 loc) 4.13 kB
/** * Enhanced Natural Name Matcher * * Combines the strengths of natural.js and Enhanced Name Matcher * for optimal name matching performance. */ const natural = require('natural'); const EnhancedMatcher = require('./enhanced-matcher'); const { parseName, reorderNameIfNeeded } = require('./name-normalizer'); class EnhancedNaturalMatcher { /** * Create a new EnhancedNaturalMatcher * @param {Object} options - Configuration options */ constructor(options = {}) { // Configure threshold - if average score is >= threshold, names match this.threshold = options.threshold || 0.75; // Initialize the component matchers this.enhancedMatcher = new EnhancedMatcher(); } /** * Calculate combined similarity score between two names * @param {string} name1 - First name * @param {string} name2 - Second name * @returns {number} - Similarity score (0-1) */ getSimilarity(name1, name2) { // Handle empty names if (!name1 || !name2) return 0; // Handle exact match if (name1 === name2) return 1; // Normalize names to handle comma format let normalized1 = parseName(name1).normalized; let normalized2 = parseName(name2).normalized; // Check and re-order name1 if it's a reversed version of name2 normalized1 = reorderNameIfNeeded(normalized2, normalized1); // Check and re-order name2 if it's a reversed version of name1 normalized2 = reorderNameIfNeeded(normalized1, normalized2); // Get scores from both matchers const naturalScore = this.getNaturalScore(normalized1, normalized2); const enhancedScore = this.enhancedMatcher.getSimilarity(normalized1, normalized2); // Calculate average of the two scores const averageScore = (naturalScore + enhancedScore) / 2; return parseFloat(averageScore.toFixed(2)); } /** * Get score using natural.js algorithms * @param {string} name1 - First name * @param {string} name2 - Second name * @returns {number} - Similarity score (0-1) */ getNaturalScore(name1, name2) { if (!name1 || !name2) return 0; // Try different algorithms and take the best result const jaroWinkler = natural.JaroWinklerDistance(name1, name2); const diceCoefficient = natural.DiceCoefficient(name1, name2); // Convert Levenshtein distance to similarity const levenshteinDistance = natural.LevenshteinDistance(name1, name2); const maxLength = Math.max(name1.length, name2.length); const levenshteinSimilarity = maxLength > 0 ? 1 - (levenshteinDistance / maxLength) : 1; // Return the best score from all algorithms return Math.max(jaroWinkler, diceCoefficient, levenshteinSimilarity); } /** * Determine if two names match based on the threshold * @param {string} name1 - First name * @param {string} name2 - Second name * @returns {boolean} - True if the names match */ isMatch(name1, name2) { const score = this.getSimilarity(name1, name2); return score >= this.threshold; } /** * Check if all names in a group refer to the same person * @param {Array} nameGroup - Array of name variants * @returns {Object} - Result with score and details */ matchNameGroup(nameGroup) { if (nameGroup.length <= 1) { return { score: 1, matches: [], isMatch: true }; } const matches = []; let totalScore = 0; let pairCount = 0; // Compare each name with every other name for (let i = 0; i < nameGroup.length; i++) { for (let j = i + 1; j < nameGroup.length; j++) { const name1 = nameGroup[i]; const name2 = nameGroup[j]; const similarity = this.getSimilarity(name1, name2); matches.push({ name1, name2, similarity }); totalScore += similarity; pairCount++; } } // Average similarity across all pairs const averageScore = parseFloat((totalScore / pairCount).toFixed(2)); const isMatch = averageScore >= this.threshold; return { score: averageScore, matches, isMatch }; } } module.exports = EnhancedNaturalMatcher;