← back to Nationalrealestate
parcels: extend deed-date sanity guard to franklin_oh, king_wa, nyc_acris
0bae506356b4da285d308173690b0c16d1dbf1cf · 2026-07-27 21:08:42 -0700 · Steve Abrams
Prior pass centralized sanitizeEventDate() into the 4 parcel_event deed
ingesters (arcgis_sales, miami_dade, wake_nc, sandiego_ca). But three more
parcel ingesters — franklin_oh, king_wa, nyc_acris — also emit a bare
YYYY-MM-DD sale date into parcel.last_sale_date (and the sales JSON detail)
with no future/absurd-date guard. Route each ingester's single date-parse
helper (toIsoDate/isoDate) through the shared sanitizeEventDate() so a
future/typo/sub-1900 date is dropped before it lands on the parcel row.
Same defect class the arcgis guard was added to stop, closed on the
parcel table for the last three date-bearing ingesters.
cook_il + saltlake_ut set last_sale_date:null (no dates) — safe, untouched.
DB verified clean (0 future, 0 ancient) for these counties: preventive.
tsc --noEmit clean; date_guard 19-case self-test passes. $0 (local).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M src/ingest/parcels/franklin_oh.tsM src/ingest/parcels/king_wa.tsM src/ingest/parcels/nyc_acris.ts
Diff
commit 0bae506356b4da285d308173690b0c16d1dbf1cf
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jul 27 21:08:42 2026 -0700
parcels: extend deed-date sanity guard to franklin_oh, king_wa, nyc_acris
Prior pass centralized sanitizeEventDate() into the 4 parcel_event deed
ingesters (arcgis_sales, miami_dade, wake_nc, sandiego_ca). But three more
parcel ingesters — franklin_oh, king_wa, nyc_acris — also emit a bare
YYYY-MM-DD sale date into parcel.last_sale_date (and the sales JSON detail)
with no future/absurd-date guard. Route each ingester's single date-parse
helper (toIsoDate/isoDate) through the shared sanitizeEventDate() so a
future/typo/sub-1900 date is dropped before it lands on the parcel row.
Same defect class the arcgis guard was added to stop, closed on the
parcel table for the last three date-bearing ingesters.
cook_il + saltlake_ut set last_sale_date:null (no dates) — safe, untouched.
DB verified clean (0 future, 0 ancient) for these counties: preventive.
tsc --noEmit clean; date_guard 19-case self-test passes. $0 (local).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
src/ingest/parcels/franklin_oh.ts | 4 +++-
src/ingest/parcels/king_wa.ts | 6 ++++--
src/ingest/parcels/nyc_acris.ts | 4 +++-
3 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/src/ingest/parcels/franklin_oh.ts b/src/ingest/parcels/franklin_oh.ts
index 9cab643..f863b1a 100644
--- a/src/ingest/parcels/franklin_oh.ts
+++ b/src/ingest/parcels/franklin_oh.ts
@@ -37,6 +37,7 @@ import { join } from 'node:path';
import { pool } from '../../../db/pool.ts';
import { openRun, closeRun } from '../run.ts';
import { upsertParcels, registerParcelSource, normAddress, type ParcelUpsertRow } from './upsert.ts';
+import { sanitizeEventDate } from './date_guard.ts';
const execFileP = promisify(execFile);
const FIPS = '39049';
@@ -47,7 +48,8 @@ const num = (v: unknown) => { const n = Number(v); return Number.isFinite(n) &&
/** "2021/12/02 00:00:00+00" -> "2021-12-02"; anything unparseable -> null */
const toIsoDate = (v: unknown): string | null => {
const m = String(v ?? '').match(/(\d{4})[/-](\d{2})[/-](\d{2})/);
- return m ? `${m[1]}-${m[2]}-${m[3]}` : null;
+ // drop future/absurd bad-entry dates so they never reach parcel.last_sale_date (shared guard)
+ return m ? sanitizeEventDate(`${m[1]}-${m[2]}-${m[3]}`) : null;
};
/** Full sortable timestamp "2021-12-02T00:00:01" — the SUB-DAY seconds matter:
* the county records a same-day $0 grantor-side transfer at HH:00 and the real
diff --git a/src/ingest/parcels/king_wa.ts b/src/ingest/parcels/king_wa.ts
index a9a60f1..4cbe76d 100644
--- a/src/ingest/parcels/king_wa.ts
+++ b/src/ingest/parcels/king_wa.ts
@@ -20,6 +20,7 @@ import { pool } from '../../../db/pool.ts';
import { openRun, closeRun, DOWNLOADS, download } from '../run.ts';
import { csvObjects } from './csv.ts';
import { upsertParcels, registerParcelSource, normAddress, type ParcelUpsertRow } from './upsert.ts';
+import { sanitizeEventDate } from './date_guard.ts';
const FIPS = '53033';
const BASE = 'https://aqua.kingcounty.gov/extranet/assessor/';
@@ -36,10 +37,11 @@ const num = (v: string | undefined) => { const n = Number(v); return Number.isFi
const num0 = (v: string | undefined) => { const n = Number(v); return Number.isFinite(n) ? n : 0; };
const pinOf = (o: Record<string, string>) => o.major.padStart(6, '0') + o.minor.padStart(4, '0');
-/** MM/DD/YYYY -> YYYY-MM-DD (null if unparseable) */
+/** MM/DD/YYYY -> YYYY-MM-DD (null if unparseable, future, or absurd) */
function isoDate(d: string): string | null {
const m = d.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
- return m ? `${m[3]}-${m[1]}-${m[2]}` : null;
+ // drop future/absurd bad-entry dates so they never reach parcel.last_sale_date (shared guard)
+ return m ? sanitizeEventDate(`${m[3]}-${m[1]}-${m[2]}`) : null;
}
interface Sale { date: string; price: number; buyer: string; seller: string; instrument: string }
diff --git a/src/ingest/parcels/nyc_acris.ts b/src/ingest/parcels/nyc_acris.ts
index 0416ebd..481ace3 100644
--- a/src/ingest/parcels/nyc_acris.ts
+++ b/src/ingest/parcels/nyc_acris.ts
@@ -27,6 +27,7 @@
*/
import { query, pool } from '../../../db/pool.ts';
import { openRun, closeRun } from '../run.ts';
+import { sanitizeEventDate } from './date_guard.ts';
const MASTER = 'https://data.cityofnewyork.us/resource/bnx9-e6tj.json';
const LEGALS = 'https://data.cityofnewyork.us/resource/8h5j-fqxa.json';
@@ -73,7 +74,8 @@ async function soda(base: string, params: Record<string, string>): Promise<any[]
function isoDate(d: string | undefined): string | null {
if (!d) return null;
const s = String(d).slice(0, 10);
- return /^\d{4}-\d{2}-\d{2}$/.test(s) ? s : null;
+ // drop future/absurd bad-entry dates so they never reach parcel.last_sale_date (shared guard)
+ return /^\d{4}-\d{2}-\d{2}$/.test(s) ? sanitizeEventDate(s) : null;
}
export async function ingestNycAcris(): Promise<{ upserted: number }> {
← 8934410 parcels: centralize deed-date sanity guard across all 4 deed
·
back to Nationalrealestate
·
parcels: add Tarrant County TX / Fort Worth (48439) — dedica 0e3ce56 →