demo: profiler — Construction Activity Signal Engine narrative + BAI
J's prompt: shoot for the stars, frame the data corpus's value as a
predictive signal, not just a contractor directory. The thesis is
that every name in this corpus is also a forward indicator on public
equities — permits filed today predict construction starts in ~45
days, staffing in ~30, revenue recognition months later. The
associated-ticker network surfaces this signal before any 10-Q does.
Two new layers above the basket:
1. HERO THESIS PANEL — "Chicago Construction Activity Signal Engine"
header + 3-line value statement, then 4 live metrics:
- BAI (Building Activity Index) — attribution-weighted average of
day-change % across surfaced issuers. Weight = attribution count
so issuers we have more depth on count more. Today: +0.76%
(9 issuers · top contributors FCBC +2.4%, ACRE +1.7%, JPM +1.5%).
Color-coded green/red.
- Indexed build value — total $ of permits attributable to ANY
public issuer in this view. Today: $344M.
- Network depth — issuers / attribution edges. Today: 9 / 15.
This is the "we see what nobody else sees" metric: how many
contractors are bridges from a private builder back to a public
equity holder.
- Market replication roadmap — chips showing "Chicago — live ·
NYC DOB — adapter ready · LA County · Houston BCD · Boston ISD
· DC DCRA". Frames the corpus as metro-agnostic from day one.
2. PER-TICKER ACTIVITY MAP — when a basket card is clicked, a leaflet
map appears below the basket plotting that ticker's geocoded permit
activity. Pulls /intelligence/contractor_profile for up to 6
attributed contractors, merges their geocoded permits, plots on a
dark Chicago tile layer. Color-banded by permit cost (green <$100K,
amber $100K-$1M, red ≥$1M). Click TGT → 23 Target permits across
Chicago; click JPM → JPMorgan-adjacent contractor activity. Cached
per ticker so toggling is instant.
Verified end-to-end on devop.live/lakehouse/profiler:
Default load: hero panel renders with all 4 metrics, basket strip
with 9 issuers + live prices in 669ms.
Click TGT : signal map activates, "23 geocoded permits across
1 contractor", table filters to 2 rows.
Tooltip on basket cards: full reason path including matched name +
contributors attributed to that ticker.
Architecture-side: zero new server code — all metrics computed
client-side from the existing profiler_index + ticker_quotes payloads.
The corpus already had the value; the page just needed to articulate it.
This commit is contained in:
parent
aa56fbce61
commit
a789000982
@ -2,6 +2,8 @@
|
||||
<html><head>
|
||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Profiler Index · 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}
|
||||
@ -37,6 +39,36 @@ td.role .pill{display:inline-block;padding:2px 7px;border-radius:9px;font-size:9
|
||||
.ticker-pill.associated{background:#0d1830;border:1px solid #58a6ff66;color:#58a6ff}
|
||||
.ticker-pill.exact{background:#0d2818;border:1px solid #2ea043;color:#3fb950}
|
||||
|
||||
/* Hero — the thesis panel that frames the data corpus's value. */
|
||||
.thesis{background:linear-gradient(135deg,#0d2818 0%,#0d1830 50%,#1a1410 100%);border:1px solid #2ea04344;border-radius:12px;padding:18px 22px;margin-bottom:14px;position:relative;overflow:hidden}
|
||||
.thesis::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,#3fb950 0%,#58a6ff 50%,#d29922 100%)}
|
||||
.thesis h2{font-size:18px;color:#e6edf3;font-weight:700;letter-spacing:-0.4px;margin-bottom:6px}
|
||||
.thesis .sub{font-size:12px;color:#8b949e;line-height:1.6;margin-bottom:14px;max-width:880px}
|
||||
.thesis .sub b{color:#3fb950;font-weight:600}
|
||||
.bai-row{display:flex;gap:24px;align-items:baseline;flex-wrap:wrap;margin-bottom:14px}
|
||||
.bai-block{display:flex;flex-direction:column;gap:2px}
|
||||
.bai-label{font-size:9px;color:#545d68;text-transform:uppercase;letter-spacing:1.4px;font-weight:700}
|
||||
.bai-value{font-size:26px;font-weight:700;color:#e6edf3;font-family:ui-monospace,monospace;letter-spacing:-0.5px;font-variant-numeric:tabular-nums}
|
||||
.bai-value.up{color:#3fb950}
|
||||
.bai-value.down{color:#f85149}
|
||||
.bai-sub{font-size:10px;color:#8b949e;margin-top:1px}
|
||||
.markets-strip{display:flex;gap:6px;flex-wrap:wrap;font-size:10px}
|
||||
.market-pill{padding:3px 9px;border-radius:9px;font-weight:600;border:1px solid;letter-spacing:0.4px}
|
||||
.market-pill.live{background:#0d2818;border-color:#3fb950;color:#3fb950}
|
||||
.market-pill.next{background:#0d1830;border-color:#58a6ff;color:#58a6ff}
|
||||
.market-pill.queue{background:#161b22;border-color:#21262d;color:#545d68}
|
||||
.market-pill.queue::before{content:'· '}
|
||||
|
||||
/* Map panel below basket — populates when a ticker is selected. */
|
||||
.signal-map-wrap{display:none;background:#0d1117;border:1px solid #171d27;border-radius:10px;padding:14px;margin-bottom:14px}
|
||||
.signal-map-wrap.active{display:block}
|
||||
.signal-map-header{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:10px}
|
||||
.signal-map-title{font-size:11px;color:#545d68;text-transform:uppercase;letter-spacing:1.2px;font-weight:600}
|
||||
.signal-map-title b{color:#58a6ff;font-family:ui-monospace,monospace}
|
||||
.signal-map-meta{font-size:11px;color:#8b949e}
|
||||
.signal-map{height:340px;border-radius:8px;border:1px solid #1f2631;overflow:hidden}
|
||||
.signal-map .leaflet-container{background:#0a0d12}
|
||||
|
||||
/* Scrolling ticker basket — top strip showing every public issuer
|
||||
the profiler index has surfaced, with live price + day-change. */
|
||||
.basket-wrap{background:#0a0d12;border:1px solid #171d27;border-radius:10px;margin-bottom:14px;overflow:hidden;position:relative}
|
||||
@ -81,6 +113,43 @@ td.role .pill{display:inline-block;padding:2px 7px;border-radius:9px;font-size:9
|
||||
</nav>
|
||||
</div>
|
||||
<div class="content">
|
||||
<!-- Hero thesis — frames what this data corpus actually is. The
|
||||
profiler index isn't just a contractor directory; it's a
|
||||
construction-activity signal that surfaces public issuers months
|
||||
before quarterly earnings does. Each metric here is computed
|
||||
from the live data, not pre-baked. -->
|
||||
<div class="thesis" id="thesis">
|
||||
<h2>Chicago Construction Activity Signal Engine</h2>
|
||||
<div class="sub">
|
||||
Every contractor name in this corpus is also a forward indicator on the public equities they touch. Permits filed today predict construction starts ~45 days out, staffing windows ~2 weeks before that, and revenue recognition months later. The associated-ticker network surfaces this signal <b>before</b> it lands in any 10-Q.
|
||||
</div>
|
||||
<div class="bai-row">
|
||||
<div class="bai-block">
|
||||
<span class="bai-label">Building Activity Index — today</span>
|
||||
<span class="bai-value" id="bai-value">—</span>
|
||||
<span class="bai-sub" id="bai-sub">awaiting basket prices</span>
|
||||
</div>
|
||||
<div class="bai-block">
|
||||
<span class="bai-label">Indexed build value</span>
|
||||
<span class="bai-value" id="bav-value">—</span>
|
||||
<span class="bai-sub" id="bav-sub">across surfaced issuers</span>
|
||||
</div>
|
||||
<div class="bai-block">
|
||||
<span class="bai-label">Network depth</span>
|
||||
<span class="bai-value" id="net-value">—</span>
|
||||
<span class="bai-sub" id="net-sub">issuers · attributions</span>
|
||||
</div>
|
||||
<div class="bai-block" style="flex:1;min-width:240px">
|
||||
<span class="bai-label">Market replication roadmap</span>
|
||||
<div class="markets-strip" style="margin-top:4px">
|
||||
<span class="market-pill live">Chicago — live</span>
|
||||
<span class="market-pill next">NYC DOB — adapter ready</span>
|
||||
<span class="market-pill queue">LA County · Houston BCD · Boston ISD · DC DCRA</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="basket-wrap" id="basket-wrap" style="display:none">
|
||||
<div class="basket-label">
|
||||
<span><span id="bk-count">0</span> public issuers in this view <span class="meta" id="bk-meta"></span></span>
|
||||
@ -88,6 +157,18 @@ td.role .pill{display:inline-block;padding:2px 7px;border-radius:9px;font-size:9
|
||||
</div>
|
||||
<div class="basket-track" id="basket"></div>
|
||||
</div>
|
||||
|
||||
<!-- Per-ticker permit map — shows where the selected issuer's
|
||||
attributed contractor activity is actually happening. Same
|
||||
leaflet pattern as the contractor profile, scoped to one ticker. -->
|
||||
<div class="signal-map-wrap" id="signal-map-wrap">
|
||||
<div class="signal-map-header">
|
||||
<span class="signal-map-title">Where <b id="signal-map-ticker">—</b> activity is happening</span>
|
||||
<span class="signal-map-meta" id="signal-map-meta">—</span>
|
||||
</div>
|
||||
<div class="signal-map" id="signal-map"></div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<input class="s" id="q" type="text" placeholder="Filter by contractor name (e.g., Target, Turner)" autocomplete="off">
|
||||
<select id="since">
|
||||
@ -115,6 +196,9 @@ document.getElementById('back-console').href = P+'/console';
|
||||
var sortKey='total_cost', sortDir='desc';
|
||||
var lastRows=[];
|
||||
var tickerFilter=null; // selected ticker for filtering the table
|
||||
var lastQuotes={}; // ticker → quote (price, day_change_pct)
|
||||
var lastBasket=[]; // basket rows aggregated from lastRows
|
||||
var signalMap=null; // leaflet map instance for the per-ticker view
|
||||
|
||||
function clearChildren(el){ while(el.firstChild) el.removeChild(el.firstChild); }
|
||||
function fmt$(n){
|
||||
@ -229,16 +313,21 @@ function buildBasket(){
|
||||
c.classList.toggle('selected', c.dataset && c.dataset.ticker===tickerFilter);
|
||||
});
|
||||
document.getElementById('bk-clear').style.display = tickerFilter ? 'inline' : 'none';
|
||||
showSignalMap(tickerFilter);
|
||||
render();
|
||||
};
|
||||
track.appendChild(card);
|
||||
});
|
||||
// Batch-fetch quotes and update each card
|
||||
lastBasket = basketRows;
|
||||
// Update the hero panel right away with what we know without prices
|
||||
updateThesisMetrics();
|
||||
// Batch-fetch quotes and update each card + thesis
|
||||
fetch(P+'/intelligence/ticker_quotes',{
|
||||
method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({tickers:basketRows.map(function(b){return b.ticker})})
|
||||
}).then(function(r){return r.json()}).then(function(qd){
|
||||
var quotes=qd.quotes||{};
|
||||
lastQuotes = quotes;
|
||||
document.getElementById('bk-meta').textContent='quotes via Stooq · '+(qd.duration_ms||0)+'ms';
|
||||
Array.prototype.forEach.call(track.children, function(card){
|
||||
var t=card.dataset.ticker; var q=quotes[t];
|
||||
@ -250,11 +339,142 @@ function buildBasket(){
|
||||
else if(q.day_change_pct>=0){ ch.textContent='+'+q.day_change_pct.toFixed(2)+'%'; ch.className='ch up'; }
|
||||
else { ch.textContent=q.day_change_pct.toFixed(2)+'%'; ch.className='ch down'; }
|
||||
});
|
||||
updateThesisMetrics();
|
||||
}).catch(function(){
|
||||
document.getElementById('bk-meta').textContent='quote fetch failed';
|
||||
});
|
||||
}
|
||||
|
||||
// Compute the Building Activity Index and update the hero panel.
|
||||
// BAI = attribution-weighted day-change % across surfaced issuers.
|
||||
// "Indexed build value" = total dollars of permits attributable to
|
||||
// any public issuer in this view (sum across attributing contractors).
|
||||
// "Network depth" = issuer count + total attributions.
|
||||
function updateThesisMetrics(){
|
||||
if(!lastBasket.length){
|
||||
document.getElementById('bai-value').textContent='—';
|
||||
document.getElementById('bai-sub').textContent='awaiting basket data';
|
||||
return;
|
||||
}
|
||||
// BAI: weighted average of day_change_pct, weight = attribution count.
|
||||
var weightedSum=0, weightTotal=0, contributors=[];
|
||||
lastBasket.forEach(function(b){
|
||||
var q = lastQuotes[b.ticker];
|
||||
if(q && q.day_change_pct!=null){
|
||||
var w = b.count || 1;
|
||||
weightedSum += q.day_change_pct * w;
|
||||
weightTotal += w;
|
||||
contributors.push({ticker:b.ticker, day:q.day_change_pct, weight:w});
|
||||
}
|
||||
});
|
||||
var bai = weightTotal>0 ? (weightedSum/weightTotal) : null;
|
||||
var baiEl = document.getElementById('bai-value');
|
||||
var baiSub = document.getElementById('bai-sub');
|
||||
if(bai==null){
|
||||
baiEl.textContent='—'; baiSub.textContent='no quotes settled yet';
|
||||
baiEl.className='bai-value';
|
||||
} else {
|
||||
var sign = bai>=0 ? '+' : '';
|
||||
baiEl.textContent = sign + bai.toFixed(2) + '%';
|
||||
baiEl.className = 'bai-value ' + (bai>=0?'up':'down');
|
||||
contributors.sort(function(a,b){return Math.abs(b.day*b.weight) - Math.abs(a.day*a.weight)});
|
||||
var top = contributors.slice(0,3).map(function(c){return c.ticker+' '+(c.day>=0?'+':'')+c.day.toFixed(1)+'%'}).join(' · ');
|
||||
baiSub.textContent = contributors.length+' issuers contributing · top: '+top;
|
||||
}
|
||||
// Indexed build value
|
||||
var totalCost = 0;
|
||||
lastRows.forEach(function(r){
|
||||
var ts=(r.tickers&&r.tickers.direct?r.tickers.direct:[]).concat(r.tickers&&r.tickers.associated?r.tickers.associated:[]);
|
||||
if(ts.length>0) totalCost += (r.total_cost||0);
|
||||
});
|
||||
var bav = totalCost>=1e9 ? '$'+(totalCost/1e9).toFixed(2)+'B' : totalCost>=1e6 ? '$'+(totalCost/1e6).toFixed(0)+'M' : '$'+Math.round(totalCost/1e3)+'K';
|
||||
document.getElementById('bav-value').textContent = bav;
|
||||
document.getElementById('bav-sub').textContent = lastBasket.length+' issuers in scope';
|
||||
// Network depth
|
||||
var totalAttrib = lastBasket.reduce(function(s,b){return s + (b.count||0)},0);
|
||||
document.getElementById('net-value').textContent = lastBasket.length + ' / ' + totalAttrib;
|
||||
document.getElementById('net-sub').textContent = 'issuers / attribution edges';
|
||||
}
|
||||
|
||||
// Per-ticker map: when a ticker is selected, plot the contractor
|
||||
// permit locations attributed to that ticker. Pulls lat/lng for each
|
||||
// attributed contractor from the contractor profile endpoint and
|
||||
// merges. Caches per-ticker so toggling is instant.
|
||||
var mapCache = {};
|
||||
function showSignalMap(ticker){
|
||||
var wrap=document.getElementById('signal-map-wrap');
|
||||
if(!ticker){ wrap.classList.remove('active'); if(signalMap){signalMap.remove(); signalMap=null;} return; }
|
||||
wrap.classList.add('active');
|
||||
document.getElementById('signal-map-ticker').textContent = ticker;
|
||||
document.getElementById('signal-map-meta').textContent = 'loading permits…';
|
||||
// Find the contractors attributed to this ticker
|
||||
var attrib = lastRows.filter(function(r){
|
||||
var ts=(r.tickers&&r.tickers.direct?r.tickers.direct:[]).concat(r.tickers&&r.tickers.associated?r.tickers.associated:[]);
|
||||
return ts.some(function(t){return t.ticker===ticker});
|
||||
});
|
||||
if(!attrib.length){
|
||||
document.getElementById('signal-map-meta').textContent='no attributed contractors';
|
||||
return;
|
||||
}
|
||||
// Use the contractor_profile endpoint per attributed contractor (cap at 6)
|
||||
// to pull their geocoded permits, then render. Cached per ticker.
|
||||
if(mapCache[ticker]){
|
||||
drawSignalMap(ticker, mapCache[ticker]);
|
||||
return;
|
||||
}
|
||||
var names = attrib.slice(0,6).map(function(r){return r.name});
|
||||
Promise.all(names.map(function(n){
|
||||
return fetch(P+'/intelligence/contractor_profile',{
|
||||
method:'POST',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({name:n})
|
||||
}).then(function(r){return r.json()}).then(function(d){
|
||||
var perms = (d.history && d.history.recent_permits) || [];
|
||||
return perms.filter(function(p){return p.lat&&p.lng}).map(function(p){
|
||||
return Object.assign({contractor:n}, p);
|
||||
});
|
||||
}).catch(function(){return []});
|
||||
})).then(function(arrs){
|
||||
var all = arrs.reduce(function(a,b){return a.concat(b)},[]);
|
||||
mapCache[ticker] = all;
|
||||
drawSignalMap(ticker, all);
|
||||
});
|
||||
}
|
||||
function drawSignalMap(ticker, permits){
|
||||
if(signalMap){ signalMap.remove(); signalMap=null; }
|
||||
if(!permits.length){
|
||||
document.getElementById('signal-map-meta').textContent='0 geocoded permits across attributed contractors';
|
||||
return;
|
||||
}
|
||||
document.getElementById('signal-map-meta').textContent = permits.length + ' geocoded permits across ' + new Set(permits.map(function(p){return p.contractor})).size + ' contractors';
|
||||
signalMap = L.map('signal-map',{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(signalMap);
|
||||
var bounds=[];
|
||||
var maxCost = Math.max.apply(null, permits.map(function(p){return Number(p.cost)||1}));
|
||||
permits.forEach(function(p){
|
||||
var c=Number(p.cost)||0;
|
||||
var radius = 4 + (c/maxCost)*14;
|
||||
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});
|
||||
var pop=document.createElement('div');
|
||||
pop.style.cssText='font-family:ui-monospace,monospace;font-size:11px;color:#0a0d12;min-width:200px';
|
||||
var top=document.createElement('div'); top.style.cssText='font-weight:700;margin-bottom:3px;color:#1f6feb';
|
||||
top.textContent=ticker+' attribution';
|
||||
pop.appendChild(top);
|
||||
var con=document.createElement('div'); con.textContent=p.contractor; con.style.fontWeight='600';
|
||||
pop.appendChild(con);
|
||||
var meta=document.createElement('div'); meta.style.color='#545d68';
|
||||
meta.textContent='$'+c.toLocaleString()+' · '+(p.date||'?')+' · '+(p.work_type||'?');
|
||||
pop.appendChild(meta);
|
||||
var addr=document.createElement('div'); addr.style.color='#545d68';
|
||||
addr.textContent=p.address||'?';
|
||||
pop.appendChild(addr);
|
||||
marker.bindPopup(pop);
|
||||
marker.addTo(signalMap);
|
||||
bounds.push([p.lat,p.lng]);
|
||||
});
|
||||
if(bounds.length>1) signalMap.fitBounds(bounds,{padding:[28,28]});
|
||||
}
|
||||
|
||||
function render(){
|
||||
var host=document.getElementById('result');
|
||||
clearChildren(host);
|
||||
@ -370,6 +590,7 @@ document.getElementById('bk-clear').addEventListener('click',function(){
|
||||
tickerFilter=null;
|
||||
document.getElementById('bk-clear').style.display='none';
|
||||
Array.prototype.forEach.call(document.querySelectorAll('.bk-card.selected'), function(c){c.classList.remove('selected')});
|
||||
showSignalMap(null);
|
||||
render();
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user