Doormile AI: fix two silent-failure bugs in entity handling
Both were found while re-checking the branch, not from a failing build — each fails quietly and would have looked like "the bot just doesn't know". riderLookup read `status`, `phonenumber` and `vehicletype` off a miler. None of those fields exist: api.js documents the confirmed live shape as `availabilitystatus`, `phone` and `defaultvehicletype`, and states outright that no `status` field is present. So "where is rider Kumar" always rendered "status unknown" with no phone and no vehicle. Now reads the real names and adds the hub. correctTypos could rewrite a named entity into a vocabulary word. It runs before matching, and orderQuery extracts tenant/rider names from the corrected text, so a tenant called "Partnerz" (one edit from "partners") or "Ordero" (one from "orders") was silently renamed and then failed to resolve — with no indication that a filter had been dropped. Words following rider/tenant/hub/vehicle/customer/partner/named/called/for are now protected from correction; genuine keyword typos still get fixed. The comment above correctTypos already claimed names were never touched. Nothing enforced it. The L2 composite-query work made the gap matter, because before it entity names barely reached the matcher. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -109,11 +109,37 @@ const KEYWORD_VOCAB = [
|
||||
'available'
|
||||
];
|
||||
|
||||
const correctTypos = (text) =>
|
||||
text
|
||||
// Words that form a NAMED ENTITY must never be "corrected". A tenant called
|
||||
// "Partnerz" is one edit from "partners" and a rider called "Delivara" is two
|
||||
// from "delivered"; rewriting either makes the name unresolvable, and
|
||||
// orderQuery extracts tenant/rider names from the corrected text, so the
|
||||
// failure is silent — the filter just never matches.
|
||||
//
|
||||
// The original comment here claimed order IDs and tenant names were never
|
||||
// touched. Nothing enforced that; this does.
|
||||
const ENTITY_NAME_SPAN = /\b(?:rider|tenant|hub|vehicle|customer|partner|named|called|for)\s+((?:[A-Za-z][A-Za-z0-9.'-]*\s*){1,4})/gi;
|
||||
|
||||
const protectedNameWords = (text) => {
|
||||
const keep = new Set();
|
||||
const re = new RegExp(ENTITY_NAME_SPAN.source, 'gi');
|
||||
let m = re.exec(text);
|
||||
while (m !== null) {
|
||||
m[1]
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.forEach((w) => keep.add(w.toLowerCase()));
|
||||
m = re.exec(text);
|
||||
}
|
||||
return keep;
|
||||
};
|
||||
|
||||
const correctTypos = (text) => {
|
||||
const keep = protectedNameWords(text);
|
||||
return text
|
||||
.split(/\b/)
|
||||
.map((token) => {
|
||||
const word = token.toLowerCase();
|
||||
if (keep.has(word)) return token;
|
||||
if (!/^[a-z]+$/.test(word) || word.length < 5 || KEYWORD_VOCAB.includes(word)) return token;
|
||||
// >=6 chars allows distance 2, which is what a single adjacent-letter
|
||||
// transposition ("riedrs" for "riders") costs in plain Levenshtein
|
||||
@@ -132,6 +158,7 @@ const correctTypos = (text) =>
|
||||
return best && bestDist <= maxDist ? best : token;
|
||||
})
|
||||
.join('');
|
||||
};
|
||||
|
||||
const TODAY = () => dayjs().format('YYYY-MM-DD');
|
||||
|
||||
@@ -789,9 +816,18 @@ const INTENTS = [
|
||||
);
|
||||
if (!found) return null;
|
||||
return {
|
||||
headline: `${found.displayname || found.name} — ${found.status || 'status unknown'}.`,
|
||||
// Field names matter here: a miler has NO `status`, `phonenumber` or
|
||||
// `vehicletype` field. The real ones are `availabilitystatus`,
|
||||
// `phone` and `defaultvehicletype` (api.js documents the confirmed
|
||||
// live shape). Reading the wrong names meant this answer always said
|
||||
// "status unknown" and never showed a phone or vehicle.
|
||||
headline: `${found.displayname || found.name} — ${found.availabilitystatus || 'availability unknown'}.`,
|
||||
detail:
|
||||
[found.phonenumber ? `Phone: ${found.phonenumber}` : null, found.vehicletype ? `Vehicle: ${found.vehicletype}` : null]
|
||||
[
|
||||
found.phone ? `Phone: ${found.phone}` : null,
|
||||
found.defaultvehicletype ? `Vehicle: ${found.defaultvehicletype}` : null,
|
||||
found.hubname ? `Hub: ${found.hubname}` : null
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n') || undefined,
|
||||
sourceCalls: [{ name: 'getMilers', target: '/admin/milers', status: 'complete', stats: `matched "${name}"` }]
|
||||
|
||||
Reference in New Issue
Block a user