feat: add unique short ID suffix to visitor labels
Each visitor gets a deterministic 2-char suffix (letter+digit) from IP hash, e.g. "Product Browser #K7". Distinguishes multiple visitors with same behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -68,25 +68,20 @@ class VisitorNickname
|
||||
];
|
||||
|
||||
/**
|
||||
* Generate a deterministic nickname from an IP address.
|
||||
* Generate a short deterministic ID suffix from an IP address.
|
||||
*
|
||||
* Same IP always produces the same name. The hash is stable
|
||||
* across PHP versions and platforms (crc32 + modulo).
|
||||
* Same IP always produces the same suffix. Used to distinguish
|
||||
* visitors with the same behavior label.
|
||||
*
|
||||
* @param string $ip IP address (v4 or v6)
|
||||
* @return string e.g. "Vermillion Falcon"
|
||||
* @return string e.g. "K7", "M3" — uppercase letter + digit
|
||||
*/
|
||||
public static function generate(string $ip): string
|
||||
public static function shortId(string $ip): string
|
||||
{
|
||||
// Use crc32 for speed — we just need distribution, not security
|
||||
$hash = crc32($ip);
|
||||
// Make it unsigned
|
||||
$hash = $hash & 0xFFFFFFFF;
|
||||
|
||||
$adjIdx = $hash % count(self::ADJECTIVES);
|
||||
$nounIdx = (int) floor($hash / count(self::ADJECTIVES)) % count(self::NOUNS);
|
||||
|
||||
return ucfirst(self::ADJECTIVES[$adjIdx]) . ' ' . ucfirst(self::NOUNS[$nounIdx]);
|
||||
$hash = crc32($ip) & 0xFFFFFFFF;
|
||||
$letter = chr(65 + ($hash % 26)); // A-Z
|
||||
$digit = ($hash >> 5) % 10; // 0-9
|
||||
return $letter . $digit;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,12 +202,13 @@ class VisitorNickname
|
||||
*
|
||||
* Priority: order > cart > demo download > demo boot > returning visitor > visitor
|
||||
*
|
||||
* @param array $tags Output of behaviorTags()
|
||||
* @param int $pages Total pages viewed
|
||||
* @param int $sessions Number of sessions
|
||||
* @return string Human-readable label, e.g. "Cart Abandoner"
|
||||
* @param array $tags Output of behaviorTags()
|
||||
* @param int $pages Total pages viewed
|
||||
* @param int $sessions Number of sessions
|
||||
* @param string $ip IP address (for unique suffix)
|
||||
* @return string Human-readable label, e.g. "Cart Abandoner #K7"
|
||||
*/
|
||||
public static function primaryLabel(array $tags, int $pages = 0, int $sessions = 0): string
|
||||
public static function primaryLabel(array $tags, int $pages = 0, int $sessions = 0, string $ip = ''): string
|
||||
{
|
||||
// Priority map — business funnel order, first match wins
|
||||
$priority = [
|
||||
@@ -231,18 +227,20 @@ class VisitorNickname
|
||||
'first-visit' => 'New Visitor',
|
||||
];
|
||||
|
||||
$suffix = $ip ? ' #' . self::shortId($ip) : '';
|
||||
|
||||
foreach ($priority as $tag => $label) {
|
||||
if (in_array($tag, $tags)) {
|
||||
return $label;
|
||||
return $label . $suffix;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback
|
||||
if ($pages > 1) {
|
||||
return 'Visitor (' . $pages . 'p)';
|
||||
return 'Visitor' . $suffix . ' (' . $pages . 'p)';
|
||||
}
|
||||
|
||||
return 'Visitor';
|
||||
return 'Visitor' . $suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user