[object Object]

← back to Rentv 2026

fix(deals): parseAmount skips per-unit/per-sf figures so a derived price isn't shown as the deal total ('$153/sf' office campus → no bogus $153; '$928k per unit' → not the price); walks all $ to find the real total

2765e5d0f60080e4d7db3d424fda1a653fba8862 · 2026-08-06 08:37:48 -0700 · Steve

Files touched

Diff

commit 2765e5d0f60080e4d7db3d424fda1a653fba8862
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Aug 6 08:37:48 2026 -0700

    fix(deals): parseAmount skips per-unit/per-sf figures so a derived price isn't shown as the deal total ('$153/sf' office campus → no bogus $153; '$928k per unit' → not the price); walks all $ to find the real total
---
 scripts/lib/deal-parse.mjs | 24 ++++++++++++++++--------
 test/deals/parse.test.mjs  |  8 ++++++++
 2 files changed, 24 insertions(+), 8 deletions(-)

diff --git a/scripts/lib/deal-parse.mjs b/scripts/lib/deal-parse.mjs
index 27bb1c14..3e3df113 100644
--- a/scripts/lib/deal-parse.mjs
+++ b/scripts/lib/deal-parse.mjs
@@ -6,15 +6,23 @@
 //
 // Every function is honest-empty: a field it can't confidently find returns null, never a guess.
 
-// $51.8 mil / $1.2 bil / $985,000 → normalized number of dollars
+// $51.8 mil / $1.2 bil / $985,000 → normalized number of dollars. Walks EVERY "$…" in the text and
+// returns the first that is a transaction TOTAL — skipping a per-unit/per-sf figure ("$153/sf",
+// "$928k per unit"), which is a derived metric, not the deal price (grabbing it showed an office campus
+// as "$153"). If a title states only a per-X price, the total is unknown → null (honest-empty).
 export function parseAmount(text) {
-  let m = text.match(/\$\s?([\d,]+(?:\.\d+)?)\s?(bil|billion|mil|million|k)?/i);
-  if (!m) return { amount: null, amount_label: null };
-  let n = parseFloat(m[1].replace(/,/g, ''));
-  const unit = (m[2] || '').toLowerCase();
-  if (/bil/.test(unit)) n *= 1e9; else if (/mil/.test(unit)) n *= 1e6; else if (unit === 'k') n *= 1e3;
-  const label = n >= 1e9 ? `$${(n / 1e9).toFixed(2)}B` : n >= 1e6 ? `$${(n / 1e6).toFixed(1)}M` : `$${n.toLocaleString()}`;
-  return { amount: Math.round(n), amount_label: label };
+  const re = /\$\s?([\d,]+(?:\.\d+)?)\s?(bil|billion|mil|million|k)?/gi;
+  let m;
+  while ((m = re.exec(text)) !== null) {
+    const after = text.slice(m.index + m[0].length);
+    if (/^\s*(?:\/|per\s+)(?:sf|s\.f\.|square\s*(?:foot|feet)|unit|door|key|bed|pad|acre)\b/i.test(after)) continue;
+    let n = parseFloat(m[1].replace(/,/g, ''));
+    const unit = (m[2] || '').toLowerCase();
+    if (/bil/.test(unit)) n *= 1e9; else if (/mil/.test(unit)) n *= 1e6; else if (unit === 'k') n *= 1e3;
+    const label = n >= 1e9 ? `$${(n / 1e9).toFixed(2)}B` : n >= 1e6 ? `$${(n / 1e6).toFixed(1)}M` : `$${n.toLocaleString()}`;
+    return { amount: Math.round(n), amount_label: label };
+  }
+  return { amount: null, amount_label: null };
 }
 
 // Classify from the TITLE first (it states the deal precisely); only fall back to
diff --git a/test/deals/parse.test.mjs b/test/deals/parse.test.mjs
index 77f58493..dd3a2cf3 100644
--- a/test/deals/parse.test.mjs
+++ b/test/deals/parse.test.mjs
@@ -79,6 +79,14 @@ test('parseAmount: units, commas, and label formatting', () => {
   assert.deepEqual(parseAmount('$500K'), { amount: 500000, amount_label: '$500,000' });
   assert.deepEqual(parseAmount('no dollars here'), { amount: null, amount_label: null });
 });
+test('parseAmount: a per-unit / per-sf figure is NOT the deal total', () => {
+  // these titles state only a derived price → no total → null (not a bogus $153 / $928k)
+  assert.deepEqual(parseAmount('Office Campus Trades for $153/sf'), { amount: null, amount_label: null });
+  assert.deepEqual(parseAmount('Multifamily Property Fetches $928k per Unit'), { amount: null, amount_label: null });
+  assert.deepEqual(parseAmount('Sale Works Out to $177k per Unit'), { amount: null, amount_label: null });
+  // when BOTH a total and a per-X are present, the TOTAL wins
+  assert.equal(parseAmount('Sold for $50 Mil ($928k per unit)').amount, 50000000);
+});
 
 // ── parseSize ─────────────────────────────────────────────────────────────────
 test('parseSize: units / sf / acres', () => {

← 65458b63 Refresh RENTV wordmark → 2026 liquid-glass logo from rentv.c  ·  back to Rentv 2026  ·  Add public geolocated news map (/map): green<3mo / red older aa6f0b5e →