[object Object]

← back to Commercialrealestate

Tighten live-comps units parse

5b97a7703aa2e579f049c5835c2a97e1536d932f · 2026-06-21 17:52:46 -0700 · Steve

- anchor units regex (\b, 1-3 digits, cap 500) so a 5-digit zip can't be read as a unit count
- prefer Crexi structured unit fields over description text
- $/unit plausibility gate: suppress implausibly-low-per-unit counts (junk source data)
- verified live: bogus '91 units @ $35k/unit' now suppressed; legit counts retained

Files touched

Diff

commit 5b97a7703aa2e579f049c5835c2a97e1536d932f
Author: Steve <steve@designerwallcoverings.com>
Date:   Sun Jun 21 17:52:46 2026 -0700

    Tighten live-comps units parse
    
    - anchor units regex (\b, 1-3 digits, cap 500) so a 5-digit zip can't be read as a unit count
    - prefer Crexi structured unit fields over description text
    - $/unit plausibility gate: suppress implausibly-low-per-unit counts (junk source data)
    - verified live: bogus '91 units @ $35k/unit' now suppressed; legit counts retained
---
 scripts/live-comps-bb.js | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/scripts/live-comps-bb.js b/scripts/live-comps-bb.js
index f3e1b24..aab5230 100644
--- a/scripts/live-comps-bb.js
+++ b/scripts/live-comps-bb.js
@@ -44,7 +44,18 @@ const URL = `https://www.crexi.com/properties/CA/${citySlug}/${SLUG}`;
               url: a.urlSlug ? 'https://www.crexi.com/properties/' + id + '/' + a.urlSlug : 'https://www.crexi.com/properties/' + id,
               price: '$' + Number(price).toLocaleString(),
               cap: a.capRate ? a.capRate + '% Cap' : null,
-              units: (String(a.description || '').match(/(\d+)\s*Units?/i) || [])[0] || null,
+              units: (() => {
+                let n = null;
+                for (const k of ['numberOfUnits', 'units', 'unitCount', 'numUnits', 'totalUnits']) {
+                  if (typeof a[k] === 'number' && a[k] >= 1 && a[k] <= 500) { n = a[k]; break; }
+                }
+                // Crexi description format: "Multifamily | 4 Units" — anchored, capped, never swallows a zip
+                if (n == null) { const m = String(a.description || '').match(/(?:\|\s*)?\b(\d{1,3})\s*units?\b/i); if (m) n = +m[1]; }
+                if (!n || n < 1 || n > 500) return null;
+                // Plausibility gate: an implausibly low $/unit means the source count is junk -> suppress it.
+                if (price && price / n < 50000) return null;
+                return n + (n === 1 ? ' Unit' : ' Units');
+              })(),
               sf: null, city: loc.city, state: (loc.state && loc.state.code) || 'CA'
             });
           }
@@ -73,8 +84,8 @@ const URL = `https://www.crexi.com/properties/CA/${citySlug}/${SLUG}`;
           title: (a.innerText || '').replace(/\s+/g, ' ').trim().slice(0, 80) || (href.split('/properties/')[1] || href).slice(0, 50),
           url: href, price,
           cap: (txt.match(/(\d+\.?\d*)%\s*Cap/i) || [])[0] || null,
-          units: (txt.match(/(\d+)\s*Unit/i) || [])[0] || null,
-          sf: (txt.match(/([\d,]+)\s*SF/i) || [])[0] || null
+          units: (txt.match(/\b(\d{1,3})\s*units?\b/i) || [])[0] || null,
+          sf: (txt.match(/\b([\d,]{2,7})\s*SF\b/i) || [])[0] || null
         });
       }
       return { comps: res.slice(0, 8), challenge, anchorCount: anchors.length, title: document.title };

← 89f1ab2 Live comps -> Browserbase/Crexi (drop Exa dependency)  ·  back to Commercialrealestate  ·  Add daily morning-report generator (top 10 SFV CRE opportuni de42fa5 →