[object Object]

← 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

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 →