← back to Homesonspec
engine: per-stream liveness — each cylinder fires only when its data stream is moving live (idle streams go dark; RPM=0 when nothing flows)
5aea7888fc71a5b55f4ad3d0eef7c1f269243c7e · 2026-07-28 09:16:47 -0700 · Steve
Files touched
M scripts/engine-dashboard.mjs
Diff
commit 5aea7888fc71a5b55f4ad3d0eef7c1f269243c7e
Author: Steve <steve@designerwallcoverings.com>
Date: Tue Jul 28 09:16:47 2026 -0700
engine: per-stream liveness — each cylinder fires only when its data stream is moving live (idle streams go dark; RPM=0 when nothing flows)
---
scripts/engine-dashboard.mjs | 208 ++++++++++++++++++++-----------------------
1 file changed, 98 insertions(+), 110 deletions(-)
diff --git a/scripts/engine-dashboard.mjs b/scripts/engine-dashboard.mjs
index 8f2f1e1..7f919d9 100644
--- a/scripts/engine-dashboard.mjs
+++ b/scripts/engine-dashboard.mjs
@@ -1,150 +1,138 @@
-// HomesOnSpec "engine room" — a live Corvette V8 whose moving parts are driven by
-// REAL data throughput (homes + permits landing, geocoding, enrichment). Zero-dep
-// (Node http + psql + pgrep). Run: node scripts/engine-dashboard.mjs (PORT, default 9797)
+// HomesOnSpec "engine room" — a live V8 where EACH cylinder fires only when ITS
+// data stream is actually moving (records landing right now). Idle streams go dark
+// and still. Zero-dep (Node http + psql + pgrep). Run: node scripts/engine-dashboard.mjs
import { createServer } from "node:http";
import { execFile } from "node:child_process";
const DBURL = "postgresql://macstudio3@localhost/homesonspec?host=/tmp";
-const PORT = Number(process.env.PORT || 9797);
+const PORT = Number(process.env.PORT || 9798);
const sh = (cmd, args) => new Promise((r) => execFile(cmd, args, { maxBuffer: 8 << 20 }, (e, o) => r(e ? "" : o.trim())));
const q = (sql) => sh("psql", [DBURL, "-tAc", sql]);
-let prev = null, prevT = 0, smooth = 0;
+// 8 cylinders = 8 live data streams, each with the SQL that counts its records.
+const STREAMS = [
+ ["Lennar", `select count(*) from "InventoryHome" h join "Builder" b on b.id=h."builderId" where b.slug='lennar' and h.status='PUBLISHED'`],
+ ["DR Horton", `select count(*) from "InventoryHome" h join "Builder" b on b.id=h."builderId" where b.slug='dr-horton' and h.status='PUBLISHED'`],
+ ["Pulte", `select count(*) from "InventoryHome" h join "Builder" b on b.id=h."builderId" where b.slug='pultegroup' and h.status='PUBLISHED'`],
+ ["Tri Pointe", `select count(*) from "InventoryHome" h join "Builder" b on b.id=h."builderId" where b.slug='tri-pointe' and h.status='PUBLISHED'`],
+ ["Discovery", `select count(*) from "InventoryHome" h join "Builder" b on b.id=h."builderId" where b.slug='discovery-homes' and h.status='PUBLISHED'`],
+ ["Permits", `select count(*) from building_permits`],
+ ["Geocode", `select count(*) from building_permits where lat is not null`],
+ ["Enrich", `select count(*) from "Community" where amenities is not null`],
+];
+
+let prev = null, prevT = 0;
+const sm = new Array(STREAMS.length).fill(0); // per-stream smoothed rate
async function stats() {
- const row = await q(`select json_build_object(
- 'homes',(select count(*) from "InventoryHome" where status='PUBLISHED'),
- 'builders',(select count(distinct "builderId") from "InventoryHome" where status='PUBLISHED'),
- 'communities',(select count(*) from "Community"),
- 'enriched',(select count(*) from "Community" where amenities is not null),
- 'permits',(select count(*) from building_permits),
- 'permits_geo',(select count(*) from building_permits where lat is not null)
- )`);
- let s; try { s = JSON.parse(row); } catch { s = { homes:0,builders:0,communities:0,enriched:0,permits:0,permits_geo:0 }; }
+ const bigsql = "select json_build_array(" + STREAMS.map(([, s]) => `(${s})`).join(",") + ")";
+ let counts; try { counts = JSON.parse(await q(bigsql)); } catch { counts = STREAMS.map(() => 0); }
const now = Date.now();
const dt = prev ? Math.max(1, (now - prevT) / 1000) : 1;
- const d = (k) => prev ? Math.max(0, (s[k] - prev[k]) / dt) : 0; // per-second
- const rates = { homes: d("homes"), permits: d("permits"), geo: d("permits_geo") + Math.max(0,(prev?d("permits_geo"):0)), enrich: d("enriched") };
- const geoRate = prev ? Math.max(0, (s.permits_geo - prev.permits_geo) / dt) : 0;
- const totalRate = rates.homes + rates.permits + geoRate + rates.enrich;
- prev = s; prevT = now;
- // procs
+ const streams = STREAMS.map(([name], i) => {
+ const rate = prev ? Math.max(0, (counts[i] - prev[i]) / dt) : 0;
+ sm[i] = Math.max(rate, sm[i] * 0.55); // decay so a burst sustains a few polls
+ const live = sm[i] > 0.05;
+ return { name, count: counts[i], rate: sm[i], live };
+ });
+ prev = counts; prevT = now;
const procs = await sh("bash", ["-lc", "pgrep -fl 'build-loop.sh|ingest-permits|geocode-permits|enrich-nearby' 2>/dev/null | wc -l"]);
- const workers = Number(procs) || 0;
- // smooth throughput so a burst sustains the revs (decays ~40%/poll) — an engine
- // with active workers idles high (running), and pulses up on each commit burst.
- smooth = Math.max(totalRate, smooth * 0.62);
- const rpm = Math.min(7200, Math.round(750 + (workers > 0 ? 720 : 0) + smooth * 210));
- return { ...s, rates: { homes: rates.homes, permits: rates.permits, geo: geoRate, enrich: rates.enrich }, totalRate: smooth, rpm, workers };
+ const totalRate = streams.reduce((a, s) => a + s.rate, 0);
+ const liveCount = streams.filter((s) => s.live).length;
+ const rpm = liveCount ? Math.min(7200, Math.round(900 + totalRate * 210)) : 0; // no live data → engine OFF
+ return {
+ streams, rpm, totalRate, liveCount, workers: Number(procs) || 0,
+ homes: counts.slice(0, 5).reduce((a, b) => a + b, 0),
+ permits: counts[5], permits_geo: counts[6], enriched: counts[7],
+ };
}
const PAGE = `<!doctype html><html><head><meta charset=utf8><title>HomesOnSpec · V8</title>
<meta name=viewport content="width=device-width,initial-scale=1">
<style>
-:root{--red:#e01b1b;--amber:#ffb020;--grn:#2dd4bf;--ink:#e9eef2;--stroke:.5s}
+:root{--red:#e01b1b;--amber:#ffb020;--grn:#2dd4bf;--ink:#e9eef2}
*{box-sizing:border-box}html,body{margin:0;height:100%;background:radial-gradient(1200px 700px at 50% -10%,#141a20,#080a0c);color:var(--ink);font:13px/1.4 ui-monospace,Menlo,monospace;overflow:hidden}
.hd{display:flex;justify-content:space-between;align-items:center;padding:12px 20px;border-bottom:1px solid #1c262e}
-.hd h1{margin:0;font:700 17px system-ui;letter-spacing:.5px}.hd .sub{color:#7f97a3;font-size:11px}
-.badge{color:var(--red);font-weight:700}
-.stage{display:grid;grid-template-columns:1fr 320px;gap:18px;padding:16px 20px;height:calc(100% - 52px)}
-.enginewrap{display:flex;align-items:center;justify-content:center}
-svg{width:100%;height:100%;max-height:78vh}
-.cyl{fill:#0e151a;stroke:#28343d}
-.piston{fill:url(#pist);}
-.rod{stroke:#5a6b76;stroke-width:5}
-.bank{}
-@keyframes stroke{0%{transform:translateY(0)}50%{transform:translateY(58px)}100%{transform:translateY(0)}}
+.hd h1{margin:0;font:700 17px system-ui;letter-spacing:.5px}.hd .sub{color:#7f97a3;font-size:11px}.badge{color:var(--red);font-weight:700}
+.stage{display:grid;grid-template-columns:1fr 300px;gap:18px;padding:16px 20px;height:calc(100% - 52px)}
+.enginewrap{display:flex;align-items:center;justify-content:center}svg{width:100%;height:100%;max-height:80vh}
+@keyframes stroke{0%{transform:translateY(0)}50%{transform:translateY(52px)}100%{transform:translateY(0)}}
@keyframes spin{to{transform:rotate(360deg)}}
-@keyframes belt{to{stroke-dashoffset:-40}}
-.pg{animation:stroke var(--stroke) linear infinite}
-.crank{transform-origin:center;animation:spin var(--stroke) linear infinite}
-.belt{stroke-dasharray:6 6;animation:belt var(--stroke) linear infinite}
-.spark{opacity:.15}
-.panel{display:flex;flex-direction:column;gap:12px}
-.gauge{background:#0d141a;border:1px solid #1e2a32;border-radius:12px;padding:12px 14px}
-.gauge .k{color:#7f97a3;font-size:10px;text-transform:uppercase;letter-spacing:1px}
-.gauge .v{font:800 26px system-ui;margin-top:2px}
-.gauge .d{color:var(--grn);font-size:11px}
-.tach{position:relative;text-align:center}
-.rpmv{font:900 40px system-ui;color:var(--red);text-shadow:0 0 18px rgba(224,27,27,.5)}
+.crank{transform-origin:310px 452px}
+.panel{display:flex;flex-direction:column;gap:11px}
+.gauge{background:#0d141a;border:1px solid #1e2a32;border-radius:12px;padding:11px 14px}
+.gauge .k{color:#7f97a3;font-size:10px;text-transform:uppercase;letter-spacing:1px}.gauge .v{font:800 25px system-ui;margin-top:2px}.gauge .d{color:var(--grn);font-size:11px}
+.rpmv{font:900 40px system-ui;text-shadow:0 0 18px rgba(224,27,27,.4)}
.rpml{color:#7f97a3;font-size:10px;letter-spacing:2px}
-.leds{display:flex;gap:8px;margin-top:6px;flex-wrap:wrap}
-.led{display:flex;align-items:center;gap:5px;font-size:10px;color:#8aa}
-.dot{width:9px;height:9px;border-radius:50%;background:#33424c;box-shadow:0 0 0 rgba(45,212,191,0)}
-.dot.on{background:var(--grn);box-shadow:0 0 10px var(--grn)}
-.dot.hot{background:var(--amber);box-shadow:0 0 10px var(--amber)}
-.foot{color:#5f7684;font-size:10px;text-align:center;margin-top:2px}
+.live{color:var(--grn)}.off{color:#4a5a64}
+.foot{color:#5f7684;font-size:10px;text-align:center}
</style></head><body>
-<div class=hd><div><h1>HOMESONSPEC <span class=badge>V8</span> ENGINE ROOM</h1><div class=sub>pistons fire at the real rate data lands · corvette-spec</div></div>
+<div class=hd><div><h1>HOMESONSPEC <span class=badge>V8</span> ENGINE ROOM</h1><div class=sub>each cylinder fires only when its data stream is moving live</div></div>
<div class=sub id=work>workers: –</div></div>
<div class=stage>
- <div class=enginewrap>
- <svg viewBox="0 0 620 520" id=eng>
- <defs>
- <linearGradient id=pist x1=0 y1=0 x2=0 y2=1><stop offset=0 stop-color=#cfd8de /><stop offset=1 stop-color=#8996a0 /></linearGradient>
- <radialGradient id=pulley><stop offset=0 stop-color=#3a4650 /><stop offset=1 stop-color=#141a1f /></radialGradient>
- </defs>
- <!-- block -->
- <rect x=70 y=70 width=480 height=300 rx=14 fill=#0c1216 stroke=#222d35 />
- <text x=310 y=58 fill=#5f7684 font-size=12 text-anchor=middle letter-spacing=3>V8 · 8 LIVE DATA STREAMS</text>
+ <div class=enginewrap><svg viewBox="0 0 620 540" id=eng>
+ <defs><linearGradient id=pist x1=0 y1=0 x2=0 y2=1><stop offset=0 stop-color=#cfd8de /><stop offset=1 stop-color=#8996a0 /></linearGradient>
+ <radialGradient id=pulley><stop offset=0 stop-color=#3a4650 /><stop offset=1 stop-color=#141a1f /></radialGradient></defs>
+ <rect x=60 y=64 width=500 height=320 rx=16 fill=#0c1216 stroke=#222d35 />
+ <text x=310 y=52 fill=#5f7684 font-size=11 text-anchor=middle letter-spacing=3>V8 · 8 LIVE DATA STREAMS</text>
<g id=cyls></g>
- <!-- crank pulley -->
- <g class=crank id=crank><circle cx=310 cy=430 r=52 fill=url(#pulley) stroke=#2a353d stroke-width=4 />
- <circle cx=310 cy=430 r=8 fill=#e01b1b /><rect x=305 y=380 width=10 height=48 fill=#3a4650 /></g>
- <!-- serpentine belt -->
- <path class=belt d="M258 430 A52 52 0 1 1 362 430" fill=none stroke=#556 stroke-width=4 />
- <circle class=crank cx=470 cy=430 r=26 fill=url(#pulley) stroke=#2a353d stroke-width=3 />
- <path class=belt d="M362 430 A52 52 0 0 0 258 430 M258 430 L444 430" fill=none stroke=#4a5a64 stroke-width=3 opacity=.6 />
- </svg>
- </div>
+ <g class=crank id=crank><circle cx=310 cy=452 r=46 fill=url(#pulley) stroke=#2a353d stroke-width=4 /><circle cx=310 cy=452 r=7 fill=#e01b1b /><rect x=306 y=410 width=8 height=42 fill=#3a4650 /></g>
+ <circle cx=470 cy=452 r=22 fill=url(#pulley) stroke=#2a353d stroke-width=3 /><circle cx=170 cy=452 r=22 fill=url(#pulley) stroke=#2a353d stroke-width=3 />
+ <path d="M170 452 L470 452" stroke=#3a4650 stroke-width=3 opacity=.5 />
+ </svg></div>
<div class=panel>
- <div class=gauge tach><div class=rpml>ENGINE RPM · THROUGHPUT</div><div class=rpmv id=rpm>—</div><div class=d id=rate></div></div>
- <div class=gauge><div class=k>Listed homes</div><div class=v id=homes>—</div><div class=d id=hd></div></div>
- <div class=gauge><div class=k>Building permits</div><div class=v id=permits>—</div><div class=d id=pd></div></div>
- <div class=gauge><div class=k>Permits geocoded</div><div class=v id=geo>—</div><div class=d id=gd></div></div>
- <div class=gauge><div class=k>Builders · communities enriched</div><div class=v id=misc>—</div></div>
- <div class=leds id=leds></div>
+ <div class=gauge><div class=rpml>ENGINE RPM · LIVE THROUGHPUT</div><div class=rpmv id=rpm>—</div><div class=d id=rate></div></div>
+ <div class=gauge><div class=k>Streams firing now</div><div class=v id=liven>—</div><div class=d id=livelist></div></div>
+ <div class=gauge><div class=k>Listed homes</div><div class=v id=homes>—</div></div>
+ <div class=gauge><div class=k>Building permits · geocoded</div><div class=v id=permits>—</div></div>
+ <div class=gauge><div class=k>Communities enriched</div><div class=v id=enriched>—</div></div>
<div class=foot id=stamp></div>
</div>
</div>
<script>
-// 8 cylinders in a V (4 per bank), each = a data stream
-const STREAMS=["Lennar","DR Horton","Pulte","Tri Pointe","Permits","Geocode","Enrich","Discovery"];
-const cyls=document.getElementById('cyls');let g='';
-for(let i=0;i<8;i++){const bank=i<4?0:1;const idx=i%4;const x=120+idx*105;const yTop=bank?250:95;const skew=bank?18:-18;
- g+='<g transform="translate('+x+','+yTop+') skewX('+skew/6+')">'
- +'<rect class=cyl x=-26 y=0 width=52 height=90 rx=6 />'
- +'<rect class="piston pg" id="p'+i+'" x=-22 y=6 width=44 height=30 rx=4 style="animation-delay:'+(i*0.09)+'s"/>'
- +'<text x=0 y=-8 fill=#6c828e font-size=9 text-anchor=middle>'+STREAMS[i]+'</text>'
- +'<circle class=spark cx=0 cy=2 r=6 fill=#ffcf5a id="s'+i+'"/></g>';
+const NAMES=[];const cyls=document.getElementById('cyls');let g='';
+for(let i=0;i<8;i++){const bank=i<4?0:1;const idx=i%4;const x=130+idx*100;const yTop=bank?230:88;
+ g+='<g transform="translate('+x+','+yTop+')">'
+ +'<rect x=-26 y=0 width=52 height=86 rx=6 fill=#0e151a stroke=#28343d />'
+ +'<rect id="p'+i+'" x=-22 y=6 width=44 height=28 rx=4 fill=url(#pist) style="transform-origin:center"/>'
+ +'<circle id="s'+i+'" cx=0 cy=2 r=6 fill=#ffcf5a opacity=.1 />'
+ +'<text id="t'+i+'" x=0 y=-8 fill=#546570 font-size=9 text-anchor=middle></text>'
+ +'<text id="r'+i+'" x=0 y=104 fill=#3f4d57 font-size=8 text-anchor=middle></text></g>';
}
cyls.innerHTML=g;
const fmt=n=>Number(n).toLocaleString();
-function setDur(rpm){const dur=Math.max(0.09,60/Math.max(300,rpm)).toFixed(3)+'s';document.documentElement.style.setProperty('--stroke',dur);}
async function tick(){
let s;try{s=await fetch('/api/stats').then(r=>r.json())}catch(e){return}
- document.getElementById('rpm').textContent=fmt(s.rpm);
- setDur(s.rpm);
- document.getElementById('rate').textContent=(s.totalRate>0?('▲ '+s.totalRate.toFixed(1)+' records/sec'):'idle');
+ // engine RPM
+ const rpmEl=document.getElementById('rpm');rpmEl.textContent=s.rpm?fmt(s.rpm):'OFF';
+ rpmEl.style.color=s.rpm?'#e01b1b':'#4a5a64';
+ document.getElementById('rate').textContent=s.totalRate>0?('▲ '+s.totalRate.toFixed(1)+' records/sec'):'no live data';
+ // crank spins only if anything is live
+ const crank=document.getElementById('crank');
+ crank.style.animation=s.rpm?('spin '+Math.max(.1,60/s.rpm).toFixed(3)+'s linear infinite'):'none';
+ // per-cylinder: fire ONLY the moving streams
+ const firing=[];
+ s.streams.forEach((st,i)=>{
+ const p=document.getElementById('p'+i),sp=document.getElementById('s'+i),t=document.getElementById('t'+i),r=document.getElementById('r'+i);
+ t.textContent=st.name;
+ if(st.live){
+ const dur=Math.max(.12, 1/Math.min(8,st.rate)).toFixed(3); // faster stream = faster piston
+ p.style.animation='stroke '+dur+'s linear infinite';
+ p.style.fill='url(#pist)';sp.style.opacity=.95;t.style.fill='#2dd4bf';
+ r.textContent='+'+st.rate.toFixed(1)+'/s';r.style.fill='#2dd4bf';
+ firing.push(st.name);
+ }else{
+ p.style.animation='none';p.style.fill='#20282e';sp.style.opacity=.06;t.style.fill='#465561';
+ r.textContent='idle';r.style.fill='#33424c';
+ }
+ });
+ document.getElementById('liven').textContent=s.liveCount+' / 8';
+ document.getElementById('livelist').textContent=firing.join(' · ')||'engine idle';
document.getElementById('homes').textContent=fmt(s.homes);
- document.getElementById('permits').textContent=fmt(s.permits);
- document.getElementById('geo').textContent=fmt(s.permits_geo);
- document.getElementById('misc').textContent=fmt(s.builders)+' · '+fmt(s.enriched);
- const d=(x)=>x>0?('+'+x.toFixed(1)+'/s'):'';
- document.getElementById('hd').textContent=d(s.rates.homes);
- document.getElementById('pd').textContent=d(s.rates.permits);
- document.getElementById('gd').textContent=d(s.rates.geo);
+ document.getElementById('permits').textContent=fmt(s.permits)+' · '+fmt(s.permits_geo);
+ document.getElementById('enriched').textContent=fmt(s.enriched);
document.getElementById('work').textContent='workers: '+s.workers;
- // spark flashes scale with rpm
- for(let i=0;i<8;i++){const sp=document.getElementById('s'+i);sp.style.opacity=s.rpm>1400?0.9:0.12;}
- // leds
- const active=s.totalRate>0.2;
- document.getElementById('leds').innerHTML=
- '<div class=led><span class="dot '+(active?'on':'')+'"></span>INGEST</div>'
- +'<div class=led><span class="dot '+(s.rates.geo>0?'on':'')+'"></span>GEOCODE</div>'
- +'<div class=led><span class="dot '+(s.workers>0?'on':'hot')+'"></span>WORKERS '+s.workers+'</div>'
- +'<div class=led><span class="dot '+(s.rpm>1200?'hot':'on')+'"></span>'+(s.rpm>1200?'REDLINE':'CRUISE')+'</div>';
- document.getElementById('stamp').textContent=new Date().toLocaleTimeString()+' · '+fmt(s.homes+s.permits)+' total records under the hood';
+ document.getElementById('stamp').textContent=new Date().toLocaleTimeString()+' · '+fmt(s.homes+s.permits)+' records under the hood';
}
tick();setInterval(tick,2500);
</script></body></html>`;
← 7080744 feat: live V8 'engine room' dashboard (:9798) — pistons/cran
·
back to Homesonspec
·
admin/ingestion: honest error signal — surface per-record ho 847b94d →