# landtrendr-demo — the smallest chain that shows LandTrendr end to end.
#
# WHAT THIS MEASURES, AND WHAT IT DOES NOT
#   Measures: per-pixel NET change in annual median NBR (fitted last minus fitted first)
#   across 2015-2026, plus the start year of the largest-loss segment. Over the Oregon
#   Coast Range checkerboard the strong negatives are clearcuts.
#   Does NOT measure: burned area, forest loss in the Hansen/GFC sense, or anything
#   restricted to forest — there is no forest mask here.
#
#   `magnitude` (band 0, what this recipe styles) is still NET change — but the note that
#   used to sit here, "the crate emits peak-to-trough as band 3; the engine wrapper slices
#   it off", is CLOSED as of 2026-08-19 (convergence LT-03). The engine now emits all four
#   kernel bands, [magnitude, year, rmse, peak_to_trough], and band 3 is the signed
#   peak-to-trough depth the disturbance literature thresholds on. It is measurably the
#   better detector: on Bootleg/MTBS through this same engine path, thresholding band 3 at
#   -0.20 scores recall 0.7772 / precision 0.9474 / kappa 0.6836 against band 0's
#   0.5292 / 0.9631 / 0.4502. This demo keeps styling band 0 ON PURPOSE — a clearcut
#   checkerboard read over 12 years is a net-change picture, and the red/green two-sided
#   palette below needs a SIGNED band (peak-to-trough is non-positive by construction, so
#   it would render as one-sided). A recipe whose question is "where was there a
#   disturbance" should style/threshold band 3 instead.
#
#   `year` dates disturbances ONE YEAR EARLY against LT-rs's own reference — that one is
#   still open, and it is the segment-START convention rather than an arithmetic bug
#   (docs/solutions/compute/landtrendr-rust-engine-e2e-findings.md; naming is convergence
#   decision D2).
#
# EXECUTION: runs on BOTH tiers.
#   * Rust engine: `folia run registry/recipes/landtrendr-demo` — measured green, see README.md.
#   * Browser: `registry/recipes/_runner/?recipe=landtrendr-demo` — the TS/WASM ops
#     (packages/compute/src/ops/{imagery-composite,landtrendr}.ts). The browser LandTrendr
#     is an independent port, NOT the LT-rs kernel the engine calls, so the two tiers are
#     not expected to be numerically identical.
#
# WHY IT EXISTS: derived from change-ensemble with the second detector and the ensemble
# removed, so composite->landtrendr is SINGLE-CONSUMER and therefore fully quotable by the
# gateway (multi-consumer chains are unpriced by design).
#
# KNOWN PLATFORM GAPS this recipe lives with (engine-side, worked around at recipe level only):
#   * No platform/collection filter reaches the grouped composite path (`query:` is threaded
#     only on the `scenes: true` arm), so Landsat 7 scenes cannot be excluded. A stray L7
#     asset that answers HTML instead of a COG is a fatal parse, not a retry. Both were in
#     range on the measured runs and neither fired; the only lever if one does is the calendar.
#   * `max_segments: 8` with 12 annual bands is under the lt-rs F-test's `bands > 2*max_segments+1`
#     comfort zone (which wants >= 17). The ladder still walks down to a defined candidate and the
#     measured output is NOT degenerate (see README.md), but the knob is left as authored on
#     purpose: LandTrendr parameter adoption (LT-rs v0.4 + LT-GEE defaults) is a separate,
#     single-transaction change and must not be smuggled in here.

name: landtrendr-demo
version: "0.1"
category: change-detection
themes: [vegetation, forest, disturbance]
family: forest-disturbance-landtrendr
description: >
  Single-chain LandTrendr demo: annual NBR composites (cloud tier) feeding
  the LandTrendr temporal segmenter (client tier). Derived from
  change-ensemble with the second detector and ensemble removed so the
  composite->landtrendr chain is single-consumer and fully quotable.

settings:
  composite_method: median
  limit: 6                       # scenes PER YEAR (imagery_composite 3.0.0), not per window
  max_segments: 8
  recovery_threshold: 0.25

inputs:
  aoi:
    bbox: [-123.57, 45.72, -123.10, 46.07]   # Oregon Coast Range clearcut checkerboard
    crs_target: EPSG:32610
  date_range:
    start: "2015-01-01"
    end: "2026-12-31"
  collection:
    # Canonical concept band codes only. The producer asset keys (nir08/swir22 on MSPC,
    # SR_B5/SR_B7 on LandsatLook) are the DISTRIBUTION's business, not the recipe's —
    # naming them here is what defect 60 was.
    uri: "@usgs/landsat/c2-l2"
    bands: [N, S2]

layers:
  # Annual NBR. imagery_composite resolves the collection, fetches the [N, S2] pair per
  # year, and the band PAIR collapses to a normalized difference = NBR (the dataset owns
  # the SR scale/offset and the fill->NaN).
  compute/nbr-annual:
    type: raster
    uri: "${inputs.collection.uri}"
    params:
      bands: "${inputs.collection.bands}"
      bbox: "${inputs.aoi.bbox}"
      temporal: ["${inputs.date_range.start}", "${inputs.date_range.end}"]
      method: "${settings.composite_method}"
      group_by: year
      limit: "${settings.limit}"
    style:
      palette: rdylgn
      clim: [-0.3, 0.7]

  # Output bands (LT-03, 2026-08-19): FOUR, band-major —
  #   b1 magnitude       net change, fitted[last] - fitted[first] (signed; styled below)
  #   b2 year            START year of the largest-loss segment
  #   b3 rmse            observed-vs-fitted RMSE
  #   b4 peak_to_trough  signed disturbance DEPTH, non-positive by construction
  # `raster_calc` binds them positionally as b1..b4.
  detector/landtrendr:
    type: raster
    compute:
      op: landtrendr
      inputs:
        time_series: { layer: compute/nbr-annual }
      params:
        max_segments: "${settings.max_segments}"
        recovery_threshold: "${settings.recovery_threshold}"
    style:
      band: magnitude
      palette: rdylgn
      clim: [-0.5, 0.15]          # loss red, stable yellow, recovery green

views:
  - name: Change magnitude
    layers: [detector/landtrendr]
  - name: Annual NBR
    layers: [compute/nbr-annual]

dashboard:
  patterns:
    # Year scrubber over the annual NBR cube: step the years the segmentation ran on.
    - slug: time-scrub-years
      slot: temporal
      layer: compute/nbr-annual
    # Click a pixel, see its NBR trajectory — the "show your work" half of the demo.
    - slug: pixel-trajectory-landtrendr
      slot: temporal
      layer: compute/nbr-annual
      element: folia-pixel-inspector

license:
  recipe: Apache-2.0
  output_dataset: CC-BY-4.0
  upstream_data:
    - { source: landsat-c2-l2, license: public-domain }
