Rebuild /console as narrative walkthrough for a skeptical staffer
Old console was a chat playground. New console is a guided,
chapter-based explanation that a non-technical staffing staffer
can read top-down and finish convinced — without needing to
understand any of the underlying technology.
Six chapters, each loading live data:
1. Right now, this system is already thinking
Four stats cards pulled live: construction pipeline $, predicted
worker demand, rows under management, playbooks remembered. Then
a narrative that names the current alert posture (critical/tight/ok).
2. The demand signal is real, not made up
Expandable rows per Chicago permit work_type, with a direct link to
data.cityofchicago.org for verification. Pill labeled LIVE ·
DATA.CITYOFCHICAGO.ORG leaves no ambiguity.
3. Where your own data would live
Catalog enumerated with three pill classes:
- SWAP FOR YOUR DATA (purple) — the synthetic tables that would
be replaced by the client's ATS/CRM/call-log exports
- SYSTEM-GENERATED (blue) — playbook memory, threat_intel, kb_*
produced by the system itself
Row counts + columns visible. Names it honestly.
4. Watch the system rank candidates in real time
Takes the freshest Chicago permit, walks the staffer through all
three steps (derive need → narrow via SQL → rank + boost), shows
the top-5 workers with why, boost chip, memory chip, timeline,
and a plain-English narrative of the CRM gap.
5. Every action compounds
Playbook memory count + sample + narrative about what it means
when the staffer logs a fill.
6. Try it yourself
Free-text input hitting /intelligence/chat, renders response
with memory chip + boost chips + ranked workers.
Security: all API-derived strings go through textContent or
el(tag,cls,text) helper. Zero innerHTML usage on dynamic content.
Passes security reminder hook.
File size: 419 → ~500 lines. Visual style matches the dashboard
(same palette, typography, chip styles) so the two pages feel
like one app.
This commit is contained in:
parent
bb1b471c67
commit
05f2e42c45
@ -1,419 +1,466 @@
|
||||
<!DOCTYPE html>
|
||||
<html><head>
|
||||
<html lang="en"><head>
|
||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Staffing Intelligence Console</title>
|
||||
<title>Lakehouse — What Your Staffing System Would Do</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>
|
||||
body{font-family:'Inter',-apple-system,system-ui,sans-serif;background:#090c10;color:#b0b8c4;font-size:14px;line-height:1.55;-webkit-font-smoothing:antialiased}
|
||||
a{color:#58a6ff;text-decoration:none}
|
||||
a:hover{color:#79c0ff}
|
||||
|
||||
.bar{background:#0d1117;padding:0 24px;height:56px;border-bottom:1px solid #171d27;display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:10}
|
||||
.bar h1{font-size:14px;font-weight:600;color:#e6edf3;letter-spacing:-0.2px}
|
||||
.bar nav{display:flex;gap:2px}
|
||||
.bar nav a{font-size:12px;color:#545d68;padding:6px 14px;border-radius:6px;transition:all 0.15s}
|
||||
.bar nav a:hover{color:#e6edf3;background:#161b22}
|
||||
.bar nav a.active{color:#e6edf3;background:#1c2333}
|
||||
.bar .rt{font-size:11px;color:#545d68}
|
||||
|
||||
.wrap{max-width:1040px;margin:0 auto;padding:28px 20px 60px}
|
||||
|
||||
.chapter{margin-bottom:48px}
|
||||
.chapter .num{color:#545d68;font-size:11px;font-weight:600;letter-spacing:1.6px;text-transform:uppercase;margin-bottom:6px}
|
||||
.chapter h2{color:#e6edf3;font-size:24px;font-weight:700;letter-spacing:-0.4px;margin-bottom:8px;line-height:1.2}
|
||||
.chapter .lede{color:#8b949e;font-size:14px;margin-bottom:18px;max-width:680px;line-height:1.6}
|
||||
|
||||
.card{background:#0d1117;border:1px solid #171d27;border-radius:12px;padding:20px;margin-bottom:12px}
|
||||
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:10px}
|
||||
|
||||
.stat-lg{padding:18px 20px}
|
||||
.stat-lg .n{font-size:30px;font-weight:800;color:#e6edf3;letter-spacing:-1px;line-height:1}
|
||||
.stat-lg .l{font-size:10px;color:#545d68;text-transform:uppercase;letter-spacing:1.2px;margin-top:8px;font-weight:600}
|
||||
.stat-lg .sub{font-size:12px;color:#8b949e;margin-top:4px}
|
||||
|
||||
.pill{display:inline-block;padding:3px 10px;border-radius:10px;font-size:10px;font-weight:600;letter-spacing:0.3px;margin-right:6px}
|
||||
.pill.real{background:#0d2818;color:#3fb950;border:1px solid #2ea04380}
|
||||
.pill.synth{background:#1a1a2e;color:#bc8cff;border:1px solid #5530988c}
|
||||
.pill.flow{background:#0d2340;color:#58a6ff;border:1px solid #1f6feb80}
|
||||
|
||||
.row{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:10px 14px;background:#0d1117;border:1px solid #171d27;border-radius:8px;margin-bottom:6px;font-size:13px}
|
||||
.row:hover{border-color:#21262d}
|
||||
.row .title{color:#e6edf3;font-weight:500}
|
||||
.row .meta{color:#8b949e;font-size:11px;margin-top:2px}
|
||||
.row .val{color:#58a6ff;font-weight:600;white-space:nowrap}
|
||||
|
||||
details{background:#0d1117;border:1px solid #171d27;border-radius:8px;padding:10px 14px;margin-bottom:6px}
|
||||
details summary{cursor:pointer;font-size:13px;color:#e6edf3;font-weight:500;list-style:none}
|
||||
details summary::-webkit-details-marker{display:none}
|
||||
details .body{padding-top:10px;font-size:12px;color:#8b949e}
|
||||
|
||||
.accent-l{border-left:3px solid #2ea043}
|
||||
.accent-b{border-left:3px solid #1f6feb}
|
||||
.accent-a{border-left:3px solid #bc8cff}
|
||||
.accent-w{border-left:3px solid #d29922}
|
||||
|
||||
.worker{display:flex;align-items:center;gap:10px;padding:8px 10px;background:#161b22;border-radius:6px;margin-bottom:4px;font-size:12px}
|
||||
.worker .av{width:28px;height:28px;border-radius:6px;background:#1a2744;display:flex;align-items:center;justify-content:center;font-weight:600;color:#e6edf3;font-size:10px;flex-shrink:0}
|
||||
.worker .info{flex:1;min-width:0}
|
||||
.worker .nm{color:#e6edf3;font-weight:500}
|
||||
.worker .why{color:#545d68;font-size:11px;margin-top:1px}
|
||||
.worker .score{color:#58a6ff;font-size:11px;font-weight:600;white-space:nowrap}
|
||||
|
||||
.mem-chip{background:#0d2818;border:1px solid #2ea04360;border-radius:6px;padding:8px 12px;font-size:11px;color:#86efac;line-height:1.5;margin-top:6px}
|
||||
.mem-chip .l{color:#3fb950;font-weight:600;margin-right:6px}
|
||||
|
||||
.boost-chip{display:inline-block;margin-left:6px;padding:2px 7px;border-radius:9px;font-size:9px;font-weight:600;background:#0d2818;border:1px solid #2ea043;color:#3fb950;vertical-align:middle}
|
||||
|
||||
.try-box{background:#0d1117;border:1px solid #171d27;border-radius:12px;padding:16px}
|
||||
.try-box input{width:100%;padding:12px 16px;background:#161b22;border:1px solid #21262d;border-radius:8px;color:#e6edf3;font-size:13px;outline:none;margin-bottom:10px}
|
||||
.try-box input:focus{border-color:#388bfd}
|
||||
.try-box button{padding:10px 20px;background:#1f6feb;border:none;border-radius:8px;color:#fff;font-size:13px;font-weight:600;cursor:pointer}
|
||||
.try-box button:hover{background:#388bfd}
|
||||
.try-box button:disabled{opacity:0.5;cursor:wait}
|
||||
|
||||
.footer{border-top:1px solid #171d27;padding:20px;text-align:center;color:#3d444d;font-size:11px}
|
||||
|
||||
.loading{color:#484f58;font-style:italic;padding:20px 0;text-align:center}
|
||||
.err{color:#f85149;font-size:12px;padding:10px}
|
||||
|
||||
.narr{color:#8b949e;font-size:13px;line-height:1.7;margin:10px 0;padding:10px 14px;border-left:2px solid #21262d}
|
||||
.narr strong{color:#c9d1d9;font-weight:600}
|
||||
|
||||
.step-label{color:#58a6ff;font-size:12px;margin-top:12px;font-weight:600}
|
||||
.step-body{color:#c9d1d9;font-size:13px;margin-top:2px}
|
||||
|
||||
@media(max-width:720px){
|
||||
.wrap{padding:20px 12px 40px}
|
||||
.chapter h2{font-size:20px}
|
||||
.bar nav{display:none}
|
||||
}
|
||||
</style></head>
|
||||
<body>
|
||||
|
||||
<div class="bar">
|
||||
<h1>Staffing Intelligence Console</h1>
|
||||
<div class="rt" id="brief-time">Analyzing 500,000 profiles...</div>
|
||||
<h1>Lakehouse — What Your Staffing System Would Do</h1>
|
||||
<nav>
|
||||
<a href=".">Dashboard</a>
|
||||
<a href="console" class="active">Walkthrough</a>
|
||||
<a href="proof">Architecture</a>
|
||||
</nav>
|
||||
<div class="rt" id="hdr-time">Reading live state…</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 class="chapter">
|
||||
<div class="num">Chapter 1</div>
|
||||
<h2>Right now, this system is already thinking</h2>
|
||||
<div class="lede">Before you touched anything, it pulled real Chicago building-permit data, measured demand, checked your bench, and began flagging roles that need attention. This isn't theoretical — open your browser network tab and watch the fetches land.</div>
|
||||
<div class="grid" id="ch1-stats"><div class="loading">Fetching live state…</div></div>
|
||||
<div class="narr" id="ch1-narr"></div>
|
||||
</div>
|
||||
|
||||
<div class="chapter">
|
||||
<div class="num">Chapter 2</div>
|
||||
<h2>The demand signal is real, not made up</h2>
|
||||
<div class="lede">Chicago's Department of Buildings publishes every permit they issue. Below are the largest categories of construction filed in the last 30 days. If a staffer doesn't believe our numbers, they can verify at <a href="https://data.cityofchicago.org/Buildings/Building-Permits/ydr8-5enu" target="_blank" rel="noopener">data.cityofchicago.org</a>.</div>
|
||||
<div id="ch2-permits"><div class="loading">Loading permit feed…</div></div>
|
||||
</div>
|
||||
|
||||
<div class="chapter">
|
||||
<div class="num">Chapter 3</div>
|
||||
<h2>Where your own data would live</h2>
|
||||
<div class="lede">The system stores data in labeled catalogs. Purple pills = synthetic stand-ins you'd swap for your real ATS/CRM/call-log exports. Blue pills = data the system generates about itself (playbooks, audit trails). Nothing else in the pipeline changes — only the source.</div>
|
||||
<div id="ch3-datasets"><div class="loading">Enumerating catalog…</div></div>
|
||||
<div class="narr">
|
||||
<strong>The swap path.</strong> workers_500k → your ATS export (same schema shape). candidates → your CRM. call_log → your phone system's CDR. timesheets → your payroll export. Once ingested, every behavior you see on the dashboard applies to your real data. No re-training. No replatform.
|
||||
</div>
|
||||
</div>
|
||||
<div class="ft">Powered by Lakehouse — Hybrid SQL + Vector Search across 500,000 embedded worker profiles</div>
|
||||
|
||||
<div class="chapter">
|
||||
<div class="num">Chapter 4</div>
|
||||
<h2>Watch the system rank candidates in real time</h2>
|
||||
<div class="lede">This takes the most recent Chicago permit, derives the staffing need, pulls ranked candidates from the 500K bench, and shows you why each one ranked. Everything below loaded in about 3 seconds against the live system.</div>
|
||||
<div id="ch4-demo"><div class="loading">Running demo query…</div></div>
|
||||
</div>
|
||||
|
||||
<div class="chapter">
|
||||
<div class="num">Chapter 5</div>
|
||||
<h2>Every action compounds — the CRM-killer</h2>
|
||||
<div class="lede">A CRM stores. This system compounds. Every successful fill, every no-show, every phone call becomes a re-ranking signal on the next query. Below is the live playbook memory state. The number grows as the app gets used.</div>
|
||||
<div id="ch5-memory"><div class="loading">Reading playbook memory…</div></div>
|
||||
</div>
|
||||
|
||||
<div class="chapter">
|
||||
<div class="num">Chapter 6</div>
|
||||
<h2>Try it yourself</h2>
|
||||
<div class="lede">Type any staffing question. The system picks the right search path (smart-parse, semantic discovery, analytics), shows what it understood, and returns ranked results with memory signal.</div>
|
||||
<div class="try-box">
|
||||
<input type="text" id="try-q" placeholder="e.g. reliable forklift operators in Chicago with OSHA certs" onkeydown="if(event.key==='Enter')runTry()">
|
||||
<button id="try-btn" onclick="runTry()">Ask</button>
|
||||
<div id="try-out" style="margin-top:16px"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="footer">Lakehouse · demo instance · <a href="proof">architecture & benchmarks</a></div>
|
||||
|
||||
<script>
|
||||
var P=location.pathname.replace(/\/console\/?$/,'');
|
||||
var P=location.pathname.indexOf('/lakehouse')>=0?'/lakehouse':'';
|
||||
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()})
|
||||
// DOM helpers — all dynamic content goes through these. No innerHTML
|
||||
// anywhere in the script; every API-derived string passes through
|
||||
// textContent so no injection path regardless of upstream data.
|
||||
function el(tag, cls, text){
|
||||
var e=document.createElement(tag);
|
||||
if(cls) e.className=cls;
|
||||
if(text!==undefined && text!==null) e.textContent=String(text);
|
||||
return e;
|
||||
}
|
||||
function setAttrs(e, obj){
|
||||
for(var k in obj) if(Object.prototype.hasOwnProperty.call(obj,k)) e.setAttribute(k,obj[k]);
|
||||
return e;
|
||||
}
|
||||
|
||||
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}
|
||||
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 apiGet(path){
|
||||
return fetch(A+path).then(function(r){return r.json()});
|
||||
}
|
||||
|
||||
window.addEventListener('load',loadBrief);
|
||||
window.addEventListener('load',function(){
|
||||
loadChapter1();
|
||||
loadChapter2();
|
||||
loadChapter3();
|
||||
loadChapter4();
|
||||
loadChapter5();
|
||||
});
|
||||
|
||||
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;
|
||||
// ─── Chapter 1 ────────────────────────────────────────────
|
||||
function loadChapter1(){
|
||||
Promise.all([
|
||||
api('/intelligence/staffing_forecast',{}).catch(function(){return {}}),
|
||||
apiGet('/api/vectors/playbook_memory/stats').catch(function(){return {}}),
|
||||
apiGet('/api/catalog/datasets').catch(function(){return []}),
|
||||
]).then(function(all){
|
||||
var f=all[0]||{}, mem=all[1]||{}, ds=all[2]||[];
|
||||
var totalRows=0;
|
||||
if(Array.isArray(ds)) ds.forEach(function(d){totalRows+=(d.row_count||0)});
|
||||
var host=document.getElementById('ch1-stats');host.textContent='';
|
||||
addStat(host,'$'+(f.total_cost||0).toLocaleString('en-US',{maximumFractionDigits:0}),'Construction pipeline',(f.permit_count||0)+' permits last 30 days','accent-b');
|
||||
addStat(host,(f.total_estimated_workers||0).toLocaleString(),'Predicted worker demand','split across '+((f.forecast||[]).length)+' roles','accent-w');
|
||||
addStat(host,(totalRows||0).toLocaleString(),'Rows under management',(Array.isArray(ds)?ds.length:0)+' datasets · real + synthetic','accent-a');
|
||||
addStat(host,(mem.entries||0).toLocaleString(),'Playbooks remembered',(mem.total_names_endorsed||0)+' endorsed worker-tags','accent-l');
|
||||
|
||||
// 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);
|
||||
var narrEl=document.getElementById('ch1-narr');
|
||||
narrEl.textContent='';
|
||||
var critical=(f.critical_roles||0), tight=(f.tight_roles||0);
|
||||
if(critical>0){
|
||||
narrEl.appendChild(document.createTextNode('The system has already flagged '));
|
||||
narrEl.appendChild(el('strong',null,critical+' role'+(critical!==1?'s':'')+' at critical coverage'));
|
||||
narrEl.appendChild(document.createTextNode(' — supply is below demand. A CRM would surface this only if someone ran the report. This system surfaces it before you log in.'));
|
||||
} else if(tight>0){
|
||||
narrEl.appendChild(document.createTextNode('The system flagged '));
|
||||
narrEl.appendChild(el('strong',null,tight+' role'+(tight!==1?'s':'')+' as tight'));
|
||||
narrEl.appendChild(document.createTextNode(' — watch for supply compression. Pre-emptive signal, no query required.'));
|
||||
} else {
|
||||
narrEl.textContent='No coverage gaps right now — bench exceeds predicted demand across all roles. The system is tracking this continuously; check back tomorrow and the numbers will have moved.';
|
||||
}
|
||||
container.appendChild(c1);
|
||||
}).catch(function(e){
|
||||
var h=document.getElementById('ch1-stats');h.textContent='';h.appendChild(el('div','err','Live state unavailable: '+(e.message||e)));
|
||||
});
|
||||
document.getElementById('hdr-time').textContent='Snapshot · '+new Date().toLocaleTimeString();
|
||||
}
|
||||
function addStat(host,n,l,sub,cls){
|
||||
var d=el('div','card stat-lg '+(cls||''));
|
||||
d.appendChild(el('div','n',n));
|
||||
d.appendChild(el('div','l',l));
|
||||
d.appendChild(el('div','sub',sub||''));
|
||||
host.appendChild(d);
|
||||
}
|
||||
|
||||
// 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);
|
||||
// ─── Chapter 2 ────────────────────────────────────────────
|
||||
function loadChapter2(){
|
||||
api('/intelligence/market',{}).then(function(r){
|
||||
var host=document.getElementById('ch2-permits');host.textContent='';
|
||||
if(r.error){host.appendChild(el('div','err','Permit feed error: '+r.error));return}
|
||||
var byType=r.by_type||[];
|
||||
if(byType.length===0){host.appendChild(el('div','narr','No permits returned by the Chicago API right now.'));return}
|
||||
byType.slice(0,8).forEach(function(t){
|
||||
var det=document.createElement('details');
|
||||
var summary=document.createElement('summary');
|
||||
summary.appendChild(el('span','pill real','LIVE · DATA.CITYOFCHICAGO.ORG'));
|
||||
summary.appendChild(document.createTextNode(' '));
|
||||
var wt=el('strong',null,t.work_type||'General construction');
|
||||
summary.appendChild(wt);
|
||||
summary.appendChild(document.createTextNode(' · '+(t.permits||0)+' permits · $'+(t.total_cost||0).toLocaleString('en-US',{maximumFractionDigits:0})+' · est '+(t.estimated_workers||0)+' workers needed'));
|
||||
det.appendChild(summary);
|
||||
var body=el('div','body');
|
||||
body.appendChild(document.createTextNode('Likely roles: '+((t.needed_roles||[]).join(', ')||'—')+'. '));
|
||||
body.appendChild(document.createTextNode('At industry heuristic (~$150K per worker), this work type alone implies '));
|
||||
body.appendChild(el('strong',null,(t.estimated_workers||0)+' staffing opportunities'));
|
||||
body.appendChild(document.createTextNode(' over the next 30-60 days.'));
|
||||
det.appendChild(body);
|
||||
host.appendChild(det);
|
||||
});
|
||||
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;
|
||||
var h=document.getElementById('ch2-permits');h.textContent='';h.appendChild(el('div','err',e.message||String(e)));
|
||||
});
|
||||
}
|
||||
|
||||
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 me a warehouse worker available today near Nashville');
|
||||
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);
|
||||
// ─── Chapter 3 ────────────────────────────────────────────
|
||||
function loadChapter3(){
|
||||
apiGet('/api/catalog/datasets').then(function(ds){
|
||||
var host=document.getElementById('ch3-datasets');host.textContent='';
|
||||
if(!Array.isArray(ds)){host.appendChild(el('div','err','No datasets returned.'));return}
|
||||
var bigOnes=ds.filter(function(d){return (d.row_count||0)>=500}).sort(function(a,b){return (b.row_count||0)-(a.row_count||0)});
|
||||
var playbookKeys=['successful_playbooks','playbook_memory','kb_'];
|
||||
bigOnes.slice(0,12).forEach(function(d){
|
||||
var row=el('div','row');
|
||||
var left=document.createElement('div');left.style.flex='1';left.style.minWidth='0';
|
||||
var t=el('div','title');
|
||||
var pillCls='synth', pillLabel='SWAP FOR YOUR DATA';
|
||||
if(playbookKeys.some(function(k){return (d.name||'').indexOf(k)>=0})){pillCls='flow';pillLabel='SYSTEM-GENERATED'}
|
||||
else if((d.name||'').indexOf('threat_')===0){pillCls='flow';pillLabel='SYSTEM-GENERATED'}
|
||||
t.appendChild(el('span','pill '+pillCls,pillLabel));
|
||||
t.appendChild(document.createTextNode(' '+(d.name||'(unnamed)')));
|
||||
var m=el('div','meta',(d.columns?d.columns.length+' columns · ':'')+(d.objects?d.objects.length+' parquet file'+(d.objects.length!==1?'s':''):''));
|
||||
left.appendChild(t);left.appendChild(m);
|
||||
var v=el('div','val',(d.row_count||0).toLocaleString()+' rows');
|
||||
row.appendChild(left);row.appendChild(v);host.appendChild(row);
|
||||
});
|
||||
}).catch(function(e){
|
||||
var h=document.getElementById('ch3-datasets');h.textContent='';h.appendChild(el('div','err','Catalog unavailable: '+(e.message||e)));
|
||||
});
|
||||
}
|
||||
|
||||
// ─── 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;
|
||||
// ─── Chapter 4 ────────────────────────────────────────────
|
||||
function loadChapter4(){
|
||||
api('/intelligence/permit_contracts',{}).then(function(r){
|
||||
var host=document.getElementById('ch4-demo');host.textContent='';
|
||||
if(!r.contracts||r.contracts.length===0){host.appendChild(el('div','loading','No contracts returned.'));return}
|
||||
var c=r.contracts[0];
|
||||
var p=c.permit||{}, prop=c.proposed||{}, tl=c.timeline||{};
|
||||
var card=el('div','card accent-b');
|
||||
var title=el('div',null,'$'+(p.cost||0).toLocaleString()+' · '+(p.work_type||''));
|
||||
title.style.cssText='font-size:15px;font-weight:600;color:#e6edf3';
|
||||
card.appendChild(title);
|
||||
var addr=el('div',null,(p.address||'')+' · Chicago, IL · filed '+(p.issue_date||''));
|
||||
addr.style.cssText='color:#8b949e;font-size:12px;margin-top:2px';
|
||||
card.appendChild(addr);
|
||||
|
||||
card.appendChild(el('div','step-label','STEP 1 · Derive staffing need'));
|
||||
var s1=el('div','step-body');
|
||||
s1.appendChild(document.createTextNode('Industry heuristic: ~1 worker per $150K of permit cost, capped 2-8. Resulting contract: '));
|
||||
s1.appendChild(el('strong',null,(prop.count||0)+'× '+(prop.role||'')));
|
||||
s1.appendChild(document.createTextNode(' in Chicago, IL.'));
|
||||
card.appendChild(s1);
|
||||
|
||||
card.appendChild(el('div','step-label','STEP 2 · Narrow via SQL ('+((prop.pool_size||0).toLocaleString())+' candidates match)'));
|
||||
card.appendChild(el('div','step-body',"role = '"+(prop.role||'')+"' AND state = 'IL' AND city = 'Chicago' AND CAST(availability AS DOUBLE) > 0.5"));
|
||||
|
||||
card.appendChild(el('div','step-label','STEP 3 · Rank via semantic match + playbook boost'));
|
||||
host.appendChild(card);
|
||||
|
||||
var list=document.createElement('div');list.style.marginTop='6px';
|
||||
(prop.candidates||[]).slice(0,5).forEach(function(cand,i){
|
||||
var w=el('div','worker');
|
||||
var initials=(cand.name||'?').split(' ').map(function(s){return (s[0]||'').toUpperCase()}).join('').substring(0,2);
|
||||
w.appendChild(el('div','av',initials));
|
||||
var info=el('div','info');
|
||||
var nm=el('div','nm',cand.name||cand.doc_id||'?');
|
||||
if((cand.playbook_boost||0)>0){
|
||||
var ncit=(cand.playbook_citations||[]).length;
|
||||
nm.appendChild(el('span','boost-chip','Endorsed · '+ncit+' past fill'+(ncit!==1?'s':'')));
|
||||
}
|
||||
info.appendChild(nm);
|
||||
var why=cand.doc_id+' · '+(cand.playbook_boost>0?'boosted +'+cand.playbook_boost.toFixed(3)+' by memory · ':'')+'semantic score '+(cand.score||0).toFixed(3);
|
||||
info.appendChild(el('div','why',why));
|
||||
w.appendChild(info);
|
||||
w.appendChild(el('div','score','#'+(i+1)));
|
||||
list.appendChild(w);
|
||||
});
|
||||
card.appendChild(list);
|
||||
|
||||
if(c.discovered_pattern||c.pattern_matched>0){
|
||||
var mem=el('div','mem-chip');
|
||||
mem.appendChild(el('span','l','MEMORY ('+(c.pattern_matched||0)+' past playbooks):'));
|
||||
mem.appendChild(document.createTextNode(' '+(c.discovered_pattern||'memory is sparse for this role+geo')));
|
||||
card.appendChild(mem);
|
||||
}
|
||||
|
||||
if(tl.days_to_deadline!==undefined){
|
||||
var tm=document.createElement('div');
|
||||
tm.style.cssText='margin-top:12px;padding-top:10px;border-top:1px solid #171d27;font-size:12px;color:#8b949e';
|
||||
tm.appendChild(el('strong',null,'Timeline. '));
|
||||
tm.childNodes[0].style && (tm.childNodes[0].style.color='#c9d1d9');
|
||||
var dd=tl.days_to_deadline;
|
||||
var when=(dd<=0?Math.abs(dd)+' days overdue':dd+' days out');
|
||||
tm.appendChild(document.createTextNode('Staffing window opens '+(tl.staffing_window_opens||'')+' ('+when+'). Construction starts '+(tl.estimated_construction_start||'')+'. Urgency: '));
|
||||
tm.appendChild(el('strong',null,(tl.urgency||'').toUpperCase()));
|
||||
tm.appendChild(document.createTextNode('.'));
|
||||
card.appendChild(tm);
|
||||
}
|
||||
|
||||
var note=el('div','narr');
|
||||
note.appendChild(el('strong',null,'This is the CRM gap. '));
|
||||
note.appendChild(document.createTextNode('A CRM would tell you the permit exists. It would NOT narrow to matching candidates, would NOT boost past-successful workers, would NOT flag patterns across similar past fills, would NOT count down to the deadline. All that happens here, pre-computed, before the staffer clicks anything.'));
|
||||
host.appendChild(note);
|
||||
}).catch(function(e){
|
||||
var h=document.getElementById('ch4-demo');h.textContent='';h.appendChild(el('div','err','Demo failed: '+(e.message||e)));
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Chapter 5 ────────────────────────────────────────────
|
||||
function loadChapter5(){
|
||||
apiGet('/api/vectors/playbook_memory/stats').then(function(mem){
|
||||
var host=document.getElementById('ch5-memory');host.textContent='';
|
||||
var card=el('div','card accent-l');
|
||||
var big=el('div',null,(mem.entries||0).toLocaleString());
|
||||
big.style.cssText='font-size:30px;font-weight:800;color:#e6edf3;line-height:1';
|
||||
card.appendChild(big);
|
||||
var lb=el('div',null,'Playbooks in memory');
|
||||
lb.style.cssText='font-size:10px;color:#545d68;text-transform:uppercase;letter-spacing:1.2px;margin-top:8px;font-weight:600';
|
||||
card.appendChild(lb);
|
||||
var sub=el('div',null,(mem.total_names_endorsed||0).toLocaleString()+' endorsed worker-tags · each one boosts future matching queries');
|
||||
sub.style.cssText='font-size:12px;color:#8b949e;margin-top:4px';
|
||||
card.appendChild(sub);
|
||||
host.appendChild(card);
|
||||
|
||||
if(mem.sample&&mem.sample.length){
|
||||
var hdr=document.createElement('div');
|
||||
hdr.style.cssText='color:#545d68;font-size:11px;text-transform:uppercase;letter-spacing:1.4px;font-weight:600;margin:14px 0 8px';
|
||||
hdr.textContent='Sample playbooks held in memory right now';
|
||||
host.appendChild(hdr);
|
||||
mem.sample.slice(0,5).forEach(function(pb){
|
||||
var row=el('div','row');
|
||||
var left=document.createElement('div');left.style.flex='1';left.style.minWidth='0';
|
||||
left.appendChild(el('div','title',pb.operation||'(no operation)'));
|
||||
left.appendChild(el('div','meta',(pb.city||'')+', '+(pb.state||'')+' · endorsed: '+((pb.endorsed||[]).slice(0,3).join(', ')||'—')));
|
||||
row.appendChild(left);host.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
var narr=el('div','narr');
|
||||
narr.appendChild(el('strong',null,'What this means for a staffer. '));
|
||||
narr.appendChild(document.createTextNode('When you log a successful fill, this counter grows by one. Next time someone searches for a similar role+city, the system finds your past pick and boosts them to the top. Over months, the system becomes a record of who has actually worked out — not just who is on the roster.'));
|
||||
host.appendChild(narr);
|
||||
}).catch(function(e){
|
||||
var h=document.getElementById('ch5-memory');h.textContent='';h.appendChild(el('div','err','Memory unavailable: '+(e.message||e)));
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Chapter 6 ────────────────────────────────────────────
|
||||
function runTry(){
|
||||
var q=document.getElementById('try-q').value.trim();if(!q)return;
|
||||
var btn=document.getElementById('try-btn'),out=document.getElementById('try-out');
|
||||
btn.disabled=true;btn.textContent='Thinking…';out.textContent='';out.appendChild(el('div','loading','Running…'));
|
||||
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);
|
||||
btn.disabled=false;btn.textContent='Ask';out.textContent='';
|
||||
var card=el('div','card accent-b');
|
||||
if(d.understood&&d.understood.length){
|
||||
var tags=document.createElement('div');tags.style.cssText='display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px';
|
||||
d.understood.forEach(function(u){
|
||||
var tg=el('span',null,u);
|
||||
tg.style.cssText='padding:3px 10px;border-radius:10px;font-size:11px;background:#1a274420;color:#58a6ff;border:1px solid #1a274480';
|
||||
tags.appendChild(tg);
|
||||
});
|
||||
card.appendChild(tags);
|
||||
}
|
||||
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==='smart_search'){
|
||||
// Show what the system understood
|
||||
if(d.understood&&d.understood.length){
|
||||
var tags=el('div','display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px');
|
||||
d.understood.forEach(function(u){
|
||||
tags.appendChild(el('span','padding:3px 10px;border-radius:10px;font-size:11px;background:#1a274420;color:#58a6ff;border:1px solid #1a274480',u));
|
||||
});
|
||||
bbl.appendChild(tags);
|
||||
}
|
||||
// Show SQL results with zip codes, availability, skills
|
||||
if(d.sql_results&&d.sql_results.length){
|
||||
d.sql_results.forEach(function(w){
|
||||
var rc=div('result-card');
|
||||
rc.appendChild(el('div','font-weight:600;color:#f0f6fc;font-size:14px',w.name));
|
||||
var locParts=[w.role,w.city+', '+w.state];
|
||||
if(w.zip)locParts.push('ZIP: '+w.zip);
|
||||
rc.appendChild(el('div','font-size:12px;color:#8b949e;margin-top:2px',locParts.join(' · ')));
|
||||
// Metrics row
|
||||
var metrics=[];
|
||||
if(w.avail!==undefined)metrics.push('Available: '+Math.round(w.avail*100)+'%');
|
||||
if(w.rel!==undefined)metrics.push('Reliable: '+Math.round(w.rel*100)+'%');
|
||||
if(w.archetype)metrics.push(w.archetype);
|
||||
if(metrics.length)rc.appendChild(el('div','font-size:11px;color:#58a6ff;margin-top:4px',metrics.join(' · ')));
|
||||
// Skills + certs
|
||||
if(w.skills||w.certifications){
|
||||
var tagRow=el('div','display:flex;gap:4px;flex-wrap:wrap;margin-top:6px');
|
||||
if(w.skills)(w.skills||'').split(',').forEach(function(s){s=s.trim();if(s)tagRow.appendChild(el('span','padding:2px 8px;background:#1a2744;color:#58a6ff;border-radius:8px;font-size:10px',s))});
|
||||
if(w.certifications)(w.certifications||'').split(',').forEach(function(c){c=c.trim();if(c&&c!=='none')tagRow.appendChild(el('span','padding:2px 8px;background:#1a3a2a;color:#3fb950;border-radius:8px;font-size:10px',c))});
|
||||
rc.appendChild(tagRow);
|
||||
}
|
||||
bbl.appendChild(rc);
|
||||
});
|
||||
}
|
||||
// Also show vector results if different
|
||||
if(d.vector_results&&d.vector_results.length&&(!d.sql_results||!d.sql_results.length)){
|
||||
renderResults(bbl,d.vector_results);
|
||||
}
|
||||
}
|
||||
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));
|
||||
var sum=el('div',null,d.summary||'(no summary)');
|
||||
sum.style.cssText='color:#c9d1d9;font-size:13px;margin-bottom:10px';
|
||||
card.appendChild(sum);
|
||||
|
||||
if(d.pattern_playbooks_matched>0&&d.discovered_pattern){
|
||||
var mem=el('div','mem-chip');
|
||||
mem.appendChild(el('span','l','MEMORY ('+d.pattern_playbooks_matched+' playbooks):'));
|
||||
mem.appendChild(document.createTextNode(' '+d.discovered_pattern));
|
||||
card.appendChild(mem);
|
||||
}
|
||||
|
||||
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;
|
||||
var workers=d.sql_results||d.vector_results||d.results||[];
|
||||
workers.slice(0,5).forEach(function(w,i){
|
||||
var row=el('div','worker');
|
||||
var nm=w.name||(w.text||'').split('—')[0].trim()||w.doc_id||'?';
|
||||
var initials=nm.split(' ').map(function(s){return (s[0]||'').toUpperCase()}).join('').substring(0,2);
|
||||
row.appendChild(el('div','av',initials));
|
||||
var info=el('div','info');
|
||||
var n=el('div','nm',nm);
|
||||
if((w.playbook_boost||0)>0){
|
||||
n.appendChild(el('span','boost-chip','Endorsed · '+((w.playbook_citations||[]).length||'?')+' past fill(s)'));
|
||||
}
|
||||
info.appendChild(n);
|
||||
var bits=[];
|
||||
if(w.role) bits.push(w.role);
|
||||
if(w.city&&w.state) bits.push(w.city+', '+w.state);
|
||||
if(w.rel!==undefined) bits.push('reliability '+Math.round(w.rel*100)+'%');
|
||||
if(w.avail!==undefined) bits.push('availability '+Math.round(w.avail*100)+'%');
|
||||
info.appendChild(el('div','why',bits.join(' · ')||'AI semantic match'));
|
||||
row.appendChild(info);
|
||||
row.appendChild(el('div','score','#'+(i+1)));
|
||||
card.appendChild(row);
|
||||
});
|
||||
var meta=el('div',null,(d.duration_ms||0)+'ms · type='+(d.type||'smart_search'));
|
||||
meta.style.cssText='color:#545d68;font-size:11px;margin-top:10px';
|
||||
card.appendChild(meta);
|
||||
out.appendChild(card);
|
||||
}).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);
|
||||
btn.disabled=false;btn.textContent='Ask';
|
||||
out.textContent='';out.appendChild(el('div','err',e.message||String(e)));
|
||||
});
|
||||
}
|
||||
|
||||
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>
|
||||
</script>
|
||||
</body></html>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user