51 lines
1.8 KiB
TypeScript
51 lines
1.8 KiB
TypeScript
/** Levenshtein edit distance between two strings */
|
||
export function editDistance(a: string, b: string): number {
|
||
const m = a.length, n = b.length;
|
||
const dp: number[] = Array(n + 1).fill(0).map((_, j) => j);
|
||
for (let i = 1; i <= m; i++) {
|
||
let prev = dp[0];
|
||
dp[0] = i;
|
||
for (let j = 1; j <= n; j++) {
|
||
const temp = dp[j];
|
||
dp[j] = a[i - 1] === b[j - 1] ? prev : 1 + Math.min(prev, dp[j], dp[j - 1]);
|
||
prev = temp;
|
||
}
|
||
}
|
||
return dp[n];
|
||
}
|
||
|
||
/** Return a score 0–1 for how well a user matches a search query */
|
||
export function scoreUser(u: any, raw: string): number {
|
||
const q = raw.toLowerCase().trim();
|
||
if (!q || q.length < 2) return 0;
|
||
|
||
const name = (u.name || "").toLowerCase();
|
||
const email = (u.email || "").toLowerCase();
|
||
const phone = (u.phoneNumber || "").replace(/\D/g, "");
|
||
const qPhone = q.replace(/\D/g, "");
|
||
|
||
let best = 0;
|
||
|
||
// Exact substring matches — highest priority
|
||
if (name.includes(q)) best = Math.max(best, 1.0);
|
||
if (email.includes(q)) best = Math.max(best, 0.95);
|
||
if (qPhone.length >= 3 && phone.includes(qPhone)) best = Math.max(best, 0.95);
|
||
|
||
// Word-level starts-with (handles partial first/last name)
|
||
const words = name.split(/\s+/);
|
||
if (words.some((w: string) => w.startsWith(q))) best = Math.max(best, 0.85);
|
||
|
||
// Fuzzy edit-distance against each name word
|
||
const maxDist = q.length <= 4 ? 1 : q.length <= 7 ? 2 : 3;
|
||
for (const w of words) {
|
||
const slice = w.slice(0, q.length + 2); // compare against similar-length slice
|
||
const d = editDistance(q, slice);
|
||
if (d <= maxDist) best = Math.max(best, 0.75 - d * 0.15);
|
||
}
|
||
// Also fuzzy against the full name (catches "jhn smth" → "john smith")
|
||
const nameSlice = name.slice(0, q.length + 4);
|
||
const fullDist = editDistance(q, nameSlice);
|
||
if (fullDist <= maxDist + 1) best = Math.max(best, 0.6 - fullDist * 0.12);
|
||
|
||
return best;
|
||
} |