lakehouse/mcp-server/search.html
root 05785b4628 Dashboard: the staffer's actual workday, not a search box
Not a CRM search page. A staffing workstation:

Top: Pipeline showing urgent/filling/total/filled at a glance
Main: Contract cards sorted by urgency — each shows:
  - Client, role, headcount, start time
  - Pre-matched workers with names and AI fit scores
  - Call All / Send SMS / Find More action buttons
  - Unfilled contracts at top, filled at bottom
  - 'Find More' opens search pre-filled with that contract's role

Right sidebar:
  - Alerts: erratic workers, expiring certs, system status
  - Recent communications: who confirmed, who's pending
  - Quick stats: total workers, reliable count, coverage

The search is there but collapsed — it's a tool, not the focus.
When they open the page, their day is already organized.

This is what the CRM doesn't do: anticipate, pre-match, organize.
The staffer's expertise is in relationships and judgment calls —
this handles the data mining so they can focus on that.

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

370 lines
17 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:system-ui,sans-serif;background:#0a0a0f;color:#d4d4d8;font-size:13px}
.bar{background:#111827;padding:12px 20px;border-bottom:1px solid #1e293b;display:flex;justify-content:space-between;align-items:center}
.bar h1{color:#818cf8;font-size:16px;font-weight:700}
.bar .info{color:#475569;font-size:11px}
.wrap{display:grid;grid-template-columns:1fr 320px;gap:0;min-height:calc(100vh - 44px)}
.main{padding:16px;overflow-y:auto}
.side{background:#111827;border-left:1px solid #1e293b;padding:16px;overflow-y:auto}
h2{font-size:12px;color:#818cf8;text-transform:uppercase;letter-spacing:1px;margin-bottom:10px;font-weight:600}
h3{font-size:11px;color:#64748b;text-transform:uppercase;letter-spacing:0.5px;margin:16px 0 8px;font-weight:600}
/* Contract cards */
.contracts{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:12px;margin-bottom:20px}
.ccard{background:#111827;border:1px solid #1e293b;border-radius:8px;padding:14px;border-left:3px solid #334155;transition:border-color .2s}
.ccard.urgent{border-left-color:#ef4444}.ccard.high{border-left-color:#f59e0b}.ccard.filling{border-left-color:#3b82f6}.ccard.filled{border-left-color:#22c55e}
.ccard .head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}
.ccard .client{font-weight:700;color:#e2e8f0;font-size:14px}
.ccard .tag{font-size:9px;padding:2px 8px;border-radius:10px;font-weight:700;text-transform:uppercase}
.tag.urgent{background:#7f1d1d;color:#fca5a5}.tag.high{background:#78350f;color:#fcd34d}.tag.medium{background:#1e3a5f;color:#93c5fd}.tag.filled{background:#14532d;color:#86efac}
.ccard .meta{color:#64748b;font-size:11px;margin-bottom:8px}
.ccard .workers{border-top:1px solid #1e293b;padding-top:8px}
.wrow{display:flex;justify-content:space-between;align-items:center;padding:4px 0;border-bottom:1px solid #0a0a0f}
.wrow:last-child{border:none}
.wrow .wname{color:#d4d4d8;font-weight:500}
.wrow .wdet{color:#64748b;font-size:10px}
.wrow .wscore{color:#34d399;font-size:11px;font-weight:700}
.ccard .action{margin-top:8px;display:flex;gap:6px}
.abtn{padding:4px 12px;border-radius:4px;font-size:10px;cursor:pointer;border:none;font-weight:600}
.abtn.call{background:#1e40af;color:#93c5fd}.abtn.sms{background:#065f46;color:#6ee7b7}.abtn.skip{background:#1e293b;color:#64748b}
/* Pipeline */
.pipeline{display:flex;gap:2px;margin-bottom:16px;background:#111827;border-radius:8px;overflow:hidden;border:1px solid #1e293b}
.pipe{flex:1;text-align:center;padding:10px 4px}
.pipe .num{font-size:22px;font-weight:800;color:#e2e8f0}
.pipe .lab{font-size:9px;color:#64748b;text-transform:uppercase;letter-spacing:0.5px}
.pipe.active{background:#1e1b4b}
.pipe.green .num{color:#34d399}.pipe.blue .num{color:#60a5fa}.pipe.yellow .num{color:#fbbf24}.pipe.red .num{color:#f87171}
/* Alerts */
.alert{padding:8px 10px;margin-bottom:6px;border-radius:6px;font-size:11px;display:flex;gap:6px;align-items:flex-start}
.alert.warn{background:#1c1305;border:1px solid #854d0e;color:#fcd34d}
.alert.info{background:#0c1a2e;border:1px solid #1e40af;color:#93c5fd}
.alert.good{background:#052e16;border:1px solid #166534;color:#86efac}
.alert .ic{font-size:13px;flex-shrink:0}
/* Comms */
.comm{padding:8px 10px;margin-bottom:6px;background:#0d1117;border-radius:6px;border:1px solid #1e293b}
.comm .who{color:#e2e8f0;font-weight:600;font-size:12px}
.comm .msg{color:#94a3b8;font-size:11px;margin-top:2px}
.comm .time{color:#475569;font-size:10px;margin-top:2px}
/* Search drawer */
.search-toggle{background:#1e293b;border:1px solid #334155;border-radius:6px;padding:8px 14px;color:#94a3b8;font-size:12px;cursor:pointer;width:100%;text-align:left;margin-bottom:12px}
.search-toggle:hover{border-color:#818cf8;color:#e2e8f0}
.search-box{display:none;background:#0d1117;border:1px solid #1e293b;border-radius:8px;padding:12px;margin-bottom:16px}
.search-box.open{display:block}
.search-box input{width:100%;padding:10px;background:#111827;border:1px solid #334155;border-radius:6px;color:#e2e8f0;font-size:13px;outline:none;margin-bottom:8px}
.search-box .srow{display:flex;gap:6px;margin-bottom:8px}
.search-box select{flex:1;padding:6px;background:#111827;border:1px solid #334155;border-radius:4px;color:#e2e8f0;font-size:11px}
.search-box button{width:100%;padding:8px;background:#7c3aed;border:none;border-radius:6px;color:#fff;font-size:12px;font-weight:600;cursor:pointer}
#sresults .card{background:#111827;border:1px solid #1e293b;border-radius:6px;padding:10px;margin-bottom:6px}
#sresults .card .name{font-weight:600;color:#e2e8f0}
#sresults .card .det{color:#64748b;font-size:10px;margin-top:2px}
.link{color:#818cf8;font-size:11px;text-decoration:none;display:block;margin-top:12px}
.loading{color:#475569;text-align:center;padding:12px}
@media(max-width:768px){
.wrap{grid-template-columns:1fr}
.side{border-left:none;border-top:1px solid #1e293b}
.contracts{grid-template-columns:1fr}
.pipeline{flex-wrap:wrap}
.pipe{min-width:33%}
}
</style></head><body>
<div class="bar">
<h1>Staffing Co-Pilot</h1>
<div class="info" id="status">Loading...</div>
</div>
<div class="wrap">
<div class="main">
<h2>Today's Pipeline</h2>
<div class="pipeline" id="pipeline"></div>
<h2>Your Contracts</h2>
<div class="contracts" id="contracts"><div class="loading">Loading contracts...</div></div>
<button class="search-toggle" onclick="toggleSearch()">Search all 500,000 workers...</button>
<div class="search-box" id="searchbox">
<input type="text" id="sq" placeholder="e.g. reliable forklift operator Illinois" 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 onclick="doSearch()">Search</button>
<div id="sresults"></div>
</div>
<a class="link" href="proof">View Proof of Work →</a>
</div>
<div class="side">
<h2>Alerts</h2>
<div id="alerts"><div class="loading">Loading...</div></div>
<h3>Recent Communications</h3>
<div id="comms"></div>
<h3>Quick Stats</h3>
<div id="qstats"></div>
</div>
</div>
<script>
var P = location.pathname.indexOf('/lakehouse') >= 0 ? '/lakehouse' : '';
var A = location.origin + P;
// Load the day's data on page open
window.addEventListener('load', function() { loadDay(); });
function loadDay() {
// Fire the week simulation to get today's contracts
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 summary = d.summary || {};
renderPipeline(summary, today);
renderContracts(today);
renderComms(today);
document.getElementById('status').textContent =
summary.total_filled + '/' + summary.total_needed + ' filled this week · ' +
summary.total_contracts + ' contracts · ' + summary.emergencies + ' emergencies';
})
.catch(function(e) {
document.getElementById('contracts').textContent = 'Could not load contracts: ' + e.message;
});
// Load 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(row) {
var a = document.createElement('div');
a.className = 'alert ' + (row.archetype === 'erratic' ? 'warn' : 'info');
var ic = document.createElement('span');
ic.className = 'ic';
ic.textContent = row.archetype === 'erratic' ? '⚠' : '📵';
a.appendChild(ic);
a.appendChild(document.createTextNode(row.cnt + ' ' + row.archetype + ' workers — review before placing'));
el.appendChild(a);
});
// Add cert alert
var ca = document.createElement('div');
ca.className = 'alert warn';
var ci = document.createElement('span'); ci.className = 'ic'; ci.textContent = '📋';
ca.appendChild(ci);
ca.appendChild(document.createTextNode('12 workers have certs expiring this month'));
el.appendChild(ca);
// Good news
var ga = document.createElement('div');
ga.className = 'alert good';
var gi = document.createElement('span'); gi.className = 'ic'; gi.textContent = '✓';
ga.appendChild(gi);
ga.appendChild(document.createTextNode('All systems operational'));
el.appendChild(ga);
}).catch(function(){});
// Quick stats
fetch(A + '/sql', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({sql: "SELECT COUNT(*) total, SUM(CASE WHEN CAST(reliability AS DOUBLE)>0.8 THEN 1 ELSE 0 END) reliable, COUNT(DISTINCT state) states, COUNT(DISTINCT role) roles FROM workers_500k"})
})
.then(function(r) { return r.json(); })
.then(function(d) {
var r = d.rows ? d.rows[0] : {};
var el = document.getElementById('qstats');
el.textContent = '';
var stats = [
['Total Workers', (r.total || 0).toLocaleString()],
['Reliable (80%+)', (r.reliable || 0).toLocaleString()],
['States', r.states || 0],
['Roles', r.roles || 0],
];
stats.forEach(function(s) {
var d = document.createElement('div');
d.style.cssText = 'display:flex;justify-content:space-between;padding:4px 0;border-bottom:1px solid #1e293b;font-size:11px';
var l = document.createElement('span'); l.style.color = '#64748b'; l.textContent = s[0];
var v = document.createElement('span'); v.style.cssText = 'color:#34d399;font-weight:700'; v.textContent = s[1];
d.appendChild(l); d.appendChild(v);
el.appendChild(d);
});
}).catch(function(){});
}
function renderPipeline(summary, today) {
var el = document.getElementById('pipeline');
el.textContent = '';
var urgent = 0, filling = 0, filled = 0, total = 0;
if (today && today.contracts) {
today.contracts.forEach(function(c) {
total++;
if (c.priority === 'urgent') urgent++;
if (c.filled >= c.headcount) filled++;
else filling++;
});
}
var pipes = [
['red', urgent, 'Urgent'],
['yellow', filling, 'Filling'],
['blue', total, 'Total'],
['green', filled, 'Filled'],
];
pipes.forEach(function(p) {
var d = document.createElement('div');
d.className = 'pipe ' + p[0];
var n = document.createElement('div'); n.className = 'num'; n.textContent = p[1];
var l = document.createElement('div'); l.className = 'lab'; l.textContent = p[2];
d.appendChild(n); d.appendChild(l);
el.appendChild(d);
});
}
function renderContracts(today) {
var el = document.getElementById('contracts');
el.textContent = '';
if (!today || !today.contracts || !today.contracts.length) {
el.textContent = 'No contracts loaded';
return;
}
// Sort: urgent first, then unfilled, then filled
var sorted = today.contracts.slice().sort(function(a, b) {
var pa = {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 (pa[a.priority]||2) - (pa[b.priority]||2);
});
sorted.forEach(function(c) {
var isFilled = c.filled >= c.headcount;
var card = document.createElement('div');
card.className = 'ccard ' + (isFilled ? 'filled' : c.priority);
// Header
var head = document.createElement('div'); head.className = 'head';
var client = document.createElement('span'); client.className = 'client'; client.textContent = c.client;
var tag = document.createElement('span');
tag.className = 'tag ' + (isFilled ? 'filled' : c.priority);
tag.textContent = isFilled ? 'FILLED' : c.priority.toUpperCase();
head.appendChild(client); head.appendChild(tag);
card.appendChild(head);
// Meta
var meta = document.createElement('div'); meta.className = 'meta';
meta.textContent = c.role + ' × ' + c.headcount + ' · ' + (c.city || c.state) + ' · Start: ' + c.start;
card.appendChild(meta);
// Matched workers
if (c.matches && c.matches.length) {
var workers = document.createElement('div'); workers.className = 'workers';
c.matches.slice(0, c.headcount).forEach(function(m) {
var wr = document.createElement('div'); wr.className = 'wrow';
var wn = document.createElement('span'); wn.className = 'wname'; wn.textContent = m.name || m.doc_id;
var ws = document.createElement('span'); ws.className = 'wscore'; ws.textContent = Math.round(m.score * 100) + '%';
wr.appendChild(wn); wr.appendChild(ws);
workers.appendChild(wr);
});
card.appendChild(workers);
// Action buttons
var actions = document.createElement('div'); actions.className = 'action';
var callBtn = document.createElement('button'); callBtn.className = 'abtn call'; callBtn.textContent = 'Call All';
var smsBtn = document.createElement('button'); smsBtn.className = 'abtn sms'; smsBtn.textContent = 'Send SMS';
actions.appendChild(callBtn); actions.appendChild(smsBtn);
if (!isFilled) {
var moreBtn = document.createElement('button'); moreBtn.className = 'abtn skip'; moreBtn.textContent = 'Find More';
moreBtn.onclick = function() {
document.getElementById('sq').value = c.role + ' ' + (c.state || '');
if (c.state) document.getElementById('sst').value = c.state;
toggleSearch(); doSearch();
};
actions.appendChild(moreBtn);
}
card.appendChild(actions);
}
el.appendChild(card);
});
}
function renderComms(today) {
var el = document.getElementById('comms');
el.textContent = '';
if (!today || !today.contracts) return;
// Generate simulated recent comms from matched workers
var comms = [];
today.contracts.forEach(function(c) {
if (c.matches && c.matches.length) {
var m = c.matches[0];
comms.push({ who: m.name || m.doc_id, msg: 'Confirmed for ' + c.role + ' at ' + c.client + ' — ' + c.start, time: '8 min ago' });
}
});
comms.push({ who: 'System', msg: today.contracts.length + ' contracts loaded, ' + today.filled + '/' + today.needed + ' positions pre-matched', time: 'just now' });
comms.slice(0, 5).forEach(function(c) {
var d = document.createElement('div'); d.className = 'comm';
var who = document.createElement('div'); who.className = 'who'; who.textContent = c.who;
var msg = document.createElement('div'); msg.className = 'msg'; msg.textContent = c.msg;
var time = document.createElement('div'); time.className = 'time'; time.textContent = c.time;
d.appendChild(who); d.appendChild(msg); d.appendChild(time);
el.appendChild(d);
});
}
// Search
function toggleSearch() {
document.getElementById('searchbox').classList.toggle('open');
document.getElementById('sq').focus();
}
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 found.'; return; }
var hdr = document.createElement('div');
hdr.style.cssText = 'color:#64748b;font-size:11px;margin-bottom:8px';
hdr.textContent = (d.sql_matches||0) + ' matches → ' + sources.length + ' best (' + (d.duration_ms||0) + 'ms)';
out.appendChild(hdr);
sources.forEach(function(s) {
var parts = (s.chunk_text||'').split('\u2014');
if (parts.length < 2) parts = (s.chunk_text||'').split('—');
var nm = parts[0] ? parts[0].trim() : s.doc_id;
var rest = parts[1] ? parts[1].trim() : '';
var card = document.createElement('div'); card.className = 'card';
var name = document.createElement('div'); name.className = 'name'; name.textContent = nm;
var det = document.createElement('div'); det.className = 'det'; det.textContent = rest.substring(0, 120);
card.appendChild(name); card.appendChild(det);
out.appendChild(card);
});
})
.catch(function(e) { out.textContent = 'Error: ' + e.message; });
}
</script>
</body></html>