# supply-shed-change — governed folia/0.1 recipe (ADR-0080, docs/spec/SUPPLY_SHED.md).
#
# Given processing facilities (sugar mills, palm crushers), partition the surrounding land to
# each facility as its plausible SUPPLY SHED (capacity-weighted gravity / Huff allocation within
# a haul bound), then compute the from-class -> to-class land-use-change transition matrix inside
# each shed. ONE parametric recipe: a new commodity or region is a different parameter set, NOT
# new code (the falsifiable test of ADR-0080). `folia run` binds this through bind.rs
# (AuthorRecipe -> BoundRecipe, `{param:}` refs substituted) and executes the layer DAG through
# the engine's supply_shed dispatch arms (products/sdk/folia-engine/src/supply_shed/host.rs),
# writing the shed assignment + transition matrix + summary as `.folia/tables/<layer>.parquet`.
#
# PROFILES. The parameter DEFAULTS are the cane-brazil profile (Rio Grande 12-mill cluster,
# Triangulo Mineiro / SP border — the demo's oracle region: demos/sugarcane-sourcing-luc/
# scripts/combined_allocate.py). palm-sumatra is the same graph with different arguments
# (see README.md; benchmark method in research/commodity-intelligence/sourcing-sheds/).
#
# HONEST SCOPE (docs/spec/SUPPLY_SHED.md#validation): plausible MODELED sourcing, not observed
# field-to-mill delivery; beta is unvalidated without observed-flow ground truth; direct LUC only.
#
# Inputs referenced by the defaults are generated by inputs/build_inputs.py (see README).
# Ticket: .tickets/impl/supply-shed-recipe/.

spec_version: folia/0.1
name: supply-shed-change
version: "0.1.0"
description: >
  Facility -> plausible sourcing shed (capacity-weighted Huff allocation of H3 cells within a
  haul bound) -> from-class -> to-class land-use-change transition matrix per shed, plus the
  native-conversion summary. Commodity and region specifics are parameters, not code.

parameters:
  facilities_uri:
    type: string
    description: Facility points (GeoJSON) with an optional capacity column.
    default: "local://inputs/mills_cluster.geojson"
  transitions_uri:
    type: string
    description: >
      Pre-binned per-pixel land-use transitions rolled to H3 (GeoParquet: h3 string, prior int,
      post int, area_ha float; [[adr:0075]]). Res may be finer than the allocation unit; the
      matrix op rolls up via H3 parent.
    default: "local://inputs/transitions_h3.parquet"
  distances_uri:
    type: string
    description: >
      Precomputed drive-time cost table (GeoParquet: facility int, h3 string at the unit res,
      minutes float), used when distance=drivetime. Generated offline by inputs/build_inputs.py
      via OSRM /table. For distance=straightline this layer loads but the allocate op ignores it.
    default: "local://inputs/drivetime_h3.parquet"
  bbox:
    type: array
    items: number
    description: The allocation-universe bounding box [w, s, e, n] in WGS84 lon/lat (straightline only).
    default: [-49.6, -20.6, -48.1, -19.4]
    min_items: 4
    max_items: 4
  method:
    type: string
    description: Allocation rule — huff (capacity-weighted gravity) or voronoi (nearest).
    default: huff
  distance:
    type: string
    description: >
      Distance metric — drivetime (road-network minutes from the distances table; the default,
      the metric road anisotropy makes matter) or straightline (in-engine haversine km over bbox).
    default: straightline
  unit:
    type: string
    description: Allocation unit (h3:N). Res-7 (~5 km2 cells) is the cloud-native default.
    default: "h3:7"
  haul_km:
    type: number
    description: Haul bound (km) for distance=straightline — facilities beyond it do not compete.
    default: 60.0
    minimum: 1
  haul_min:
    type: number
    description: Haul bound (drive-time MINUTES) for distance=drivetime.
    default: 90.0
    minimum: 1
  beta:
    type: number
    description: >
      Huff distance-decay exponent (assign to argmax capacity/dist^beta). NOTE the degeneracy:
      capacity-neutrality (method voronoi, or no capacity_field) gives nearest/Voronoi — NOT
      beta=0, at which distance drops out entirely.
    default: 2.0
    minimum: 0
  capacity_field:
    type: string
    description: Per-facility capacity column in the facilities table (tonnes cane/season here).
    default: capacity_tc
  target_class:
    type: number
    description: The commodity class transitions are screened INTO (MapBiomas 20 = sugar cane).
    default: 20
  native_forest:
    type: array
    items: number
    description: Native-forest prior classes (MapBiomas Collection 10, mapbiomas_legend.py).
    default: [1, 3, 5, 6, 49]
  native_veg:
    type: array
    items: number
    description: Native non-forest vegetation prior classes (savanna, wetland, grassland...).
    default: [4, 10, 11, 12, 13, 50]

layers:

  # ── SOURCE: facility points (mills), capacity column included. ──
  # The sanctioned spelling (ADR-0074 D3): a pointer + the read that consumes it. `mechanism:`
  # names the read because the pointer is a PARAMETER — there is no suffix to infer from.
  facilities:
    type: vector
    description: >
      Facility points with capacity. Row order is the facility index the shed assignment's
      `facility` column refers to.
    uri: { param: facilities_uri }
    mechanism: local-file

  # ── SOURCE: the pre-binned H3 transition table ([[adr:0075]]). ──
  transitions:
    type: table
    description: >
      Per-H3-cell prior->post transition areas (lossless vs the raster kernel — the
      prebinned_equals_per_pixel property). The land-use-change input.
    uri: { param: transitions_uri }
    mechanism: table-query

  # ── SOURCE: precomputed drive-time cost table (facility, h3, minutes). ──
  # Used when distance=drivetime; the straightline arm ignores this edge (see the param docs).
  distances:
    type: table
    description: >
      Per-(facility, cell) one-way road drive-time minutes (OSRM /table, offline). The cell
      universe + candidate costs for drivetime allocation come from here — the "precompute then
      query" twin of the H3 transition table.
    uri: { param: distances_uri }
    mechanism: table-query

  # ── ALLOCATION: the shed assignment. ──
  shed:
    type: table
    description: >
      Per-cell facility assignment: h3, facility (int; -1 = unreachable), score, distance_km
      (km for straightline, drive-time MINUTES for drivetime), n_within_haul (the fixed-radius
      double-count metric, shown not hidden).
    compute:
      op: supply_shed_allocate
      inputs:
        facilities: { layer: facilities }
        distances: { layer: distances }
      params:
        method: { param: method }
        distance: { param: distance }
        unit: { param: unit }
        haul_km: { param: haul_km }
        haul_min: { param: haul_min }
        beta: { param: beta }
        bbox: { param: bbox }
        capacity_field: { param: capacity_field }

  # ── TRANSITIONS: the from->to crosstab per shed. ──
  transitions_matrix:
    type: table
    description: >
      From-class -> to-class area (ha) per shed: zone (facility int; -1 = unallocated area,
      kept — "no shed" is an answer), prior, post, area_ha.
    compute:
      op: raster_transition_matrix
      inputs:
        transitions: { layer: transitions }
        assignment: { layer: shed }
      params:
        mode: prebinned
        output: matrix

  # ── SUMMARY: the honesty roll-up per shed. ──
  shed_summary:
    type: table
    description: >
      Per shed: total target-class area, newly converted target area since the transition
      layer's cutoff, and the native-forest vs other-native-vegetation slices of that
      conversion (the RED III / EUDR question).
    compute:
      op: raster_transition_matrix
      inputs:
        transitions: { layer: transitions }
        assignment: { layer: shed }
      params:
        mode: prebinned
        output: summary
        class_scheme:
          target: { param: target_class }
          native_forest: { param: native_forest }
          native_veg: { param: native_veg }

outputs:
  shed:
    layer: shed
    value_kind: table
    description: Per-cell facility assignment (h3, facility, score, distance_km, n_within_haul).
  transitions_matrix:
    layer: transitions_matrix
    value_kind: table
    description: From-class -> to-class area (ha) per shed.
  shed_summary:
    layer: shed_summary
    value_kind: table
    description: Per-shed target area, new conversion, native-forest / native-veg slices.

# Shelf facets. A governed recipe carries these under `extensions:`, never at the
# top level: AuthorRecipe is deny_unknown_fields, so a top-level `category:` stops
# the recipe binding at all.
extensions:
  folia/category: compliance-reporting
  folia/themes: [agriculture, land-cover]
  folia/family: sourcing-shed-land-use-history
