[object Object]

← back to Commercialrealestate

CRCP: snapLeads() fallback for /api/leads/top — homepage broker panel now populates on prod (no cre DB) from snapshot top[]+brokerDetail, incl. created_at

fe2521c16416e5904c663bd043290318f14e71d7 · 2026-08-06 16:33:47 -0700 · Steve Abrams

Files touched

Diff

commit fe2521c16416e5904c663bd043290318f14e71d7
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Aug 6 16:33:47 2026 -0700

    CRCP: snapLeads() fallback for /api/leads/top — homepage broker panel now populates on prod (no cre DB) from snapshot top[]+brokerDetail, incl. created_at
---
 scripts/serve.js | 32 ++++++++++++++++++++++++++++----
 1 file changed, 28 insertions(+), 4 deletions(-)

diff --git a/scripts/serve.js b/scripts/serve.js
index 9c233b5..b4e9bae 100644
--- a/scripts/serve.js
+++ b/scripts/serve.js
@@ -522,9 +522,31 @@ app.get('/api/gov-agents', async (req, res) => {
 // commercial weighted highest). Returns ranked brokers WITH their current listings (MLS-like fields).
 // Business listing data only. No owner names.
 app.get('/api/leads/top', async (req, res) => {
-  if (!brokerdb) return res.json({ leads: [], unavailable: true });
+  const limit = Math.min(+(req.query.limit) || 40, 200);
+  // PROD has no `cre` Postgres → rebuild the top-brokers leads (incl. per-listing rows + created_at)
+  // from the existing snapshot's top[] + brokerDetail[].current, mirroring the DB query's shape.
+  // Without this, the homepage "TOP BROKERS" panel is empty on prod (every sibling endpoint already
+  // has a snap* fallback — this one was the lone gap).
+  const snapLeads = () => {
+    const s = readBrokerSnap(); if (!s) return null;
+    const fitOf = t => { const x = String(t || '').toLowerCase();
+      if (x.includes('mixed') || x.includes('condo')) return 3;
+      if (x.includes('multi')) return 2;
+      if (/retail|office|industrial|commercial/.test(x)) return 2;
+      return 1; };
+    return (s.top || []).slice(0, limit).map(t => {
+      const cur = (s.brokerDetail && s.brokerDetail[t.id] && s.brokerDetail[t.id].current) || [];
+      const listings_json = cur.map(p => ({ id: p.id, address: p.address, city: p.city, zip: p.zip,
+          type: p.type, price: p.price, units: p.units, cap_rate: p.cap_rate, created_at: p.listed_at }))
+        .sort((a, b) => Number(b.price || 0) - Number(a.price || 0));
+      return { broker_id: t.id, name: t.name, firm: t.firm, agent_type: t.agent_type,
+        listings: (t.listings != null ? t.listings : cur.length),
+        fit_score: cur.reduce((n, p) => n + fitOf(p.type), 0),
+        total_value: cur.reduce((n, p) => n + Number(p.price || 0), 0), listings_json };
+    }).sort((a, b) => (b.fit_score - a.fit_score) || (b.total_value - a.total_value));
+  };
+  if (!brokerdb) { const leads = snapLeads(); return res.json(leads ? { leads, source: 'snapshot' } : { leads: [], unavailable: true }); }
   try {
-    const limit = Math.min(+(req.query.limit) || 40, 200);
     const leads = (await brokerdb.pool.query(`
       WITH ll AS (
         SELECT b.id broker_id, b.name, f.name firm, b.agent_type,
@@ -546,8 +568,10 @@ app.get('/api/leads/top', async (req, res) => {
         ORDER BY fit_score DESC, total_value DESC LIMIT $1`, [limit])).rows;
     res.json({ leads, scoredBy: 'Listing mix quality (mixed-use/condo×3, multifamily/commercial×2). Business data; no owner names.' });
   } catch (e) {
-    // Graceful degrade (prod has no `cre` Postgres → pool.query throws): the front-end iterates
-    // d.leads, so an empty-array 200 renders "no leads" cleanly instead of a 502 console error.
+    // Graceful degrade (prod has no `cre` Postgres → pool.query throws): fall back to the
+    // snapshot-built leads so the panel populates instead of showing empty.
+    const leads = snapLeads();
+    if (leads && leads.length) return res.json({ leads, source: 'snapshot', degraded: String(e.message).split('\n')[0] });
     res.json({ leads: [], unavailable: true, reason: String(e.message).split('\n')[0] });
   }
 });

← d30373f enrich-property: fix Cody-verified field bugs + honest label  ·  back to Commercialrealestate  ·  CRCP: snapLeads rank-then-slice — rank full snapshot pool by fccb4e7 →