# ADR-0002: Boolean mapper flags (`sparse_data` / `sampled` / `continuous_*domain`) as the transform interface contract

| Field         | Value       |
|---------------|-------------|
| Kind          | ADR         |
| Status        | Accepted    |
| Decided       | historical  |
| Deciders      | Antoine Berchet |
| Supersedes    | —           |
| Superseded by | —           |

## Context

Transforms in CIF's pipeline exchange data through a shared `mapper` dict, one entry per
`(component, parameter)`. Data at each end of a transform is held in one of two container
types: a gridded `xarray.Dataset` (dims `time, lev, lat, lon`) for full-field data, or a
`pandas.DataFrame` (one row per point) for sparse data such as observations. A precursor and
its successor transform don't necessarily agree on which container is expected on each side,
and some transforms (e.g. Lagrangian footprint models) already interpolate to observation
locations internally, while others (Eulerian models) need pyCIF to insert that interpolation
for them. As the number of transforms and plugins grew, this had to be resolved automatically
for the whole pipeline rather than negotiated ad hoc inside each plugin.

See also: the full mapper-field reference in {doc}`/documentation/plugins/transforms/index`
(docstring of `pycif.plugins.transforms`), and the auto-wiring implementation in
[`pycif/plugins/obsoperators/standard/transforms/utils/init_sparse.py`](https://gitlab.in2p3.fr/satinv/cif/-/blob/devel/pycif/plugins/obsoperators/standard/transforms/utils/init_sparse.py).

## Decision

We represent this state as a handful of independent boolean flags on each mapper entry —
`sparse_data`, `sampled`, `continuous_hdomain`, `continuous_vdomain` (plus `domain` itself) —
not as a single `geometry_kind` enumeration. During pipeline construction, `init_sparse.py`
compares a precursor's flags against its successor's and automatically inserts the matching
conversion transform (`sparse2sample`, `array2sampled`, `regrid`, `vertical_interpolation`,
`time_interpolation`) whenever they disagree.

## Consequences

- Plugin/transform authors declare a handful of independent flags per mapper entry; the
  pipeline — not the plugin — is responsible for inserting the right conversion between a
  precursor and its successor.
- Because the flags are independent booleans rather than a closed enum, invalid combinations
  aren't ruled out by construction — only by the explicit checks each transform's init step
  happens to encode (e.g. `init_sparse.py` raises `CifError("Sparse to sample should not
  happen")` if `sampled` is set on a target that expects sparse input).
- Adding a genuinely new data representation means threading a new boolean through every
  place `sparse_data` / `sampled` / `continuous_*domain` are checked (`init_sparse.py`,
  `aggreg_deaggreg_inout.py`, `fetch_inoutputs.py`, and others), rather than adding one enum
  member — this is real, ongoing maintenance cost, not a hypothetical one.
