[object Object]

← back to Wallco Ai

drunk-animals: /api/drunk-animals/stats + 'tonight in numbers' bar on /live

4c0ff53bcbb034a4ce91046dedee894eb2212642 · 2026-05-13 01:32:18 -0700 · SteveStudio2

Steve wakes up to a single-glance readout above the design stream.
Stats bar shows:
  - Total designs (live counter)
  - Designs per hour (derived from earliest→latest timestamps)
  - Average saturation across the collection
  - Top-8 animal-tag pills (frog 23, orangutan 21, elephant 14, ...)

Endpoint reads from in-memory DESIGNS (filtered to category='drunk-
animals') and counts ANIMAL_TAGS occurrences in each design's motifs[]
array — only shows animals that llava actually identified, not just
prompted. Stats refresh every 60s on the live page.

Smoke-tested: 123 designs, 11 animal species, 16/hr, avg sat 0.273.

Files touched

Diff

commit 4c0ff53bcbb034a4ce91046dedee894eb2212642
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Wed May 13 01:32:18 2026 -0700

    drunk-animals: /api/drunk-animals/stats + 'tonight in numbers' bar on /live
    
    Steve wakes up to a single-glance readout above the design stream.
    Stats bar shows:
      - Total designs (live counter)
      - Designs per hour (derived from earliest→latest timestamps)
      - Average saturation across the collection
      - Top-8 animal-tag pills (frog 23, orangutan 21, elephant 14, ...)
    
    Endpoint reads from in-memory DESIGNS (filtered to category='drunk-
    animals') and counts ANIMAL_TAGS occurrences in each design's motifs[]
    array — only shows animals that llava actually identified, not just
    prompted. Stats refresh every 60s on the live page.
    
    Smoke-tested: 123 designs, 11 animal species, 16/hr, avg sat 0.273.
---
 server.js | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 66 insertions(+)

diff --git a/server.js b/server.js
index 8d01ce0..cdacd65 100644
--- a/server.js
+++ b/server.js
@@ -2214,6 +2214,42 @@ app.get('/api/drunk-animals/feed', (req, res) => {
   res.json({ count: rows.length, total: DESIGNS.filter(d => d.category === 'drunk-animals').length, designs: rows });
 });
 
+// ── /api/drunk-animals/stats — derived snapshot for the live viewer's
+// "tonight in numbers" bar. Animals are extracted from motifs[] (when
+// llava tagged them) so the bar reflects what actually rendered, not
+// just what was prompted.
+app.get('/api/drunk-animals/stats', (_req, res) => {
+  res.setHeader('Cache-Control', 'no-store');
+  const all = DESIGNS.filter(d => d.category === 'drunk-animals');
+  const total = all.length;
+  if (total === 0) return res.json({ total: 0 });
+  const ANIMAL_TAGS = ['frog','elephant','giraffe','orangutan','capybara','lemur','sloth','macaw','panda','peacock','parrot','monkey'];
+  const animalCounts = {};
+  let satSum = 0, satCount = 0, earliest = null, latest = null;
+  for (const d of all) {
+    const motifs = (d.motifs || []);
+    for (const a of ANIMAL_TAGS) if (motifs.includes(a)) animalCounts[a] = (animalCounts[a] || 0) + 1;
+    if (typeof d.saturation === 'number') { satSum += d.saturation; satCount++; }
+    const t = Date.parse(d.created_at || '');
+    if (t) {
+      if (!earliest || t < earliest) earliest = t;
+      if (!latest || t > latest)   latest = t;
+    }
+  }
+  const animals = Object.entries(animalCounts).sort((a,b)=>b[1]-a[1]);
+  const elapsedMs = (earliest && latest) ? (latest - earliest) : 0;
+  const hoursElapsed = elapsedMs / 3_600_000;
+  const designsPerHour = hoursElapsed > 0.1 ? (total / hoursElapsed) : null;
+  res.json({
+    total,
+    animals,                                    // [['frog',18],['orangutan',14],...]
+    avg_saturation: satCount ? +(satSum/satCount).toFixed(3) : null,
+    earliest_at: earliest ? new Date(earliest).toISOString() : null,
+    latest_at:   latest   ? new Date(latest).toISOString()   : null,
+    designs_per_hour: designsPerHour ? +designsPerHour.toFixed(1) : null
+  });
+});
+
 // ── /drunk-animals/live — auto-polling stream view of the overnight run.
 // New designs slide in at the top every 30s. Dark immersive theme matches
 // the collection's botanical-pop-Warhol aesthetic.
@@ -2236,6 +2272,14 @@ header .pulse::before { content:""; display:inline-block; width:7px; height:7px;
 @keyframes pulse { 0%,100%{opacity:.4} 50%{opacity:1} }
 header a { color:#e6d4f0; text-decoration:none; font-size:12px; padding:4px 10px; border:1px solid var(--line); border-radius:999px }
 header a:hover { border-color:var(--pink); color:var(--pink) }
+.stats-bar { padding:14px 20px; background:rgba(255,255,255,.02); border-bottom:1px solid var(--line); display:flex; gap:16px; flex-wrap:wrap; align-items:center; font-size:12px }
+.stats-bar .stat { display:flex; flex-direction:column; gap:2px; padding:0 14px; border-right:1px solid var(--line) }
+.stats-bar .stat:last-child { border-right:0 }
+.stats-bar .stat-label { font-size:10px; letter-spacing:.18em; text-transform:uppercase; color:#9586a3 }
+.stats-bar .stat-value { font-family:'Playfair Display',serif; font-size:18px; color:#fff }
+.stats-bar .animals { display:flex; gap:6px; flex-wrap:wrap; padding-left:14px }
+.stats-bar .animal-pill { padding:3px 10px; border-radius:999px; background:rgba(255,121,198,.12); color:var(--pink); font-size:11px; white-space:nowrap }
+.stats-bar .animal-pill strong { color:#fff; margin-left:4px; font-weight:600 }
 main { padding:20px; max-width:1500px; margin:0 auto }
 .grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(280px,1fr)); gap:18px }
 .card { background:#1a0f1a; border:1px solid var(--line); border-radius:14px; overflow:hidden; position:relative; opacity:0; transform:translateY(-12px); animation:slidein .5s ease forwards }
@@ -2260,6 +2304,12 @@ main { padding:20px; max-width:1500px; margin:0 auto }
   <a href="/drunk-animals">Grid view</a>
   <a href="/">wallco.ai</a>
 </header>
+<div class="stats-bar" id="statsbar">
+  <div class="stat"><span class="stat-label">Total</span><span class="stat-value" id="s-total">—</span></div>
+  <div class="stat"><span class="stat-label">Per hour</span><span class="stat-value" id="s-rate">—</span></div>
+  <div class="stat"><span class="stat-label">Avg saturation</span><span class="stat-value" id="s-sat">—</span></div>
+  <div class="animals" id="s-animals"></div>
+</div>
 <main>
   <div class="grid" id="grid"></div>
   <div class="empty" id="empty">Loading the menagerie…</div>
@@ -2317,8 +2367,24 @@ function updateNext() {
   nextEl.textContent = 'next in ' + m + ':' + String(s).padStart(2,'0');
 }
 
+async function pollStats() {
+  try {
+    const r = await fetch('/api/drunk-animals/stats');
+    const j = await r.json();
+    if (!j.total) return;
+    document.getElementById('s-total').textContent = j.total;
+    document.getElementById('s-rate').textContent  = j.designs_per_hour != null ? j.designs_per_hour.toFixed(1) : '—';
+    document.getElementById('s-sat').textContent   = j.avg_saturation   != null ? j.avg_saturation.toFixed(2)  : '—';
+    const ani = document.getElementById('s-animals');
+    ani.innerHTML = (j.animals || []).slice(0, 8)
+      .map(([name, n]) => '<span class="animal-pill">' + name + '<strong>' + n + '</strong></span>').join('');
+  } catch (e) { console.warn('stats poll failed', e); }
+}
+
 poll();
+pollStats();
 setInterval(poll, 30_000);
+setInterval(pollStats, 60_000);
 setInterval(updateNext, 1000);
 </script>
 </body></html>`);

← 4dd3794 drunk-animals: bump dedup window 5 to 8  ·  back to Wallco Ai  ·  drunk-animals: 'Best of the night' rail on /live 768a775 →