Staffing Intelligence Console: workforce command center with conversational AI
New page at /lakehouse/console — a $200/hr consultant's intelligence product: Morning Brief (auto-loads in ~120ms across 500K profiles): - Workforce Pulse: total, reliable %, elite %, archetype breakdown - Geographic Bench: state-by-state reliable % with weakest-state alert - Comeback Watch: 15K improving workers who crossed 80% reliability - Risk Watch: 5K erratic + 5K silent workers flagged automatically - Ready & Waiting: available + reliable workers to call first - Role Supply: 20 roles with supply/available/reliability Conversational Chat with 5 intelligent routes: - "Find someone like [Name] but in OH" → vector similarity search - "Who could handle industrial electrical work?" → semantic role discovery (finds workers for roles that DON'T EXIST in the database) - "What if we lose our top 5 forklift operators?" → scenario analysis with risk rating, bench depth, state-by-state breakdown - "Which workers should we stop placing?" → risk flagging - Default: hybrid SQL+vector search with LLM summary Every response shows: query steps, records scanned, response time. Transparency kills the "AI is making it up" argument. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
37c68d9567
commit
37804d7195
380
mcp-server/console.html
Normal file
380
mcp-server/console.html
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html><head>
|
||||||
|
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>Staffing Intelligence Console</title>
|
||||||
|
<style>
|
||||||
|
*{margin:0;padding:0;box-sizing:border-box}
|
||||||
|
body{font-family:-apple-system,system-ui,sans-serif;background:#06090f;color:#c9d1d9;font-size:14px;line-height:1.5}
|
||||||
|
.bar{background:#0d1117;padding:14px 20px;border-bottom:1px solid #1b2130;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px}
|
||||||
|
.bar h1{font-size:15px;font-weight:600;color:#f0f6fc;letter-spacing:0.5px}
|
||||||
|
.bar .rt{font-size:11px;color:#484f58}
|
||||||
|
.wrap{max-width:960px;margin:0 auto;padding:20px 16px}
|
||||||
|
.brief-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:12px;margin-bottom:24px}
|
||||||
|
.bcard{background:#0d1117;border:1px solid #1b2130;border-radius:10px;padding:18px;position:relative;overflow:hidden}
|
||||||
|
.bcard .label{font-size:9px;text-transform:uppercase;letter-spacing:1.5px;color:#484f58;margin-bottom:8px}
|
||||||
|
.bcard .headline{font-size:16px;font-weight:700;color:#f0f6fc;margin-bottom:4px}
|
||||||
|
.bcard .sub{font-size:12px;color:#8b949e;margin-bottom:12px}
|
||||||
|
.bcard .big{font-size:36px;font-weight:800;line-height:1}
|
||||||
|
.bcard.pulse{border-left:3px solid #58a6ff}
|
||||||
|
.bcard.bench{border-left:3px solid #d29922}
|
||||||
|
.bcard.gems{border-left:3px solid #3fb950}
|
||||||
|
.bcard.risk{border-left:3px solid #f85149}
|
||||||
|
.bcard.talent{border-left:3px solid #bc8cff}
|
||||||
|
.bcard.supply{border-left:3px solid #79c0ff}
|
||||||
|
.arch-row{display:flex;gap:4px;flex-wrap:wrap;margin-top:10px}
|
||||||
|
.arch-pill{padding:3px 10px;border-radius:10px;font-size:11px;font-weight:500}
|
||||||
|
.bench-row{display:flex;align-items:center;gap:8px;margin-bottom:6px;font-size:12px}
|
||||||
|
.bench-row .st{width:24px;font-weight:600;color:#f0f6fc}
|
||||||
|
.bench-bar{flex:1;height:14px;background:#161b22;border-radius:4px;overflow:hidden;position:relative}
|
||||||
|
.bench-fill{height:100%;border-radius:4px;transition:width 0.8s}
|
||||||
|
.bench-row .pct{width:40px;text-align:right;font-size:11px;font-weight:600}
|
||||||
|
.wk{display:flex;align-items:center;gap:10px;padding:8px;background:#161b22;border-radius:6px;margin-bottom:4px}
|
||||||
|
.wk .av{width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px;color:#f0f6fc;flex-shrink:0}
|
||||||
|
.wk .info{flex:1;min-width:0}
|
||||||
|
.wk .nm{font-weight:600;color:#f0f6fc;font-size:13px}
|
||||||
|
.wk .det{color:#8b949e;font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||||
|
.wk .badge{padding:2px 8px;border-radius:8px;font-size:10px;font-weight:600;flex-shrink:0}
|
||||||
|
.prompts{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px}
|
||||||
|
.prompt-btn{padding:8px 16px;background:#161b22;border:1px solid #1b2130;border-radius:20px;color:#8b949e;font-size:12px;cursor:pointer;transition:all 0.2s}
|
||||||
|
.prompt-btn:hover{border-color:#58a6ff;color:#58a6ff;background:#0d1525}
|
||||||
|
.chat-box{background:#0d1117;border:1px solid #1b2130;border-radius:12px;overflow:hidden;margin-bottom:16px}
|
||||||
|
.chat-messages{min-height:60px;max-height:600px;overflow-y:auto;padding:16px}
|
||||||
|
.msg{margin-bottom:16px;animation:fadeIn 0.3s ease}
|
||||||
|
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
||||||
|
.msg.user{text-align:right}
|
||||||
|
.msg.user .bubble{display:inline-block;background:#1f3d68;color:#c9d6ff;padding:10px 16px;border-radius:16px 16px 4px 16px;max-width:70%;text-align:left;font-size:13px}
|
||||||
|
.msg.system .bubble{background:#161b22;border:1px solid #1b2130;border-radius:4px 16px 16px 16px;padding:16px;font-size:13px}
|
||||||
|
.msg .meta{font-size:10px;color:#484f58;margin-top:6px}
|
||||||
|
.msg .steps{margin-bottom:10px}
|
||||||
|
.msg .step{font-size:11px;color:#58a6ff;padding:2px 0}
|
||||||
|
.msg .summary{font-size:14px;font-weight:600;color:#f0f6fc;margin-bottom:8px}
|
||||||
|
.msg .note{font-size:11px;color:#8b949e;font-style:italic;margin-top:8px;padding:8px;background:#0d1117;border-radius:6px}
|
||||||
|
.result-card{background:#0d1117;border:1px solid #1b2130;border-radius:8px;padding:12px;margin-bottom:6px}
|
||||||
|
.result-card .rc-name{font-weight:600;color:#f0f6fc;font-size:13px}
|
||||||
|
.result-card .rc-detail{font-size:11px;color:#8b949e;margin-top:2px}
|
||||||
|
.result-card .rc-score{font-size:10px;color:#58a6ff;margin-top:4px}
|
||||||
|
.result-card .rc-skills{display:flex;gap:4px;flex-wrap:wrap;margin-top:6px}
|
||||||
|
.result-card .rc-skill{padding:2px 8px;background:#1a2744;color:#58a6ff;border-radius:8px;font-size:10px}
|
||||||
|
.result-card .rc-cert{padding:2px 8px;background:#1a3a2a;color:#3fb950;border-radius:8px;font-size:10px}
|
||||||
|
.source-card{background:#1a1a00;border:1px solid #854d0e;border-radius:8px;padding:12px;margin-bottom:10px}
|
||||||
|
.whatif-severity{display:inline-block;padding:3px 10px;border-radius:8px;font-size:11px;font-weight:700;margin-left:8px}
|
||||||
|
.input-bar{display:flex;gap:8px;padding:12px 16px;border-top:1px solid #1b2130;background:#0d1117}
|
||||||
|
.input-bar input{flex:1;padding:12px 16px;background:#06090f;border:1px solid #1b2130;border-radius:10px;color:#f0f6fc;font-size:14px;outline:none}
|
||||||
|
.input-bar input:focus{border-color:#58a6ff}
|
||||||
|
.input-bar button{padding:12px 20px;background:#58a6ff;border:none;border-radius:10px;color:#0d1117;font-weight:700;font-size:14px;cursor:pointer}
|
||||||
|
.input-bar button:hover{background:#79c0ff}
|
||||||
|
.input-bar button:disabled{opacity:0.4;cursor:default}
|
||||||
|
.thinking{display:flex;align-items:center;gap:8px;color:#58a6ff;font-size:12px;padding:12px}
|
||||||
|
.dots span{animation:blink 1.4s infinite both;margin:0 1px}
|
||||||
|
.dots span:nth-child(2){animation-delay:0.2s}
|
||||||
|
.dots span:nth-child(3){animation-delay:0.4s}
|
||||||
|
@keyframes blink{0%,80%,100%{opacity:0.2}40%{opacity:1}}
|
||||||
|
.supply-row{display:flex;justify-content:space-between;align-items:center;padding:5px 8px;font-size:12px;border-bottom:1px solid #0d1117}
|
||||||
|
.supply-row:last-child{border-bottom:none}
|
||||||
|
.supply-role{color:#f0f6fc;font-weight:500}
|
||||||
|
.supply-nums{color:#8b949e;font-size:11px}
|
||||||
|
.ft{text-align:center;padding:16px;color:#2d333b;font-size:10px}
|
||||||
|
@media(max-width:768px){.brief-grid{grid-template-columns:1fr}.msg.user .bubble{max-width:90%}}
|
||||||
|
</style></head><body>
|
||||||
|
<div class="bar">
|
||||||
|
<h1>Staffing Intelligence Console</h1>
|
||||||
|
<div class="rt" id="brief-time">Analyzing 500,000 profiles...</div>
|
||||||
|
</div>
|
||||||
|
<div class="wrap">
|
||||||
|
<div id="brief" class="brief-grid"><div style="grid-column:1/-1;text-align:center;padding:40px;color:#484f58">Scanning your entire workforce...</div></div>
|
||||||
|
<div class="prompts" id="prompts"></div>
|
||||||
|
<div class="chat-box">
|
||||||
|
<div class="chat-messages" id="chat"></div>
|
||||||
|
<div class="input-bar">
|
||||||
|
<input type="text" id="q" placeholder="Ask anything about your workforce..." onkeydown="if(event.key==='Enter'&&!event.shiftKey)send()">
|
||||||
|
<button id="send-btn" onclick="send()">Send</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ft">Powered by Lakehouse — Hybrid SQL + Vector Search across 500,000 embedded worker profiles</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
var P=location.pathname.replace(/\/console\/?$/,'');
|
||||||
|
var A=location.origin+P;
|
||||||
|
var AC=['#1a2744','#1a3a2a','#2a1a3a','#3a2a1a','#1a3a3a','#2a2a1a','#3a1a2a','#1a2a3a'];
|
||||||
|
var briefData=null;
|
||||||
|
|
||||||
|
function api(path,body){
|
||||||
|
return fetch(A+path,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body||{})}).then(function(r){return r.json()})
|
||||||
|
}
|
||||||
|
|
||||||
|
function el(tag,css,text){var e=document.createElement(tag);if(css)e.style.cssText=css;if(text)e.textContent=text;return e}
|
||||||
|
function div(cls,text){var d=document.createElement('div');if(cls)d.className=cls;if(text)d.textContent=text;return d}
|
||||||
|
|
||||||
|
window.addEventListener('load',loadBrief);
|
||||||
|
|
||||||
|
function loadBrief(){
|
||||||
|
api('/intelligence/brief').then(function(d){
|
||||||
|
briefData=d;
|
||||||
|
var container=document.getElementById('brief');
|
||||||
|
container.textContent='';
|
||||||
|
document.getElementById('brief-time').textContent='Analyzed '+(d.pool.total||0).toLocaleString()+' profiles in '+(d.duration_ms||0)+'ms';
|
||||||
|
var pool=d.pool;
|
||||||
|
var relPct=pool.total?Math.round(pool.reliable/pool.total*100):0;
|
||||||
|
var elitePct=pool.total?Math.round(pool.elite/pool.total*100):0;
|
||||||
|
|
||||||
|
// Card 1: Workforce Pulse
|
||||||
|
var c1=makeCard('pulse','WORKFORCE PULSE','Your A-Team',pool.reliable.toLocaleString()+' workers with 80%+ reliability — '+relPct+'% of your pool');
|
||||||
|
var bigRow=el('div','display:flex;gap:20px;margin-bottom:12px');
|
||||||
|
addBigNum(bigRow,pool.total.toLocaleString(),'Total Workers','#f0f6fc');
|
||||||
|
addBigNum(bigRow,relPct+'%','Reliable','#3fb950');
|
||||||
|
addBigNum(bigRow,elitePct+'%','Elite (90%+)','#58a6ff');
|
||||||
|
c1.appendChild(bigRow);
|
||||||
|
if(d.archetypes&&d.archetypes.length){
|
||||||
|
var ar=div('arch-row');
|
||||||
|
var archColors={reliable:'#3fb950',communicator:'#58a6ff',flexible:'#d29922',leader:'#bc8cff',specialist:'#f0883e',improving:'#79c0ff',erratic:'#f85149',silent:'#484f58'};
|
||||||
|
d.archetypes.forEach(function(a){
|
||||||
|
var pct=pool.total?Math.round(a.cnt/pool.total*100):0;
|
||||||
|
var color=archColors[a.archetype]||'#484f58';
|
||||||
|
var pill=el('span','background:'+color+'20;color:'+color+';border:1px solid '+color+'40;padding:3px 10px;border-radius:10px;font-size:11px;font-weight:500',a.archetype+' '+pct+'%');
|
||||||
|
pill.className='arch-pill';ar.appendChild(pill);
|
||||||
|
});
|
||||||
|
c1.appendChild(ar);
|
||||||
|
}
|
||||||
|
container.appendChild(c1);
|
||||||
|
|
||||||
|
// Card 2: Geographic Bench
|
||||||
|
var c2=makeCard('bench','GEOGRAPHIC BENCH','Where You\'re Strong — and Thin','Reliable worker density by state');
|
||||||
|
var sorted=(d.bench||[]).slice().sort(function(a,b){return (b.total?b.reliable/b.total:0)-(a.total?a.reliable/a.total:0)});
|
||||||
|
sorted.forEach(function(s){
|
||||||
|
var pct=s.total?Math.round(s.reliable/s.total*100):0;
|
||||||
|
var color=pct>45?'#3fb950':pct>35?'#d29922':'#f85149';
|
||||||
|
var row=div('bench-row');
|
||||||
|
row.appendChild(el('span','width:24px;font-weight:600;color:#f0f6fc',s.state));
|
||||||
|
var bar=el('div','flex:1;height:14px;background:#161b22;border-radius:4px;overflow:hidden');
|
||||||
|
bar.appendChild(el('div','height:100%;border-radius:4px;background:'+color+';width:'+pct+'%;transition:width 0.8s'));
|
||||||
|
row.appendChild(bar);
|
||||||
|
row.appendChild(el('span','width:40px;text-align:right;font-size:11px;font-weight:600;color:'+color,pct+'%'));
|
||||||
|
row.appendChild(el('span','font-size:10px;color:#484f58;width:60px',s.total.toLocaleString()));
|
||||||
|
c2.appendChild(row);
|
||||||
|
});
|
||||||
|
var weakest=sorted[sorted.length-1];
|
||||||
|
if(weakest){
|
||||||
|
var wpct=weakest.total?Math.round(weakest.reliable/weakest.total*100):0;
|
||||||
|
c2.appendChild(makeCallout('Warning: '+weakest.state+' is your thinnest bench at '+wpct+'% reliable. If a client there ramps up, you may struggle to fill.','#1a1500','#854d0e'));
|
||||||
|
}
|
||||||
|
container.appendChild(c2);
|
||||||
|
|
||||||
|
// Card 3: Comeback Watch
|
||||||
|
if(d.gems&&d.gems.length){
|
||||||
|
var c3=makeCard('gems','COMEBACK WATCH',pool.improving.toLocaleString()+' Workers Improving','These workers crossed 80% reliability — they earned a second look');
|
||||||
|
d.gems.forEach(function(w){
|
||||||
|
addWorkerMini(c3,w.name,w.role+' · '+w.city+', '+w.state,'Reliability: '+Math.round(w.rel*100)+'% rising','#79c0ff','improving');
|
||||||
|
});
|
||||||
|
c3.appendChild(makeCallout('These workers were previously below threshold. The system detected their improvement automatically — no staffer had to track it.','#0d261a','#238636'));
|
||||||
|
container.appendChild(c3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Card 4: Risk Watch
|
||||||
|
if(d.risks&&d.risks.length){
|
||||||
|
var c4=makeCard('risk','RISK WATCH',pool.erratic.toLocaleString()+' Erratic + '+pool.silent_cnt.toLocaleString()+' Silent Workers','Low reliability + behavioral flags — review placement priority');
|
||||||
|
d.risks.forEach(function(w){
|
||||||
|
addWorkerMini(c4,w.name,w.role+' · '+w.city+', '+w.state,'Reliability: '+Math.round(w.rel*100)+'% · Responsiveness: '+Math.round(w.resp*100)+'%','#f85149',w.archetype);
|
||||||
|
});
|
||||||
|
c4.appendChild(makeCallout('Not firing — deprioritizing. Every failed placement costs time and client trust. The system flags these automatically so you don\'t learn the hard way.','#2d0d0d','#7f1d1d'));
|
||||||
|
container.appendChild(c4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Card 5: Ready & Waiting
|
||||||
|
if(d.untapped&&d.untapped.length){
|
||||||
|
var c5=makeCard('talent','READY & WAITING','Available + Reliable — Call These First','85%+ reliability and actively available right now');
|
||||||
|
d.untapped.forEach(function(w){
|
||||||
|
addWorkerMini(c5,w.name,w.role+' · '+w.city+', '+w.state,'Available: '+Math.round(w.avail*100)+'% · Reliable: '+Math.round(w.rel*100)+'%','#bc8cff','ready');
|
||||||
|
});
|
||||||
|
container.appendChild(c5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Card 6: Role Supply
|
||||||
|
if(d.supply&&d.supply.length){
|
||||||
|
var c6=makeCard('supply','ROLE SUPPLY','Workers by Role','Roles with high supply but low availability may indicate scheduling problems');
|
||||||
|
d.supply.slice(0,10).forEach(function(r){
|
||||||
|
var row=div('supply-row');
|
||||||
|
row.appendChild(el('span','color:#f0f6fc;font-weight:500',r.role));
|
||||||
|
var nums=el('span','color:#8b949e;font-size:11px');
|
||||||
|
nums.appendChild(el('strong','color:#f0f6fc',r.supply.toLocaleString()));
|
||||||
|
nums.appendChild(document.createTextNode(' total · '));
|
||||||
|
nums.appendChild(el('strong','color:#f0f6fc',r.available.toLocaleString()));
|
||||||
|
nums.appendChild(document.createTextNode(' available · rel '+Math.round(r.avg_rel*100)+'%'));
|
||||||
|
row.appendChild(nums);c6.appendChild(row);
|
||||||
|
});
|
||||||
|
container.appendChild(c6);
|
||||||
|
}
|
||||||
|
setupPrompts(d);
|
||||||
|
}).catch(function(e){
|
||||||
|
document.getElementById('brief').textContent='Failed to load brief: '+e.message;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeCard(cls,label,headline,sub){
|
||||||
|
var d=div('bcard '+cls);
|
||||||
|
d.appendChild(div('label',label));d.appendChild(div('headline',headline));d.appendChild(div('sub',sub));return d;
|
||||||
|
}
|
||||||
|
function addBigNum(parent,num,label,color){
|
||||||
|
var d=el('div','');
|
||||||
|
var n=el('div','font-size:36px;font-weight:800;line-height:1;color:'+color,num);
|
||||||
|
d.appendChild(n);d.appendChild(el('div','font-size:10px;color:#484f58;margin-top:2px',label));parent.appendChild(d);
|
||||||
|
}
|
||||||
|
function addWorkerMini(parent,name,detail,extra,color,badge){
|
||||||
|
var w=div('wk');
|
||||||
|
var av=el('div','width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px;color:#f0f6fc;flex-shrink:0;background:'+AC[Math.floor(Math.random()*AC.length)]);
|
||||||
|
av.textContent=(name||'').split(' ').map(function(n){return(n[0]||'').toUpperCase()}).slice(0,2).join('');
|
||||||
|
var info=div('info');
|
||||||
|
info.appendChild(div('nm',name));info.appendChild(div('det',detail));
|
||||||
|
if(extra){var ex=div('det');ex.style.color='#58a6ff';ex.textContent=extra;info.appendChild(ex)}
|
||||||
|
w.appendChild(av);w.appendChild(info);
|
||||||
|
if(badge){var b=el('span','padding:2px 8px;border-radius:8px;font-size:10px;font-weight:600;background:'+(color||'#484f58')+'20;color:'+(color||'#484f58'),badge);w.appendChild(b)}
|
||||||
|
parent.appendChild(w);
|
||||||
|
}
|
||||||
|
function makeCallout(text,bg,border){
|
||||||
|
return el('div','font-size:11px;color:#8b949e;margin-top:8px;padding:8px 10px;background:'+(bg||'#0d1117')+';border:1px solid '+(border||'#1b2130')+';border-radius:6px;font-style:italic',text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupPrompts(data){
|
||||||
|
var container=document.getElementById('prompts');container.textContent='';
|
||||||
|
var prompts=[];
|
||||||
|
if(data.gems&&data.gems[0])prompts.push('Find someone like '+data.gems[0].name+' but in OH');
|
||||||
|
prompts.push('Who could handle industrial electrical work?');
|
||||||
|
prompts.push('What if we lose our top 5 forklift operators?');
|
||||||
|
prompts.push('Which workers should we stop placing?');
|
||||||
|
if(data.supply&&data.supply.length>2){
|
||||||
|
var under=data.supply.slice().sort(function(a,b){return (a.available/a.supply)-(b.available/b.supply)})[0];
|
||||||
|
if(under)prompts.push('Show me available '+under.role+'s');
|
||||||
|
}
|
||||||
|
prompts.push('Find bilingual workers with leadership skills');
|
||||||
|
prompts.forEach(function(p){
|
||||||
|
var btn=el('button','',p);btn.className='prompt-btn';
|
||||||
|
btn.onclick=function(){document.getElementById('q').value=p;send()};
|
||||||
|
container.appendChild(btn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Chat ───
|
||||||
|
function send(){
|
||||||
|
var input=document.getElementById('q');
|
||||||
|
var q=input.value.trim();if(!q)return;
|
||||||
|
input.value='';
|
||||||
|
var chat=document.getElementById('chat');
|
||||||
|
var userMsg=div('msg user');var bubble=div('bubble',q);userMsg.appendChild(bubble);chat.appendChild(userMsg);
|
||||||
|
var thinking=el('div','display:flex;align-items:center;gap:8px;color:#58a6ff;font-size:12px;padding:12px');
|
||||||
|
thinking.appendChild(document.createTextNode('Querying 500K profiles'));
|
||||||
|
var dots=el('span','');dots.className='dots';
|
||||||
|
dots.appendChild(el('span','','.'));dots.appendChild(el('span','','.'));dots.appendChild(el('span','','.'));
|
||||||
|
thinking.appendChild(dots);chat.appendChild(thinking);chat.scrollTop=chat.scrollHeight;
|
||||||
|
document.getElementById('send-btn').disabled=true;
|
||||||
|
|
||||||
|
api('/intelligence/chat',{message:q}).then(function(d){
|
||||||
|
thinking.remove();document.getElementById('send-btn').disabled=false;
|
||||||
|
var msg=div('msg system');var bbl=div('bubble');
|
||||||
|
|
||||||
|
// Steps
|
||||||
|
if(d.queries_run&&d.queries_run.length){
|
||||||
|
var steps=div('steps');
|
||||||
|
d.queries_run.forEach(function(s){steps.appendChild(el('div','font-size:11px;color:#58a6ff;padding:2px 0','→ '+s))});
|
||||||
|
bbl.appendChild(steps);
|
||||||
|
}
|
||||||
|
bbl.appendChild(div('summary',d.summary||''));
|
||||||
|
|
||||||
|
// Type-specific rendering
|
||||||
|
if(d.type==='similar'&&d.source){
|
||||||
|
var sc=div('source-card');
|
||||||
|
sc.appendChild(el('div','font-size:10px;color:#d29922;text-transform:uppercase;letter-spacing:1px;margin-bottom:4px','Source Worker'));
|
||||||
|
sc.appendChild(el('div','font-weight:700;color:#f0f6fc;font-size:14px',d.source.name));
|
||||||
|
sc.appendChild(el('div','font-size:12px;color:#d29922',d.source.role+' · '+d.source.city+', '+d.source.state+' · Reliability: '+Math.round(d.source.rel*100)+'%'));
|
||||||
|
sc.appendChild(el('div','font-size:11px;color:#8b949e;margin-top:4px','Skills: '+(d.source.skills||'none')+' · Archetype: '+(d.source.archetype||'unknown')));
|
||||||
|
bbl.appendChild(sc);
|
||||||
|
renderResults(bbl,d.results);
|
||||||
|
if(d.sql_matches)bbl.appendChild(el('div','font-size:10px;color:#484f58;margin-top:6px','Filtered pool: '+d.sql_matches.toLocaleString()+' workers'));
|
||||||
|
}
|
||||||
|
else if(d.type==='discovery'){
|
||||||
|
renderResults(bbl,d.results);
|
||||||
|
if(d.note)bbl.appendChild(el('div','font-size:11px;color:#8b949e;font-style:italic;margin-top:8px;padding:8px;background:#0d1117;border-radius:6px',d.note));
|
||||||
|
}
|
||||||
|
else if(d.type==='whatif'){
|
||||||
|
var sevColors={HIGH:['#7f1d1d','#fca5a5'],MEDIUM:['#854d0e','#fcd34d'],LOW:['#238636','#86efac']};
|
||||||
|
var sc2=sevColors[d.risk_level]||sevColors.MEDIUM;
|
||||||
|
var sev=el('span','display:inline-block;padding:3px 10px;border-radius:8px;font-size:11px;font-weight:700;margin-left:8px;background:'+sc2[0]+';color:'+sc2[1],'RISK: '+d.risk_level);
|
||||||
|
bbl.querySelector('.summary').appendChild(sev);
|
||||||
|
|
||||||
|
bbl.appendChild(el('div','font-size:11px;color:#f85149;margin:8px 0 4px;font-weight:600','Workers you\'d lose:'));
|
||||||
|
(d.lost||[]).forEach(function(w){
|
||||||
|
var rc=div('result-card');rc.style.borderLeft='3px solid #f85149';
|
||||||
|
rc.appendChild(el('div','font-weight:600;color:#f0f6fc;font-size:13px',w.name));
|
||||||
|
rc.appendChild(el('div','font-size:11px;color:#8b949e;margin-top:2px',w.role+' · '+w.city+', '+w.state+' · Reliability: '+Math.round(w.rel*100)+'%'));
|
||||||
|
bbl.appendChild(rc);
|
||||||
|
});
|
||||||
|
bbl.appendChild(el('div','font-size:11px;color:#3fb950;margin:12px 0 4px;font-weight:600','Remaining bench: '+d.reliable_remaining+' reliable workers across '+(d.bench||[]).length+' states ('+d.total_in_role+' total in role)'));
|
||||||
|
(d.bench||[]).forEach(function(b){
|
||||||
|
var row=el('div','display:flex;justify-content:space-between;padding:4px 8px;font-size:12px');
|
||||||
|
row.appendChild(el('span','color:#f0f6fc',b.state+' — '+b.total+' total'));
|
||||||
|
row.appendChild(el('span','color:'+(b.reliable>5?'#3fb950':'#f85149'),b.reliable+' reliable'));
|
||||||
|
bbl.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if(d.type==='risk'){
|
||||||
|
(d.results||[]).forEach(function(w){
|
||||||
|
var rc=div('result-card');rc.style.borderLeft='3px solid #f85149';
|
||||||
|
rc.appendChild(el('div','font-weight:600;color:#f0f6fc;font-size:13px',w.name));
|
||||||
|
rc.appendChild(el('div','font-size:11px;color:#8b949e;margin-top:2px',w.role+' · '+w.city+', '+w.state));
|
||||||
|
rc.appendChild(el('div','font-size:10px;color:#f85149;margin-top:4px','Reliability: '+Math.round(w.rel*100)+'% · Responsiveness: '+Math.round(w.resp*100)+'% · Compliance: '+Math.round(w.compl*100)+'% · '+w.archetype));
|
||||||
|
bbl.appendChild(rc);
|
||||||
|
});
|
||||||
|
if(d.total_flagged)bbl.appendChild(el('div','font-size:10px;color:#484f58;margin-top:6px',d.total_flagged.toLocaleString()+' total workers match this risk profile'));
|
||||||
|
}
|
||||||
|
else if(d.type==='search'||d.type==='answer'){
|
||||||
|
renderResults(bbl,d.results||d.sources||[]);
|
||||||
|
}
|
||||||
|
else if(d.type==='error'){
|
||||||
|
bbl.appendChild(el('div','color:#f85149;font-size:13px',d.summary));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(d.duration_ms){
|
||||||
|
var meta=div('meta');
|
||||||
|
meta.appendChild(el('span','margin-right:12px',d.duration_ms+'ms'));
|
||||||
|
if(d.sql_matches)meta.appendChild(el('span','',d.sql_matches.toLocaleString()+' records scanned'));
|
||||||
|
bbl.appendChild(meta);
|
||||||
|
}
|
||||||
|
msg.appendChild(bbl);chat.appendChild(msg);chat.scrollTop=chat.scrollHeight;
|
||||||
|
}).catch(function(e){
|
||||||
|
thinking.remove();document.getElementById('send-btn').disabled=false;
|
||||||
|
var msg=div('msg system');var bbl=div('bubble');bbl.style.borderColor='#f85149';
|
||||||
|
bbl.appendChild(el('div','color:#f85149','Error: '+e.message));
|
||||||
|
msg.appendChild(bbl);chat.appendChild(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderResults(parent,results){
|
||||||
|
if(!results||!results.length)return;
|
||||||
|
results.forEach(function(r){
|
||||||
|
var text=r.text||r.chunk_text||'';
|
||||||
|
var parts=text.split(/\u2014|\u2013|\u002D{2,}/);
|
||||||
|
var name=parts[0]?parts[0].trim():(r.name||r.doc_id||'Worker');
|
||||||
|
var rest=parts[1]?parts[1].trim():'';
|
||||||
|
var roleM=rest.match(/^(.+?)\s+in\s+(.+?)\./);
|
||||||
|
var skillM=rest.match(/Skills:\s*([^.]+)/);
|
||||||
|
var certM=rest.match(/Cert(?:s|ifications)?:\s*([^.]+)/);
|
||||||
|
var relM=rest.match(/Reliability:\s*([\d.]+)/);
|
||||||
|
var archM=rest.match(/Archetype:\s*(\w+)/);
|
||||||
|
|
||||||
|
var rc=div('result-card');
|
||||||
|
rc.appendChild(el('div','font-weight:600;color:#f0f6fc;font-size:13px',name));
|
||||||
|
if(roleM)rc.appendChild(el('div','font-size:11px;color:#8b949e;margin-top:2px',roleM[1]+' · '+roleM[2]));
|
||||||
|
var scoreParts=[];
|
||||||
|
if(r.score)scoreParts.push('Match: '+Math.round(r.score*100)+'%');
|
||||||
|
if(relM)scoreParts.push('Reliability: '+Math.round(relM[1]*100)+'%');
|
||||||
|
if(archM)scoreParts.push(archM[1]);
|
||||||
|
if(scoreParts.length)rc.appendChild(el('div','font-size:10px;color:#58a6ff;margin-top:4px',scoreParts.join(' · ')));
|
||||||
|
|
||||||
|
if(skillM||certM){
|
||||||
|
var tags=el('div','display:flex;gap:4px;flex-wrap:wrap;margin-top:6px');
|
||||||
|
if(skillM)skillM[1].split(/[|,]/).forEach(function(s){if(s.trim()){tags.appendChild(el('span','padding:2px 8px;background:#1a2744;color:#58a6ff;border-radius:8px;font-size:10px',s.trim()))}});
|
||||||
|
if(certM)certM[1].split(/[|,]/).forEach(function(c){if(c.trim()&&c.trim()!=='none'){tags.appendChild(el('span','padding:2px 8px;background:#1a3a2a;color:#3fb950;border-radius:8px;font-size:10px',c.trim()))}});
|
||||||
|
rc.appendChild(tags);
|
||||||
|
}
|
||||||
|
parent.appendChild(rc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script></body></html>
|
||||||
@ -1029,8 +1029,139 @@ tr:hover{background:#111827}
|
|||||||
return ok(await runWeekSimulation());
|
return ok(await runWeekSimulation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Staffing Intelligence Console ───
|
||||||
|
if (url.pathname === "/console") {
|
||||||
|
return new Response(Bun.file(import.meta.dir + "/console.html"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intelligence Brief — parallel analytics across 500K profiles
|
||||||
|
if (url.pathname === "/intelligence/brief" && req.method === "POST") {
|
||||||
|
const start = Date.now();
|
||||||
|
const [poolR, benchR, supplyR, gemsR, risksR, untappedR, archetypeR] = await Promise.all([
|
||||||
|
api("POST", "/query/sql", { sql: `SELECT COUNT(*) total, ROUND(AVG(CAST(reliability AS DOUBLE)),3) avg_rel, SUM(CASE WHEN CAST(reliability AS DOUBLE)>0.9 THEN 1 ELSE 0 END) elite, SUM(CASE WHEN CAST(reliability AS DOUBLE)>0.8 THEN 1 ELSE 0 END) reliable, SUM(CASE WHEN archetype='erratic' THEN 1 ELSE 0 END) erratic, SUM(CASE WHEN archetype='silent' THEN 1 ELSE 0 END) silent_cnt, SUM(CASE WHEN archetype='improving' THEN 1 ELSE 0 END) improving FROM workers_500k` }),
|
||||||
|
api("POST", "/query/sql", { sql: `SELECT state, COUNT(*) total, ROUND(AVG(CAST(reliability AS DOUBLE)),3) avg_rel, SUM(CASE WHEN CAST(reliability AS DOUBLE)>0.8 THEN 1 ELSE 0 END) reliable, SUM(CASE WHEN CAST(availability AS DOUBLE)>0.5 THEN 1 ELSE 0 END) available FROM workers_500k GROUP BY state ORDER BY total DESC` }),
|
||||||
|
api("POST", "/query/sql", { sql: `SELECT role, COUNT(*) supply, SUM(CASE WHEN CAST(availability AS DOUBLE)>0.5 THEN 1 ELSE 0 END) available, ROUND(AVG(CAST(reliability AS DOUBLE)),3) avg_rel FROM workers_500k GROUP BY role ORDER BY supply DESC` }),
|
||||||
|
api("POST", "/query/sql", { sql: `SELECT name, role, city, state, ROUND(CAST(reliability AS DOUBLE),2) rel, ROUND(CAST(availability AS DOUBLE),2) avail, archetype, skills FROM workers_500k WHERE archetype='improving' AND CAST(reliability AS DOUBLE)>0.8 ORDER BY CAST(reliability AS DOUBLE) DESC LIMIT 5` }),
|
||||||
|
api("POST", "/query/sql", { sql: `SELECT name, role, city, state, ROUND(CAST(reliability AS DOUBLE),2) rel, ROUND(CAST(responsiveness AS DOUBLE),2) resp, ROUND(CAST(compliance AS DOUBLE),2) compl, archetype FROM workers_500k WHERE archetype IN ('erratic','silent') AND CAST(reliability AS DOUBLE)<0.5 ORDER BY CAST(reliability AS DOUBLE) ASC LIMIT 5` }),
|
||||||
|
api("POST", "/query/sql", { sql: `SELECT name, role, city, state, ROUND(CAST(availability AS DOUBLE),2) avail, ROUND(CAST(reliability AS DOUBLE),2) rel, skills, archetype FROM workers_500k WHERE CAST(availability AS DOUBLE)>0.8 AND CAST(reliability AS DOUBLE)>0.85 ORDER BY CAST(availability AS DOUBLE) DESC LIMIT 5` }),
|
||||||
|
api("POST", "/query/sql", { sql: `SELECT archetype, COUNT(*) cnt, ROUND(AVG(CAST(reliability AS DOUBLE)),3) avg_rel FROM workers_500k GROUP BY archetype ORDER BY cnt DESC` }),
|
||||||
|
]);
|
||||||
|
return ok({
|
||||||
|
pool: poolR.rows?.[0] || {},
|
||||||
|
bench: benchR.rows || [],
|
||||||
|
supply: supplyR.rows || [],
|
||||||
|
gems: gemsR.rows || [],
|
||||||
|
risks: risksR.rows || [],
|
||||||
|
untapped: untappedR.rows || [],
|
||||||
|
archetypes: archetypeR.rows || [],
|
||||||
|
duration_ms: Date.now() - start,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intelligence Chat — natural language → routed queries → structured results
|
||||||
|
if (url.pathname === "/intelligence/chat" && req.method === "POST") {
|
||||||
|
const b = await json();
|
||||||
|
const q = (b.message || "").trim();
|
||||||
|
const lower = q.toLowerCase();
|
||||||
|
const start = Date.now();
|
||||||
|
const queries: string[] = [];
|
||||||
|
|
||||||
|
// Route 1: "Find someone like [Name]"
|
||||||
|
const likeMatch = q.match(/(?:like|similar to)\s+([A-Z][a-z]+(?:\s+[A-Z]\.?\s*)?(?:[A-Z][a-z]+)?)/i);
|
||||||
|
if (likeMatch) {
|
||||||
|
const name = likeMatch[1].trim();
|
||||||
|
queries.push(`SQL: Looking up ${name}'s profile`);
|
||||||
|
const profileR = await api("POST", "/query/sql", { sql: `SELECT * FROM workers_500k WHERE name LIKE '%${name.replace(/'/g,"''")}%' LIMIT 1` });
|
||||||
|
if (profileR.rows?.length) {
|
||||||
|
const worker = profileR.rows[0];
|
||||||
|
const stateMatch = lower.match(/\b(?:in|from)\s+([A-Z]{2})\b/i) || lower.match(/\b(IL|IN|OH|MO|TN|KY|WI|MI|IA|MN)\b/i);
|
||||||
|
const stateFilter = stateMatch ? `state = '${stateMatch[1].toUpperCase()}'` : `state != '${worker.state}'`;
|
||||||
|
queries.push(`Vector: Semantic similarity on ${worker.name}'s full profile → ${stateFilter}`);
|
||||||
|
const searchR = await api("POST", "/vectors/hybrid", {
|
||||||
|
question: worker.resume_text || `${worker.role} in ${worker.city} with skills ${worker.skills}`,
|
||||||
|
index_name: "workers_500k_v1",
|
||||||
|
sql_filter: stateFilter + ` AND CAST(reliability AS DOUBLE) >= 0.7`,
|
||||||
|
filter_dataset: "ethereal_workers", id_column: "worker_id", top_k: 5, generate: false,
|
||||||
|
});
|
||||||
|
return ok({ type: "similar", summary: `Found ${(searchR.sources||[]).length} workers similar to ${worker.name}${stateMatch ? ' in '+stateMatch[1].toUpperCase() : ' (other states)'}`,
|
||||||
|
source: { name: worker.name, role: worker.role, city: worker.city, state: worker.state, rel: worker.reliability, skills: worker.skills, archetype: worker.archetype },
|
||||||
|
results: (searchR.sources||[]).map((s:any) => ({ doc_id: s.doc_id, score: s.score, text: s.chunk_text })),
|
||||||
|
sql_matches: searchR.sql_matches, queries_run: queries, duration_ms: Date.now() - start });
|
||||||
|
}
|
||||||
|
return ok({ type: "error", summary: `Couldn't find "${name}" in the database. Try a full name.`, queries_run: queries, duration_ms: Date.now() - start });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route 2: "What if we lose"
|
||||||
|
if (/what if|lose|happens if/i.test(lower)) {
|
||||||
|
const roleMatch = lower.match(/(?:lose|lost?)\s+(?:our\s+)?(?:top\s+)?(\d+)?\s*(.+?)(?:\?|$)/i);
|
||||||
|
if (roleMatch) {
|
||||||
|
const count = parseInt(roleMatch[1]) || 5;
|
||||||
|
const subject = roleMatch[2].trim().replace(/\s*workers?\s*$/,'').replace(/s$/,'');
|
||||||
|
queries.push(`SQL: Top ${count} ${subject}s by reliability`);
|
||||||
|
const topR = await api("POST", "/query/sql", { sql: `SELECT name, role, city, state, ROUND(CAST(reliability AS DOUBLE),2) rel, skills FROM workers_500k WHERE LOWER(role) LIKE '%${subject.replace(/'/g,"''")}%' ORDER BY CAST(reliability AS DOUBLE) DESC LIMIT ${count}` });
|
||||||
|
if (topR.rows?.length) {
|
||||||
|
const states = [...new Set(topR.rows.map((r:any) => r.state))];
|
||||||
|
queries.push(`SQL: Bench depth for ${subject}s in ${states.join(', ')}`);
|
||||||
|
const benchR = await api("POST", "/query/sql", { sql: `SELECT state, COUNT(*) total, SUM(CASE WHEN CAST(reliability AS DOUBLE)>0.8 THEN 1 ELSE 0 END) reliable FROM workers_500k WHERE LOWER(role) LIKE '%${subject.replace(/'/g,"''")}%' AND state IN (${states.map((s:string)=>`'${s}'`).join(',')}) GROUP BY state` });
|
||||||
|
const totalInRole = (benchR.rows||[]).reduce((s:number,r:any) => s + r.total, 0);
|
||||||
|
const reliableRemaining = (benchR.rows||[]).reduce((s:number,r:any) => s + r.reliable, 0) - topR.rows.length;
|
||||||
|
return ok({ type: "whatif", summary: `Impact: losing top ${topR.rows.length} ${subject} workers`,
|
||||||
|
lost: topR.rows, bench: benchR.rows||[], total_in_role: totalInRole, reliable_remaining: Math.max(0, reliableRemaining),
|
||||||
|
risk_level: reliableRemaining < count * 2 ? "HIGH" : reliableRemaining < count * 5 ? "MEDIUM" : "LOW",
|
||||||
|
queries_run: queries, duration_ms: Date.now() - start });
|
||||||
|
}
|
||||||
|
return ok({ type: "error", summary: `Couldn't find workers in the "${subject}" role. Try: welder, forklift operator, assembler, etc.`, queries_run: queries, duration_ms: Date.now() - start });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route 3: "Who could handle" — semantic role discovery
|
||||||
|
if (/could handle|capable of|suitable for|qualified for|try.*for|can do/i.test(lower)) {
|
||||||
|
const roleDesc = q.replace(/^.*?(?:handle|capable of|suitable for|qualified for|try\s+\w+\s+for|can do)\s*/i,'').replace(/\?$/,'').trim();
|
||||||
|
queries.push(`Vector: Semantic search for "${roleDesc}" — no exact role match needed`);
|
||||||
|
const searchR = await api("POST", "/vectors/hybrid", {
|
||||||
|
question: `Worker experienced in ${roleDesc}, relevant skills and certifications`,
|
||||||
|
index_name: "workers_500k_v1", sql_filter: "CAST(reliability AS DOUBLE) >= 0.75",
|
||||||
|
filter_dataset: "ethereal_workers", id_column: "worker_id", top_k: 8, generate: false,
|
||||||
|
});
|
||||||
|
return ok({ type: "discovery", summary: `${(searchR.sources||[]).length} workers found through semantic skill matching for: "${roleDesc}"`,
|
||||||
|
role_searched: roleDesc, results: (searchR.sources||[]).map((s:any) => ({ doc_id: s.doc_id, score: s.score, text: s.chunk_text })),
|
||||||
|
sql_matches: searchR.sql_matches,
|
||||||
|
note: "None of these workers have this exact role title. They were found because their skills, certifications, and experience are semantically similar. This is talent discovery — finding people for roles that don't exist in your database yet.",
|
||||||
|
queries_run: queries, duration_ms: Date.now() - start });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route 4: "Stop placing" / risk workers
|
||||||
|
if (/stop placing|worst|problem|flag|risk|underperform|fire|let go/i.test(lower)) {
|
||||||
|
queries.push("SQL: erratic/silent workers with reliability < 50%");
|
||||||
|
const riskR = await api("POST", "/query/sql", { sql: `SELECT name, role, city, state, ROUND(CAST(reliability AS DOUBLE),2) rel, ROUND(CAST(responsiveness AS DOUBLE),2) resp, ROUND(CAST(compliance AS DOUBLE),2) compl, archetype FROM workers_500k WHERE archetype IN ('erratic','silent') AND CAST(reliability AS DOUBLE)<0.5 ORDER BY CAST(reliability AS DOUBLE) ASC LIMIT 10` });
|
||||||
|
const countR = await api("POST", "/query/sql", { sql: `SELECT COUNT(*) cnt FROM workers_500k WHERE archetype IN ('erratic','silent') AND CAST(reliability AS DOUBLE)<0.5` });
|
||||||
|
return ok({ type: "risk", summary: `${countR.rows?.[0]?.cnt || 0} workers flagged — showing the 10 lowest performers`,
|
||||||
|
results: riskR.rows||[], total_flagged: countR.rows?.[0]?.cnt || 0,
|
||||||
|
queries_run: queries, duration_ms: Date.now() - start });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route 5: Analytics / counts
|
||||||
|
if (/how many|count|total|percentage|average|breakdown/i.test(lower)) {
|
||||||
|
queries.push("RAG: analytical question → vector retrieval + LLM reasoning");
|
||||||
|
const ragR = await api("POST", "/vectors/rag", { index_name: "workers_500k_v1", question: q, top_k: 3 });
|
||||||
|
return ok({ type: "answer", summary: ragR.answer || "Couldn't determine the answer from the data",
|
||||||
|
sources: (ragR.sources||[]).map((s:any) => ({ doc_id: s.doc_id, text: s.chunk_text, score: s.score })),
|
||||||
|
queries_run: queries, duration_ms: Date.now() - start });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default: hybrid search with generation
|
||||||
|
queries.push("Hybrid: SQL filter + vector semantic search + LLM summary");
|
||||||
|
const searchR = await api("POST", "/vectors/hybrid", {
|
||||||
|
question: q, index_name: "workers_500k_v1", sql_filter: "CAST(reliability AS DOUBLE) >= 0.5",
|
||||||
|
filter_dataset: "ethereal_workers", id_column: "worker_id", top_k: 5, generate: true,
|
||||||
|
});
|
||||||
|
return ok({ type: "search", summary: searchR.answer || `Found ${(searchR.sources||[]).length} matching workers`,
|
||||||
|
results: (searchR.sources||[]).map((s:any) => ({ doc_id: s.doc_id, score: s.score, text: s.chunk_text })),
|
||||||
|
sql_matches: searchR.sql_matches, queries_run: queries, duration_ms: Date.now() - start });
|
||||||
|
}
|
||||||
|
|
||||||
activeTrace = null;
|
activeTrace = null;
|
||||||
return err("Unknown path. Available: / /health /search /sql /match /worker/:id /ask /log /playbooks /profile/:id /vram /context /verify /simulation/run", 404);
|
return err("Unknown path. Available: / /health /search /sql /match /worker/:id /ask /log /playbooks /profile/:id /vram /context /verify /simulation/run /console /intelligence/brief /intelligence/chat", 404);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (activeTrace) { scoreTrace(activeTrace, "error", 0, e.message); }
|
if (activeTrace) { scoreTrace(activeTrace, "error", 0, e.message); }
|
||||||
activeTrace = null;
|
activeTrace = null;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user