[object Object]

← back to Ventura Corridor

iter 61: building prioritization 'walk here next' β€” /api/buildings/priority?max_miles=&min_unpitched=&limit= ranks unwalked-but-walkable buildings by priority_score = unpitched Γ— (1/miles), proximity boost so close-by small buildings beat distant towers; /buildings.html gains a hero priority section above the main list with 15-card grid (top 15 by default, radius selector 0.25/0.5/1.0/2.0 mi), each card shows order # + address + city + miles + unpitched count + score + 🚢 walk + 🏒 roster shortcuts; surfaces next-action queue (top 5 today: 15821=556, 15260=292, 15456=38, 15303=172, 15300=123 unpitched doors)

40a7c216f855e0dfac1e8f57e554633567bfc044 Β· 2026-05-06 14:11:52 -0700 Β· SteveStudio2

Files touched

Diff

commit 40a7c216f855e0dfac1e8f57e554633567bfc044
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Wed May 6 14:11:52 2026 -0700

    iter 61: building prioritization 'walk here next' β€” /api/buildings/priority?max_miles=&min_unpitched=&limit= ranks unwalked-but-walkable buildings by priority_score = unpitched Γ— (1/miles), proximity boost so close-by small buildings beat distant towers; /buildings.html gains a hero priority section above the main list with 15-card grid (top 15 by default, radius selector 0.25/0.5/1.0/2.0 mi), each card shows order # + address + city + miles + unpitched count + score + 🚢 walk + 🏒 roster shortcuts; surfaces next-action queue (top 5 today: 15821=556, 15260=292, 15456=38, 15303=172, 15300=123 unpitched doors)
---
 public/buildings.html | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/server/index.ts   | 37 ++++++++++++++++++++++++++++++
 2 files changed, 100 insertions(+)

diff --git a/public/buildings.html b/public/buildings.html
index 380632a..6bb54be 100644
--- a/public/buildings.html
+++ b/public/buildings.html
@@ -174,6 +174,27 @@
   </div>
 </div>
 
+<section id="priority-section" style="padding:24px 32px;border-bottom:1px solid var(--rule);background:radial-gradient(ellipse at top right, rgba(212,182,131,0.06), transparent 70%)">
+  <div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:14px;flex-wrap:wrap;gap:12px">
+    <h2 style="font-family:var(--serif);font-style:italic;font-weight:400;font-size:24px;color:var(--metal-glow);margin:0">Walk here next</h2>
+    <div style="display:flex;gap:14px;align-items:baseline;font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-mute)">
+      <span>radius
+        <select id="prio-radius" style="background:var(--noir);border:1px solid var(--rule);color:var(--ink);padding:3px 8px;font-size:11px">
+          <option value="0.25">0.25 mi</option>
+          <option value="0.5" selected>0.5 mi</option>
+          <option value="1.0">1.0 mi</option>
+          <option value="2.0">2.0 mi</option>
+        </select>
+      </span>
+      <span id="prio-summary">β€”</span>
+    </div>
+  </div>
+  <div id="priority-list" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:10px"></div>
+  <div style="font-size:10px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-mute);margin-top:10px">
+    score = unpitched doors Γ— (1 / miles from DW) Β· clears once Walked > 0
+  </div>
+</section>
+
 <main id="list">
   <div class="loading">Loading building roster…</div>
 </main>
@@ -411,6 +432,48 @@ async function refreshDupes() {
 
 loadDupeTotal();
 
+// ─── Priority "walk here next" section (iter 61) ─────────────────────
+async function loadPriority() {
+  const radius = document.getElementById('prio-radius').value;
+  const list = document.getElementById('priority-list');
+  list.innerHTML = `<div style="color:var(--ink-mute);padding:14px;font-style:italic">scanning corridor…</div>`;
+  try {
+    const data = await fetch(`/api/buildings/priority?max_miles=${radius}&min_unpitched=2&limit=15`).then(r => r.json());
+    const rows = data.rows || [];
+    document.getElementById('prio-summary').innerHTML =
+      `<b style="color:var(--metal-glow);font-family:var(--mono)">${data.count}</b> bldgs Β· <b style="color:var(--metal);font-family:var(--mono)">${data.total_unpitched_in_view}</b> unpitched doors`;
+    if (!rows.length) {
+      list.innerHTML = `<div style="color:var(--ink-mute);padding:14px;font-style:italic">No unwalked walkable buildings β€” all priority targets done. Widen the radius?</div>`;
+      return;
+    }
+    list.innerHTML = rows.map((b, i) => {
+      const isDW = (b.bldg_address || '').startsWith('15442 ');
+      return `
+      <div style="background:var(--noir-rise);border:1px solid var(--rule);${isDW ? 'border-color:var(--metal-glow);' : ''}padding:12px 14px;display:grid;grid-template-rows:auto auto auto;gap:6px">
+        <div style="display:flex;align-items:baseline;gap:10px">
+          <span style="font-family:var(--serif);font-style:italic;font-size:24px;color:var(--metal);min-width:30px">${String(i+1).padStart(2,'0')}</span>
+          <a href="/buildings.html?bldg=${encodeURIComponent(b.bldg_address)}" style="font-family:var(--serif);font-size:18px;color:var(--ink);text-decoration:none">${escapeHtml(b.bldg_address)}${isDW ? ' ⚑' : ''}</a>
+        </div>
+        <div style="display:flex;gap:14px;font-size:10px;letter-spacing:.15em;color:var(--ink-mute);text-transform:uppercase">
+          <span>${b.city || ''}</span>
+          <span style="color:var(--metal-glow);font-family:var(--mono)">${b.dw_miles} mi</span>
+          <span><b style="color:var(--metal);font-family:var(--mono);font-size:13px">${b.unpitched}</b> unpitched</span>
+          <span style="color:var(--ink-mute)">/ ${b.tenants_total} total</span>
+        </div>
+        <div style="display:flex;gap:8px;font-size:9px;letter-spacing:.18em;text-transform:uppercase">
+          <span style="color:var(--metal-glow);font-family:var(--mono);background:rgba(212,182,131,0.06);padding:2px 8px">score ${b.priority_score}</span>
+          <a href="/walk-route.html?focus=${b.bldg_address.split(' ')[0]}" target="_blank" style="color:var(--metal);text-decoration:none;padding:2px 8px;border:1px solid var(--rule)">🚢 walk</a>
+          <a href="/buildings.html?bldg=${encodeURIComponent(b.bldg_address)}" target="_blank" style="color:var(--metal);text-decoration:none;padding:2px 8px;border:1px solid var(--rule)">🏒 roster</a>
+        </div>
+      </div>`;
+    }).join('');
+  } catch (e) {
+    list.innerHTML = `<div style="color:var(--red);padding:14px">priority load failed: ${e.message}</div>`;
+  }
+}
+document.getElementById('prio-radius').addEventListener('change', loadPriority);
+loadPriority();
+
 // Deep-link via ?bldg= URL param: pre-filter + auto-expand the matching card
 (async () => {
   const urlBldg = new URLSearchParams(location.search).get('bldg');
diff --git a/src/server/index.ts b/src/server/index.ts
index 72546b9..85ba2fe 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1143,6 +1143,43 @@ app.get('/api/buildings', async (req, res) => {
   }
 });
 
+// Priority list: buildings ranked by where Steve gets the most walkable doors per minute of walking time.
+// Score = unpitched_count Γ— (1 / max(dw_miles, 0.05))   (proximity boost; closer = higher)
+// Filters: walking distance only, hasn't been walked yet, has unpitched doors.
+app.get('/api/buildings/priority', async (req, res) => {
+  try {
+    const limit = Math.min(parseInt(String(req.query.limit ?? '50'), 10) || 50, 200);
+    const maxMiles = parseFloat(String(req.query.max_miles ?? '1.0'));
+    const minUnpitched = parseInt(String(req.query.min_unpitched ?? '2'), 10) || 2;
+    const r = await query(
+      `SELECT bldg_address, city, zip, lat, lng,
+              tenants_total, pitched, walked, unpitched, replied, won, dw_miles, dominant_pitch_type,
+              ROUND(
+                (unpitched::numeric / GREATEST(dw_miles, 0.05)::numeric)::numeric,
+                1
+              ) AS priority_score
+       FROM v_building_roster
+       WHERE dw_miles IS NOT NULL
+         AND dw_miles <= $1
+         AND walked = 0
+         AND unpitched >= $2
+       ORDER BY priority_score DESC, dw_miles ASC
+       LIMIT $3`,
+      [maxMiles, minUnpitched, limit]
+    );
+    const totalUnpitched = r.rows.reduce((a: number, x: any) => a + Number(x.unpitched || 0), 0);
+    res.json({
+      count: r.rowCount,
+      total_unpitched_in_view: totalUnpitched,
+      max_miles: maxMiles,
+      min_unpitched: minUnpitched,
+      rows: r.rows
+    });
+  } catch (e: any) {
+    res.status(500).json({ error: e.message });
+  }
+});
+
 app.get('/api/buildings/dupe-stats', async (_req, res) => {
   try {
     const r = await query(`

← 6dbbf08 iter 60: recent activity feed on /today.html β€” /api/activity  Β·  back to Ventura Corridor  Β·  iter 62: walk-route priority-aware ordering β€” /api/pitches/w 31c4243 β†’