Short answer
A consulting deliverable answers one question, once, for one client. A data product keeps answering questions — across projects, analysts, and applications — because it has a stable schema, documented provenance, versioning, a repeatable update process, and a defined way to be accessed. Making the leap is mostly architectural discipline: separate raw sources from a normalized canonical store from published outputs, decide on versioning and metadata before you build, and expose the data through an interface (an OGC API, vector tiles, or a clean download) chosen for an actual consumer rather than emailed as a folder of files.
What actually separates a deliverable from a product
It is tempting to think the difference is technology — "a product has an API." It is not. The difference is maintainability under change. A deliverable is finished when the report is sent. A product must absorb new source data, survive the analyst who built it leaving, and let a consumer six months later understand exactly what they are looking at and how much to trust it. Concretely, a data product has four properties a deliverable usually lacks:
- A stable, documented schema — field names, types, units, and allowed values that do not change silently between updates.
- Provenance and metadata — where each layer came from, its CRS, scale, date, and licence.
- Versioning — every release identifiable and, ideally, reproducible.
- A defined access method — one supported way in, not a pile of formats.
Layer the architecture
The single most useful structural decision is to stop mixing source data, working data, and outputs. Three logical tiers, even if they all live in one PostGIS database:
- Raw / staging. Immutable copies of source data exactly as received, with the date and origin recorded. You never edit these; you re-derive from them. This is what makes a version reproducible.
- Canonical store. The normalized, cleaned, validated truth — one schema, one CRS (commonly EPSG:4326 for storage, reprojected on the way out), validated geometry, foreign keys between related tables. This is the asset you maintain.
- Published / serving. The formats and views consumers actually touch: a GeoPackage export, vector tiles, an OGC API endpoint, a materialized view feeding a dashboard.
Keeping these apart means a source update flows raw → canonical → published deterministically, and you can regenerate any published output from the canonical store without manual rework. In PostGIS, separate schemas (raw, core, pub) make the tiers explicit and permissions easy.
Design the schema and metadata first
Decide the schema before loading data, and write it down. For a geological units product that might be:
CREATE SCHEMA core;
CREATE TABLE core.units (
unit_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
unit_code text NOT NULL, -- lithostratigraphic code
description text NOT NULL,
age_period text,
confidence text CHECK (confidence IN ('high','medium','low')),
geom geometry(MultiPolygon, 4326) NOT NULL,
src_doc text NOT NULL, -- provenance
version text NOT NULL, -- e.g. '2026.1'
effective date NOT NULL,
CONSTRAINT geom_valid CHECK (ST_IsValid(geom))
);
CREATE INDEX units_gix ON core.units USING GIST (geom);
The constraints are doing real work: CHECK (ST_IsValid(geom)) stops invalid geometry from ever entering the canonical store, and the confidence check enforces a controlled vocabulary so the field stays queryable. Attach dataset-level metadata too — title, abstract, lineage, CRS, spatial extent, update frequency, and licence — ideally to a recognised standard (ISO 19115 for catalogues, or a STAC collection for raster/imagery products) so the product is discoverable and self-describing.
Versioning that consumers can rely on
A data product that updates needs versions consumers can pin to. A workable, low-ceremony scheme:
- A version string (
2026.1,2026.2) and an effective date on every release. - The raw/staging copies archived per version, so any version can be rebuilt from scratch.
- A short changelog describing what changed and why ("added 14 new units from the 2026 survey; reclassified Unit Jm confidence from low to medium").
For consumers who need history, keep prior published exports immutable (object storage with versioning), or use snapshot tables / valid_from–valid_to temporal columns so a query can ask for the state as of a date. The principle: an update should add a version, never silently mutate the one a client already cited.
Choose the access method for a real consumer
Do not build an API on reflex. Match the interface to who consumes the data:
- Analysts doing bulk work want a download: GeoPackage (multi-layer, robust) or FlatGeobuf (streamable, cloud-friendly). Generate it from the canonical store:
ogr2ogr -f GPKG product_2026.1.gpkg "PG:dbname=geo" core.units. - Applications and partners want an OGC API - Features endpoint (paged GeoJSON over HTTP, filterable) or vector tiles for map rendering. OGC API - Features is the modern, web-friendly successor to WFS and is well supported by servers like pygeoapi.
- Dashboards want a stable query layer — a materialized view with the exact fields the dashboard binds to, refreshed on update.
Exposing one well-supported method beats half-supporting four. You can add formats later from the same canonical store.
A migration, end to end
Suppose a finished consulting project left a folder of Shapefiles and a PDF. To productize:
- Copy the Shapefiles into
rawunchanged, recording date and source. - Validate and load into
corewith the designed schema:ogr2ogr -f PostgreSQL "PG:dbname=geo" units.shp -nln core.units -makevalid -nlt MULTIPOLYGON -lco SCHEMA=core, then populate provenance and version columns. - Add metadata (ISO 19115 record or a README with lineage, CRS, extent, licence).
- Publish version
2026.1: a GeoPackage download and a pygeoapi OGC API - Features collection overcore.units. - Write the update runbook so the next survey flows through the same three tiers and produces
2026.2.
Common pitfalls and why they happen
- Calling a folder of files a product. No schema contract, no versioning, no metadata — the next person rebuilds it. Add the four properties above.
- Editing the canonical store in place on each update. It destroys reproducibility and breaks any consumer who cited the old state. Re-derive from raw and version the output.
- Schema drift. Field names or units change quietly between updates and downstream code breaks. Lock the schema with constraints and treat changes as a versioned, announced event.
- Building an API nobody asked for. Effort goes to an interface no consumer uses while the real need was a maintained GeoPackage. Define the consumer first.
- Provenance lost in the move. Without source, CRS, scale, and date, the product cannot be trusted or licensed correctly. Carry provenance on the data, not in a separate email.
Validation
Before publishing a version, check that the published output regenerates cleanly from raw through core (the reproducibility test). Confirm every geometry is valid and in the declared CRS, the schema matches the documented contract, and provenance and version columns are populated on every row. Open the OGC API or download in QGIS and verify counts, extent, and attributes against the source. Finally, hand the metadata to someone outside the project and check they can understand what the dataset is and how current it is without asking you.
Bathyl perspective
The value of geological and geospatial work compounds only when each project leaves behind an asset the next one can build on. We productize deliverables by layering raw, canonical, and published tiers, locking schema and provenance into the data, and versioning every release — so the science stays traceable and the dataset keeps earning its keep long after the report is filed.
Related reading
- From PDF Report to Spatial System
- Metadata Strategy for Earth Data Platforms
- Data Licensing for Geospatial Products
- Spatial data products