@ 2017-07-13T19:13:02Z <?php
class Callcenter_Flagcustomer_Model_Observer {
/**
* Possibly flag an address.
*
* Matching an existing flagged address is an inexact science. We want
* to catch most trivial circumventions but we don't want to incorrectly
* flag good orders - the latter we care about MUCH more than the former.
*
* At the end of the day, I decided to do a basic normalized levenshtein
* comparison, and if there are more than 3 total mistakes between the
* flagged address and the incoming address, we flag the order. Region
* and Country are exact matches due to not being input by hand.
*
* @param array[string] $a1
* @param array[string] $a2
* @return boolean
*/
public static function shouldFlag($a1, $a2) {
if ($a1['region'] !== $a2['region']) {
return false;
} elseif ($a1['country'] !== $a2['country']) {
return false;
}
$score = 0;
$score += self::sCompare($a1['street'], $a2['street']);
$score += self::compare($a1['city'], $a2['city']);
$score += self::pCompare($a1['postcode'], $a2['postcode'], $a1['country'], $a2['country']);
if ($score > 3) {
return false;
}
return true;
}
/**
* Compare two normalized strings via levenshtein distance.
*
* @param string $s1 String to compare.
* @param string $s2 String to compare against.
* @return float Score.
*/
private static function compare($s1, $s2) {
$sn1 = self::normalize($s1);
$sn2 = self::normalize($s2);
$score = levenshtein($sn1, $sn2);
return $score;
}
/**
* Compare two normalized postcodes via levenshtein distance.
*
* @param string $s1 String to compare.
* @param string $s2 String to compare against.
* @param string $c1 Country of $s1.
* @param string $c2 Country of $s2.
* @return float Score.
*/
private static function pCompare($s1, $s2, $c1, $c2) {
$sn1 = self::normalize($s1);
$sn2 = self::normalize($s2);
if (strtoupper($c1) === 'US' && strlen($sn1) === 9) {
$sn1 = substr($sn1, 0, 5);
}
if (strtoupper($c2) === 'US' && strlen($sn2) === 9) {
$sn2 = substr($sn2, 0, 5);
}
$score = levenshtein($sn1, $sn2);
return $score;
}
/**
* Compare two normalized street names via levenshtein distance.
*
* @param string $s1 String to compare.
* @param string $s2 String to compare against.
* @return float Score.
*/
private static function sCompare($s1, $s2) {
$sn1 = self::sNormalize($s1);
$sn2 = self::sNormalize($s2);
$score = levenshtein($sn1, $sn2);
return $score;
}
/**
* Normalize a string for comparison.
*
* @param string $s String to normalize.
* @return string
*/
private static function normalize($s) {
// Normalize to lowercase alphanumeric.
$s = strtolower(preg_replace('/[^a-zA-Z0-9]+/', '', $s));
return trim($s);
}
/**
* Normalize an address for comparison. Has a few more steps that
* are specific to addresses.
*
* @param string $s String to normalize.
* @return string
*/
private static function sNormalize($s) {
static $mappings = [
'apt' => 'apartment',
'ave' => 'avenue',
'blvd' => 'boulevard',
'ct' => 'court',
'dr' => 'drive',
'e' => 'east',
'n' => 'north',
'ne' => 'northeast',
'nw' => 'northwest',
'pkwy' => 'parkway',
'pl' => 'place',
'rd' => 'road',
's' => 'south',
'se' => 'southeast',
'st' => 'street',
'sw' => 'southwest',
'w' => 'west',
];
$from = [];
$to = [];
foreach ($mappings as $key => $value) {
$from[] = ' '.$key.' ';
$to[] = ' '.$value. ' ';
}
// Normalize to lowercase alphanumeric with spaces.
$s = strtolower(preg_replace('/[^a-zA-Z0-9 ]+/', '', $s));
// Ensure surrounded with spaces.
$s = ' '.$s.' ';
// Get rid of multiple spaces - turn into single spaces.
$s = preg_replace('/\s{2,}/', ' ', $s);
// Get rid of ordinal indicators.
$s = preg_replace('/(\d+)(?:st|nd|rd|th)/', '\1', $s);
// Replace street abbreviations.
$s = str_replace($from, $to, $s);
return trim($s);
}
}
$result = Callcenter_Flagcustomer_Model_Observer::shouldFlag([
'street' => '65 JANIS RD',
'city' => 'WESTFIELD',
'region' => 'MA',
'postcode' => '01085-4016',
'country' => 'US',
], [
'street' => '65 janis road',
'city' => 'westfield',
'region' => 'MA',
'postcode' => '01085',
'country' => 'US',
]);
var_dump($result);
Enable javascript to submit You have javascript disabled. You will not be able to edit any code.
Output for 5.6.0 - 5.6.40 , 7.0.0 - 7.0.33 , 7.1.0 - 7.1.33 , 7.2.0 - 7.2.33 , 7.3.0 - 7.3.33 , 7.4.0 - 7.4.33 , 8.0.0 - 8.0.30 , 8.1.0 - 8.1.28 , 8.2.0 - 8.2.18 , 8.3.0 - 8.3.4 , 8.3.6 bool(true)
Output for 8.3.5 Warning: PHP Startup: Unable to load dynamic library 'sodium.so' (tried: /usr/lib/php/8.3.5/modules/sodium.so (libsodium.so.23: cannot open shared object file: No such file or directory), /usr/lib/php/8.3.5/modules/sodium.so.so (/usr/lib/php/8.3.5/modules/sodium.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0
bool(true)
preferences:dark mode live preview
217.72 ms | 402 KiB | 291 Q