id: bulcd
name: BULC-D Change Detection
description: >
  Bayesian Updating of Land Cover — Deviation-based change detection. Splits a multi-band
  annual stack into a reference period and a target period, z-scores each target
  observation against the per-pixel reference baseline (mean/std), bins the deviance, and
  Bayesian-updates P(decrease/stable/increase). Output band order follows the eMapR
  forestChangeEnsemble detector contract, loss_prob FIRST so the `ensemble` op reads it as
  the detector's score: [loss_prob = P(decrease)*100, change_magnitude = P(inc)-P(dec),
  p_decrease, p_increase].
version: 1.1.0
op_identity: builtin:bulcd@1                   # ADR-0065 D4 — bump on numerics change
category: analysis
type: raster-to-raster
aliases: [bulc_d]

inputs:
  - name: time_series
    type: raster
    description: >
      Multi-band annual stack spanning reference + target periods (band-major, one band per
      year). The engine rasters carry no band names, so band years are start_year + index
      (default 2000); the reference period is every band year <= reference_end.
    required: true

# Structural: Bayesian deviation-based change detection on any temporal stack.
# requires: omitted; change magnitude is recipe-specific.

outputs:
  - name: change
    type: raster
    format: cog
    dtype: float32
    description: >
      4 bands: loss_prob (0-100), change_magnitude (-1..1), p_decrease, p_increase.
      NaN where the reference baseline is empty.

params:
  start_year:
    type: integer
    default: 2000
    description: Calendar year of the first band; bands are consecutive annual composites.
  reference_end:
    type: integer
    default: 2017
    description: Last year of the reference period (bands with year <= this are baseline).
  bin_thresholds:
    type: array
    default: [1.0, 2.0]
    description: >
      z-score thresholds [t1, t2] for the 5 deviance bins; a scalar t is read as [t, 2t].

# 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-engine:
    op: bulc_d                        # folia-engine dispatch arm (sdk/011); alias: bulcd
    function: temporal_ops::bulcd_flat
  rust-wasm:
    function: temporal_ops::bulcd_flat
    handle_function: h_bulcd
  js:
    function: bulc_d                  # retiring with the strangler (packages/compute)
