cleanName($name1); $t2 = $this->cleanName($name2); if (empty($t1) || empty($t2)) { return null; } /* |-------------------------------------------------------------------------- | OVERLAP |-------------------------------------------------------------------------- */ $overlap = count(array_intersect($t1,$t2)); /* |-------------------------------------------------------------------------- | TOKEN SCORE |-------------------------------------------------------------------------- */ $tokenScore = $this->tokenSetRatio( implode(' ', $t1), implode(' ', $t2) ); /* |-------------------------------------------------------------------------- | SURNAME SCORE |-------------------------------------------------------------------------- */ $surname = max($this->similarity(end($t1),end($t2)), $this->similarity($t1[0],end($t2)), $this->similarity(end($t1),$t2[0])); /* |-------------------------------------------------------------------------- | GIVEN NAME |-------------------------------------------------------------------------- */ list($g1, $g2) = $this->probableGiven($t1,$t2); $first = $this->givenNameScore($g1,$g2); /* |-------------------------------------------------------------------------- | MIDDLE NAME |-------------------------------------------------------------------------- */ $middle = 100; if (count($t1) > 2 && count($t2) > 2) { $middle = $this->partialRatio(implode(' ',array_slice($t1,1,-1)), implode(' ',array_slice($t2,1,-1))); } /* |-------------------------------------------------------------------------- | FAMILY MATCH REDUCTION |-------------------------------------------------------------------------- */ if ($first == 0) { if ($surname >= 90 && $overlap >= 2) { return round(min($tokenScore, 45), 2); } return 0; } /* |-------------------------------------------------------------------------- | FINAL SCORE |-------------------------------------------------------------------------- */ $final = ( $first * 0.50 + $surname * 0.20 + $tokenScore * 0.20 + $middle * 0.10 ); /* |-------------------------------------------------------------------------- | REORDER / PARTIAL BOOST |-------------------------------------------------------------------------- */ if ($overlap >= 2 && $tokenScore >= 85) { $final = max($final,90); } return round(min($final, 100),2); } /* |-------------------------------------------------------------------------- | CLEAN NAME |-------------------------------------------------------------------------- */ private function cleanName($name) { if (!$name) { return []; } $name = strtolower($name); $name = explode('&', $name)[0]; $name = preg_replace('/\b(w\/o|s\/o|d\/o|c\/o)\b/',' ',$name); $name = preg_replace('/[^a-z0-9\s]/',' ',$name); $tokens = []; foreach (preg_split('/\s+/', $name) as $token) { if (!$token || in_array($token, $this->REMOVE)) { continue; } $tokens[] = $this->normalizeToken($token); } return $this->splitCompound($tokens); } /* |-------------------------------------------------------------------------- | NORMALIZE TOKEN |-------------------------------------------------------------------------- */ private function normalizeToken($x) { $x = strtolower(trim($x)); $x = str_replace( ' ', '', $x ); foreach ($this->SUFFIXES as $s) { if (substr($x,-strlen($s)) == $s && strlen($x) > strlen($s) + 2) { $x = substr($x,0,-strlen($s)); break; } } return $x; } /* |-------------------------------------------------------------------------- | SPLIT COMPOUND |-------------------------------------------------------------------------- */ private function splitCompound($tokens) { $out = []; foreach ($tokens as $token) { $found = false; foreach ($this->SUFFIXES as $suffix) { if (substr($token,-strlen($suffix)) == $suffix && $token != $suffix) { $root = substr($token,0,-strlen($suffix)); if (strlen($root) >= 3) { $out[] = $root; $out[] = $suffix; $found = true; break; } } } if (!$found) { $out[] = $token; } } return $out; } /* |-------------------------------------------------------------------------- | PROBABLE GIVEN NAME |-------------------------------------------------------------------------- */ private function probableGiven($t1,$t2) { if (count($t1) >= 3 && count($t2) >= 3 && $t1[0] == $t2[0]) { return [$t1[1], $t2[1]]; } return [$t1[0], $t2[0]]; } /* |-------------------------------------------------------------------------- | GIVEN NAME SCORE |-------------------------------------------------------------------------- */ private function givenNameScore($a,$b) { if ($a == $b) { return 100; } if (strlen($a) == 1 && strpos($b, $a) === 0) { return 95; } if (strlen($b) == 1 && strpos($a, $b) === 0) { return 95; } return $this->typoScore($a,$b); } /* |-------------------------------------------------------------------------- | TYPO SCORE |-------------------------------------------------------------------------- */ private function typoScore($a,$b) { if ($a == $b) { return 100; } if (str_replace(' ', '', $a) == str_replace(' ', '', $b)) { return 95; } similar_text($a,$b,$percent); if (strlen($a) >= 5 && strlen($b) >= 5 && $percent >= 88) { return 90; } return 0; } /* |-------------------------------------------------------------------------- | SIMILARITY |-------------------------------------------------------------------------- */ private function similarity($a,$b) { similar_text($a,$b,$percent); return round($percent,2); } /* |-------------------------------------------------------------------------- | TOKEN SET RATIO |-------------------------------------------------------------------------- */ private function tokenSetRatio($a,$b) { $aTokens = array_unique(explode(' ', $a)); $bTokens = array_unique(explode(' ', $b)); sort($aTokens); sort($bTokens); return $this->similarity(implode(' ',$aTokens), implode(' ', $bTokens)); } /* |-------------------------------------------------------------------------- | PARTIAL RATIO |-------------------------------------------------------------------------- */ private function partialRatio($a,$b) { similar_text($a, $b, $percent); return round($percent,2); } }