# catalog/operations/raster/ndvi.yaml

id: raster_ndvi
name: NDVI
description: >
  Compute Normalized Difference Vegetation Index (NDVI) from NIR and Red bands.
  NDVI = (NIR - Red) / (NIR + Red). Values range from -1 to 1, where higher
  values indicate more vegetation.
version: 1.0.0
category: raster
type: raster-to-raster
default_implementation: native

inputs:
  - name: nir
    type: raster
    format: geotiff
    description: Near-infrared band raster
    required: true
  - name: red
    type: raster
    format: geotiff
    description: Red band raster
    required: true

requires:
  bands: [red, nir]  # cross-sensor — works on any sensor with red + nir common-names

outputs:
  - name: ndvi
    type: raster
    format: cog
    dtype: float32
    index: NDVI  # formula artifact (registry/indices/ndvi.yaml short_name);
                 # NDVI is an index, not a phenomenon — no concept slug.
                 # See OPERATIONS.md #outputs-concept-vs-index
    description: NDVI raster with values from -1 to 1

params: {}

granularity:
  min_resolution_m: 10
  max_resolution_m: 1000
  recommended_resolution_m: 30
  output_resolution: inherit
  minimum_bands: 2
  requires_continuous: true
  suitable_aoi_km2:
    min: 0.01
    max: 500000
  realtime_max_km2: 500

uncertainty:
  error_model: propagated
  factors:
    - name: atmospheric_effects
      description: "Uncorrected atmosphere causes NDVI bias, especially at low sun angles"
      severity: medium
      mitigations:
        - "Use surface reflectance (L2A) instead of top-of-atmosphere (L1C)"
        - "Apply atmospheric correction before computing NDVI"
    - name: sensor_calibration
      description: "Cross-sensor NDVI values are not directly comparable"
      severity: low
      mitigations:
        - "Use same sensor for time series comparisons"
        - "Apply cross-calibration coefficients between sensors"
  limitations:
    - "Saturates in dense vegetation (NDVI > 0.8)"
    - "Sensitive to soil background in sparse vegetation"

execution:
  realtime_max_km2: 500
  cost_per_km2: 0.0005
  time_per_km2_sec: 0.3
  memory_profile: low
  profile:
    cpu: low
    memory: low
    io: medium
  scaling:
    model: linear
    parallelizable: true
    min_chunk_km2: 1

cache_policy:
  ttl_days: 30
  invalidate_on: [source_update]

# backends: audited 2026-08-14 (defect 50). A key means a runtime that DISPATCHES this op —
# folia-engine `dispatch_op` (products/sdk/folia-engine/src/lib.rs), a `registerOp`/OP_TABLE
# entry in packages/compute, `_BUILTIN_OP_MAP` in folia/compute.py, or a backend manifest
# (folia/backends/*/backend.yaml).
backends:
  rust-wasm:
    function: core_ops::raster_ndvi
    handle_function: h_raster_ndvi
  js:
    function: raster_ndvi
  python:
    function: geo.raster.ndvi
    dispatch: folia/compute.py _BUILTIN_OP_MAP

display_hints:
  map:
    renderer: maplibre
    palette: vegetation
    opacity: 0.8
  info:
    fields: [ndvi]
    format: "{value}"
    section: Vegetation

ui:
  icon: feather
  color: "#228B22"
