Interactive permit heat map with live data verification

- Leaflet.js map with dark tiles showing real Chicago building permits
- Dots sized and colored by project cost ($1B+ red, $100M+ orange, $10M+ blue)
- Hover any dot for project details — address, cost, description, date
- LIVE indicator with green pulse dot
- Timestamp showing when data was fetched
- "Verify source" link goes directly to Chicago Open Data portal
- "Refresh" button re-fetches from the API on click
- Expanded to 50 permits for denser map coverage
- Legend showing dot size scale

No one can say "you just typed those numbers in" when they can
click a dot on the map, see 10000 W OHARE ST, and verify it
themselves on data.cityofchicago.org.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
root 2026-04-17 20:24:43 -05:00
parent 9acbe5c369
commit 2da8562c90
2 changed files with 83 additions and 29 deletions

View File

@ -1042,7 +1042,7 @@ tr:hover{background:#111827}
const permitUrl = "https://data.cityofchicago.org/resource/ydr8-5enu.json";
const [bigR, byTypeR, recentR, benchR] = await Promise.all([
// Top 8 largest permits by cost
fetch(`${permitUrl}?$select=permit_type,work_type,work_description,reported_cost,street_number,street_direction,street_name,community_area,issue_date,latitude,longitude&$where=reported_cost>1000000 AND issue_date>'2025-06-01'&$order=reported_cost DESC&$limit=8`).then(r => r.json()),
fetch(`${permitUrl}?$select=permit_type,work_type,work_description,reported_cost,street_number,street_direction,street_name,community_area,issue_date,latitude,longitude&$where=reported_cost>1000000 AND issue_date>'2025-06-01'&$order=reported_cost DESC&$limit=50`).then(r => r.json()),
// Permits grouped by work type
fetch(`${permitUrl}?$select=work_type,count(*) as cnt,sum(reported_cost) as total_cost&$where=reported_cost>10000 AND issue_date>'2025-06-01'&$group=work_type&$order=total_cost DESC&$limit=10`).then(r => r.json()),
// Most recent permits

View File

@ -2,6 +2,8 @@
<html><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>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}
body{font-family:-apple-system,system-ui,sans-serif;background:#0b0f19;color:#c9d1d9;font-size:14px;line-height:1.5}
@ -604,37 +606,68 @@ function doSearch(){
}
// ─── Market Intelligence ───
var marketMap=null;
function loadMarket(){
api('/intelligence/market',{}).then(function(d){
if(d.error||!d.major_permits)return;
var el=document.getElementById('market');el.textContent='';
var card=document.createElement('div');card.className='insight warning';
var lb=document.createElement('div');lb.className='label';lb.textContent='MARKET INTELLIGENCE';
var hl=document.createElement('div');hl.className='headline';hl.textContent='Chicago Construction Pipeline — Live Permit Data';
// Header with live indicator + source link + refresh
var hdr=document.createElement('div');hdr.style.cssText='display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:6px;margin-bottom:4px';
var lb=document.createElement('div');lb.className='label';lb.style.cssText='font-size:9px;text-transform:uppercase;letter-spacing:1.5px;color:#484f58;display:flex;align-items:center;gap:8px';
lb.textContent='MARKET INTELLIGENCE';
var live=document.createElement('span');live.style.cssText='display:inline-flex;align-items:center;gap:4px;font-size:9px;color:#3fb950;letter-spacing:0';
var dot=document.createElement('span');dot.style.cssText='width:6px;height:6px;border-radius:50%;background:#3fb950;animation:blink 2s infinite';
live.appendChild(dot);live.appendChild(document.createTextNode('LIVE'));
lb.appendChild(live);
var rhs=document.createElement('div');rhs.style.cssText='display:flex;gap:8px;align-items:center';
var ts=document.createElement('span');ts.style.cssText='font-size:9px;color:#484f58';ts.textContent='Updated: '+new Date().toLocaleString();
var srcLink=document.createElement('a');srcLink.href='https://data.cityofchicago.org/Buildings/Building-Permits/ydr8-5enu';
srcLink.target='_blank';srcLink.style.cssText='font-size:9px;color:#58a6ff;text-decoration:none';srcLink.textContent='Verify source';
var refresh=document.createElement('button');refresh.style.cssText='font-size:9px;padding:2px 8px;background:#161b22;border:1px solid #21262d;border-radius:4px;color:#8b949e;cursor:pointer';
refresh.textContent='Refresh';refresh.onclick=function(){loadMarket()};
rhs.appendChild(ts);rhs.appendChild(srcLink);rhs.appendChild(refresh);
hdr.appendChild(lb);hdr.appendChild(rhs);card.appendChild(hdr);
var hl=document.createElement('div');hl.className='headline';hl.textContent='Chicago Construction Pipeline';
var sub=document.createElement('div');sub.className='sub';
sub.textContent='$'+(d.total_construction_value/1e9).toFixed(1)+'B in active permits → estimated '+d.total_estimated_workers.toLocaleString()+' workers needed. Source: City of Chicago Open Data';
card.appendChild(lb);card.appendChild(hl);card.appendChild(sub);
sub.textContent='$'+(d.total_construction_value/1e9).toFixed(1)+'B in active permits → '+d.total_estimated_workers.toLocaleString()+' workers needed · Fetched in '+d.duration_ms+'ms';
card.appendChild(hl);card.appendChild(sub);
// Major permits
if(d.major_permits&&d.major_permits.length){
var ph=document.createElement('div');ph.style.cssText='font-size:12px;font-weight:600;color:#f0f6fc;margin-bottom:6px';
ph.textContent='Largest Active Projects';card.appendChild(ph);
d.major_permits.slice(0,5).forEach(function(p){
var row=document.createElement('div');row.style.cssText='display:flex;justify-content:space-between;padding:6px 10px;background:#0d1117;border-radius:6px;margin-bottom:3px;font-size:12px;align-items:flex-start;gap:8px';
var left=document.createElement('div');left.style.cssText='flex:1;min-width:0';
var desc=document.createElement('div');desc.style.cssText='color:#f0f6fc;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis';
desc.textContent=p.description||p.type||'Construction';
var addr=document.createElement('div');addr.style.cssText='color:#484f58;font-size:10px;margin-top:1px';
addr.textContent=p.address+' · '+p.date;
left.appendChild(desc);left.appendChild(addr);
var cost=document.createElement('div');cost.style.cssText='color:#d29922;font-weight:700;font-size:13px;flex-shrink:0';
cost.textContent=p.cost>=1e9?'$'+(p.cost/1e9).toFixed(1)+'B':p.cost>=1e6?'$'+(p.cost/1e6).toFixed(0)+'M':'$'+(p.cost/1e3).toFixed(0)+'K';
row.appendChild(left);row.appendChild(cost);card.appendChild(row);
});
}
// MAP — real lat/lng from permit data
var mapWrap=document.createElement('div');mapWrap.style.cssText='border-radius:8px;overflow:hidden;margin-bottom:12px;border:1px solid #21262d';
var mapDiv=document.createElement('div');mapDiv.id='permit-map';mapDiv.style.cssText='height:280px;width:100%;background:#0d1117';
mapWrap.appendChild(mapDiv);card.appendChild(mapWrap);
// Supply gaps — where demand exceeds our bench
// Legend
var legend=document.createElement('div');legend.style.cssText='display:flex;gap:16px;justify-content:center;margin-bottom:12px;font-size:10px;color:#8b949e';
var sizes=[['$1B+','20px','#f85149'],['$100M+','14px','#d29922'],['$10M+','10px','#58a6ff'],['$1M+','6px','#3fb950']];
sizes.forEach(function(s){
var item=document.createElement('span');item.style.cssText='display:flex;align-items:center;gap:4px';
var circ=document.createElement('span');circ.style.cssText='width:'+s[1]+';height:'+s[1]+';border-radius:50%;background:'+s[2]+';opacity:0.7;display:inline-block';
item.appendChild(circ);item.appendChild(document.createTextNode(s[0]));legend.appendChild(item);
});
card.appendChild(legend);
// Major permits list
var ph=document.createElement('div');ph.style.cssText='font-size:12px;font-weight:600;color:#f0f6fc;margin-bottom:6px';
ph.textContent='Largest Active Projects';card.appendChild(ph);
d.major_permits.slice(0,5).forEach(function(p){
var row=document.createElement('div');row.style.cssText='display:flex;justify-content:space-between;padding:6px 10px;background:#0d1117;border-radius:6px;margin-bottom:3px;font-size:12px;align-items:flex-start;gap:8px';
var left=document.createElement('div');left.style.cssText='flex:1;min-width:0';
var desc=document.createElement('div');desc.style.cssText='color:#f0f6fc;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis';
desc.textContent=p.description||p.type||'Construction';
var addr=document.createElement('div');addr.style.cssText='color:#484f58;font-size:10px;margin-top:1px';
addr.textContent=p.address+' · '+p.date;
left.appendChild(desc);left.appendChild(addr);
var cost=document.createElement('div');cost.style.cssText='color:#d29922;font-weight:700;font-size:13px;flex-shrink:0';
cost.textContent=p.cost>=1e9?'$'+(p.cost/1e9).toFixed(1)+'B':p.cost>=1e6?'$'+(p.cost/1e6).toFixed(0)+'M':'$'+(p.cost/1e3).toFixed(0)+'K';
row.appendChild(left);row.appendChild(cost);card.appendChild(row);
});
// Bench vs demand
if(d.gaps&&d.gaps.length){
var gh=document.createElement('div');gh.style.cssText='font-size:12px;font-weight:600;color:#f0f6fc;margin:10px 0 6px';
gh.textContent='Your Bench vs. Market Demand (Illinois)';card.appendChild(gh);
@ -643,21 +676,42 @@ function loadMarket(){
if(seen[g.role])return;seen[g.role]=true;
var row=document.createElement('div');row.style.cssText='display:flex;justify-content:space-between;padding:5px 10px;background:#0d1117;border-radius:6px;margin-bottom:3px;font-size:12px;align-items:center';
var role=document.createElement('span');role.style.cssText='color:#f0f6fc;font-weight:500';role.textContent=g.role;
var nums=document.createElement('span');nums.style.cssText='font-size:11px';
var color=g.available>g.demand?'#3fb950':'#d29922';
nums.style.color=color;
var nums=document.createElement('span');nums.style.cssText='font-size:11px;color:'+(g.available>g.demand?'#3fb950':'#d29922');
nums.textContent=g.available.toLocaleString()+' available / '+g.reliable.toLocaleString()+' reliable ('+g.supply.toLocaleString()+' total)';
row.appendChild(role);row.appendChild(nums);card.appendChild(row);
});
}
// Insight callout
var insight=document.createElement('div');insight.style.cssText='font-size:11px;color:#d29922;margin-top:10px;padding:8px 10px;background:#1a1500;border:1px solid #854d0e;border-radius:6px';
insight.textContent='This data updates from live city records. When a $50M warehouse gets permitted, you\'ll know about it here before the contractor calls looking for workers. The system cross-references permits with your worker bench to show where you\'re covered and where to recruit.';
insight.textContent='Live from City of Chicago Open Data. Click "Verify source" to see the raw permit database. Each dot is a real permitted project — hover for details. The system cross-references this with your worker bench automatically.';
card.appendChild(insight);
el.appendChild(card);
}).catch(function(){});
// Initialize Leaflet map after DOM insertion
setTimeout(function(){
if(marketMap){marketMap.remove();marketMap=null}
marketMap=L.map('permit-map',{zoomControl:true,attributionControl:false}).setView([41.88,-87.7],11);
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',{maxZoom:18}).addTo(marketMap);
// Plot permits as circles sized by cost
d.major_permits.forEach(function(p){
if(!p.lat||!p.lng)return;
var cost=p.cost||0;
var radius=cost>=1e9?20:cost>=1e8?14:cost>=1e7?10:6;
var color=cost>=1e9?'#f85149':cost>=1e8?'#d29922':cost>=1e7?'#58a6ff':'#3fb950';
var costLabel=cost>=1e9?'$'+(cost/1e9).toFixed(1)+'B':cost>=1e6?'$'+(cost/1e6).toFixed(0)+'M':'$'+(cost/1e3).toFixed(0)+'K';
var circle=L.circleMarker([parseFloat(p.lat),parseFloat(p.lng)],{
radius:radius,fillColor:color,color:color,weight:1,opacity:0.8,fillOpacity:0.5
}).addTo(marketMap);
circle.bindPopup('<div style="font-size:12px;max-width:250px"><strong>'+costLabel+'</strong><br>'+
(p.description||'Construction').substring(0,120)+'<br><span style="color:#888">'+p.address+' · '+p.date+'</span></div>');
});
},100);
}).catch(function(e){
var el=document.getElementById('market');
el.textContent='Market data unavailable: '+e.message;
});
}
// ─── Learning Loop ───