name: landtrendr-recovery
version: "0.1"
category: change-detection
themes: [vegetation, forest, disturbance, recovery, wildfire]
family: forest-disturbance-landtrendr
cites_papers: ["kennedy-2010"]   # reproduces the LandTrendr temporal-segmentation method (Kennedy, Yang & Cohen 2010)
description: >
  Where did the forest change, what year, and how is recovery going — the Kennedy
  recovery-questions deliverable (sdk/011, signed I5+I6) on one LandTrendr-segmented
  NBR trajectory. Runs OFFLINE end to end on a vendored real annual-NBR stack (Landsat
  C2 L2, 2021 Bootleg Fire bbox, 8 years 2017-2024) via local://, so it reproduces on a
  laptop with no network. Three layers answer the three questions from the SAME fitted
  trajectory:
    WHERE  — compute/loss-2021: windowed LandTrendr loss magnitude around the fire year
             (landtrendr_loss_window recovers a multi-year disturbance the single-year
             read under-reports).
    WHEN   — compute/landtrendr band 1 (onset year): the largest-loss year per pixel.
    HOW    — compute/recovery-2024: the fitted NBR change in the last year
             (landtrendr_ftvdiff, target 2024) — positive = regrowth, ~0 = static.
  The cloud/at-scale variant swaps source/nbr for an imagery_composite over @usgs/landsat/
  c2-l2 (see registry/recipes/landtrendr-change); the ops and question-framing are identical.

# ── STATUS: runs green OFFLINE through the Rust engine (folia_engine::run_recipe_file) ──
# Params are LITERAL (not ${settings.*}): the pure-Rust engine has no `${...}` settings
# interpolation yet (that lands with the DSL work), so like tests/parity/recipes/
# landtrendr-frozen this recipe inlines its values. start_year=2017 maps the 8 annual
# bands to 2017-2024; the Bootleg Fire (July 2021) is band index 4. LandTrendr knobs are
# the crate:landtrendr (LT-GEE) validated set except the explicit ones below.

inputs:
  aoi:
    # Bootleg Fire interior (Fremont-Winema NF, southern Oregon), UTM 10N 30 m — the
    # bbox the vendored stack was frozen over (freeze_goldens.py).
    bbox: [-121.49, 42.70, -121.45, 42.73]
    crs_target: EPSG:32610
  date_range:
    start: "2017-01-01"
    end: "2024-12-31"

layers:

  # ── ANNUAL NBR (offline) ────────────────────────────────────────────────────────
  # Vendored 8-band annual-NBR stack (one band per year 2017-2024), read via local:// so
  # the recipe runs with no network. The cloud variant replaces this with imagery_composite
  # over @usgs/landsat/c2-l2 (bands [N, S2] -> normalized difference = NBR); everything
  # downstream is unchanged.
  source/nbr:
    uri: local://data/nbr_annual.tif
    type: raster
    style:
      palette: rdylgn
      clim: [-0.3, 0.7]

  # ── TRAJECTORY SEGMENTATION ─────────────────────────────────────────────────────
  # Output bands: magnitude (fitted last - first, <=0 = loss), year (largest-loss onset).
  # start_year sets the band->year map so the onset band reads calendar years.
  compute/landtrendr:
    type: raster
    compute:
      op: landtrendr
      inputs:
        time_series: { layer: source/nbr }
      params:
        start_year: 2017
        max_segments: 8
        spike_threshold: 0.9
        recovery_threshold: 0.25
    style:
      band: year
      palette: viridis
      clim: [2017, 2024]                  # onset year per pixel

  # ── WHERE + INTENSITY: windowed loss around the fire year ────────────────────────
  # landtrendr_loss_window sums the fitted per-year NBR drops over 2021 ± 1yr, so a
  # disturbance whose spectral drop straddles two annual composites is fully counted
  # (single-year ftvdiff under-reports it). Non-negative; higher = more confident loss.
  compute/loss-2021:
    type: raster
    compute:
      op: landtrendr_loss_window
      inputs:
        time_series: { layer: source/nbr }
      params:
        start_year: 2017
        target_year: 2021
        half_window: 1
        max_segments: 8
        spike_threshold: 0.9
        recovery_threshold: 0.25
    style:
      palette: ylorrd
      clim: [0, 0.6]                       # 0 = no loss (pale), high = confident burn (red)

  # ── HOW IS RECOVERY GOING: fitted NBR change in the last year ────────────────────
  # landtrendr_ftvdiff at 2024 = fitted[2024] - fitted[2023]. On the segmented trajectory
  # this is the recovery slope's last step: positive = regrowth, ~0 = static, negative =
  # still declining. The signed twin of the loss window.
  compute/recovery-2024:
    type: raster
    compute:
      op: landtrendr_ftvdiff
      inputs:
        time_series: { layer: source/nbr }
      params:
        start_year: 2017
        target_year: 2024
        max_segments: 8
        spike_threshold: 0.9
        recovery_threshold: 0.25
    style:
      band: recovery
      palette: rdylgn
      clim: [-0.1, 0.1]                    # recovery green, decline red

views:
  - name: Where + when it burned
    layers: [compute/loss-2021, compute/landtrendr]
  - name: How recovery is going
    layers: [compute/recovery-2024]

dashboard:
  patterns:
    - slug: pixel-trajectory-landtrendr
      slot: temporal
      layer: source/nbr
      element: folia-pixel-inspector

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

# References:
#   Kennedy RE, Yang Z, Cohen WB (2010) RSE 114:2897-2910 (LandTrendr).
#   Recovery-questions framing + detector output contract:
#     research/change-detection/emapr-ensemble/CONTRACT.md
# Ops (single engine, crate:landtrendr kernel):
#   landtrendr, landtrendr_loss_window, landtrendr_ftvdiff — products/sdk/folia-engine
