lakehouse/mcp-server/console.html
root a1066db87b demo: contractor profile — heat map, project index, 12 awaiting sources
The contractor.html click-target J asked for: a separate page (not a
modal, not a fall-through search) showing every angle on a contractor.
Reachable from the Co-Pilot dashboard, the staffers console, and the
search box — all anchor-wrap contractor names to /contractor?name=...

What's new on the page:

1. PROJECT INDEX — build-signal score
   Single 0-100 number with the drivers laid out beneath. Driver list
   is staffer-readable: "59 Chicago permits in 180d (+30) · OSHA 20
   inspections (-25) · federal contractor (+15)". Score weights are
   placeholders to be replaced by an ML model once the 12 awaiting
   sources ship — the current 6 wired signals would not give a real
   model enough features.

2. HEAT MAP — every Chicago permit they've been contact_1 or contact_2
   on, last 24 months, plotted on a leaflet dark map. Color by cost
   (green <$100K, amber $100K-$1M, red ≥$1M), radius proportional to
   cost so the staffer sees where money + activity concentrates. Click
   a marker for permit detail (cost, date, work type, address, permit
   ID). All 50 of Turner Construction's geocoded recent permits in
   Chicago plot end-to-end.

3. ACTIVITY TIMELINE — monthly permit count, bar chart, with the
   first/last month labels so the staffer sees momentum. Tooltip on
   each bar gives the count and total cost for that month.

4. 12 AWAITING SOURCES — placeholder cards for the public datasets
   that would 3× the build-signal feature count. Each card has:
     - source name (real, e.g. DOL Wage & Hour, EPA ECHO, MSHA, BBB)
     - one-liner in coordinator language ("Has this contractor stiffed
       workers? Will they pay our staffing invoices?")
     - "Would show:" sample shape so the engineering scope is concrete
   Order is staffing-decision relevance:
     1. DOL Wage & Hour (WHD violations)
     2. State Licensure Boards (active license + expiry)
     3. Surety Bond Capacity (bonding ceiling)
     4. EPA ECHO Compliance (env violations at sites)
     5. DOT/FMCSA Carrier Safety (crash + OOS rates)
     6. BBB Complaints + Rating
     7. PACER Civil Suits (FLSA / Title VII / ADA)
     8. UCC Lien Filings (cash flow distress)
     9. D&B / Credit Bureau (PAYDEX, payment behavior)
    10. State UI Employer Claims (workforce stability)
    11. MSHA Mine Safety (excavation / aggregate / heavy)
    12. Registered Apprenticeships (DOL RAPIDS pipeline)

Server-side: entity.ts fetchContractorHistory now pulls the 50 most
recent permits with id + lat/lng + work_description, so the heat map
and timeline have what they need without a second SQL hop. The
ContractorHistory.recent_permits type gained the optional fields.

Front-end: contractor.html got 4 new render sections, leaflet wiring
(stylesheet + script in head), placeholder grid CSS, and a PLACEHOLDERS
const at the bottom with the 12 sources. All popup HTML is built via
DOM construction (textContent + appendChild) — no innerHTML, no XSS.

console.html: contractor names from /intelligence/permit_contracts now
anchor-wrapped to /contractor?name=... so the click-through J described
works from the staffers console too. Click stops propagation so the
permit details element doesn't toggle on the same click.

Verified end-to-end via playwright — Turner Construction profile shows:
  PIX score "Mixed signals — review drivers below"
  Heat map: "50 permits plotted · green/amber/red"
  4 section labels in order
  12 placeholder cards in the documented order
2026-04-28 06:01:04 -05:00

495 lines
27 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 lang="en"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Lakehouse — What Your Staffing System Would Do</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
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>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>
<a href="spec">Spec</a>
<a href="onboard">Onboard</a>
<a href="alerts">Alerts</a>
<a href="workspaces">Workspaces</a>
</nav>
<div class="rt" id="hdr-time">Reading live state…</div>
</div>
<div class="wrap">
<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="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 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 &amp; benchmarks</a></div>
<script>
var P=location.pathname.indexOf('/lakehouse')>=0?'/lakehouse':'';
var A=location.origin+P;
// 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 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',function(){
loadChapter1();
loadChapter2();
loadChapter3();
loadChapter4();
loadChapter5();
});
// ─── 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');
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.';
}
}).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);
}
// ─── 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);
});
}).catch(function(e){
var h=document.getElementById('ch2-permits');h.textContent='';h.appendChild(el('div','err',e.message||String(e)));
});
}
// ─── 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)));
});
}
// ─── 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);
// Contractor names link to the full /contractor profile page —
// heat map, project index, history, 12 awaiting public-data
// sources. The staffer click-through J asked for.
if(p.contact_1_name || p.contact_2_name){
var contractors=document.createElement('div');
contractors.style.cssText='color:#8b949e;font-size:12px;margin-top:4px';
contractors.appendChild(document.createTextNode('Contractors: '));
var seen=[];
[p.contact_1_name, p.contact_2_name].forEach(function(n,i){
if(!n || seen.indexOf(n)>=0) return;
seen.push(n);
if(seen.length>1) contractors.appendChild(document.createTextNode(' · '));
var a=document.createElement('a');
a.href='/contractor?name='+encodeURIComponent(n);
a.target='_blank';
a.rel='noopener';
a.style.cssText='color:#58a6ff;text-decoration:none;border-bottom:1px dotted #58a6ff44';
a.title='Open full contractor profile';
a.textContent=n;
contractors.appendChild(a);
});
card.appendChild(contractors);
}
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){
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);
}
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);
}
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){
btn.disabled=false;btn.textContent='Ask';
out.textContent='';out.appendChild(el('div','err',e.message||String(e)));
});
}
</script>
</body></html>