id: normalize_soil_moisture
name: Normalize Soil Moisture
description: >
  Harmonize in-situ soil moisture observations from heterogeneous networks
  (USCRN, ISMN, SCAN, SNOTEL, etc.) into a common schema:
  (station_id, time, depth_cm, sm_vol, qc_flag, provider_class, nvs_uri, geom).

  Per-source operations:
    - Unit conversion to target_units (m^3/m^3 by default).
    - Depth selection — choose the observation closest to target_depth_cm,
      with vertical interpolation only when adjacent measurements straddle
      the target depth (otherwise drop the row).
    - QC filtering — retain only rows whose qc_flag is in the qc_filter list
      (ISMN flag system: G=good, D=dry, C=cold, M=missing/instrument).
    - Provider-class tagging — preserved from the input source metadata
      (`provider_class: reference|research|citizen`).

  Output rows carry the canonical NVS URI for surface soil moisture
  (SDN:P02::SOIL — UNVERIFIED, confirm against BODC NVS) so downstream
  consumers can resolve across networks.

version: 0.1.0
category: harmonize
type: timeseries-to-timeseries
default_implementation: native

inputs:
  - name: sources
    type: list[timeseries]
    description: >
      List of in-situ soil-moisture timeseries from one or more networks.
      Each source must carry a `provider_class` attribute and station
      metadata (lat, lon, depth_cm, qc_flag).
    required: true

requires:
  concepts: [soil-moisture]  # consumes the soil-moisture concept (in-situ networks)

outputs:
  - name: harmonized
    type: timeseries
    concept: soil-moisture  # emits the normalized soil-moisture concept
    description: >
      Single concatenated timeseries with schema
      (station_id, time, depth_cm, sm_vol, qc_flag, provider_class,
       nvs_uri, geom). Sorted by (station_id, time).

params:
  target_units:
    type: string
    default: "m^3/m^3"
    description: Target SI units for volumetric soil moisture.
  target_depth_cm:
    type: number
    default: 5
    description: >
      Depth (cm) to select / interpolate to. Surface SM is conventionally
      0-5 cm for SMAP cal/val.
  qc_filter:
    type: list[string]
    default: ["G"]
    description: >
      QC flags to retain (ISMN convention: G=good, D=dry, C=cold/snow,
      M=missing). Default keeps only "Good".
  nvs_uri:
    type: string
    default: "SDN:P02::SOIL"
    description: NVS URI tag to attach to all output rows. UNVERIFIED — confirm against vocab.nerc.ac.uk.

uncertainty:
  error_model: bounded
  factors:
    - name: depth_interpolation
      description: >
        Vertical interpolation between adjacent depths assumes monotonic SM
        profile, which fails near wetting fronts and frozen layers.
      severity: medium
      mitigations:
        - "Drop rows when no measurement is within ±2 cm of target_depth_cm."
        - "Flag interpolated rows with `qc_flag=I` for downstream filtering."
    - name: network_qc_heterogeneity
      description: >
        Networks use incompatible QC vocabularies. We map to the ISMN
        G/D/C/M convention; some lossiness in the translation.
      severity: low
      mitigations:
        - "Preserve raw `qc_source` column for audit."
  limitations:
    - "Volumetric soil moisture only; gravimetric / matric potential are out of scope."
    - "Surface (0-10 cm) layer only in v0; root-zone variants are a follow-up."
  methodology_reference: "products/insitu-harmonization/candidates/soil-moisture.md"

# backends: audited 2026-08-14 (defect 50). NOTHING dispatches this op: no arm in folia-engine
# `dispatch_op`, no `registerOp`/OP_TABLE entry in packages/compute, no `_BUILTIN_OP_MAP`
# key in folia/compute.py, no backend manifest. Declared EMPTY on purpose — an absent
# block would be indistinguishable from one nobody ever wrote.
# the declared native function does not exist and `_BUILTIN_OP_MAP` has no key for it.
backends: {}
