lakehouse/mcp-server/search.html
root 845acfdcda Rich worker cards: skills, certs, reliability bars — not just names
Each worker in a contract card now shows:
- Initials avatar (color-coded)
- Name + location on same line
- Skill tags (blue pills, top 3 relevant)
- Cert badges (green pills — OSHA, Forklift, Hazmat)
- Archetype tag (purple — reliable, leader, etc)
- Reliability bar with color (green >80%, yellow >50%, red <50%)
- Availability bar with color
- Individual Call/SMS buttons per worker

Contract headers show:
- Urgency dot (red/yellow/blue/green)
- Client name, role × headcount, location, start time
- Progress bar with fill count

GitHub-style dark theme. Every piece of info visible at a glance
without clicking anything. The staffer sees skills, certs, and
reliability for every matched worker the moment the page loads.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 15:27:27 -05:00

335 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Staffing Co-Pilot</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;background:#0b0f19;color:#c9d1d9;font-size:13px;-webkit-font-smoothing:antialiased}
/* Top bar */
.bar{background:#161b22;padding:10px 20px;border-bottom:1px solid #21262d;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px}
.bar h1{font-size:15px;font-weight:600;color:#f0f6fc}
.bar h1 span{color:#7c3aed}
.bar .right{display:flex;gap:12px;align-items:center;font-size:11px;color:#8b949e}
/* Pipeline counters */
.pipeline{display:flex;gap:1px;margin:16px 16px 0;background:#161b22;border-radius:10px;overflow:hidden;border:1px solid #21262d}
.pip{flex:1;padding:14px 8px;text-align:center}
.pip .n{font-size:28px;font-weight:800;font-variant-numeric:tabular-nums}
.pip .l{font-size:9px;text-transform:uppercase;letter-spacing:1px;color:#8b949e;margin-top:2px}
.pip.red .n{color:#f85149}.pip.yel .n{color:#d29922}.pip.blu .n{color:#58a6ff}.pip.grn .n{color:#3fb950}
/* Content */
.content{padding:16px;max-width:1200px;margin:0 auto}
.section-label{font-size:11px;color:#8b949e;text-transform:uppercase;letter-spacing:1px;font-weight:600;margin:20px 0 10px;display:flex;justify-content:space-between;align-items:center}
/* Contract card */
.contract{background:#161b22;border:1px solid #21262d;border-radius:10px;margin-bottom:14px;overflow:hidden}
.contract .hdr{padding:14px 16px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid #21262d;flex-wrap:wrap;gap:8px}
.contract .hdr .left{display:flex;align-items:center;gap:10px}
.contract .urgency{width:10px;height:10px;border-radius:50%;flex-shrink:0}
.urgency.urgent{background:#f85149}.urgency.high{background:#d29922}.urgency.medium{background:#58a6ff}.urgency.low{background:#3fb950}.urgency.filled{background:#3fb950}
.contract .client{font-size:15px;font-weight:600;color:#f0f6fc}
.contract .need{font-size:12px;color:#8b949e}
.contract .hdr .right{display:flex;align-items:center;gap:8px}
.progress{background:#21262d;border-radius:10px;height:6px;width:80px;overflow:hidden}
.progress .fill{height:100%;border-radius:10px;transition:width .3s}
.fill.full{background:#3fb950}.fill.partial{background:#d29922}.fill.empty{background:#484f58}
.contract .hdr .count{font-size:12px;font-weight:600;color:#f0f6fc}
/* Worker card inside contract */
.workers{padding:6px}
.worker{display:flex;align-items:flex-start;gap:12px;padding:10px 12px;border-radius:8px;transition:background .15s}
.worker:hover{background:#1c2333}
.worker .avatar{width:36px;height:36px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;color:#f0f6fc;flex-shrink:0}
.avatar.a1{background:#1a3a2a}.avatar.a2{background:#1a2a3a}.avatar.a3{background:#2a1a3a}.avatar.a4{background:#3a2a1a}.avatar.a5{background:#1a3a3a}
.worker .info{flex:1;min-width:0}
.worker .name-row{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.worker .wname{font-weight:600;color:#f0f6fc;font-size:13px}
.worker .wloc{color:#8b949e;font-size:11px}
.worker .tags{display:flex;gap:4px;flex-wrap:wrap;margin-top:4px}
.tag{padding:1px 7px;border-radius:10px;font-size:10px;font-weight:500}
.tag.skill{background:#1a2744;color:#58a6ff;border:1px solid #1f3d68}
.tag.cert{background:#1a3a2a;color:#3fb950;border:1px solid #238636}
.tag.type{background:#2a1a3a;color:#bc8cff;border:1px solid #553098}
.worker .metrics{display:flex;gap:12px;margin-top:6px;align-items:center}
.metric{display:flex;align-items:center;gap:4px}
.metric .label{color:#8b949e;font-size:10px}
.metric .bar{width:48px;height:4px;background:#21262d;border-radius:2px;overflow:hidden}
.metric .bar .val{height:100%;border-radius:2px}
.bar .high{background:#3fb950}.bar .med{background:#d29922}.bar .low{background:#f85149}
.metric .pct{font-size:10px;font-weight:600;font-variant-numeric:tabular-nums}
.worker .actions{display:flex;gap:4px;flex-shrink:0;align-self:center}
.wbtn{padding:4px 10px;border-radius:4px;font-size:10px;cursor:pointer;border:1px solid #21262d;background:#161b22;color:#8b949e;font-weight:500}
.wbtn:hover{border-color:#58a6ff;color:#58a6ff}
.wbtn.primary{background:#1f3d68;color:#58a6ff;border-color:#1f3d68}
/* Alerts strip */
.alerts{display:flex;gap:8px;margin:16px 16px 0;flex-wrap:wrap}
.alrt{padding:6px 12px;border-radius:6px;font-size:11px;display:flex;align-items:center;gap:4px}
.alrt.w{background:#2d1b00;color:#d29922;border:1px solid #533d13}
.alrt.i{background:#0d1d33;color:#58a6ff;border:1px solid #1f3d68}
.alrt.g{background:#0d261a;color:#3fb950;border:1px solid #238636}
/* Search */
.search-area{margin-top:20px;background:#161b22;border:1px solid #21262d;border-radius:10px;padding:14px}
.search-area summary{cursor:pointer;color:#8b949e;font-size:12px;list-style:none;display:flex;align-items:center;gap:6px}
.search-area summary::-webkit-details-marker{display:none}
.search-area[open] summary{margin-bottom:10px}
.search-area input[type=text]{width:100%;padding:10px 14px;background:#0b0f19;border:1px solid #21262d;border-radius:6px;color:#f0f6fc;font-size:13px;outline:none;margin-bottom:8px}
.search-area input:focus{border-color:#58a6ff}
.srow{display:flex;gap:6px;margin-bottom:8px}
.search-area select{flex:1;padding:7px;background:#0b0f19;border:1px solid #21262d;border-radius:4px;color:#c9d1d9;font-size:11px}
.sbtn{width:100%;padding:8px;background:#7c3aed;border:none;border-radius:6px;color:#fff;font-size:12px;font-weight:600;cursor:pointer}
.sbtn:hover{background:#6d28d9}
#sresults{margin-top:10px}
.footer{text-align:center;padding:16px;color:#484f58;font-size:11px;margin-top:20px}
.footer a{color:#58a6ff;text-decoration:none}
.loading{color:#484f58;text-align:center;padding:16px}
@media(max-width:768px){
.pipeline{flex-wrap:wrap}.pip{min-width:25%}
.worker{flex-direction:column}.worker .actions{align-self:flex-start}
.alerts{flex-direction:column}
.contract .hdr{flex-direction:column;align-items:flex-start}
}
</style></head><body>
<div class="bar">
<h1><span></span> Staffing Co-Pilot</h1>
<div class="right"><span id="status">Loading...</span></div>
</div>
<div class="pipeline" id="pipeline"></div>
<div class="alerts" id="alerts"></div>
<div class="content">
<div class="section-label"><span>Your Contracts</span><span id="day-label"></span></div>
<div id="contracts"><div class="loading">Loading your day...</div></div>
<details class="search-area">
<summary>🔍 Search all 500,000 workers</summary>
<input type="text" id="sq" placeholder="Type what you need..." onkeydown="if(event.key==='Enter')doSearch()">
<div class="srow">
<select id="sst"><option value="">Any State</option><option>IL</option><option>IN</option><option>OH</option><option>MO</option><option>TN</option><option>KY</option><option>WI</option><option>MI</option></select>
<select id="srl"><option value="">Any Role</option><option>Forklift Operator</option><option>Machine Operator</option><option>Assembler</option><option>Loader</option><option>Quality Tech</option><option>Welder</option><option>Sanitation Worker</option><option>Maintenance Tech</option></select>
</div>
<button class="sbtn" onclick="doSearch()">Search</button>
<div id="sresults"></div>
</details>
<div class="footer"><a href="proof">View Proof of Work</a> · 500K workers · 673K AI-indexed</div>
</div>
<script>
var P = location.pathname.indexOf('/lakehouse') >= 0 ? '/lakehouse' : '';
var A = location.origin + P;
var colors = ['a1','a2','a3','a4','a5'];
window.addEventListener('load', loadDay);
function loadDay() {
fetch(A + '/simulation/run', { method: 'POST', headers: {'Content-Type':'application/json'} })
.then(function(r) { return r.json(); })
.then(function(d) {
var today = d.days ? d.days[0] : null;
var sum = d.summary || {};
renderPipeline(today);
renderContracts(today);
document.getElementById('status').textContent = sum.total_filled + '/' + sum.total_needed + ' filled · ' + sum.emergencies + ' urgent';
document.getElementById('day-label').textContent = today ? today.label + ' · ' + today.staffer : '';
})
.catch(function(e) { document.getElementById('contracts').textContent = 'Error: ' + e.message; });
// Alerts
fetch(A + '/sql', { method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({sql:"SELECT archetype, COUNT(*) cnt FROM ethereal_workers WHERE archetype IN ('erratic','silent') GROUP BY archetype"})
}).then(function(r){return r.json()}).then(function(d){
var el = document.getElementById('alerts');
el.textContent = '';
(d.rows||[]).forEach(function(r){
var a=document.createElement('div');
a.className='alrt '+(r.archetype==='erratic'?'w':'i');
a.textContent=(r.archetype==='erratic'?'⚠ ':'📵 ')+r.cnt+' '+r.archetype+' workers';
el.appendChild(a);
});
var g=document.createElement('div');g.className='alrt g';g.textContent='✓ All systems online';
el.appendChild(g);
}).catch(function(){});
}
function renderPipeline(today) {
var el = document.getElementById('pipeline');
el.textContent = '';
var u=0,f=0,fl=0,t=0;
if (today&&today.contracts) today.contracts.forEach(function(c){
t++;if(c.priority==='urgent')u++;if(c.filled>=c.headcount)fl++;else f++;
});
[['red',u,'Urgent'],['yel',f,'Filling'],['blu',t,'Contracts'],['grn',fl,'Filled']].forEach(function(p){
var d=document.createElement('div');d.className='pip '+p[0];
var n=document.createElement('div');n.className='n';n.textContent=p[1];
var l=document.createElement('div');l.className='l';l.textContent=p[2];
d.appendChild(n);d.appendChild(l);el.appendChild(d);
});
}
function parseWorker(text) {
var parts = (text||'').split(/—|/);
var name = parts[0] ? parts[0].trim() : '?';
var rest = parts[1] ? parts[1].trim() : '';
var rm = rest.match(/^(.+?) in (.+?)\./);
var sm = rest.match(/Skills: ([^.]+)/);
var cm = rest.match(/Certs?: ([^.]+)/);
var rr = rest.match(/Reliability: ([\d.]+)/);
var av = rest.match(/Availability: ([\d.]+)/);
var ar = rest.match(/Archetype: (\w+)/);
return {
name: name, role: rm?rm[1]:'', location: rm?rm[2]:'',
skills: sm?sm[1].split('|').slice(0,4):[],
certs: cm?cm[1].split('|').filter(function(c){return c!=='none'}):[],
reliability: rr?parseFloat(rr[1]):0,
availability: av?parseFloat(av[1]):0,
archetype: ar?ar[1]:''
};
}
function renderContracts(today) {
var el = document.getElementById('contracts');
el.textContent = '';
if (!today||!today.contracts||!today.contracts.length) { el.textContent='No contracts'; return; }
var sorted = today.contracts.slice().sort(function(a,b){
var p={urgent:0,high:1,medium:2,low:3};
if(a.filled>=a.headcount&&b.filled<b.headcount)return 1;
if(a.filled<a.headcount&&b.filled>=b.headcount)return -1;
return (p[a.priority]||2)-(p[b.priority]||2);
});
sorted.forEach(function(c) {
var isFilled = c.filled >= c.headcount;
var card = document.createElement('div'); card.className = 'contract';
// Header
var hdr = document.createElement('div'); hdr.className = 'hdr';
var left = document.createElement('div'); left.className = 'left';
var dot = document.createElement('div'); dot.className = 'urgency ' + (isFilled?'filled':c.priority);
var cl = document.createElement('span'); cl.className = 'client'; cl.textContent = c.client;
var need = document.createElement('span'); need.className = 'need';
need.textContent = c.role + ' × ' + c.headcount + ' · ' + (c.city||c.state) + ' · ' + c.start;
left.appendChild(dot); left.appendChild(cl); left.appendChild(need);
var right = document.createElement('div'); right.className = 'right';
var prog = document.createElement('div'); prog.className = 'progress';
var fill = document.createElement('div'); fill.className = 'fill ' + (isFilled?'full':c.filled>0?'partial':'empty');
fill.style.width = Math.round(c.filled/c.headcount*100) + '%';
prog.appendChild(fill);
var cnt = document.createElement('span'); cnt.className = 'count'; cnt.textContent = c.filled + '/' + c.headcount;
right.appendChild(prog); right.appendChild(cnt);
hdr.appendChild(left); hdr.appendChild(right);
card.appendChild(hdr);
// Workers
if (c.matches && c.matches.length) {
var wdiv = document.createElement('div'); wdiv.className = 'workers';
c.matches.slice(0, c.headcount).forEach(function(m, idx) {
var w = parseWorker(m.chunk_text || '');
var wcard = document.createElement('div'); wcard.className = 'worker';
// Avatar
var av = document.createElement('div');
av.className = 'avatar ' + colors[idx % colors.length];
av.textContent = (w.name||'?').split(' ').map(function(n){return n[0]||''}).join('').substring(0,2);
wcard.appendChild(av);
// Info
var info = document.createElement('div'); info.className = 'info';
var nameRow = document.createElement('div'); nameRow.className = 'name-row';
var nm = document.createElement('span'); nm.className = 'wname'; nm.textContent = w.name || m.name || m.doc_id;
var loc = document.createElement('span'); loc.className = 'wloc'; loc.textContent = w.location;
nameRow.appendChild(nm); nameRow.appendChild(loc);
info.appendChild(nameRow);
// Tags
var tags = document.createElement('div'); tags.className = 'tags';
w.skills.slice(0,3).forEach(function(s) {
var t = document.createElement('span'); t.className = 'tag skill'; t.textContent = s.trim(); tags.appendChild(t);
});
w.certs.slice(0,2).forEach(function(c) {
var t = document.createElement('span'); t.className = 'tag cert'; t.textContent = c.trim(); tags.appendChild(t);
});
if (w.archetype) {
var t = document.createElement('span'); t.className = 'tag type'; t.textContent = w.archetype; tags.appendChild(t);
}
info.appendChild(tags);
// Metrics
var metrics = document.createElement('div'); metrics.className = 'metrics';
addMetric(metrics, 'Reliability', w.reliability);
addMetric(metrics, 'Availability', w.availability);
info.appendChild(metrics);
wcard.appendChild(info);
// Actions
var actions = document.createElement('div'); actions.className = 'actions';
var call = document.createElement('button'); call.className = 'wbtn primary'; call.textContent = 'Call';
var sms = document.createElement('button'); sms.className = 'wbtn'; sms.textContent = 'SMS';
actions.appendChild(call); actions.appendChild(sms);
wcard.appendChild(actions);
wdiv.appendChild(wcard);
});
card.appendChild(wdiv);
}
el.appendChild(card);
});
}
function addMetric(parent, label, val) {
var m = document.createElement('div'); m.className = 'metric';
var l = document.createElement('span'); l.className = 'label'; l.textContent = label;
var bar = document.createElement('div'); bar.className = 'bar';
var v = document.createElement('div');
v.className = 'val ' + (val >= 0.8 ? 'high' : val >= 0.5 ? 'med' : 'low');
v.style.width = Math.round(val * 100) + '%';
bar.appendChild(v);
var pct = document.createElement('span'); pct.className = 'pct';
pct.style.color = val >= 0.8 ? '#3fb950' : val >= 0.5 ? '#d29922' : '#f85149';
pct.textContent = Math.round(val * 100) + '%';
m.appendChild(l); m.appendChild(bar); m.appendChild(pct);
parent.appendChild(m);
}
function doSearch() {
var q=document.getElementById('sq').value.trim();if(!q)return;
var st=document.getElementById('sst').value;
var rl=document.getElementById('srl').value;
var out=document.getElementById('sresults');
out.textContent=''; var ld=document.createElement('div');ld.className='loading';ld.textContent='Searching...';out.appendChild(ld);
var f="CAST(reliability AS DOUBLE)>=0.5";
if(st)f+=" AND state='"+st+"'";if(rl)f+=" AND role='"+rl+"'";
fetch(A+'/search',{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({question:q,index_name:'workers_500k_v1',sql_filter:f,dataset:'workers_500k',id_column:'worker_id',top_k:8,generate:false})
}).then(function(r){return r.json()}).then(function(d){
out.textContent='';
var sources=d.sources||[];if(!sources.length){out.textContent='No matches.';return}
var h=document.createElement('div');h.style.cssText='color:#8b949e;font-size:11px;margin-bottom:8px';
h.textContent=(d.sql_matches||0)+' matches → '+sources.length+' best ('+(d.duration_ms||0)+'ms)';out.appendChild(h);
sources.forEach(function(s,idx){
var w=parseWorker(s.chunk_text);
var wcard=document.createElement('div');wcard.className='worker';wcard.style.cssText='background:#161b22;border:1px solid #21262d;border-radius:8px;padding:10px;margin-bottom:6px';
var av=document.createElement('div');av.className='avatar '+colors[idx%colors.length];
av.textContent=(w.name||'?').split(' ').map(function(n){return n[0]||''}).join('').substring(0,2);wcard.appendChild(av);
var info=document.createElement('div');info.className='info';
var nr=document.createElement('div');nr.className='name-row';
var nm=document.createElement('span');nm.className='wname';nm.textContent=w.name;
var loc=document.createElement('span');loc.className='wloc';loc.textContent=w.location;
nr.appendChild(nm);nr.appendChild(loc);info.appendChild(nr);
var tags=document.createElement('div');tags.className='tags';
w.skills.slice(0,3).forEach(function(sk){var t=document.createElement('span');t.className='tag skill';t.textContent=sk.trim();tags.appendChild(t)});
w.certs.slice(0,2).forEach(function(c){var t=document.createElement('span');t.className='tag cert';t.textContent=c.trim();tags.appendChild(t)});
info.appendChild(tags);
var metrics=document.createElement('div');metrics.className='metrics';
addMetric(metrics,'Rel',w.reliability);addMetric(metrics,'Avail',w.availability);
info.appendChild(metrics);wcard.appendChild(info);out.appendChild(wcard);
});
}).catch(function(e){out.textContent='Error: '+e.message});
}
</script>
</body></html>