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:
parent
9acbe5c369
commit
2da8562c90
@ -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
|
||||
|
||||
@ -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 ───
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user