← back to Nationalrealestate
Add San Diego full (SANDAG 1.09M): authoritative ASR_ZONE->label map (data dictionary) makes coded use descriptive -> from_parcel classifies. SD commercial verified plausible (commercial+industrial mix, not Cook-skew). +commercial keyword in from_parcel pre-filter
6ad5d70aa184ef1ea72a1d7413884caf4e9f6822 · 2026-07-30 21:38:51 -0700 · steve@designerwallcoverings.com
Files touched
M src/ingest/commercial/engine.tsM src/ingest/commercial/from_parcel.tsM src/ingest/parcels/arcgis_sales.tsM src/ingest/parcels/engine.ts
Diff
commit 6ad5d70aa184ef1ea72a1d7413884caf4e9f6822
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date: Thu Jul 30 21:38:51 2026 -0700
Add San Diego full (SANDAG 1.09M): authoritative ASR_ZONE->label map (data dictionary) makes coded use descriptive -> from_parcel classifies. SD commercial verified plausible (commercial+industrial mix, not Cook-skew). +commercial keyword in from_parcel pre-filter
---
src/ingest/commercial/engine.ts | 1 +
src/ingest/commercial/from_parcel.ts | 2 +-
src/ingest/parcels/arcgis_sales.ts | 17 +++++++++++++++++
src/ingest/parcels/engine.ts | 1 +
4 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/src/ingest/commercial/engine.ts b/src/ingest/commercial/engine.ts
index 05bcc61..3822771 100644
--- a/src/ingest/commercial/engine.ts
+++ b/src/ingest/commercial/engine.ts
@@ -12,6 +12,7 @@ const ADAPTERS: Record<string, () => Promise<{ run: () => Promise<{ upserted: nu
// Materialize commercial from already-loaded parcels (use_desc is descriptive) — $0.
king: async () => ({ run: (await import('./from_parcel.ts')).ingestCommercialFromParcel('53033', 'King County WA (Seattle)') }),
saltlake: async () => ({ run: (await import('./from_parcel.ts')).ingestCommercialFromParcel('49035', 'Salt Lake County UT') }),
+ sandiego: async () => ({ run: (await import('./from_parcel.ts')).ingestCommercialFromParcel('06073', 'San Diego County CA') }),
// NOTE: Cook(17031)/NY(36061)/Bexar-TX(48029) use CLASS CODES ('Class 5-93','C7','F1')
// not descriptive text, so from_parcel can't classify them (verified: Cook came back
// 100% industrial — implausible). They need a per-county class-code map (future task).
diff --git a/src/ingest/commercial/from_parcel.ts b/src/ingest/commercial/from_parcel.ts
index efd5338..8d05543 100644
--- a/src/ingest/commercial/from_parcel.ts
+++ b/src/ingest/commercial/from_parcel.ts
@@ -15,7 +15,7 @@ import { classifyCommercialByDesc } from '../../lib/commercial_types.ts';
const digits = (v: unknown): string | null => { const t = (v == null ? '' : String(v)).replace(/\D/g, ''); return t || null; };
// Broad SQL pre-filter covering the classifier's keywords — precise typing happens in JS.
-const KEYWORDS = 'office|professional|bank|savings|warehous|distribution|storage|manufactur|industrial|store|shop|supermarket|department|restaurant|cocktail|hotel|motel|hospitality|lodging|parking|studio|motion picture|television|radio|retail|wholesale|outlet|nursery|greenhouse|kennel|laundry|repair|service station|auto|recreation';
+const KEYWORDS = 'commercial|office|professional|bank|savings|warehous|distribution|storage|manufactur|industrial|store|shop|supermarket|department|restaurant|cocktail|hotel|motel|hospitality|lodging|parking|studio|motion picture|television|radio|retail|wholesale|outlet|nursery|greenhouse|kennel|laundry|repair|service station|auto|recreation';
const COLS = ['county_fips', 'ain', 'address', 'city', 'zip', 'ctype', 'use_desc', 'use_class',
'assessed_total', 'assessed_land', 'assessed_imp', 'roll_year', 'recording_date', 'sqft', 'year_built', 'units'];
diff --git a/src/ingest/parcels/arcgis_sales.ts b/src/ingest/parcels/arcgis_sales.ts
index 69d166a..01cab93 100644
--- a/src/ingest/parcels/arcgis_sales.ts
+++ b/src/ingest/parcels/arcgis_sales.ts
@@ -214,6 +214,23 @@ const SOURCES: Record<string, Source> = {
use: clean(a.UseDescription), year: num(a.YearBuilt1), beds: num(a.Bedrooms1), baths: num(a.Bathrooms1),
sqft: num(a.SQFTmain1), totalValue: (land || imp) ? (land || 0) + (imp || 0) : null, raw: a } as Rec; }).filter(Boolean) as Rec[],
},
+ 'san-diego-sandag': {
+ // Full San Diego County (1.09M) from SANDAG. Codes only, BUT the SanGIS data
+ // dictionary gives the authoritative ASR_ZONE map, so we translate the zone code
+ // to its descriptive label at ingest → use_desc becomes text → from_parcel classifies.
+ key: 'san-diego-sandag', label: 'San Diego County CA (SANDAG full)', cursorKey: 'san_diego_sandag', geometry: false, orderBy: 'objectid', pageSize: 2000,
+ layer: 'https://geo.sandag.org/server/rest/services/Hosted/Parcels/FeatureServer/0',
+ where: 'apn IS NOT NULL',
+ outFields: 'apn,situs_address,situs_street,situs_juris,asr_zone,asr_total',
+ fieldMap: { source_id: 'apn', address: 'situs_address', use_desc: 'asr_zone(mapped)', total_value: 'asr_total' },
+ toRecords: (fs) => fs.map(f => { const a = f.attributes; const apn = s(a.apn); if (!apn) return null;
+ // Authoritative ASR_ZONE → label (SanGIS data dictionary, verified 2026-07-30).
+ const ZONE: Record<string, string> = { '0': 'Unzoned', '1': 'Single Family Residential', '2': 'Minor Multiple Residential', '3': 'Restricted Multiple Residential', '4': 'Multiple Residential', '5': 'Restricted Commercial', '6': 'Commercial', '7': 'Industrial', '8': 'Agricultural', '9': 'Special/Misc' };
+ const use = ZONE[String(a.asr_zone)] ?? null;
+ const addr = clean(a.situs_address) || [s(a.situs_street)].filter(Boolean).join(' ') || null;
+ return { fips: '06073', apn, address: addr, city: clean(a.situs_juris), zip: null,
+ use, totalValue: num(a.asr_total), raw: a } as Rec; }).filter(Boolean) as Rec[],
+ },
'santa-clara': {
// Santa Clara County (San Jose) — public layer is address+APN only (no value/use),
// maxRecordCount=2000. Parcels key on county_fips so no region-resolve needed.
diff --git a/src/ingest/parcels/engine.ts b/src/ingest/parcels/engine.ts
index 310abcc..60bf8ce 100644
--- a/src/ingest/parcels/engine.ts
+++ b/src/ingest/parcels/engine.ts
@@ -30,6 +30,7 @@ const ADAPTERS: Record<string, () => Promise<{ run: () => Promise<{ upserted: nu
maricopa: async () => { const m = await import('./arcgis_sales.ts'); return { run: m.ingestArcgisSales('maricopa') }; },
'la-county': async () => { const m = await import('./arcgis_sales.ts'); return { run: m.ingestArcgisSales('la-county') }; },
'santa-clara': async () => { const m = await import('./arcgis_sales.ts'); return { run: m.ingestArcgisSales('santa-clara') }; },
+ 'san-diego-sandag': async () => { const m = await import('./arcgis_sales.ts'); return { run: m.ingestArcgisSales('san-diego-sandag') }; },
thurston: async () => { const m = await import('./arcgis_sales.ts'); return { run: m.ingestArcgisSales('thurston') }; },
'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') }; },
← b3ca8a1 Commercial from_parcel: add bare-'Commercial' fallback class
·
back to Nationalrealestate
·
Add Maricopa (Phoenix) bulk loader — free assessor st42060 f 36c7c42 →