demo: P1 — search filter now actually filters by state and role
The Co-Pilot search box read state and role from the dropdowns (#sst, #srl)
but appended them to the message string as ' in '+st. The server's NL
parser then matched the literal preposition "in" against the case-insensitive
regex /\b(IL|IN|...)\b/i and assigned state IN (Indiana) to every search.
Result: typing "forklift in IL" returned Indiana workers. Same for WI, TX,
any state — all silently became Indiana. That was the "cached/generic
response" the legacy staffing client was seeing.
Two prongs:
1. search.html doSearch() now passes structured fields:
{message, state, role}
instead of munging into the message text. Dropdown selections bypass
NL parsing entirely.
2. /intelligence/chat smart_search route accepts those structured fields
and prefers them over regex archaeology. Falls back to NL parsing only
when fields aren't provided. Fixed the regex too: the prepositional
form (?:in|from)\s+(STATE) wins, the standalone form requires uppercase
(drops /i flag) so the lowercase preposition "in" can no longer match.
Verified live:
- POST /intelligence/chat {"message":"forklift","state":"IL"}
→ 167 IL forklift operators (Galesburg, Joliet, ...)
- POST /intelligence/chat {"message":"forklift","state":"WI","role":"Forklift Operator"}
→ 16 WI Forklift Operators (Milwaukee, Madison, ...)
- POST /intelligence/chat {"message":"forklift in IL"} (NL fallback)
→ 167 IL workers (regex now correctly distinguishes preposition from state code)
Playwright drove the live UI through devop.live/lakehouse and confirmed the
front-end posts the structured body and the result panel renders the right
state. Restart sequence: kill old bun :3700, bun run mcp-server/index.ts.
This commit is contained in:
parent
ed57eda1d8
commit
fb99e92a60
@ -1698,7 +1698,14 @@ async function main() {
|
||||
const filters: string[] = ["CAST(reliability AS DOUBLE) >= 0.5"];
|
||||
const understood: string[] = [];
|
||||
|
||||
// Extract role keywords
|
||||
// Structured input from the search-form dropdowns. When set,
|
||||
// these win over NL parsing — typing "forklift in IL" used to
|
||||
// misparse the preposition "in" as state IN (Indiana). Trust
|
||||
// explicit user selection over regex archaeology.
|
||||
const explicitState = String(b.state || "").trim().toUpperCase();
|
||||
const explicitRole = String(b.role || "").trim();
|
||||
|
||||
// Extract role keywords (skip if dropdown picked one)
|
||||
const roleKeywords: Record<string, string> = {
|
||||
"warehouse": "warehouse", "forklift": "forklift", "welder": "weld", "assembler": "assembl",
|
||||
"loader": "loader", "machine operator": "machine operator", "shipping": "shipping",
|
||||
@ -1707,8 +1714,13 @@ async function main() {
|
||||
"line lead": "line lead", "electrician": "electric", "packaging": "packaging",
|
||||
"tool and die": "tool", "logistics": "logistics", "safety": "safety", "cnc": "cnc",
|
||||
};
|
||||
for (const [kw, sqlPart] of Object.entries(roleKeywords)) {
|
||||
if (lower.includes(kw)) { filters.push(`LOWER(role) LIKE '%${sqlPart}%'`); understood.push(`role: ${kw}`); break; }
|
||||
if (explicitRole) {
|
||||
filters.push(`LOWER(role) LIKE '%${explicitRole.toLowerCase().replace(/'/g, "''")}%'`);
|
||||
understood.push(`role: ${explicitRole}`);
|
||||
} else {
|
||||
for (const [kw, sqlPart] of Object.entries(roleKeywords)) {
|
||||
if (lower.includes(kw)) { filters.push(`LOWER(role) LIKE '%${sqlPart}%'`); understood.push(`role: ${kw}`); break; }
|
||||
}
|
||||
}
|
||||
|
||||
// Extract city
|
||||
@ -1726,18 +1738,33 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Extract state
|
||||
// Extract state — dropdown wins; otherwise NL parse, but
|
||||
// require either an explicit "in/from <STATE>" preposition
|
||||
// OR an UPPERCASE 2-letter code, never a bare lowercase
|
||||
// 2-letter token. Old regex matched "in" (preposition) as
|
||||
// state IN (Indiana) because the /i flag made the standalone
|
||||
// pattern case-insensitive — "forklift in IL" always returned
|
||||
// Indiana workers.
|
||||
const stateNames: Record<string, string> = {
|
||||
"illinois":"IL","indiana":"IN","ohio":"OH","missouri":"MO","tennessee":"TN",
|
||||
"kentucky":"KY","wisconsin":"WI","michigan":"MI","iowa":"IA","minnesota":"MN"
|
||||
};
|
||||
const stateMatch = lower.match(/\b(IL|IN|OH|MO|TN|KY|WI|MI|IA|MN)\b/i);
|
||||
if (stateMatch && !understood.some(u => u.startsWith('city'))) {
|
||||
filters.push(`state = '${stateMatch[1].toUpperCase()}'`);
|
||||
understood.push(`state: ${stateMatch[1].toUpperCase()}`);
|
||||
if (explicitState) {
|
||||
if (!understood.some(u => u.startsWith('city'))) {
|
||||
filters.push(`state = '${explicitState.replace(/'/g, "''")}'`);
|
||||
understood.push(`state: ${explicitState}`);
|
||||
}
|
||||
} else {
|
||||
for (const [name, abbr] of Object.entries(stateNames)) {
|
||||
if (lower.includes(name)) { filters.push(`state = '${abbr}'`); understood.push(`state: ${abbr}`); break; }
|
||||
const prepMatch = q.match(/\b(?:in|from)\s+(IL|IN|OH|MO|TN|KY|WI|MI|IA|MN)\b/i);
|
||||
const upperMatch = q.match(/\b(IL|IN|OH|MO|TN|KY|WI|MI|IA|MN)\b/); // no /i — must be uppercase
|
||||
const stateMatch = prepMatch || upperMatch;
|
||||
if (stateMatch && !understood.some(u => u.startsWith('city'))) {
|
||||
filters.push(`state = '${stateMatch[1].toUpperCase()}'`);
|
||||
understood.push(`state: ${stateMatch[1].toUpperCase()}`);
|
||||
} else {
|
||||
for (const [name, abbr] of Object.entries(stateNames)) {
|
||||
if (lower.includes(name)) { filters.push(`state = '${abbr}'`); understood.push(`state: ${abbr}`); break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2274,13 +2274,15 @@ function doSearch(){
|
||||
var q=document.getElementById('sq').value.trim();if(!q)return;
|
||||
lastQuery=q;
|
||||
var st=document.getElementById('sst').value,rl=document.getElementById('srl').value;
|
||||
// Append dropdown filters to the query so the smart parser picks them up
|
||||
var fullQ=q;
|
||||
if(st&&q.indexOf(st)<0)fullQ+=' in '+st;
|
||||
if(rl&&q.toLowerCase().indexOf(rl.toLowerCase())<0)fullQ+=' '+rl;
|
||||
// Pass dropdown filters as structured fields. Old code appended
|
||||
// ' in '+st to the message, which the server misparsed: the
|
||||
// preposition "in" matched the regex for state code "IN" (Indiana)
|
||||
// and every search returned Indiana workers regardless of dropdown.
|
||||
// Sending structured state/role lets the server skip NL parsing
|
||||
// for those fields entirely.
|
||||
var out=document.getElementById('sresults');out.textContent='Finding the best matches...';
|
||||
fetch(A+'/intelligence/chat',{method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({message:fullQ})
|
||||
body:JSON.stringify({message:q,state:st||undefined,role:rl||undefined})
|
||||
}).then(function(r){return r.json()}).then(function(d){
|
||||
out.textContent='';
|
||||
// Show what the system understood
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user