Skip to content

Ocotillo OGC API — Backend Changes for AMP Parity #776

Description

@jirhiker

Ocotillo OGC API — Backend Changes for AMP Parity

Status: draft / not committed
Audience: maintainers of the Ocotillo OGC API backend (pygeoapi + PostGIS) at
https://ocotillo-api.newmexicowaterdata.org/ogcapi
Related: DIG connector backend/connectors/ocotillo/ (added in branch
feature/ocotillo-ogcapi-source)

Purpose

DIG now consumes Ocotillo as a data source intended to replace the legacy
NMBGMR AMP API (ampapidev-dot-waterdatainitiative-271000.appspot.com,
DIG source key nmbgmr_amp). As currently published, Ocotillo only exposes
pre-aggregated latest / summary feature collections and no raw
observations
. Because of that, the DIG Ocotillo connector is summary-only:

  • Timeseries output (timeseries_unified, timeseries_separated) produces
    nothing for Ocotillo — it is a logged no-op.
  • Summary output omits several columns that AMP can populate (see below).

This document lists the backend changes required for Ocotillo to reach feature
parity with the AMP API, so the nmbgmr_amp source can be fully retired.

What AMP provides today (the parity target)

The DIG nmbgmr_amp connector uses these AMP endpoints:

AMP endpoint Purpose Shape
locations Site list + metadata point_id, lon/lat/elev, lonlat_datum, altitude_datum, site_id (USGS), alternate_site_id
wells Per-well construction formation, well_depth_ftbgs
waterlevels/manual Full DTW time series paginated list of {DateMeasured, TimeMeasured, DepthToWaterBGS, DepthToWaterBGSUnits, PointID}
waterchemistry Full analyte time series per pointid per analyte: list of {SampleValue, Units, AnalyteMeaning, info.CollectionDate}

The two endpoints that matter most are waterlevels/manual and
waterchemistry: both return every observation over time, which is what
lets DIG compute summary statistics (count, min, max, mean, earliest, latest)
and emit full time series.

What Ocotillo provides today

OGC API - Features, 24 collections. Relevant ones:

Collection Item type Content
water_wells feature (1/well) name, coords, well_depth, nma_pk_welldata, nma_formation_zone (no elevation/datum/USGS id)
water_well_summary feature (1/well) total_water_levels, min_water_level, max_water_level, last_water_level + last_water_level_datetime, water_level_trend_ft_per_year, elevation, well_depth
latest_depth_to_water_wells feature (1/well) latest DTW obs only
major_chemistry_results feature (1/well) latest value per major-ion analyte as static columns + <analyte>_units, latest_chemistry_date, analyte_count
minor_chemistry_wells feature (1/well) latest value per trace analyte as static columns
latest_tds_wells feature (1/well) latest_tds_value, latest_tds_units, latest_tds_observation_date
avg_tds_wells feature (1/well) average TDS

Every collection is one row per well holding a snapshot (latest or
aggregate). None expose per-observation history.

Gaps and required backend changes

Ordered by impact. Items 1–3 are required for parity; 4–5 are correctness/
completeness.

1. Raw observation collections (blocks time series entirely) — REQUIRED

Problem. There is no collection of individual observations, so DIG cannot
produce time series output and cannot compute mean or the earliest
observation for summaries.

Change. Publish per-observation OGC API - Features collections:

  • depth_to_water_observations — one feature per DTW measurement:
    well name/id, observation datetime, depth-to-water value, units.
  • chemistry_observations — one feature per analyte result:
    well name/id, analyte name, value, units, sample/collection datetime.

Requirements.

  • Filterable by well identifier (queryable name, matching water_wells.name,
    e.g. NM-28368) and by datetime (OGC datetime parameter or a queryable
    timestamp property).
  • Support paging (already standard in pygeoapi via limit/offset).
  • Publish in WGS84 GeoJSON (consistent with existing collections).

DIG impact when available. The connector can be switched from summary-only
to the standard pattern (model on nmbgmr): full timeseries + locally computed
count/min/max/mean/earliest/latest. --output-type timeseries_* starts working.

2. Chemistry history (blocks analyte summary stats) — REQUIRED

Problem. major_chemistry_results / minor_chemistry_wells expose only the
latest value per analyte. DIG therefore reports analyte summaries with only
latest_value; nrecords, min, max, mean, earliest_* are blank.

Change. Covered by the chemistry_observations collection in item 1 — it
must include the full history of analyte results, not just the latest. AMP's
waterchemistry returns all samples per analyte; match that.

Analyte coverage. At minimum the analytes DIG maps today (see
backend/connectors/ocotillo/mappings.py): arsenic, bicarbonate, calcium,
carbonate, chloride, fluoride, magnesium, nitrate, ph, potassium, silica,
sodium, specific_conductance, sulfate, tds, uranium.

3. Site metadata on water_wells (degraded site records) — REQUIRED

Problem. water_wells lacks fields AMP's locations provides. Current DIG
behavior:

DIG site field AMP source Ocotillo today
elevation / elevation_units present null (not in water_wells)
horizontal_datum lonlat_datum defaulted to WGS84
vertical_datum altitude_datum blank
aquifer / formation wells.formation nma_formation_zone (partial)
usgs_site_id locations.site_id blank
alternate_site_id present nma_pk_welldata (GUID)
well_depth wells.well_depth_ftbgs present

Change. Add elevation (+ units), horizontal_datum, vertical_datum,
aquifer/formation, and usgs_site_id to water_wells — or expose a
locations-equivalent collection carrying full site metadata that DIG can seed
sites from. Note: water_well_summary already carries elevation, but it is
keyed to wells that have water levels, so it is not a complete site source for
chemistry-only wells.

Coverage note. DIG currently seeds sites from water_wells (all wells).
Wells that have chemistry/water-level data but are absent from water_wells
will not appear. Ensure water_wells (or the chosen site collection) is the
authoritative superset of monitored wells.

4. Nitrate reporting basis (silent value error risk) — REQUIRED for correctness

Problem. AMP reports Nitrate (as N) and DIG applies a 4.427 conversion to
nitrate-as-NO3. The Ocotillo nitrate column basis is undocumented; the
connector currently takes it as-is (factor 1.0). If Ocotillo reports as-N, DIG
will under-report nitrate by ~4.4x.

Change. Document and unit-tag the nitrate basis. Preferably expose the
value with an explicit unit (mg/L as N vs mg/L as NO3) in
<analyte>_units / the observation record so DIG's converter can normalize
correctly. Same care for any analyte reported on an alternate basis
(e.g. bicarbonate/carbonate as CaCO3).

5. TDS history — REQUIRED for TDS parity

Problem. latest_tds_wells is latest-only; avg_tds_wells is a single
average. DIG cannot report TDS count/min/max/earliest.

Change. Include TDS in the chemistry_observations history collection (item
1/2), or add an explicit TDS observation collection. Then the latest/avg
snapshot collections become optional conveniences rather than the only source.

Field-mapping reference (post-parity, expected)

Once observation collections exist, the DIG connector would map like AMP:

Water level (per observation):
depth_to_water_observations.{depth_to_water_bgs, observation_datetime, units, name}
→ DIG {parameter_value, date_measured/time_measured, source_parameter_units, id}.

Analyte (per observation):
chemistry_observations.{value, sample_datetime, units, analyte, name}
→ DIG {parameter_value, date_measured, source_parameter_units, source_parameter_name, id}.

Summary stats (count/min/max/mean/earliest/latest) are then computed by DIG, as
they are for AMP — the backend need not pre-aggregate.

Acceptance criteria (definition of parity)

  • depth_to_water_observations and chemistry_observations collections
    exist, filterable by well name and datetime, returning full history.
  • DIG --output-type timeseries_unified for waterlevels produces a
    non-empty timeseries file from Ocotillo.
  • DIG summary for water levels and for each mapped analyte populates
    nrecords, min, max, mean, earliest_*, latest_* (no blank
    stats columns).
  • Site records carry elevation, horizontal + vertical datum, and USGS id
    where AMP has them.
  • Nitrate (and any CaCO3-basis analytes) carry an explicit reporting basis;
    DIG output matches AMP within rounding for a shared set of wells.
  • For a shared bbox, Ocotillo and nmbgmr_amp summary outputs agree
    (well set, latest values, counts) within expected tolerances.

Until parity lands (current DIG behavior)

  • Ocotillo runs alongside nmbgmr_amp (not a replacement). Toggle with
    --no-ocotillo / --no-nmbgmr-amp.
  • Summary-only. Timeseries requests log a warning and yield nothing.
  • Blank-by-design summary columns: mean, earliest_* (all parameters);
    additionally min, max, nrecords for chemistry and TDS.
  • Site elevation blank; datum defaults to WGS84.
  • Base URL override: env var OCOTILLO_URL.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions