[object Object]

← back to Nationalrealestate

parcels: add Yakima County WA (53077) free priced-deed feed

1e35d6a703b83687d381639e8970c53d682d98bb · 2026-07-28 06:51:43 -0700 · Steve Abrams

56,030 priced sales from the Yakima County Assessor ArcGIS MapServer
(gis.yakimawa.gov). SQL-Server-backed layer returns fully-qualified
DBO.Parcels.* attribute keys, so outFields/toRecords/orderBy all use the
qualified names. Maps GROSS_SALE_PRICE + SALE_DATE (M/D/YYYY) + EXCISE_NUMBER
+ GRANTOR_NAME + owner (ORG or FIRST/LAST) + situs + use + yrblt + sqft +
beds/baths + summed market land/impvt -> total_value. Verified end-to-end vs
local usre DB (1921 parcels + 1938 sale events w/ doc+grantor+grantee+GIS
deep-links, dates 2005-2026, source registered). tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 1e35d6a703b83687d381639e8970c53d682d98bb
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jul 28 06:51:43 2026 -0700

    parcels: add Yakima County WA (53077) free priced-deed feed
    
    56,030 priced sales from the Yakima County Assessor ArcGIS MapServer
    (gis.yakimawa.gov). SQL-Server-backed layer returns fully-qualified
    DBO.Parcels.* attribute keys, so outFields/toRecords/orderBy all use the
    qualified names. Maps GROSS_SALE_PRICE + SALE_DATE (M/D/YYYY) + EXCISE_NUMBER
    + GRANTOR_NAME + owner (ORG or FIRST/LAST) + situs + use + yrblt + sqft +
    beds/baths + summed market land/impvt -> total_value. Verified end-to-end vs
    local usre DB (1921 parcels + 1938 sale events w/ doc+grantor+grantee+GIS
    deep-links, dates 2005-2026, source registered). tsc clean.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 SOURCES.md                         |  4 +++-
 src/ingest/parcels/arcgis_sales.ts | 19 +++++++++++++++++++
 src/ingest/parcels/engine.ts       |  1 +
 src/jobs/hourly_loop.ts            |  1 +
 4 files changed, 24 insertions(+), 1 deletion(-)

diff --git a/SOURCES.md b/SOURCES.md
index 2b56d1c..55aa55a 100644
--- a/SOURCES.md
+++ b/SOURCES.md
@@ -108,8 +108,10 @@ incl. 8 gated/blocked states → `BROKER-COVERAGE.md`. Adapters: `src/ingest/bro
 
 ## Free priced-DEED feeds — config-driven ArcGIS (`src/ingest/parcels/arcgis_sales.ts`)
 
-Each entry is a keyless ArcGIS REST layer that publishes real recorded SALE PRICE; one `SOURCES` config maps a page of features → parcel row + `parcel_event(event_type='sale')` with grantor/grantee/doc + a `source_url` deep-link. Registered in `parcel_source`, paginated over time by the hourly loop (`src/jobs/hourly_loop.ts`, `runSources` = the config's `cursorKey`). Live configs: `oregon-rlis` (41051/67/05), `spokane` (53063), `alameda` (06001), `deschutes` (41017), `crook` (41013), `sonoma` (06097), `colorado` (statewide composite `08*`), `maricopa` (04013), `thurston` (53067), `palm-beach` (12099), `klamath` (41035), `marion` (41047).
+Each entry is a keyless ArcGIS REST layer that publishes real recorded SALE PRICE; one `SOURCES` config maps a page of features → parcel row + `parcel_event(event_type='sale')` with grantor/grantee/doc + a `source_url` deep-link. Registered in `parcel_source`, paginated over time by the hourly loop (`src/jobs/hourly_loop.ts`, `runSources` = the config's `cursorKey`). Live configs: `oregon-rlis` (41051/67/05), `spokane` (53063), `alameda` (06001), `deschutes` (41017), `crook` (41013), `sonoma` (06097), `colorado` (statewide composite `08*`), `maricopa` (04013), `thurston` (53067), `palm-beach` (12099), `klamath` (41035), `marion` (41047), `yakima` (53077).
 
 - **Klamath County OR (41035)** — added 2026-07-27 (TK-16). ArcGIS Online FeatureServer `https://services.arcgis.com/H6Mh1bySxR4oHx6x/arcgis/rest/services/KC_ParcelSales/FeatureServer/0` (item `f6314b12b8cc4c35899157172601bf59`, "KC_ParcelSales" = Klamath County Parcel Sales). `where=SALE_PRICE>1000` → **38,796** priced sales. Rich fields: `SALE_PRICE` (int), `SALE_DATE` (epoch-ms), `OWNER_NAME` (→ grantee), `MIN_SITUS_ADDRESS`+`FIRST_CITY_NAME`, `SUM_FINSQFT`/`SUM_BEDRMS`/`SUM_BATH`, `MIN_YRBLT`, `SUM_Tot_Appr` (total appraised → `total_value`), `SALEBK` (→ doc_number, internal double-spaces collapsed by `clean()`), `FIRST_PROP_ID` (→ apn/source_id), `FIRST_PCLCD` (→ use_desc). Server caps pages at 1000. Verified end-to-end against the local `usre` DB (1,000 parcels + 1,000 sale events + links + source registration on first batch).
 
 - **Marion County OR / Salem (41047)** — added 2026-07-27 (TK-16, pass 2). ArcGIS Online FeatureServer `https://services1.arcgis.com/sYGZnQPdJ0azuLyn/arcgis/rest/services/TaxParcel_Assessment/FeatureServer/0` (Marion County official org). `where=SALEPRICE>1000` → **96,115** priced sales. Rich fields: `SALEPRICE` (int), `INSTDATE` (instrument/deed date, epoch-ms → `last_sale_date`), `INSTNUM` (→ doc_number), `INSTTYPE` (deed type WD/B&S/DEED → doc_type), `OWNERNAME` (→ grantee), `SITUS` (situs address, often blank on land parcels), `YEARBUILT`, `LIVINGAREA` (→ sqft), `RMVTOTAL` (real market total value → `total_value`), `PROPCLASS`+`STCLSDESC` (→ use_desc, prefers the description). `TAXLOT` (→ apn/source_id). Deed dates span decades so the future-date guard passes all rows. Verified end-to-end against the local `usre` DB (1,000 parcels + sale events + GIS links + `parcel_source` registration on first batch); `tsc` clean.
+
+- **Yakima County WA (53077)** — added 2026-07-28 (TK-16, pass 3). Yakima County Assessor ArcGIS MapServer `https://gis.yakimawa.gov/arcgis/rest/services/Assessor/AssessorParcels/MapServer/0`. `where=DBO.Parcels.GROSS_SALE_PRICE>1000` → **56,030** priced sales. NOTE: this SQL-Server-backed MapServer returns attribute keys **fully-qualified** (`DBO.Parcels.*`), so `outFields` and `toRecords` both use the qualified names, and `orderBy` = `DBO.Parcels.OBJECTID`. Rich fields: `GROSS_SALE_PRICE` (double → `last_sale_price`), `SALE_DATE` (string `M/D/YYYY`, handled by `anyDate()` → `last_sale_date`), `EXCISE_NUMBER` (→ doc_number), `GRANTOR_NAME` (→ grantor), owner from `ORG_NAME` else `FIRST_NAME`+`LAST_NAME` (→ grantee/owner_name), `SITUS_ADDR`/`SITUS_CITY`/`SITUS_ZIP` (situs "UNASSIGNED" mapped to null), `USE_CODE` (→ use_desc), `YEAR_BLT`, `MAIN_SQFT` (→ sqft), `BEDROOMS`/`FULL_BATH`, `MKT_LAND`+`MKT_IMPVT` (summed → `total_value`), `ASSESSOR_NO` (→ apn/source_id). Empty fields come back as `" "` (single space) and are trimmed to null by `s()`/`clean()`. Verified end-to-end against the local `usre` DB (1,000 parcels + sale events + GIS links + `parcel_source` registration on first batch); `tsc` clean.
diff --git a/src/ingest/parcels/arcgis_sales.ts b/src/ingest/parcels/arcgis_sales.ts
index 586bab7..1019f9f 100644
--- a/src/ingest/parcels/arcgis_sales.ts
+++ b/src/ingest/parcels/arcgis_sales.ts
@@ -214,6 +214,25 @@ const SOURCES: Record<string, Source> = {
         use: clean(a.STCLSDESC) || clean(a.PROPCLASS),
         grantee: clean(a.OWNERNAME), docNum: s(a.INSTNUM), docType: clean(a.INSTTYPE) } as Rec; }).filter(Boolean) as Rec[],
   },
+  yakima: {
+    key: 'yakima', label: 'Yakima County WA', cursorKey: 'yakima', geometry: false, orderBy: 'DBO.Parcels.OBJECTID', pageSize: 2000,
+    // rich Yakima assessor parcel layer — attribute keys come back FULLY-QUALIFIED (DBO.Parcels.*).
+    // GROSS_SALE_PRICE(double)+SALE_DATE(string M/D/YYYY)+EXCISE_NUMBER(doc)+GRANTOR_NAME+owner(LAST/FIRST/ORG)+situs+use+yrblt+sqft+beds/baths+market land/impvt.
+    layer: 'https://gis.yakimawa.gov/arcgis/rest/services/Assessor/AssessorParcels/MapServer/0',
+    where: 'DBO.Parcels.GROSS_SALE_PRICE>1000',
+    outFields: 'DBO.Parcels.ASSESSOR_NO,DBO.Parcels.GROSS_SALE_PRICE,DBO.Parcels.SALE_DATE,DBO.Parcels.EXCISE_NUMBER,DBO.Parcels.GRANTOR_NAME,DBO.Parcels.SITUS_ADDR,DBO.Parcels.SITUS_CITY,DBO.Parcels.SITUS_ZIP,DBO.Parcels.USE_CODE,DBO.Parcels.YEAR_BLT,DBO.Parcels.MAIN_SQFT,DBO.Parcels.BEDROOMS,DBO.Parcels.FULL_BATH,DBO.Parcels.MKT_LAND,DBO.Parcels.MKT_IMPVT,DBO.Parcels.LAST_NAME,DBO.Parcels.FIRST_NAME,DBO.Parcels.ORG_NAME',
+    toRecords: (fs) => fs.map(f => { const a = f.attributes; const apn = s(a['DBO.Parcels.ASSESSOR_NO']); if (!apn) return null;
+      let situs = clean(a['DBO.Parcels.SITUS_ADDR']); if (situs && /^UNASSIGNED$/i.test(situs)) situs = null;
+      const org = clean(a['DBO.Parcels.ORG_NAME']);
+      const person = [clean(a['DBO.Parcels.FIRST_NAME']), clean(a['DBO.Parcels.LAST_NAME'])].filter(Boolean).join(' ').trim() || null;
+      const owner = org || person;
+      const land = num(a['DBO.Parcels.MKT_LAND']) || 0, impvt = num(a['DBO.Parcels.MKT_IMPVT']) || 0;
+      return { fips: '53077', apn, price: anyPrice(a['DBO.Parcels.GROSS_SALE_PRICE']), saleDate: anyDate(a['DBO.Parcels.SALE_DATE']),
+        address: situs, city: clean(a['DBO.Parcels.SITUS_CITY']), zip: s(a['DBO.Parcels.SITUS_ZIP']),
+        sqft: num(a['DBO.Parcels.MAIN_SQFT']), beds: num(a['DBO.Parcels.BEDROOMS']), baths: num(a['DBO.Parcels.FULL_BATH']),
+        year: num(a['DBO.Parcels.YEAR_BLT']), totalValue: (land + impvt) || null, use: clean(a['DBO.Parcels.USE_CODE']),
+        grantor: clean(a['DBO.Parcels.GRANTOR_NAME']), grantee: owner, docNum: s(a['DBO.Parcels.EXCISE_NUMBER']) } as Rec; }).filter(Boolean) as Rec[],
+  },
 };
 
 async function fetchPage(src: Source, offset: number): Promise<any[]> {
diff --git a/src/ingest/parcels/engine.ts b/src/ingest/parcels/engine.ts
index 9035304..0108f96 100644
--- a/src/ingest/parcels/engine.ts
+++ b/src/ingest/parcels/engine.ts
@@ -31,6 +31,7 @@ const ADAPTERS: Record<string, () => Promise<{ run: () => Promise<{ upserted: nu
   'palm-beach': async () => { const m = await import('./arcgis_sales.ts'); return { run: m.ingestArcgisSales('palm-beach') }; },
   klamath: async () => { const m = await import('./arcgis_sales.ts'); return { run: m.ingestArcgisSales('klamath') }; },
   marion: async () => { const m = await import('./arcgis_sales.ts'); return { run: m.ingestArcgisSales('marion') }; },
+  yakima: async () => { const m = await import('./arcgis_sales.ts'); return { run: m.ingestArcgisSales('yakima') }; },
 };
 
 async function main() {
diff --git a/src/jobs/hourly_loop.ts b/src/jobs/hourly_loop.ts
index c11104b..2616f16 100644
--- a/src/jobs/hourly_loop.ts
+++ b/src/jobs/hourly_loop.ts
@@ -64,6 +64,7 @@ const JOBS: Job[] = [
   { name: 'deeds-pb',   script: 'src/ingest/parcels/engine.ts',     args: ['palm-beach'],  minIntervalHours: 4, runSources: ['palm_beach'] },
   { name: 'deeds-klam', script: 'src/ingest/parcels/engine.ts',     args: ['klamath'],     minIntervalHours: 4, runSources: ['klamath'] },
   { name: 'deeds-marion',script:'src/ingest/parcels/engine.ts',     args: ['marion'],      minIntervalHours: 3, runSources: ['marion'] },
+  { name: 'deeds-yakima',script:'src/ingest/parcels/engine.ts',     args: ['yakima'],      minIntervalHours: 3, runSources: ['yakima'] },
 ];
 
 /** hours since this job's freshest OK run across its runSources (∞ if never). */

← f29163c parcels: add Bexar County TX / San Antonio (48029) — dedicat  ·  back to Nationalrealestate  ·  parcels: guard negative sale prices — shared sanitizeSalePri 994fedb →