← back to Rentv
feat(deals): parseAcquirer recall 19→23/30 — handle development announcements (dev verbs: starts work on / breaks ground / underway → GO Industrial, Meta Housing) + passive voice ('Acquired by <Firm>' / 'sold to <Firm>' → WareSpace, Blackstone). Still zero asset-as-firm false positives (asset/place-fronted guard unchanged). +5 test cases, 63/0.
6190cac56c833658b0acd41603d2284e8e252b72 · 2026-08-06 15:51:51 -0700 · Steve
Files touched
M scripts/lib/deal-parse.mjsM test/deals/parse.test.mjs
Diff
commit 6190cac56c833658b0acd41603d2284e8e252b72
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Aug 6 15:51:51 2026 -0700
feat(deals): parseAcquirer recall 19→23/30 — handle development announcements (dev verbs: starts work on / breaks ground / underway → GO Industrial, Meta Housing) + passive voice ('Acquired by <Firm>' / 'sold to <Firm>' → WareSpace, Blackstone). Still zero asset-as-firm false positives (asset/place-fronted guard unchanged). +5 test cases, 63/0.
---
scripts/lib/deal-parse.mjs | 10 ++++++++--
test/deals/parse.test.mjs | 10 ++++++++--
2 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/scripts/lib/deal-parse.mjs b/scripts/lib/deal-parse.mjs
index ae3a4a0a..359cd7ea 100644
--- a/scripts/lib/deal-parse.mjs
+++ b/scripts/lib/deal-parse.mjs
@@ -132,14 +132,20 @@ export function parseYearBuilt(body) { const m = body.match(/\bbuilt in (\d{4})/
// ("Gilbert, AZ … Refinanced …") as a firm — a broker/firm profile page keyed on an asset description
// would be wrong. Passive voice ("X Acquired by Y" — no active verb match) and unusual verbs stay null
// (documented follow-on). Verified 19/30 clean on the live corpus with zero asset-as-firm false positives.
-const TXN_VERB = /\b(acquires?|buys?|closes?(?: on)?|secures?|sells?|trades?|snaps up|picks up|lands|nabs?|fetches?|pays?|spends?|scores?|obtains?|refinances?|purchases?|recapitalizes?|signs?|inks?|leases?)\b/i;
+const TXN_VERB = /\b(acquires?|buys?|closes?(?: on)?|secures?|sells?|trades?|snaps up|picks up|lands|nabs?|fetches?|pays?|spends?|scores?|obtains?|refinances?|purchases?|recapitalizes?|signs?|inks?|leases?|starts? work on|breaks? ground(?: on)?|tops? out|delivers?|(?:well )?underway)\b/i;
+// Passive: the acquiring party trails the verb ("Acquired by <Firm>", "sold to <Firm>").
+const PASSIVE = /\b(?:[Aa]cquired|[Pp]urchased|[Bb]ought)\s+by\s+([A-Z][\w&.'-]*(?:\s+[A-Z][\w&.'-]*){0,4})|\b[Ss]old\s+to\s+([A-Z][\w&.'-]*(?:\s+[A-Z][\w&.'-]*){0,4})/;
// "hard" asset/place nouns that essentially never appear inside a real firm name, plus a singular
// "… Property" suffix (asset descriptor — distinct from the plural "… Properties" that IS a firm name).
const ASSET_NOUN = /\b(campus|facility|portfolio|building|complex|community|centers?|centres?|plaza|mall|warehouse|distribution|site)\b|\bproperty$/i;
export function parseAcquirer(title) {
const s = String(title || '');
const m = s.match(TXN_VERB);
- if (!m) return null;
+ if (!m) { // no active verb → try passive "Acquired by <Firm>" / "sold to <Firm>"
+ const pm = s.match(PASSIVE);
+ if (pm) { const pc = (pm[1] || pm[2] || '').trim(); if (pc.length >= 2 && pc.length <= 48 && !ASSET_NOUN.test(pc)) return pc; }
+ return null;
+ }
const c = s.slice(0, m.index).trim().replace(/[,\s]+$/, '');
if (c.length < 2 || c.length > 48) return null; // too short/long to be a clean firm token
if (ASSET_NOUN.test(c)) return null; // asset-fronted → the subject is the asset, not a party
diff --git a/test/deals/parse.test.mjs b/test/deals/parse.test.mjs
index f4a1cfcb..7e27c0a9 100644
--- a/test/deals/parse.test.mjs
+++ b/test/deals/parse.test.mjs
@@ -172,13 +172,19 @@ test('parseAcquirer: extracts the leading firm before the transaction verb', ()
assert.equal(parseAcquirer('PCCP and Stonemont Acquire 5.9 msf Industrial Portfolio'), 'PCCP and Stonemont');
assert.equal(parseAcquirer('MG Properties Buys 188-Unit Portland Res Asset'), 'MG Properties'); // plural "Properties" = firm
assert.equal(parseAcquirer('Camden Property Trust Buys Gilbert, AZ Res Community in $124 Mil Deal'), 'Camden Property Trust');
+ // development announcements (the developer fronts the title)
+ assert.equal(parseAcquirer('GO Industrial Starts Work on 1.1 msf Industrial Facility in Phoenix'), 'GO Industrial');
+ assert.equal(parseAcquirer('Meta Housing Well Underway with 300-Unit Res Community in Woodland Hills'), 'Meta Housing');
+ // passive voice — the acquiring party trails the verb
+ assert.equal(parseAcquirer('Carson Distribution Facility Acquired by WareSpace in a $23 Mil Deal'), 'WareSpace');
+ assert.equal(parseAcquirer('Downtown Tower Sold to Blackstone for $200 Mil'), 'Blackstone');
});
test('parseAcquirer: null on ASSET-fronted / place-fronted / passive titles (never mislabels an asset as a firm)', () => {
assert.equal(parseAcquirer('Santa Monica Multifamily Property Fetches $928k per Unit'), null); // singular "Property" = asset
assert.equal(parseAcquirer('Washington Industrial Facility Sells in $174 Mil Transaction'), null); // "Facility"
assert.equal(parseAcquirer('Retail Portfolio in Downtown Long Beach Fetches $30 Mil'), null); // "Portfolio"
- assert.equal(parseAcquirer('Gilbert, AZ Multifamily Community Refinanced with New Loan'), null); // "Community" + place-fronted
- assert.equal(parseAcquirer('Carson Distribution Facility Acquired by WareSpace in a $23 Mil Deal'), null); // passive, no active verb
+ assert.equal(parseAcquirer('Gilbert, AZ Multifamily Community Refinanced with New Loan'), null); // "Community" + place-fronted, no named party
+ assert.equal(parseAcquirer('Oxnard Independent Living Facility Lands New Owner'), null); // asset-fronted, buyer not named
assert.equal(parseAcquirer(''), null);
assert.equal(parseAcquirer(null), null);
});
← e393d66a feat(services): DTD-verdict guardrails on the Featured layer
·
back to Rentv
·
feat(services): CSV export + per-view SEO/JSON-LD + provider 8e0541c9 →