← back to Nationalrealestate
db/migrations/012_parcel_provenance.sql
39 lines
-- TK-50: FIELD-LEVEL DATA PROVENANCE — "source every bit of info."
-- No BEGIN/COMMIT here — migrate.ts wraps each file in a transaction.
--
-- One source record (e.g. one ArcGIS feature) → one parcel row, so ALL of a
-- parcel's fields share the same source. Provenance is therefore
-- (record source_url + fetched_at + source_key) + (which source attribute
-- each of our fields mapped from) + the raw record.
--
-- We store the record-level part on the parcel row (source_url/source_key/
-- fetched_at/raw_source) and the per-field attribute mapping ONCE per source
-- in source_field_map (our_field → source_attr). "Where did field X come
-- from?" resolves to:
-- parcel.source_url (the addressable record link)
-- + source_field_map[source_key][X].source_attr (which raw attribute)
-- + parcel.raw_source[source_attr] (the raw value we mapped from)
--
-- Additive only (ADD COLUMN / CREATE TABLE) — safe + reversible. Existing
-- rows get source_url=NULL until they are re-ingested (backfill is a separate,
-- gated prod step).
ALTER TABLE parcel ADD COLUMN IF NOT EXISTS source_url TEXT; -- per-record addressable link back to the exact source record
ALTER TABLE parcel ADD COLUMN IF NOT EXISTS source_key TEXT; -- ingest source id, e.g. 'oregon-rlis' — FK-ish into source_field_map
ALTER TABLE parcel ADD COLUMN IF NOT EXISTS fetched_at TIMESTAMPTZ; -- when the record was pulled (run start time)
ALTER TABLE parcel ADD COLUMN IF NOT EXISTS raw_source JSONB; -- the raw source-record attributes we mapped from
CREATE INDEX IF NOT EXISTS idx_parcel_source_key ON parcel (source_key);
-- Per-source declaration of which raw source attribute each of our fields maps
-- from. One row per (source_key, our_field). Registered at ingest time by
-- registerSourceFieldMap() so provenance is queryable without re-reading code.
CREATE TABLE IF NOT EXISTS source_field_map (
source_key TEXT NOT NULL, -- matches parcel.source_key
our_field TEXT NOT NULL, -- our parcel column, e.g. 'owner_name'
source_attr TEXT NOT NULL, -- the raw source attribute, e.g. 'OWNER_NAME'
notes TEXT, -- free-text (e.g. 'grantee/current owner')
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (source_key, our_field)
);