← back to Nationalrealestate
auto-save: 2026-07-26T17:14:46 (2 files) — src/ingest/parcels/engine.ts src/ingest/parcels/saltlake_ut.ts
6e08a2bff0115f2c40650786d1c356b66f145190 · 2026-07-26 17:14:47 -0700 · Steve Abrams
Files touched
M src/ingest/parcels/engine.tsA src/ingest/parcels/saltlake_ut.ts
Diff
commit 6e08a2bff0115f2c40650786d1c356b66f145190
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Jul 26 17:14:47 2026 -0700
auto-save: 2026-07-26T17:14:46 (2 files) — src/ingest/parcels/engine.ts src/ingest/parcels/saltlake_ut.ts
---
src/ingest/parcels/engine.ts | 1 +
src/ingest/parcels/saltlake_ut.ts | 126 ++++++++++++++++++++++++++++++++++++++
2 files changed, 127 insertions(+)
diff --git a/src/ingest/parcels/engine.ts b/src/ingest/parcels/engine.ts
index db3b2e2..9d77b8b 100644
--- a/src/ingest/parcels/engine.ts
+++ b/src/ingest/parcels/engine.ts
@@ -14,6 +14,7 @@ const ADAPTERS: Record<string, () => Promise<{ run: () => Promise<{ upserted: nu
franklin: async () => ({ run: (await import('./franklin_oh.ts')).ingestFranklin }),
miami: async () => ({ run: (await import('./miami_dade.ts')).ingestMiami }),
wake: async () => ({ run: (await import('./wake_nc.ts')).ingestWake }),
+ saltlake: async () => ({ run: (await import('./saltlake_ut.ts')).ingestSaltLake }),
sandiego: async () => { const m = await import('./sandiego_ca.ts'); return { run: () => m.ingestSanDiego('poway') }; },
'sandiego-county': async () => { const m = await import('./sandiego_ca.ts'); return { run: () => m.ingestSanDiego('county') }; },
'oregon-rlis': async () => { const m = await import('./arcgis_sales.ts'); return { run: m.ingestArcgisSales('oregon-rlis') }; },
diff --git a/src/ingest/parcels/saltlake_ut.ts b/src/ingest/parcels/saltlake_ut.ts
new file mode 100644
index 0000000..05fc566
--- /dev/null
+++ b/src/ingest/parcels/saltlake_ut.ts
@@ -0,0 +1,126 @@
+/**
+ * Salt Lake County UT (FIPS 49035) parcel ingest — dedicated full-universe adapter
+ * (DTD verdict B, 2026-07-26: broadened the rich-county hunt to a fresh state after
+ * NC/FL adjacent counties dead-ended).
+ *
+ * Source: Utah's standardized LIR (Land Information Records) parcel FeatureServer —
+ * a hosted, keyless, paginated ArcGIS service (394,610 parcels, polygon geom, $0):
+ * https://services1.arcgis.com/99lidPhWCzftIe9K/arcgis/rest/services/Parcels_SaltLake_LIR/FeatureServer/0
+ *
+ * Rich on VALUE + characteristics: PARCEL_ADD/CITY, TOTAL_MKT_VALUE + LAND_MKT_VALUE
+ * (market, not assessed), BLDG_SQFT, BUILT_YR, PROP_CLASS (use), PARCEL_ACRES, floors,
+ * construction material. Utah withholds OWNER name and there is no SALE feed in the
+ * public LIR layer — both recorded null. No sale ⇒ no bulk-deed logic needed.
+ *
+ * Run: NODE_OPTIONS=--max-old-space-size=4096 npm run ingest:parcels saltlake
+ */
+import { pool } from '../../../db/pool.ts';
+import { openRun, closeRun } from '../run.ts';
+import { upsertParcels, registerParcelSource, normAddress, type ParcelUpsertRow } from './upsert.ts';
+
+const FIPS = '49035';
+const LAYER = 'https://services1.arcgis.com/99lidPhWCzftIe9K/arcgis/rest/services/Parcels_SaltLake_LIR/FeatureServer/0';
+const PAGE = 2000;
+
+const s = (v: unknown): string | null => { const t = v == null ? '' : String(v).trim(); return t ? t : null; };
+const num = (v: unknown): number | null => { const x = Number(v); return Number.isFinite(x) && x !== 0 ? x : null; };
+const yr = (v: unknown): number | null => { const x = Number(v); return Number.isFinite(x) && x > 1700 && x < 2100 ? x : null; };
+function centroid(geom: any): [number | null, number | null] {
+ const ring = geom?.rings?.[0]; if (!Array.isArray(ring) || !ring.length) return [null, null];
+ let sx = 0, sy = 0; for (const [x, y] of ring) { sx += x; sy += y; }
+ return [+(sx / ring.length).toFixed(6), +(sy / ring.length).toFixed(6)];
+}
+
+const OUT = ['PARCEL_ID', 'SERIAL_NUM', 'PARCEL_ADD', 'PARCEL_CITY', 'TOTAL_MKT_VALUE', 'LAND_MKT_VALUE',
+ 'BLDG_SQFT', 'BUILT_YR', 'EFFBUILT_YR', 'PROP_CLASS', 'PROP_TYPE', 'PARCEL_ACRES', 'FLOORS_CNT',
+ 'PRIMARY_RES', 'HOUSE_CNT', 'CONST_MATERIAL', 'CURRENT_ASOF'].join(',');
+
+async function fetchPage(offset: number): Promise<any[]> {
+ const u = new URL(LAYER + '/query');
+ u.searchParams.set('where', '1=1');
+ u.searchParams.set('outFields', OUT);
+ u.searchParams.set('orderByFields', 'OBJECTID ASC');
+ u.searchParams.set('resultOffset', String(offset));
+ u.searchParams.set('resultRecordCount', String(PAGE));
+ u.searchParams.set('returnGeometry', 'true');
+ u.searchParams.set('outSR', '4326');
+ u.searchParams.set('f', 'json');
+ let lastErr: any;
+ for (let a = 0; a < 3; a++) {
+ try {
+ const res = await fetch(u, { signal: AbortSignal.timeout(120_000) });
+ if (!res.ok) throw new Error(`saltlake ${res.status}: ${(await res.text()).slice(0, 140)}`);
+ const j: any = await res.json();
+ if (j.error) throw new Error(`saltlake error: ${JSON.stringify(j.error).slice(0, 140)}`);
+ return j.features || [];
+ } catch (e) { lastErr = e; if (a < 2) await new Promise(r => setTimeout(r, 2000 * (a + 1))); }
+ }
+ throw lastErr;
+}
+
+export async function ingestSaltLake(): Promise<{ upserted: number }> {
+ const runId = await openRun('parcel_saltlake_ut', LAYER);
+ try {
+ let offset = 0, seen = 0, upserted = 0;
+ for (;;) {
+ const feats = await fetchPage(offset);
+ if (!feats.length) break;
+ // PARCEL_ID is NOT unique per feature (multi-part polygons / condo units share
+ // one id) — dedupe within the page so a single INSERT never has duplicate
+ // (county_fips, source_id) keys ("cannot affect row a second time"). Cross-page
+ // repeats are handled cleanly by ON CONFLICT DO UPDATE. Largest-value row wins.
+ const batchMap = new Map<string, ParcelUpsertRow>();
+ for (const f of feats) {
+ const a = f.attributes || {};
+ const pid = s(a.PARCEL_ID) || s(a.SERIAL_NUM); if (!pid) continue;
+ seen++;
+ const [lng, lat] = centroid(f.geometry);
+ const addr = s(a.PARCEL_ADD);
+ const norm = addr ? normAddress(addr) : null;
+ const total = num(a.TOTAL_MKT_VALUE);
+ const land = num(a.LAND_MKT_VALUE);
+ const row: ParcelUpsertRow = {
+ county_fips: FIPS, source_id: pid,
+ address: norm, norm_address: norm,
+ city: s(a.PARCEL_CITY), zip: null,
+ lat, lng,
+ year_built: yr(a.BUILT_YR) ?? yr(a.EFFBUILT_YR), sqft: num(a.BLDG_SQFT), beds: null, baths: null,
+ units: num(a.HOUSE_CNT),
+ use_desc: s(a.PROP_CLASS) || s(a.PROP_TYPE),
+ land_value: land, improvement_value: (total != null && land != null) ? Math.max(total - land, 0) : null,
+ total_value: total, tax_year: null,
+ owner_name: null, zoning: null,
+ last_sale_date: null, last_sale_price: null,
+ extra: JSON.stringify({
+ serial_num: s(a.SERIAL_NUM) || undefined, acres: num(a.PARCEL_ACRES) || undefined,
+ floors: num(a.FLOORS_CNT) || undefined, primary_res: s(a.PRIMARY_RES) || undefined,
+ const_material: s(a.CONST_MATERIAL) || undefined, eff_year_built: yr(a.EFFBUILT_YR) || undefined,
+ prop_type: s(a.PROP_TYPE) || undefined, as_of: s(a.CURRENT_ASOF) || undefined,
+ value_basis: total != null ? 'MARKET value (Utah LIR)' : undefined,
+ note: 'Utah LIR — market value + characteristics; no owner name or sale feed in the public statewide layer',
+ }),
+ };
+ const prev = batchMap.get(pid);
+ if (!prev || (row.total_value ?? 0) > (prev.total_value ?? 0)) batchMap.set(pid, row);
+ }
+ upserted += await upsertParcels([...batchMap.values()]);
+ offset += feats.length;
+ if (seen % 100000 < feats.length) console.log(`[saltlake] ${upserted} upserted`);
+ if (feats.length < PAGE) break;
+ }
+ if (upserted < 340000) throw new Error(`only ${upserted} Salt Lake parcels (expected ~394k) — layer/paging drift?`);
+
+ const n = await registerParcelSource(FIPS, LAYER,
+ `Salt Lake County UT — Utah LIR standardized parcel FeatureServer (keyless ArcGIS) — ${upserted} parcels: site address, MARKET total+land value, building sqft, year built, use class, acres, floors, construction material, polygon centroid. No owner name or sale feed in the public LIR layer.`);
+ await closeRun(runId, 'ok', { upserted, notes: `Salt Lake UT: ${upserted} parcels (value+characteristics, no owner/sale)` });
+ console.log(`[saltlake] ok: ${upserted} parcels (registry ${n})`);
+ return { upserted };
+ } catch (e: any) {
+ await closeRun(runId, 'failed', { notes: String(e.message || e).slice(0, 500) });
+ throw e;
+ }
+}
+
+if (import.meta.url === `file://${process.argv[1]}`) {
+ ingestSaltLake().then(() => pool.end()).catch(e => { console.error(e); process.exit(1); });
+}
← d9054bf USRE grid: mobile filter access — rail opens as off-canvas d
·
back to Nationalrealestate
·
USRE grid: CSV export honors active filters — export filtere 966b628 →