demo: P2 — staffer-language routes (zip, headcount, name, late-triage, ingest log)

Built from a playwright run as three personas:
  Maria   — "8 production workers near 60607 by next Friday, prior-fill at this client"
  Devon   — "what came in last night?"
  Aisha   — "Marcus running late site 4422"

Each one previously fell through to smart_search and returned irrelevant
results (geo wrong, headcount ignored, no triage, no temporal). Now:

A. Zip code → city/state lookup. Chicago zips (606xx, 607xx, 608xx)
   resolve to {city: Chicago, state: IL}; 13 metro prefixes covered.
   Maria's "near 60607" now returns Chicago workers, not Dayton/Green Bay.

B. Headcount parser. "8 production workers" / "12 forklift operators" /
   "5 welders" set top_k 1..200, capped 5..25 for SQL+vector LIMIT.
   Allows 0-2 role words between the count and the worker noun so
   "8 production workers" matches as well as "8 workers".

C. Bare-name profile lookup. Single short capitalized phrase
   ("Marcus" / "Sarah Lopez") triggers a profile route. Per-token LIKE
   AND-joined so "Marcus Rivera" matches "Marcus L. Rivera" without
   hardcoding middle initials.

E. Late-worker / no-show triage. Pattern: <Name> (running late|late|
   no show|sick|out today|called out|can't make it) — pulls profile +
   reliability + responsiveness + recent calls, sources 5 same-role
   same-geo backfills sorted by responsiveness, drafts a client SMS
   the coordinator can copy. Front-end renders triage card + Copy SMS
   button + green backfill list.

F. Contractor name preview anchor. The PROJECT INDEX preview line on
   each permit card now wraps contact_1_name and contact_2_name in
   anchors to /contractor?name=... — clicking a contractor finally
   navigates instead of doing nothing. Click handler stops propagation
   so the details element doesn't toggle.

D. Temporal "what came in" route. last night / today / past N hours /
   recent — surfaces datasets from the catalog whose updated_at is
   within the window, samples one row per dataset to detect worker-
   shape, groups by role for worker tables. Schema-agnostic — drop
   any dataset and it shows up. Currently sparse because no fresh
   ingest has happened today; will populate as ingest runs.

Server: /intelligence/chat smart_search route accepts structured
state/role from the search-form dropdowns (P1 from prior commit) and
now ALSO honors b.state, b.role, q.match for headcount + zip + name +
triage patterns BEFORE falling through to NL parsing.

Front-end: doSearch dispatches on response.type and renders triage,
profile, ingest_log, and miss states with type-specific UI. All DOM
construction uses textContent / appendChild — no innerHTML, no XSS.

Verified end-to-end via playwright drive of devop.live/lakehouse:
  Maria  → 8 Chicago Production Workers (60685, 60662, 60634)
           tags: "headcount: 8 · zip 60607 → Chicago, IL · ..."
  Aisha  → Marcus V. Campbell card + draft SMS + 5 Quincy IL backfills
           "I'm dispatching Scott B. Cooper (96% reliability) to cover."
  Devon  → ingest_log surfaces successful_playbooks_live (last 1h)
  Marcus → 5 profiles (Adams Louisville KY, Jenkins Green Bay WI, ...)

Screenshots: /tmp/persona_v2/{01_maria,02_aisha,03_devon,04_marcus}.png

Restart sequence after these edits: pkill -9 -f "mcp-server/index.ts" ;
cd /home/profit/lakehouse ; bun run mcp-server/index.ts. The bun on
:3700 is not systemd-managed (pre-existing convention).
This commit is contained in:
root
2026-04-28 06:01:04 -05:00
parent fb99e92a60
commit 677065de76
2 changed files with 426 additions and 15 deletions
+152 -1
View File
@@ -1609,10 +1609,29 @@ function loadLiveContracts(){
var ebLabel=document.createElement('span');ebLabel.style.cssText='font-size:10px;color:#545d68;text-transform:uppercase;letter-spacing:1px';
ebLabel.textContent='PROJECT INDEX — Build Signals';
var ebTags=document.createElement('span');ebTags.style.cssText='color:#e6edf3;font-size:11px;flex:1;font-weight:500';
// Contractor names link to the full profile page. Without anchors,
// clicking the preview did nothing — the only working contractor
// link was inside the lazy-loaded entity brief, which a coordinator
// wouldn't reach without first expanding the details.
var preview=[];
function contactLink(n){
var a=document.createElement('a');
a.href='/contractor?name='+encodeURIComponent(n);
a.target='_blank';a.rel='noopener';
a.style.cssText='color:inherit;text-decoration:none;border-bottom:1px dotted #58a6ff44';
a.title='Open full contractor profile';
a.textContent=n;
a.addEventListener('click',function(e){e.stopPropagation()}); // don't toggle the details
return a;
}
if(p.contact_1_name) preview.push(p.contact_1_name);
if(p.contact_2_name && p.contact_2_name!==p.contact_1_name) preview.push(p.contact_2_name);
ebTags.textContent=preview.join(' · ');
if(preview.length){
preview.forEach(function(n,i){
if(i>0) ebTags.appendChild(document.createTextNode(' · '));
ebTags.appendChild(contactLink(n));
});
}
var ebMeta=document.createElement('span');ebMeta.style.cssText='color:#545d68;font-size:10px';
ebMeta.textContent='click → fetch OSHA + ILSOS';
ebSum.appendChild(ebCaret);ebSum.appendChild(ebLabel);ebSum.appendChild(ebTags);ebSum.appendChild(ebMeta);
@@ -2270,6 +2289,130 @@ function pw(text){
rel:rr?parseFloat(rr[1]):0,avail:av?parseFloat(av[1]):0,arch:ar?ar[1]:'',hasM:!!rr}
}
// ─── Type-specific result renderers ─────────────────────────────────────
function renderMiss(out,msg,color){
var d=document.createElement('div');
d.style.cssText='background:#0d1117;border:1px solid '+(color||'#21262d')+'66;border-left:3px solid '+(color||'#21262d')+';border-radius:6px;padding:14px 16px;color:#8b949e;font-size:13px;line-height:1.5';
d.textContent=msg;
out.appendChild(d);
}
function workerLine(w){
var bits=[];
if(w.role) bits.push(w.role);
if(w.city||w.state) bits.push((w.city||'')+(w.city&&w.state?', ':'')+(w.state||''));
if(w.zip) bits.push('ZIP '+w.zip);
return bits.join(' · ');
}
function appendStat(parent,label,val){
var s=document.createElement('span');
var l=document.createElement('span');l.textContent=label+': ';
var b=document.createElement('b');b.style.color='#e6edf3';b.textContent=val;
s.appendChild(l);s.appendChild(b);
parent.appendChild(s);
}
function renderTriage(out,d){
var w=d.worker, bf=d.backfills||[];
var card=document.createElement('div');
card.style.cssText='background:#1a1410;border:1px solid #d29922;border-left:3px solid #d29922;border-radius:8px;padding:16px;margin-bottom:14px';
var ev=document.createElement('div');
ev.style.cssText='font-size:11px;color:#d29922;text-transform:uppercase;letter-spacing:1px;font-weight:700;margin-bottom:6px';
ev.textContent='⚠ TRIAGE — '+(d.event||'event').toUpperCase();
card.appendChild(ev);
var hdr=document.createElement('div');
hdr.style.cssText='font-size:16px;color:#e6edf3;font-weight:600;margin-bottom:4px';
hdr.textContent=w.name;
card.appendChild(hdr);
var line=document.createElement('div');
line.style.cssText='font-size:12px;color:#8b949e;margin-bottom:10px';
line.textContent=workerLine(w);
card.appendChild(line);
var stats=document.createElement('div');
stats.style.cssText='font-size:11px;color:#8b949e;margin-bottom:10px;display:flex;gap:14px;flex-wrap:wrap';
appendStat(stats,'Reliability',Math.round((w.rel||0)*100)+'%');
appendStat(stats,'Responsiveness',Math.round((w.resp||0)*100)+'%');
appendStat(stats,'Availability',Math.round((w.avail||0)*100)+'%');
if(w.archetype) appendStat(stats,'Archetype',w.archetype);
if(w.recent_calls!=null) appendStat(stats,'Prior calls',w.recent_calls);
card.appendChild(stats);
var smsLabel=document.createElement('div');
smsLabel.style.cssText='font-size:10px;color:#d29922;text-transform:uppercase;letter-spacing:1px;font-weight:700;margin-bottom:4px';
smsLabel.textContent='DRAFT SMS — TO CLIENT';
card.appendChild(smsLabel);
var smsBox=document.createElement('div');
smsBox.style.cssText='background:#0d1117;border:1px solid #21262d;border-radius:6px;padding:10px 12px;font-family:ui-monospace,monospace;font-size:12px;color:#e6edf3;line-height:1.5;white-space:pre-wrap';
smsBox.textContent=d.draft_sms||'';
card.appendChild(smsBox);
var copyBtn=document.createElement('button');
copyBtn.style.cssText='margin-top:8px;background:#1f6feb;border:none;color:#fff;padding:6px 14px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer';
copyBtn.textContent='Copy SMS';
copyBtn.onclick=function(){
if(navigator.clipboard) navigator.clipboard.writeText(d.draft_sms||'');
copyBtn.textContent='Copied ✓';
setTimeout(function(){copyBtn.textContent='Copy SMS'},1500);
};
card.appendChild(copyBtn);
out.appendChild(card);
if(bf.length){
var bfHdr=document.createElement('div');
bfHdr.style.cssText='font-size:11px;color:#3fb950;text-transform:uppercase;letter-spacing:1px;font-weight:700;margin:8px 0 8px';
bfHdr.textContent='✓ BACKFILLS READY — '+bf.length+' local '+(w.role||'workers')+' available, sorted by responsiveness';
out.appendChild(bfHdr);
bf.forEach(function(c,i){
addWorkerInsight(out,c.name,workerLine(c),
'Reliability '+Math.round((c.rel||0)*100)+'% · Responds '+Math.round((c.resp||0)*100)+'% · Available '+Math.round((c.avail||0)*100)+'%'+(c.archetype?' · '+c.archetype:''),
i,'#3fb950',c);
});
}else{
var bfNone=document.createElement('div');
bfNone.style.cssText='background:#1a1010;border:1px solid #f85149;border-radius:6px;padding:10px 14px;color:#fca5a5;font-size:12px';
bfNone.textContent='No same-role workers available locally. Widen the search — try a neighboring city or relax availability threshold.';
out.appendChild(bfNone);
}
}
function renderProfiles(out,d){
var hdr=document.createElement('div');
hdr.style.cssText='font-size:12px;color:#8b949e;margin-bottom:10px';
hdr.textContent=d.summary;
out.appendChild(hdr);
(d.profiles||[]).forEach(function(w,i){
addWorkerInsight(out,w.name,workerLine(w),
'Reliability '+Math.round((w.rel||0)*100)+'%'+(w.resp?' · Responds '+Math.round(w.resp*100)+'%':'')+(w.archetype?' · '+w.archetype:''),
i,null,w);
});
}
function renderIngestLog(out,d){
var hdr=document.createElement('div');
hdr.style.cssText='font-size:12px;color:#e6edf3;margin-bottom:10px;padding:10px 12px;background:#0d2818;border:1px solid #2ea04340;border-left:3px solid #3fb950;border-radius:6px';
hdr.textContent=d.summary;
out.appendChild(hdr);
(d.datasets||[]).forEach(function(ds){
var card=document.createElement('div');
card.style.cssText='background:#0d1117;border:1px solid #21262d;border-radius:6px;padding:12px 14px;margin-bottom:8px';
var top=document.createElement('div');
top.style.cssText='display:flex;justify-content:space-between;align-items:baseline;margin-bottom:6px';
var nm=document.createElement('span');
nm.style.cssText='font-size:13px;color:#e6edf3;font-weight:600';
nm.textContent=ds.name;
var ago=document.createElement('span');
ago.style.cssText='font-size:11px;color:#545d68';
ago.textContent=(ds.hours_ago||0)+'h ago · '+(ds.row_count||0).toLocaleString()+' rows';
top.appendChild(nm);top.appendChild(ago);
card.appendChild(top);
if(ds.looks_like_workers && ds.role_breakdown && ds.role_breakdown.length){
var rb=document.createElement('div');
rb.style.cssText='font-size:11px;color:#8b949e;display:flex;gap:10px;flex-wrap:wrap;margin-top:4px';
ds.role_breakdown.forEach(function(r){
var pill=document.createElement('span');
pill.style.cssText='background:#161b22;border:1px solid #21262d;padding:2px 8px;border-radius:9px';
pill.textContent=(r.role||'?')+' · '+r.cnt;
rb.appendChild(pill);
});
card.appendChild(rb);
}
out.appendChild(card);
});
}
function doSearch(){
var q=document.getElementById('sq').value.trim();if(!q)return;
lastQuery=q;
@@ -2285,6 +2428,14 @@ function doSearch(){
body:JSON.stringify({message:q,state:st||undefined,role:rl||undefined})
}).then(function(r){return r.json()}).then(function(d){
out.textContent='';
// Type-specific renderers — added 2026-04-27 for the persona-driven
// routes (triage / profile / ingest_log). Default falls through to
// the smart_search renderer below.
if(d.type==='triage' && d.worker){return renderTriage(out,d)}
if(d.type==='triage_miss'){return renderMiss(out,d.summary,'#f85149')}
if(d.type==='profile' && d.profiles && d.profiles.length){return renderProfiles(out,d)}
if(d.type==='profile_miss'){return renderMiss(out,d.summary,'#d29922')}
if(d.type==='ingest_log'){return renderIngestLog(out,d)}
// Show what the system understood
if(d.understood&&d.understood.length){
var tags=document.createElement('div');tags.style.cssText='display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px';