fix: match disposition abbreviations like "Recon" via prefix, not just typos
Levenshtein distance alone scored "Recon" too far from "Reconnaissance" (9 edits) to pass the typo threshold, since it's a truncation rather than a misspelling. Add a prefix check against each canonical name (and a filler-word-stripped "core" form, so "Take Hold" matches "Take and Hold") gated to 3+ characters to avoid collisions. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+20
-6
@@ -137,17 +137,31 @@ function levenshtein(a, b) {
|
||||
return prev[n];
|
||||
}
|
||||
|
||||
const normalizeDispositionText = s => String(s || "").trim().toLowerCase().replace(/[^a-z ]/g, "").replace(/\s+/g, " ");
|
||||
|
||||
// Filler-word-stripped form of each canonical name, so abbreviations that
|
||||
// drop "and"/"the" (e.g. "Take Hold") still line up: "Take and Hold" -> "take hold".
|
||||
const dispositionCore = canonical => normalizeDispositionText(canonical).replace(/\b(and|the)\b/g, "").replace(/\s+/g, " ").trim();
|
||||
|
||||
// Nearest canonical disposition, or null if nothing is close enough to be
|
||||
// confident it's a typo rather than unrelated text. Threshold scales with
|
||||
// name length (~25%) so e.g. "Priority Assests" (1 char off "Priority
|
||||
// Assets") matches, but a short unrelated line doesn't accidentally match
|
||||
// "Disruption".
|
||||
// confident it's a typo/abbreviation rather than unrelated text.
|
||||
// Two ways to match:
|
||||
// - Prefix: shorthand like "Recon" or "Disrupt" that's a truncation, not a
|
||||
// typo, so Levenshtein distance alone would score it as far away. Gated
|
||||
// to 3+ chars so short unrelated words can't collide (the five names'
|
||||
// first three letters — pur/tak/pri/dis/rec — are all distinct).
|
||||
// - Levenshtein: everything else, e.g. "Priority Assests". Threshold scales
|
||||
// with name length (~25%) so a short unrelated line doesn't accidentally
|
||||
// match "Disruption".
|
||||
function closestDisposition(name) {
|
||||
const norm = String(name || "").trim().toLowerCase().replace(/[^a-z ]/g, "").replace(/\s+/g, " ");
|
||||
const norm = normalizeDispositionText(name);
|
||||
if (!norm) return null;
|
||||
let best = null, bestDist = Infinity;
|
||||
for (const canonical of CANONICAL_DISPOSITIONS) {
|
||||
const dist = levenshtein(norm, canonical.toLowerCase());
|
||||
const full = canonical.toLowerCase();
|
||||
const core = dispositionCore(canonical);
|
||||
if (norm.length >= 3 && (full.startsWith(norm) || core.startsWith(norm))) return canonical;
|
||||
const dist = Math.min(levenshtein(norm, full), levenshtein(norm, core));
|
||||
if (dist < bestDist) { bestDist = dist; best = canonical; }
|
||||
}
|
||||
const threshold = Math.max(1, Math.round(best.length * 0.25));
|
||||
|
||||
Reference in New Issue
Block a user