← back to Rentv
fix(deals): magnitude-aware parseSize — parse '104k sf'/'1.27 msf' so a title's real size (104,000 SF) isn't skipped and replaced by a stray body 'N sf' leak (was rendering 104k-sf buildings as '26 SF')
28068930417f8289d8d0ae1dcffa8f75e0800cc7 · 2026-08-06 07:43:44 -0700 · Steve
Files touched
M scripts/lib/deal-parse.mjsM test/deals/parse.test.mjs
Diff
commit 28068930417f8289d8d0ae1dcffa8f75e0800cc7
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Aug 6 07:43:44 2026 -0700
fix(deals): magnitude-aware parseSize — parse '104k sf'/'1.27 msf' so a title's real size (104,000 SF) isn't skipped and replaced by a stray body 'N sf' leak (was rendering 104k-sf buildings as '26 SF')
---
scripts/lib/deal-parse.mjs | 14 +++++++++++---
test/deals/parse.test.mjs | 8 ++++++++
2 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/scripts/lib/deal-parse.mjs b/scripts/lib/deal-parse.mjs
index 06c74b6c..c06cb28e 100644
--- a/scripts/lib/deal-parse.mjs
+++ b/scripts/lib/deal-parse.mjs
@@ -55,15 +55,23 @@ export function classify(title, body) {
export function parseSize(text) {
const units = text.match(/([\d,]+)[- ]unit/i);
- const sf = text.match(/([\d,]+)\s?(?:sf|sq\.?\s?ft|square[ -]feet|square[ -]foot)/i);
const acres = text.match(/([\d,.]+)[- ]acre/i);
+ // Square footage, MAGNITUDE-AWARE. CRE headlines write the asset size as "104k sf" (thousand) or
+ // "1.27 msf" (million square feet); the old plain "N sf" regex couldn't match those (the number isn't
+ // directly followed by "sf"), so it SKIPPED the title's real size and grabbed a stray small "N sf" from
+ // the body — a 104,000 sf building rendered as "26 SF". Try million, then thousand, then plain: the
+ // magnitude notations live in the title (front of the text) so matching them here wins over a body leak.
+ let sqft = null, m;
+ if ((m = text.match(/([\d,.]+)\s?(?:msf|m\s?sf|million\s?(?:sf|square[ -]feet))/i))) sqft = Math.round(parseFloat(m[1].replace(/,/g, '')) * 1e6);
+ else if ((m = text.match(/([\d,.]+)k\s?(?:sf|sq\.?\s?ft|square[ -]feet|square[ -]foot)/i))) sqft = Math.round(parseFloat(m[1].replace(/,/g, '')) * 1e3);
+ else if ((m = text.match(/([\d,]+)\s?(?:sf|sq\.?\s?ft|square[ -]feet|square[ -]foot)/i))) sqft = +m[1].replace(/,/g, '');
const parts = [];
if (units) parts.push(`${units[1]} units`);
- if (sf) parts.push(`${sf[1]} SF`);
+ if (sqft) parts.push(`${sqft.toLocaleString()} SF`);
if (acres) parts.push(`${acres[1]} acres`);
return { size_label: parts.join(' · ') || null,
units: units ? +units[1].replace(/,/g, '') : null,
- sqft: sf ? +sf[1].replace(/,/g, '') : null };
+ sqft };
}
export function parseAddress(body) {
diff --git a/test/deals/parse.test.mjs b/test/deals/parse.test.mjs
index 8401923a..8c8c156c 100644
--- a/test/deals/parse.test.mjs
+++ b/test/deals/parse.test.mjs
@@ -88,6 +88,14 @@ test('parseSize: units / sf / acres', () => {
assert.equal(acres.size_label, '12.5 acres');
assert.equal(parseSize('nothing measurable').size_label, null);
});
+test('parseSize: magnitude-aware (k = thousand, msf = million) and title-first-wins', () => {
+ assert.equal(parseSize('SFF Realty Buys 104k sf Silicon Valley Office').sqft, 104000);
+ assert.equal(parseSize('CapRock Signs Tenant at 1.27 msf Facility').sqft, 1270000);
+ assert.equal(parseSize('1.1 msf industrial portfolio').sqft, 1100000);
+ // the title's "104k sf" wins over a stray small "26 sf" leaked later in the body
+ assert.equal(parseSize('SFF Buys 104k sf office. The lobby has 26 sf of signage.').sqft, 104000);
+ assert.equal(parseSize('GO builds a 250,000 square feet facility').sqft, 250000);
+});
// ── parseOccupancy / parseYearBuilt ───────────────────────────────────────────
test('parseOccupancy + parseYearBuilt', () => {
← 9300439d harden(deals): Cody gate — add 5 summarize() tests (prod pat
·
back to Rentv
·
harden(deals): Cody gate — parseSize picks the EARLIEST-in-t 2930a208 →