recipe: safety/openates
version: 0.1.0-r0
instance: openates-alta

inputs:
  aoi:
    name: Little Cottonwood Canyon, Alta, UT
    bbox: [-111.700, 40.535, -111.550, 40.625]   # WGS84 [W, S, E, N]
    crs_target: EPSG:32612                        # UTM 12N
    mgrs_tile: "12T VL"
    area_km2: 112.0

  scale_mode: regional_10m   # one of {global_30m, regional_10m}

  dem:
    source: 3dep-lidar-dtm                        # bare-earth lidar
    stac_endpoint: "https://planetarycomputer.microsoft.com/api/stac/v1"
    asset_key: data
    resample_to_m: 10                             # operational resolution; native 2m too costly for full AOI
    fallback: glo30_minus_potapov                 # global mode would use this

  forest:
    source: sentinel-2-l2a
    sub_recipe: forest/canopy_density_s2          # ← genuinely new Folia primitive
    stac_endpoint: "https://planetarycomputer.microsoft.com/api/stac/v1"
    seasonal_windows:
      summer: { start: 2025-07-01, end: 2025-09-15, label: leaf-on }
      winter: { start: 2025-12-15, end: 2026-02-28, label: leaf-off }   # cross-check coniferous
    max_cloud_cover_pct: 20
    indices: [ndvi, ndre, ndmi]                   # NDVI baseline; red-edge + moisture for sparse alpine canopy
    density_transform: linear_clamp_2_98          # 2nd–98th percentile NDVI → 0–100% canopy
    license: copernicus-open

  glacier_mask:
    source: rgi_v7
    enabled: false                                # Wasatch has none — confirms recipe optional handling

  autoates:
    upstream: "github.com/AutoATES/AutoATES-v2.0"
    upstream_license: GPL-3.0
    upstream_commit: HEAD                         # pinned per-run in evidence bundle
    parameters:
      pra:
        slope_threshold_min: 28                   # degrees
        slope_threshold_max: 60
        ruggedness_threshold: 0.4
        forest_density_max_pct: 60                # PRA suppressed above this canopy cover
      flowpy:
        alpha_threshold_deg: 25
        exp_persistence: 8
      ates:
        forest_dampen_density_pct: 30
        min_polygon_area_m2: 5000

stages:
  - id: define_aoi
    op: geo:define_aoi
    inputs:
      bbox: $.inputs.aoi.bbox
      crs_target: $.inputs.aoi.crs_target
    out: aoi_polygon

  - id: fetch_dem
    op: dem:fetch_3dep_dtm
    inputs:
      aoi: aoi_polygon
      collection: $.inputs.dem.source
      target_resolution_m: $.inputs.dem.resample_to_m
    out: dem_raster

  - id: fetch_s2_summer
    op: optical:s2_seasonal_composite
    inputs:
      aoi: aoi_polygon
      window: $.inputs.forest.seasonal_windows.summer
      max_cloud_cover_pct: $.inputs.forest.max_cloud_cover_pct
      reducer: median
      bands: [B02, B03, B04, B05, B08, B8A, B11, B12, SCL]
    out: s2_summer

  - id: fetch_s2_winter
    op: optical:s2_seasonal_composite
    inputs:
      aoi: aoi_polygon
      window: $.inputs.forest.seasonal_windows.winter
      max_cloud_cover_pct: $.inputs.forest.max_cloud_cover_pct
      reducer: median
      bands: [B02, B03, B04, B05, B08, B8A, B11, B12, SCL]
    out: s2_winter

  - id: derive_canopy_density
    op: forest:canopy_density_s2
    inputs:
      summer: s2_summer
      winter: s2_winter
      indices: $.inputs.forest.indices
      transform: $.inputs.forest.density_transform
      align_to: dem_raster
    out: canopy_density

  - id: run_autoates
    op: hazard:autoates_v2
    inputs:
      dem: dem_raster
      forest_density: canopy_density
      glacier_mask: null
      parameters: $.inputs.autoates.parameters
      upstream_commit: $.inputs.autoates.upstream_commit
    out:
      class_raster: ates_class
      pra: pra
      overhead: overhead_exposure

  - id: polygonize
    op: geo:polygonize_classes
    inputs:
      raster: ates_class
      class_labels: [non_avalanche, simple, challenging, complex, extreme]
      simplify_epsilon_m: 5
      min_area_m2: $.inputs.autoates.parameters.ates.min_polygon_area_m2
    out: ates_polygons

  - id: tile
    op: tiles:pmtiles_from_parquet
    inputs:
      polygons: ates_polygons
      attributes: [class, area_m2, mean_slope, mean_canopy]
      max_zoom: 14
    out: ates_pmtiles

  - id: validate
    op: validation:against_references
    inputs:
      ates_polygons: ates_polygons
      references:
        - { id: uac_forecast_zone_salt_lake, role: forecast_zone_overlap }
        - { id: udot_lcc_avalanche_paths,   role: known_path_overlap, status: requested }
    out: validation_report

  - id: evidence_bundle
    op: provenance:sign_bundle
    inputs:
      artifacts:
        - ates_class
        - ates_polygons
        - pra
        - overhead_exposure
        - ates_pmtiles
      sources:
        - dem_raster
        - s2_summer
        - s2_winter
        - canopy_density
      validation: validation_report
      recipe_hash: auto
    out: evidence_bundle

outputs:
  primary:
    - ates_class            # COG, uint8
    - ates_polygons         # GeoParquet
    - ates_pmtiles          # PMTiles
  show_your_work:
    - pra                   # COG, float32
    - overhead_exposure     # COG, uint8
    - canopy_density        # COG, uint8 % cover
  provenance:
    - evidence_bundle       # JSON

license:
  recipe: Apache-2.0
  output_dataset: CC-BY-4.0
  upstream_code: GPL-3.0   # AutoATES v2 wrapped not forked
  disclaimer: |
    Research-grade reproduction of AutoATES v2.0. Not a substitute for
    professional avalanche assessment. Mirrors Parks Canada / DOC NZ /
    Alberta Parks disclaimer language.
