J reported https://devop.live/contractor?name=3115%20W%20POLK%20ST.%20LLC returned 404. Cause: the anchor href was a bare /contractor, which on devop.live routes to the LLM Team UI (port 5000) at the main site root, not the lakehouse mcp-server (which lives under /lakehouse/*). Every page that renders a contractor link now uses the same prefix detector the dashboard already had: var P = location.pathname.indexOf('/lakehouse') >= 0 ? '/lakehouse' : ''; Files updated: - search.html: entity-brief anchor + preview anchor → P+/contractor - console.html: permit-card contractor list → P+/contractor - contractor.html: history.replaceState + back-link + the /intelligence/contractor_profile fetch all use P prefix. The page is reachable at /lakehouse/contractor on the public URL and bare /contractor on localhost; both work without further config. Verified: https://devop.live/lakehouse/contractor?name=3115%20W%20POLK%20ST.%20LLC → 200, 29.9 KB, full profile renders. Contractor has 1 permit on file (a small LLC), 1 geocoded so the heat map plots one marker.
607 lines
29 KiB
HTML
607 lines
29 KiB
HTML
<!DOCTYPE html>
|
||
<html><head>
|
||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Contractor Profile · Staffing Co-Pilot</title>
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||
<style>
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
html,body{overflow-x:hidden}
|
||
body{font-family:'Inter',-apple-system,system-ui,sans-serif;background:#090c10;color:#b0b8c4;font-size:14px;line-height:1.6}
|
||
.bar{background:#0d1117;padding:0 24px;height:56px;border-bottom:1px solid #171d27;display:flex;justify-content:space-between;align-items:center}
|
||
.bar h1{font-size:14px;font-weight:600;color:#e6edf3}
|
||
.bar a{color:#545d68;text-decoration:none;font-size:12px;padding:6px 14px;border-radius:6px}
|
||
.bar a:hover{color:#e6edf3;background:#161b22}
|
||
.content{max-width:1100px;margin:0 auto;padding:24px 20px 40px}
|
||
.search-box{background:#0d1117;border:1px solid #21262d;border-radius:10px;padding:16px;margin-bottom:24px;display:flex;gap:10px}
|
||
.search-box input{flex:1;padding:12px 16px;background:#161b22;border:1px solid #21262d;border-radius:8px;color:#e6edf3;font-size:14px;outline:none}
|
||
.search-box input:focus{border-color:#388bfd}
|
||
.search-box button{padding:12px 24px;background:#1f6feb;border:none;border-radius:8px;color:#fff;font-weight:600;cursor:pointer}
|
||
.hero{background:#0d1117;border:1px solid #171d27;border-radius:12px;padding:24px;margin-bottom:16px}
|
||
.hero h2{color:#e6edf3;font-size:22px;font-weight:700;letter-spacing:-0.5px;margin-bottom:6px}
|
||
.hero .ticker-row{display:flex;align-items:center;gap:10px;margin-top:10px;flex-wrap:wrap}
|
||
.hero .ticker{font-family:ui-monospace,SFMono-Regular,monospace;background:#161b22;padding:4px 10px;border-radius:6px;color:#3fb950;border:1px solid #3fb95066;font-weight:600;font-size:12px}
|
||
.hero .meta{font-size:12px;color:#8b949e}
|
||
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:14px}
|
||
.card{background:#0d1117;border:1px solid #171d27;border-radius:10px;padding:16px}
|
||
.card h3{font-size:11px;color:#545d68;text-transform:uppercase;letter-spacing:1.2px;margin-bottom:10px;font-weight:600}
|
||
.card .big{font-size:24px;font-weight:700;color:#e6edf3;letter-spacing:-0.5px;margin-bottom:4px}
|
||
.card .sub{font-size:11px;color:#8b949e;line-height:1.5}
|
||
.card a{color:#58a6ff;text-decoration:none;font-size:11px}
|
||
.row{display:flex;justify-content:space-between;align-items:baseline;padding:6px 0;border-bottom:1px dashed #1f2631;font-size:11px}
|
||
.row:last-child{border:none}
|
||
.row .l{color:#8b949e}
|
||
.row .v{color:#e6edf3;font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums}
|
||
.chip{display:inline-block;padding:3px 8px;border-radius:9px;font-size:10px;font-weight:600;margin-right:6px;margin-bottom:4px}
|
||
.ld{color:#3d444d;text-align:center;padding:60px;font-size:13px}
|
||
.empty{color:#545d68;font-size:11px;font-style:italic;line-height:1.5}
|
||
.wide{grid-column:1/-1}
|
||
.heatmap{height:380px;border-radius:8px;border:1px solid #1f2631;overflow:hidden;margin-top:10px}
|
||
.heatmap .leaflet-container{background:#0a0d12}
|
||
.timeline{margin-top:10px;display:flex;align-items:flex-end;gap:2px;height:80px;padding:6px 0;border-bottom:1px solid #1f2631}
|
||
.timeline .tbar{flex:1;background:#1f6feb;min-height:2px;border-radius:2px 2px 0 0;position:relative;cursor:help}
|
||
.timeline .tbar:hover{background:#58a6ff}
|
||
.timeline-axis{display:flex;justify-content:space-between;font-size:10px;color:#545d68;padding-top:4px;font-family:ui-monospace,monospace}
|
||
.placeholder-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:10px;margin-top:14px}
|
||
.ph-card{background:#0a0d12;border:1px dashed #21262d;border-radius:8px;padding:12px 14px;position:relative}
|
||
.ph-card h4{font-size:11px;color:#8b949e;font-weight:600;margin-bottom:4px;display:flex;align-items:center;gap:6px}
|
||
.ph-card h4 .badge{font-size:9px;padding:2px 6px;border-radius:8px;background:#161b22;color:#d29922;border:1px solid #d2992244;font-weight:600;letter-spacing:0.5px;text-transform:uppercase}
|
||
.ph-card .why{font-size:11px;color:#e6edf3;line-height:1.5;margin-bottom:6px}
|
||
.ph-card .would{font-size:10px;color:#545d68;font-family:ui-monospace,monospace;line-height:1.5;border-top:1px dashed #1f2631;padding-top:6px;margin-top:6px}
|
||
.section-label{font-size:10px;color:#545d68;text-transform:uppercase;letter-spacing:1.4px;font-weight:600;margin:24px 0 8px}
|
||
@media(max-width:640px){.bar{padding:0 14px}.content{padding:14px}.hero{padding:16px}.hero h2{font-size:18px}.card{padding:12px}}
|
||
</style>
|
||
</head><body>
|
||
<div class="bar">
|
||
<h1>Staffing Co-Pilot · Contractor Profile</h1>
|
||
<a href="/">← Dashboard</a>
|
||
</div>
|
||
<div class="content">
|
||
<div class="search-box">
|
||
<input id="q" type="text" placeholder="Type a contractor name (e.g., Turner Construction Company)" onkeydown="if(event.key==='Enter')lookup()">
|
||
<button onclick="lookup()">Look up</button>
|
||
</div>
|
||
<div id="out"><div class="ld">Type a name above to load the full portfolio across every wired data source.</div></div>
|
||
</div>
|
||
<script>
|
||
function $(id){return document.getElementById(id)}
|
||
|
||
// Path prefix detection — devop.live serves this page under /lakehouse,
|
||
// localhost:3700 serves it at root. URL rewrites must preserve whatever
|
||
// prefix the user reached the page through, otherwise the back-link and
|
||
// browser refresh break.
|
||
var P=location.pathname.indexOf('/lakehouse')>=0?'/lakehouse':'';
|
||
|
||
// Bootstrap from URL: /contractor?name=Turner+Construction
|
||
window.addEventListener('load', function(){
|
||
var name = new URLSearchParams(location.search).get('name');
|
||
if(name){
|
||
$('q').value = name;
|
||
lookup();
|
||
}
|
||
// Back link respects the prefix too
|
||
var back=document.querySelector('.bar a');
|
||
if(back) back.href=P+'/';
|
||
});
|
||
|
||
function lookup(){
|
||
var name = $('q').value.trim();
|
||
if(!name){ $('out').textContent = ''; return; }
|
||
history.replaceState({}, '', P+'/contractor?name='+encodeURIComponent(name));
|
||
var out = $('out');
|
||
out.textContent = '';
|
||
var ld = document.createElement('div');
|
||
ld.className = 'ld';
|
||
ld.textContent = 'Pulling OSHA, SEC, Stooq, Chicago history, USASpending… (~5-10s on cold cache)';
|
||
out.appendChild(ld);
|
||
fetch(P+'/intelligence/contractor_profile',{
|
||
method:'POST',
|
||
headers:{'Content-Type':'application/json'},
|
||
body:JSON.stringify({name:name})
|
||
}).then(function(r){return r.json()}).then(function(d){
|
||
render(d);
|
||
}).catch(function(e){
|
||
out.textContent = '';
|
||
var err = document.createElement('div');
|
||
err.className = 'ld';
|
||
err.style.color = '#f85149';
|
||
err.textContent = 'profile failed: '+e.message;
|
||
out.appendChild(err);
|
||
});
|
||
}
|
||
|
||
function render(d){
|
||
var out = $('out');
|
||
out.textContent = '';
|
||
|
||
// ─── Hero — name, ticker, parent ─────────────
|
||
var hero = document.createElement('div');
|
||
hero.className = 'hero';
|
||
var h2 = document.createElement('h2');
|
||
h2.textContent = d.display_name;
|
||
hero.appendChild(h2);
|
||
var sub = document.createElement('div');
|
||
sub.className = 'meta';
|
||
sub.textContent = 'Internal ticker: '+(d.ticker||'?')+' · profile generated '+new Date(d.generated_at).toLocaleTimeString();
|
||
hero.appendChild(sub);
|
||
|
||
var trow = document.createElement('div');
|
||
trow.className = 'ticker-row';
|
||
// Direct ticker
|
||
var s = d.stock;
|
||
if(s && s.status==='ok'){
|
||
var tk = document.createElement('span');
|
||
tk.className = 'ticker';
|
||
tk.textContent = s.ticker;
|
||
trow.appendChild(tk);
|
||
var px = document.createElement('span');
|
||
px.className = 'meta';
|
||
px.textContent = (s.company_name||'')+(s.exchange?' · '+s.exchange:'')+(s.price?' · $'+s.price.toFixed(2):'');
|
||
if(s.day_change_pct!=null && !isNaN(s.day_change_pct)){
|
||
var ch = (s.day_change_pct>=0?'+':'')+s.day_change_pct.toFixed(2)+'%';
|
||
var chSpan = document.createElement('span');
|
||
chSpan.style.color = s.day_change_pct>=0?'#3fb950':'#f85149';
|
||
chSpan.style.marginLeft = '6px';
|
||
chSpan.textContent = ch;
|
||
px.appendChild(chSpan);
|
||
}
|
||
trow.appendChild(px);
|
||
} else {
|
||
var noTk = document.createElement('span');
|
||
noTk.className = 'meta';
|
||
noTk.textContent = 'Private — no direct US ticker';
|
||
trow.appendChild(noTk);
|
||
}
|
||
// Parent link
|
||
var pl = d.parent_link;
|
||
if(pl && pl.status==='ok'){
|
||
var arrow = document.createElement('span');
|
||
arrow.className = 'meta';
|
||
arrow.style.color = '#545d68';
|
||
arrow.textContent = ' → parent ';
|
||
trow.appendChild(arrow);
|
||
var pTk = document.createElement('span');
|
||
pTk.className = 'ticker';
|
||
pTk.style.color = '#d29922';
|
||
pTk.style.borderColor = '#d2992266';
|
||
pTk.textContent = pl.parent_ticker || '?';
|
||
pTk.title = pl.link_source || '';
|
||
trow.appendChild(pTk);
|
||
var pName = document.createElement('span');
|
||
pName.className = 'meta';
|
||
pName.textContent = pl.parent_name+(pl.parent_exchange?' · '+pl.parent_exchange:'')+(pl.parent_country?' · '+pl.parent_country:'');
|
||
trow.appendChild(pName);
|
||
} else if(pl && pl.status==='no_link'){
|
||
var pp = document.createElement('span');
|
||
pp.className = 'meta';
|
||
pp.style.fontStyle = 'italic';
|
||
pp.textContent = ' · '+(pl.reason||'no public parent identified');
|
||
trow.appendChild(pp);
|
||
}
|
||
hero.appendChild(trow);
|
||
out.appendChild(hero);
|
||
|
||
// ─── Grid of cards ─────────────────────────────
|
||
var grid = document.createElement('div');
|
||
grid.className = 'grid';
|
||
|
||
// OSHA
|
||
var oCard = card('OSHA SAFETY HISTORY (NATIONAL)');
|
||
var osha = d.osha || {};
|
||
if(osha.status==='ok'){
|
||
big(oCard, osha.inspection_count + ' inspections', 'most recent '+(osha.most_recent_date||'?'));
|
||
rowEl(oCard, 'States seen', (osha.states_seen||[]).join(', ') || '?');
|
||
rowEl(oCard, 'Most recent', osha.most_recent_date||'?');
|
||
if(osha.recent_inspections && osha.recent_inspections.length){
|
||
var rep = document.createElement('div');
|
||
rep.style.marginTop = '8px';
|
||
rep.style.fontSize = '10px';
|
||
rep.style.color = '#545d68';
|
||
rep.textContent = 'Recent inspections:';
|
||
oCard.appendChild(rep);
|
||
osha.recent_inspections.slice(0,5).forEach(function(i){
|
||
var r = document.createElement('div');
|
||
r.style.fontSize = '10px';
|
||
r.style.color = '#8b949e';
|
||
r.style.fontFamily = 'ui-monospace,monospace';
|
||
r.style.padding = '2px 0';
|
||
var a = document.createElement('a');
|
||
a.href = i.detail_url;
|
||
a.target = '_blank';
|
||
a.textContent = i.id;
|
||
r.appendChild(a);
|
||
r.appendChild(document.createTextNode(' · '+i.date+' · '+i.state+' · '+i.type+' · '+i.scope));
|
||
oCard.appendChild(r);
|
||
});
|
||
}
|
||
} else if(osha.status==='no_match'){
|
||
big(oCard, 'No inspections', 'clean record');
|
||
} else {
|
||
empty(oCard, 'OSHA fetch error: '+(osha.error||'unknown'));
|
||
}
|
||
grid.appendChild(oCard);
|
||
|
||
// Chicago history
|
||
var hCard = card('CHICAGO PERMIT HISTORY (24mo + LIFETIME)');
|
||
var hist = d.history || {};
|
||
if(hist.status==='ok'){
|
||
big(hCard, hist.permits_historical_total+' permits all-time',
|
||
hist.permits_last_180d+' in last 180d · '+hist.permits_last_24mo+' in 24mo · trend: '+hist.trend);
|
||
rowEl(hCard, 'Cost (24mo)', hist.total_cost_last_24mo>=1e6 ? '$'+(hist.total_cost_last_24mo/1e6).toFixed(1)+'M' : '$'+Math.round(hist.total_cost_last_24mo/1e3)+'K');
|
||
if(hist.recent_permits && hist.recent_permits.length){
|
||
var rh = document.createElement('div');
|
||
rh.style.marginTop = '8px';
|
||
rh.style.fontSize = '10px';
|
||
rh.style.color = '#545d68';
|
||
rh.textContent = 'Recent Chicago permits:';
|
||
hCard.appendChild(rh);
|
||
hist.recent_permits.slice(0,5).forEach(function(p){
|
||
var r = document.createElement('div');
|
||
r.style.fontSize = '10px';
|
||
r.style.color = '#8b949e';
|
||
r.style.padding = '2px 0';
|
||
r.textContent = '· '+(p.date||'?')+' · '+p.work_type+' · $'+(p.cost||0).toLocaleString()+' · '+p.address;
|
||
hCard.appendChild(r);
|
||
});
|
||
}
|
||
} else {
|
||
empty(hCard, 'Chicago history error');
|
||
}
|
||
grid.appendChild(hCard);
|
||
|
||
// Federal contracts
|
||
var fCard = card('FEDERAL CONTRACTS (USASpending.gov)');
|
||
var fed = d.federal || {};
|
||
if(fed.status==='ok' && fed.total_awards_count>0){
|
||
var dollars = fed.total_awards_value>=1e9 ? '$'+(fed.total_awards_value/1e9).toFixed(2)+'B'
|
||
: fed.total_awards_value>=1e6 ? '$'+(fed.total_awards_value/1e6).toFixed(1)+'M'
|
||
: '$'+Math.round(fed.total_awards_value/1e3)+'K';
|
||
big(fCard, dollars, fed.total_awards_count+' awards · most recent '+(fed.most_recent_award_date||'?'));
|
||
if(fed.top_agencies && fed.top_agencies.length){
|
||
var ta = document.createElement('div');
|
||
ta.style.marginTop = '6px';
|
||
ta.style.fontSize = '10px';
|
||
ta.style.color = '#545d68';
|
||
ta.textContent = 'Top awarding agencies:';
|
||
fCard.appendChild(ta);
|
||
fed.top_agencies.forEach(function(a){
|
||
var r = document.createElement('div');
|
||
r.style.fontSize = '11px';
|
||
r.style.color = '#8b949e';
|
||
r.style.padding = '3px 0';
|
||
var dollars2 = a.value>=1e6 ? '$'+(a.value/1e6).toFixed(1)+'M' : '$'+Math.round(a.value/1e3)+'K';
|
||
r.textContent = '· '+a.agency+' — '+dollars2;
|
||
fCard.appendChild(r);
|
||
});
|
||
}
|
||
if(fed.source_url){
|
||
var lnk = document.createElement('a');
|
||
lnk.href = fed.source_url;
|
||
lnk.target = '_blank';
|
||
lnk.style.display = 'inline-block';
|
||
lnk.style.marginTop = '8px';
|
||
lnk.textContent = 'View on usaspending.gov ↗';
|
||
fCard.appendChild(lnk);
|
||
}
|
||
} else if(fed.status==='no_match'){
|
||
big(fCard, 'No federal contracts', 'on file under this name');
|
||
} else {
|
||
empty(fCard, 'usaspending error');
|
||
}
|
||
grid.appendChild(fCard);
|
||
|
||
// Debarment + NLRB combined
|
||
var rCard = card('DEBARMENT + LABOR ACTIONS');
|
||
var deb = d.debarment || {};
|
||
var nlrb = d.nlrb || {};
|
||
rowEl(rCard, 'SAM.gov excluded', deb.status==='needs_setup' ? 'awaiting API key' : (deb.sam_excluded?'YES':'no'));
|
||
rowEl(rCard, 'IDOL debarred', deb.status==='needs_setup' ? 'awaiting scrape' : (deb.idol_debarred?'YES':'no'));
|
||
rowEl(rCard, 'NLRB cases', nlrb.status==='needs_setup' ? 'awaiting scrape' : (nlrb.total_cases||0));
|
||
if(deb.status==='needs_setup' || nlrb.status==='needs_setup'){
|
||
var dn = document.createElement('div');
|
||
dn.className = 'empty';
|
||
dn.style.marginTop = '8px';
|
||
dn.textContent = 'Both sources pending wire-up: '+(deb.reason||nlrb.reason||'');
|
||
rCard.appendChild(dn);
|
||
}
|
||
grid.appendChild(rCard);
|
||
|
||
// ILSOS
|
||
var iCard = card('CORPORATE REGISTRY (Illinois SoS)');
|
||
var ilsos = d.ilsos || {};
|
||
if(ilsos.status==='source_unreachable'){
|
||
rowEl(iCard, 'Status', 'source blocked at our ASN');
|
||
var en = document.createElement('div');
|
||
en.className = 'empty';
|
||
en.style.marginTop = '8px';
|
||
en.textContent = ilsos.reason||'';
|
||
iCard.appendChild(en);
|
||
} else if(ilsos.status==='ok'){
|
||
rowEl(iCard, 'Entity name', ilsos.entity_name||'?');
|
||
rowEl(iCard, 'File #', ilsos.file_number||'?');
|
||
rowEl(iCard, 'Status', ilsos.status_text||'?');
|
||
rowEl(iCard, 'Formed', ilsos.formation_date||'?');
|
||
rowEl(iCard, 'Registered agent', ilsos.registered_agent||'?');
|
||
} else {
|
||
empty(iCard, 'no ILSOS data');
|
||
}
|
||
grid.appendChild(iCard);
|
||
|
||
out.appendChild(grid);
|
||
|
||
// ─── Project Index summary — the staffer-facing build-signal score ──
|
||
var pixHeader = document.createElement('div');
|
||
pixHeader.className = 'section-label';
|
||
pixHeader.textContent = '◆ Project Index — build-signal score';
|
||
out.appendChild(pixHeader);
|
||
|
||
var pixCard = document.createElement('div');
|
||
pixCard.className = 'card wide';
|
||
// Score is a simple weighted blend of the wired signals — designed to
|
||
// be replaced with a real model once enough placeholders are wired.
|
||
var hist2 = d.history || {};
|
||
var pixScore = 0;
|
||
var pixDrivers = [];
|
||
if(hist2.permits_last_180d){ pixScore += Math.min(hist2.permits_last_180d * 5, 30); pixDrivers.push(hist2.permits_last_180d+' Chicago permits in 180d (+'+Math.min(hist2.permits_last_180d*5,30)+')'); }
|
||
if(hist2.trend === 'rising'){ pixScore += 10; pixDrivers.push('permit trend rising (+10)'); }
|
||
if(d.osha && d.osha.status==='ok' && d.osha.inspection_count>0){ pixScore -= Math.min(d.osha.inspection_count*5, 25); pixDrivers.push(d.osha.inspection_count+' OSHA inspections (-'+Math.min(d.osha.inspection_count*5,25)+')'); }
|
||
if(d.federal && d.federal.status==='ok' && d.federal.total_awards_count>0){ pixScore += 15; pixDrivers.push('federally-vetted contractor (+15)'); }
|
||
if(d.debarment && d.debarment.sam_excluded){ pixScore -= 50; pixDrivers.push('SAM.gov excluded (-50)'); }
|
||
if(d.stock && d.stock.status==='ok'){ pixScore += 5; pixDrivers.push('public ticker (+5)'); }
|
||
pixScore = Math.max(0, Math.min(100, 50 + pixScore));
|
||
var pixColor = pixScore >= 70 ? '#3fb950' : pixScore >= 40 ? '#d29922' : '#f85149';
|
||
var pixHero = document.createElement('div');
|
||
pixHero.style.cssText = 'display:flex;align-items:baseline;gap:14px;margin-bottom:8px';
|
||
var pixBig = document.createElement('span');
|
||
pixBig.style.cssText = 'font-size:42px;font-weight:700;color:'+pixColor+';letter-spacing:-1px';
|
||
pixBig.textContent = pixScore;
|
||
pixHero.appendChild(pixBig);
|
||
var pixLabel = document.createElement('span');
|
||
pixLabel.style.cssText = 'font-size:12px;color:#8b949e';
|
||
pixLabel.textContent = pixScore >= 70 ? 'Strong staffing partner — wired signals positive' : pixScore >= 40 ? 'Mixed signals — review drivers below' : 'Caution — wired signals negative';
|
||
pixHero.appendChild(pixLabel);
|
||
pixCard.appendChild(pixHero);
|
||
if(pixDrivers.length){
|
||
var pixDrv = document.createElement('div');
|
||
pixDrv.style.cssText = 'font-size:11px;color:#8b949e;line-height:1.7;font-family:ui-monospace,monospace';
|
||
pixDrv.textContent = pixDrivers.join(' · ');
|
||
pixCard.appendChild(pixDrv);
|
||
}
|
||
var pixFoot = document.createElement('div');
|
||
pixFoot.style.cssText = 'font-size:10px;color:#545d68;margin-top:8px;font-style:italic;line-height:1.5';
|
||
pixFoot.textContent = 'Score is a placeholder weighted blend of the 6 wired signals above. Real ML model lands once 12 awaiting sources below ship — that gives the index 18 features instead of 6.';
|
||
pixCard.appendChild(pixFoot);
|
||
out.appendChild(pixCard);
|
||
|
||
// ─── Heat map — every Chicago permit they're contact_1 or contact_2 on ─
|
||
var mapHeader = document.createElement('div');
|
||
mapHeader.className = 'section-label';
|
||
mapHeader.textContent = '◆ Where they\'ve worked — Chicago permits, last 24 months';
|
||
out.appendChild(mapHeader);
|
||
var mapCard = document.createElement('div');
|
||
mapCard.className = 'card wide';
|
||
var mapDiv = document.createElement('div');
|
||
mapDiv.className = 'heatmap';
|
||
mapDiv.id = 'cmap';
|
||
mapCard.appendChild(mapDiv);
|
||
var mapHint = document.createElement('div');
|
||
mapHint.style.cssText = 'font-size:11px;color:#545d68;margin-top:8px';
|
||
mapHint.textContent = 'Loading geo from chicago_permits…';
|
||
mapCard.appendChild(mapHint);
|
||
out.appendChild(mapCard);
|
||
|
||
// Plot the recent_permits embedded in the contractor profile (now
|
||
// includes lat/lng/permit_id/description per the entity.ts change).
|
||
// Color by cost: green <$100K, amber $100K-$1M, red ≥$1M.
|
||
var permits = (hist2.recent_permits||[]).filter(function(p){return p.lat&&p.lng});
|
||
if(!permits.length){
|
||
mapHint.textContent = 'No geocoded permits in the contractor history (Socrata may not have lat/lng for these records).';
|
||
} else {
|
||
// Construct map only after the div is in the DOM; defer one tick.
|
||
setTimeout(function(){
|
||
var map = L.map('cmap', {zoomControl:true, attributionControl:false}).setView([41.88,-87.63], 11);
|
||
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',{maxZoom:19}).addTo(map);
|
||
var bounds = [];
|
||
var costs = permits.map(function(p){return Number(p.cost)||0});
|
||
var maxCost = Math.max.apply(null, costs.concat([1]));
|
||
permits.forEach(function(p){
|
||
var c = Number(p.cost)||0;
|
||
var radius = 4 + (c/maxCost)*16;
|
||
var color = c >= 1000000 ? '#f85149' : c >= 100000 ? '#d29922' : '#3fb950';
|
||
var marker = L.circleMarker([p.lat,p.lng],{radius:radius, color:color, weight:1, fillOpacity:0.55});
|
||
// Build popup via DOM (no innerHTML — keeps the XSS hook happy)
|
||
var pop = document.createElement('div');
|
||
pop.style.cssText = 'font-family:ui-monospace,monospace;font-size:11px;color:#0a0d12;min-width:160px';
|
||
var costRow = document.createElement('div');
|
||
costRow.style.cssText = 'font-weight:700;margin-bottom:4px';
|
||
costRow.textContent = '$'+c.toLocaleString()+' · '+(p.date||'?');
|
||
pop.appendChild(costRow);
|
||
var wt = document.createElement('div');
|
||
wt.textContent = p.work_type||'?';
|
||
pop.appendChild(wt);
|
||
var addr = document.createElement('div');
|
||
addr.style.color = '#545d68';
|
||
addr.textContent = p.address||'?';
|
||
pop.appendChild(addr);
|
||
if(p.permit_id){
|
||
var pid = document.createElement('div');
|
||
pid.style.cssText = 'color:#545d68;margin-top:4px;font-size:10px';
|
||
pid.textContent = 'permit '+p.permit_id;
|
||
pop.appendChild(pid);
|
||
}
|
||
marker.bindPopup(pop);
|
||
marker.addTo(map);
|
||
bounds.push([p.lat, p.lng]);
|
||
});
|
||
if(bounds.length>1) map.fitBounds(bounds, {padding:[24,24]});
|
||
mapHint.textContent = permits.length+' permits plotted · green <$100K, amber $100K-$1M, red ≥$1M · radius: relative cost';
|
||
}, 50);
|
||
}
|
||
|
||
// ─── History timeline — monthly permit volume + cost trend ─────────
|
||
if(hist2.recent_permits && hist2.recent_permits.length){
|
||
var tlHeader = document.createElement('div');
|
||
tlHeader.className = 'section-label';
|
||
tlHeader.textContent = '◆ Activity timeline — Chicago permits by month';
|
||
out.appendChild(tlHeader);
|
||
var tlCard = document.createElement('div');
|
||
tlCard.className = 'card wide';
|
||
// Bucket by year-month
|
||
var buckets = {};
|
||
hist2.recent_permits.forEach(function(p){
|
||
var d = (p.date||'').substring(0,7); // YYYY-MM
|
||
if(!d) return;
|
||
buckets[d] = buckets[d] || {count:0, cost:0};
|
||
buckets[d].count++;
|
||
buckets[d].cost += Number(p.cost)||0;
|
||
});
|
||
var months = Object.keys(buckets).sort();
|
||
if(months.length){
|
||
var maxC = Math.max.apply(null, months.map(function(m){return buckets[m].count}));
|
||
var tl = document.createElement('div'); tl.className='timeline';
|
||
months.forEach(function(m){
|
||
var b = buckets[m];
|
||
var bar = document.createElement('div'); bar.className='tbar';
|
||
bar.style.height = Math.max(2, Math.round(b.count/maxC*72)) + 'px';
|
||
bar.title = m+' · '+b.count+' permit'+(b.count===1?'':'s')+' · $'+Math.round(b.cost).toLocaleString();
|
||
tl.appendChild(bar);
|
||
});
|
||
tlCard.appendChild(tl);
|
||
var ax = document.createElement('div'); ax.className='timeline-axis';
|
||
var first = document.createElement('span'); first.textContent = months[0];
|
||
var last = document.createElement('span'); last.textContent = months[months.length-1];
|
||
ax.appendChild(first); ax.appendChild(last);
|
||
tlCard.appendChild(ax);
|
||
}
|
||
out.appendChild(tlCard);
|
||
}
|
||
|
||
// ─── 12 awaiting-source placeholders ──────────────────────────────
|
||
// Each one names a real public data source that would feed the
|
||
// build-signal index, with a one-line "why a staffer cares" framing
|
||
// and a sample shape of what the panel would show once wired.
|
||
var phHeader = document.createElement('div');
|
||
phHeader.className = 'section-label';
|
||
phHeader.textContent = '◆ 12 awaiting sources — what plugs in next';
|
||
out.appendChild(phHeader);
|
||
var phGrid = document.createElement('div');
|
||
phGrid.className = 'placeholder-grid';
|
||
PLACEHOLDERS.forEach(function(p){
|
||
var c = document.createElement('div'); c.className='ph-card';
|
||
var h = document.createElement('h4');
|
||
var name = document.createElement('span'); name.textContent = p.name;
|
||
var badge = document.createElement('span'); badge.className='badge'; badge.textContent='AWAITING';
|
||
h.appendChild(name); h.appendChild(badge);
|
||
c.appendChild(h);
|
||
var why = document.createElement('div'); why.className='why'; why.textContent = p.why;
|
||
c.appendChild(why);
|
||
var would = document.createElement('div'); would.className='would';
|
||
would.textContent = 'Would show: ' + p.would;
|
||
c.appendChild(would);
|
||
phGrid.appendChild(c);
|
||
});
|
||
out.appendChild(phGrid);
|
||
|
||
// Roadmap footer
|
||
var foot = document.createElement('div');
|
||
foot.style.marginTop = '20px';
|
||
foot.style.fontSize = '10px';
|
||
foot.style.color = '#484f58';
|
||
foot.style.lineHeight = '1.6';
|
||
foot.textContent = 'Wired: OSHA Enforcement · SEC EDGAR + Stooq · Chicago Socrata permits (lat/lng) · USASpending.gov · curated parent-ticker map · ILSOS (datacenter ASN blocked). 12 awaiting sources above are real public datasets that would 3× the feature count of the build-signal index — each one labeled with the one-liner the staffer would ask before placing a worker.';
|
||
out.appendChild(foot);
|
||
}
|
||
|
||
// Twelve real public data sources, framed in coordinator language.
|
||
// Each is a placeholder; the panel renders them as "AWAITING" with a
|
||
// description of what they'd add once wired. Order is roughly: highest
|
||
// staffing-decision relevance first.
|
||
var PLACEHOLDERS = [
|
||
{
|
||
name: 'DOL Wage & Hour (WHD)',
|
||
why: 'Has this contractor stiffed workers before? WHD posts every back-wage settlement and unpaid-overtime case.',
|
||
would: 'cases last 24mo · total back wages owed · status by state · most recent settlement date · whether the workers got paid',
|
||
},
|
||
{
|
||
name: 'State Licensure Boards',
|
||
why: 'Is the contractor legally allowed to do this work today, in this state?',
|
||
would: 'license # · status (active / expired / suspended) · trade scope · expiration date · disciplinary history',
|
||
},
|
||
{
|
||
name: 'Surety Bond Capacity',
|
||
why: 'How big a job can this contractor actually take? Bond ceiling = upper bound on what they\'re bonded for.',
|
||
would: 'bonding company · single-contract ceiling · aggregate cap · current utilization · recent bond denials',
|
||
},
|
||
{
|
||
name: 'EPA ECHO Compliance',
|
||
why: 'If a worker shows up to a site with hazmat issues, that\'s the staffing company\'s problem too.',
|
||
would: 'facility-level violations · last enforcement action · pollutants · whether OSHA escalated',
|
||
},
|
||
{
|
||
name: 'DOT/FMCSA Carrier Safety',
|
||
why: 'For warehouses with on-site driving or carriers we cross-staff: crash rate, driver out-of-service rate, IFTA filings.',
|
||
would: 'crash rate per million miles · driver OOS % · vehicle OOS % · safety rating · last compliance review',
|
||
},
|
||
{
|
||
name: 'BBB Complaints + Rating',
|
||
why: 'What do this contractor\'s own employees say happens to them? BBB aggregates complaints from workers and clients.',
|
||
would: 'rating · complaint count last 36mo · complaint categories (pay, safety, ghosted) · response rate',
|
||
},
|
||
{
|
||
name: 'PACER Civil Suits (Federal)',
|
||
why: 'Are they being sued for FLSA, discrimination, or wrongful termination? Filings predate enforcement actions.',
|
||
would: 'open suits · FLSA / Title VII / ADA breakdowns · counterparties · year-over-year filing rate',
|
||
},
|
||
{
|
||
name: 'UCC Lien Filings',
|
||
why: 'When a contractor stops paying suppliers, mechanics liens hit the public record. Cash-flow distress signal.',
|
||
would: 'open liens · total face value · filers (suppliers, banks) · last filing · whether resolved',
|
||
},
|
||
{
|
||
name: 'D&B / Credit Bureau',
|
||
why: 'Will they pay our staffing invoices? D&B PAYDEX score is the standard.',
|
||
would: 'PAYDEX (1-100) · days-beyond-terms · credit limit recommendation · UCC link · trade payment trend',
|
||
},
|
||
{
|
||
name: 'State UI Employer Claims',
|
||
why: 'Workforce stability proxy. A spike in unemployment claims at this employer = layoffs or churn we should know about.',
|
||
would: 'claims filed against this employer last 12mo · approval rate · separation-reason breakdown',
|
||
},
|
||
{
|
||
name: 'MSHA Mine Safety',
|
||
why: 'For excavation, demolition, materials, aggregate — MSHA owns the citation history.',
|
||
would: 'citations · S&S violations · most recent fatality / serious injury · pattern-of-violation flag',
|
||
},
|
||
{
|
||
name: 'Registered Apprenticeships (DOL RAPIDS)',
|
||
why: 'A contractor with active apprenticeship programs has built a workforce pipeline — different staffing partnership story than one without.',
|
||
would: 'active programs · apprentice count · trades covered · graduation rate · ethnic/gender diversity reported',
|
||
},
|
||
];
|
||
|
||
function card(title){
|
||
var c = document.createElement('div');
|
||
c.className = 'card';
|
||
var h = document.createElement('h3');
|
||
h.textContent = title;
|
||
c.appendChild(h);
|
||
return c;
|
||
}
|
||
function big(c, value, sub){
|
||
var b = document.createElement('div'); b.className='big'; b.textContent=value;
|
||
var s = document.createElement('div'); s.className='sub'; s.textContent=sub;
|
||
c.appendChild(b); c.appendChild(s);
|
||
}
|
||
function rowEl(c, label, value){
|
||
var r = document.createElement('div'); r.className='row';
|
||
var l = document.createElement('span'); l.className='l'; l.textContent=label;
|
||
var v = document.createElement('span'); v.className='v'; v.textContent=value||'—';
|
||
r.appendChild(l); r.appendChild(v); c.appendChild(r);
|
||
}
|
||
function empty(c, msg){
|
||
var e = document.createElement('div'); e.className='empty'; e.textContent=msg;
|
||
c.appendChild(e);
|
||
}
|
||
</script>
|
||
</body></html>
|