[object Object]

← back to Commercialrealestate

CRE Job B Tier 3: filter data-aggregator emails (signalhire/rocketreach/zoominfo/apollo/etc) — keep only real broker/firm contacts; scrub 1 false-positive

c5507a7fdf5925e23e5e22b4018fee9de9190dd4 · 2026-06-28 07:23:12 -0700 · steve@designerwallcoverings.com

Files touched

Diff

commit c5507a7fdf5925e23e5e22b4018fee9de9190dd4
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date:   Sun Jun 28 07:23:12 2026 -0700

    CRE Job B Tier 3: filter data-aggregator emails (signalhire/rocketreach/zoominfo/apollo/etc) — keep only real broker/firm contacts; scrub 1 false-positive
---
 scripts/enrich-tier3-search.js | 3 ++-
 scripts/serve.js               | 8 +++++++-
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/scripts/enrich-tier3-search.js b/scripts/enrich-tier3-search.js
index e4f708f..df2e6f7 100644
--- a/scripts/enrich-tier3-search.js
+++ b/scripts/enrich-tier3-search.js
@@ -28,7 +28,8 @@ const TIER3_MAX = process.env.CC_TIER3_MAX ? +process.env.CC_TIER3_MAX : Infinit
 
 const EMAIL_RE = /[a-z0-9](?:[a-z0-9._%+-]{0,40})@(?:[a-z0-9-]+\.)+[a-z]{2,8}/gi;
 const PHONE_RE = /(?:\+?1[\s.-]?)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}/g;
-const BAD_EMAIL = /\.(png|jpg|jpeg|gif|webp|svg|css|js)$|@(?:sentry|wix|example|email|domain|2x|godaddy|sentry)\b/i;
+// Exclude data-aggregator / lead-broker domains (their OWN contact, not the real broker) + asset noise.
+const BAD_EMAIL = /\.(png|jpg|jpeg|gif|webp|svg|css|js)$|@(?:sentry|wix|example|email|domain|2x|godaddy)\b|@(?:signalhire|rocketreach|zoominfo|apollo|lusha|hunter|leadiq|contactout|usphonebook|spokeo|beenverified|truepeoplesearch|fastpeoplesearch|whitepages|radaris)\./i;
 
 async function braveSearch(q) {
   const ctrl = new AbortController(); const t = setTimeout(() => ctrl.abort(), 12000);
diff --git a/scripts/serve.js b/scripts/serve.js
index 63bceef..8e0c5b1 100644
--- a/scripts/serve.js
+++ b/scripts/serve.js
@@ -204,7 +204,13 @@ app.get('/api/crcp/stats', async (req, res) => {
       try { out.brokers_web = (await q(`SELECT count(*)::int n FROM broker WHERE website IS NOT NULL`))[0].n; } catch (_) { out.brokers_web = null; }
       try { out.condos = (await q(`SELECT count(*)::int n FROM condo`))[0].n;
             out.condosByStatus = {}; (await q(`SELECT warrantable_status s,count(*)::int n FROM condo GROUP BY 1`)).forEach(r => out.condosByStatus[r.s] = r.n); } catch (_) { out.condos = 0; }
-      out.topBrokers = await q(`SELECT name, firm, listings, total_assets, phone, email FROM broker_node ORDER BY listings DESC NULLS LAST LIMIT 12`);
+      out.topBrokers = await q(`SELECT b.name, f.name firm, count(DISTINCT bl.listing_id)::int listings, b.total_assets, b.phone, b.email,
+        CASE WHEN to_regclass('public.broker') IS NOT NULL THEN NULL END AS _x, b.website
+        FROM broker b LEFT JOIN firm f ON f.id=b.firm_id LEFT JOIN broker_listing bl ON bl.broker_id=b.id
+        GROUP BY b.id, f.name ORDER BY listings DESC NULLS LAST LIMIT 20`).catch(async()=>
+        q(`SELECT b.name, f.name firm, count(DISTINCT bl.listing_id)::int listings, b.total_assets, b.phone, b.email
+           FROM broker b LEFT JOIN firm f ON f.id=b.firm_id LEFT JOIN broker_listing bl ON bl.broker_id=b.id
+           GROUP BY b.id, f.name ORDER BY listings DESC NULLS LAST LIMIT 20`));
       out.topFirms = await q(`SELECT f.name firm, count(DISTINCT bl.listing_id)::int listings, count(DISTINCT b.id)::int brokers FROM firm f JOIN broker b ON b.firm_id=f.id JOIN broker_listing bl ON bl.broker_id=b.id GROUP BY 1 ORDER BY 2 DESC LIMIT 12`);
     }
     res.json(out);

← 63ed0e2 CRE Job B Tier 3: Brave-search enrichment (~$0.004/q) for br  ·  back to Commercialrealestate  ·  CRCP: broker contact columns (phone/email/web) + click-throu d7ca649 →