← 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 →