← back to Homesonspec
fulton: parse bed/bath RANGES correctly (contrarian fix) — recovers 36 dropped homes
e3c33f7b11f9fef31c42439e180c6faecdc06b56 · 2026-07-28 22:25:57 -0700 · Steve Abrams
Cody's gate caught 36 real AZ homes (valid price+sqft) being validation-rejected as '34/56/57-
bedroom' homes. Root cause: posNum() stripped the dash from flex-bedroom ranges like '3-4' -> '34'.
Added firstNum() to take the base value of a range. Verified: Fulton 166 -> 202 homes, beds now
3-6, 0 rejects (was 36). Also killed dead code (Math.min(1,PAGE_LIMIT) always=1).
Files touched
M collectors/fulton-homes/src/index.ts
Diff
commit e3c33f7b11f9fef31c42439e180c6faecdc06b56
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jul 28 22:25:57 2026 -0700
fulton: parse bed/bath RANGES correctly (contrarian fix) — recovers 36 dropped homes
Cody's gate caught 36 real AZ homes (valid price+sqft) being validation-rejected as '34/56/57-
bedroom' homes. Root cause: posNum() stripped the dash from flex-bedroom ranges like '3-4' -> '34'.
Added firstNum() to take the base value of a range. Verified: Fulton 166 -> 202 homes, beds now
3-6, 0 rejects (was 36). Also killed dead code (Math.min(1,PAGE_LIMIT) always=1).
---
collectors/fulton-homes/src/index.ts | 20 +++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/collectors/fulton-homes/src/index.ts b/collectors/fulton-homes/src/index.ts
index af071582..424eebe1 100644
--- a/collectors/fulton-homes/src/index.ts
+++ b/collectors/fulton-homes/src/index.ts
@@ -87,6 +87,15 @@ const posNum = (v: unknown): number | null => {
const n = typeof v === "number" ? v : typeof v === "string" ? Number(String(v).replace(/[^0-9.]/g, "")) : NaN;
return Number.isFinite(n) && n > 0 ? n : null;
};
+// First positive number in a string, or null. Handles bed/bath RANGES ("3-4", "5 - 6")
+// by taking the base (first) value — posNum() would strip the dash and read "3-4" as 34,
+// producing bogus 34-bedroom homes the validator rejects (contrarian fix, verified 2026-07-28).
+const firstNum = (v: unknown): number | null => {
+ const m = String(v ?? "").match(/\d+(?:\.\d+)?/);
+ if (!m) return null;
+ const n = Number(m[0]);
+ return Number.isFinite(n) && n > 0 ? n : null;
+};
const str = (v: unknown): string | null => {
if (v == null) return null;
const s = String(v).trim();
@@ -191,8 +200,8 @@ function parseSpecRows(html: string): SpecRow[] {
price,
available: price !== null,
status,
- beds: posNum(text(bedsC ?? "")),
- baths: posNum(text(bathC ?? "")),
+ beds: firstNum(text(bedsC ?? "")),
+ baths: firstNum(text(bathC ?? "")),
});
}
return rows;
@@ -216,9 +225,10 @@ export const fultonHomesAdapter: SourceAdapter = {
return;
}
const fetcher = new LiveFetcher(ctx.registry);
- // The GetSpecs feed is single-call (returns the whole statewide inventory);
- // FULTON_PAGE_LIMIT is a safety cap, so we fetch at most one page.
- for (let pageNo = 0; pageNo < Math.max(1, Math.min(1, PAGE_LIMIT)); pageNo++) {
+ // The GetSpecs feed is single-call (returns the whole statewide inventory in one response),
+ // so there is exactly one page regardless of FULTON_PAGE_LIMIT (which stays for interface
+ // parity with the other adapters). Fetch it once. [was Math.min(1,PAGE_LIMIT) dead code]
+ for (let pageNo = 0; pageNo < 1; pageNo++) {
let page: RawPage;
try {
page = await fetcher.fetch(GETSPECS_URL);
← d9a0af8c ashton-woods: tighten image blocklist + purge residual pollu
·
back to Homesonspec
·
geocode: Census-batch enrichment for no-geo homes (yolo iter 139ce339 →