[object Object]

← back to Rentv

fix(location): sanitizeLocation maps a full state NAME to its code instead of blind slice(0,2) — a model returning 'Arizona' was truncated to 'AR' (Arkansas!), 'Nevada'→'NE' (Nebraska!), 'Texas'→'TE' (dropped), silently mis-attributing RENTV's core states. Valid 2-letter codes unchanged; non-US dropped. +6 tests, 59/0

3b60464d7ccd4790451f5a9c1dfbeb289251f200 · 2026-08-06 12:54:43 -0700 · Steve

Files touched

Diff

commit 3b60464d7ccd4790451f5a9c1dfbeb289251f200
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Aug 6 12:54:43 2026 -0700

    fix(location): sanitizeLocation maps a full state NAME to its code instead of blind slice(0,2) — a model returning 'Arizona' was truncated to 'AR' (Arkansas!), 'Nevada'→'NE' (Nebraska!), 'Texas'→'TE' (dropped), silently mis-attributing RENTV's core states. Valid 2-letter codes unchanged; non-US dropped. +6 tests, 59/0
---
 scripts/lib/parse-location.mjs | 11 +++++++++--
 test/deals/parse.test.mjs      | 10 +++++++++-
 2 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/scripts/lib/parse-location.mjs b/scripts/lib/parse-location.mjs
index 083e07ba..91040fab 100644
--- a/scripts/lib/parse-location.mjs
+++ b/scripts/lib/parse-location.mjs
@@ -67,11 +67,18 @@ const TITLE_ST = /\b(CA|AZ|NV|TX|WA)\b/;
 //  • city is rejected only on an EXACT single-word denylist match, so multi-word real
 //    places ("Orange County", "Silicon Valley", "Bay Area") are preserved.
 const US_STATES = new Set(['AL','AK','AZ','AR','CA','CO','CT','DE','FL','GA','HI','ID','IL','IN','IA','KS','KY','LA','ME','MD','MA','MI','MN','MS','MO','MT','NE','NV','NH','NJ','NM','NY','NC','ND','OH','OK','OR','PA','RI','SC','SD','TN','TX','UT','VT','VA','WA','WV','WI','WY','DC']);
+// Full state NAME → code, so a model that ignores "2-letter state" and returns "Arizona" isn't truncated
+// to "AR" (Arkansas!) / "Nevada"→"NE" (Nebraska!) / "Texas"→"TE" (dropped) by a blind slice(0,2).
+const STATE_NAMES = { alabama:'AL', alaska:'AK', arizona:'AZ', arkansas:'AR', california:'CA', colorado:'CO', connecticut:'CT', delaware:'DE', florida:'FL', georgia:'GA', hawaii:'HI', idaho:'ID', illinois:'IL', indiana:'IN', iowa:'IA', kansas:'KS', kentucky:'KY', louisiana:'LA', maine:'ME', maryland:'MD', massachusetts:'MA', michigan:'MI', minnesota:'MN', mississippi:'MS', missouri:'MO', montana:'MT', nebraska:'NE', nevada:'NV', 'new hampshire':'NH', 'new jersey':'NJ', 'new mexico':'NM', 'new york':'NY', 'north carolina':'NC', 'north dakota':'ND', ohio:'OH', oklahoma:'OK', oregon:'OR', pennsylvania:'PA', 'rhode island':'RI', 'south carolina':'SC', 'south dakota':'SD', tennessee:'TN', texas:'TX', utah:'UT', vermont:'VT', virginia:'VA', washington:'WA', 'west virginia':'WV', wisconsin:'WI', wyoming:'WY', 'district of columbia':'DC', 'washington dc':'DC' };
 const BAD_CITY = new Set(['county','valley','beauty','america','fresco','downtown','city','various','region','regional','metro','usa','us','na','unknown','none','tbd','multiple']);
 export function sanitizeLocation(loc = {}) {
   let city = loc.city == null ? null : String(loc.city).trim();
-  let state = loc.state == null ? null : String(loc.state).toUpperCase().trim().slice(0, 2);
-  if (state && !US_STATES.has(state)) state = null;          // country codes / cities-as-states → drop
+  let state = loc.state == null ? null : String(loc.state).trim();
+  if (state) {
+    const up = state.toUpperCase();
+    if (up.length === 2 && US_STATES.has(up)) state = up;            // already a valid 2-letter code
+    else state = STATE_NAMES[state.toLowerCase().replace(/\./g, '')] || null; // full name → code, else drop
+  }
   if (city && BAD_CITY.has(city.toLowerCase())) city = null; // bare non-city token → drop
   return { ...loc, city: city || null, state: state || null };
 }
diff --git a/test/deals/parse.test.mjs b/test/deals/parse.test.mjs
index 2342b130..82d610c5 100644
--- a/test/deals/parse.test.mjs
+++ b/test/deals/parse.test.mjs
@@ -7,7 +7,7 @@ import {
   parseAmount, txnOf, typeOf, classify, parseSize,
   parseAddress, parseOccupancy, parseYearBuilt, summarize,
 } from '../../scripts/lib/deal-parse.mjs';
-import { parseLocation } from '../../scripts/lib/parse-location.mjs';
+import { parseLocation, sanitizeLocation } from '../../scripts/lib/parse-location.mjs';
 
 // ── parseAddress (cycle 28) ───────────────────────────────────────────────────
 test('parseAddress: bare "at" is word-boundary-anchored (no false extraction from prose)', () => {
@@ -202,3 +202,11 @@ test('parseLocation: ambiguous 2-letter tokens (OR/ID/CO) are not treated as sta
   assert.equal(parseLocation('Investor Buys OR Leases Phoenix Industrial Asset', 'the property in Phoenix').state, 'AZ');
   assert.equal(parseLocation('Asset ID 44291 Trades in Phoenix', 'in Phoenix').state, 'AZ');
 });
+test('sanitizeLocation: a full state NAME maps to its code (not slice(0,2) → wrong state)', () => {
+  assert.equal(sanitizeLocation({ city: 'Phoenix', state: 'Arizona' }).state, 'AZ'); // was 'AR' (Arkansas!)
+  assert.equal(sanitizeLocation({ city: 'Las Vegas', state: 'Nevada' }).state, 'NV'); // was 'NE' (Nebraska!)
+  assert.equal(sanitizeLocation({ city: 'Dallas', state: 'Texas' }).state, 'TX');     // was dropped
+  assert.equal(sanitizeLocation({ city: 'x', state: 'AZ' }).state, 'AZ');             // valid code unchanged
+  assert.equal(sanitizeLocation({ city: 'x', state: 'AT' }).state, null);             // non-US 2-letter dropped
+  assert.equal(sanitizeLocation({ city: 'county', state: 'CA' }).city, null);         // denylist city dropped
+});

← d3fe7c22 feat(services): admin-only 'Solicit' → pipes firm into /soci  ·  back to Rentv  ·  auto-data-snapshot: 2026-08-06T12:57:07 (7 data files) — dat 33804967 →