# ============================================================
# Solar-on-Forest Trajectory Search — Massachusetts (2019-2024)
#
# Click a documented "forest -> built solar" site in MA and the
# pipeline ranks every other AEF embedding pixel in the state by
# how closely its 6-year embedding trajectory matches.
#
# Pipeline:
#
#   AEF v1/annual COGs (Source Coop, tge-labs)
#       2019, 2020, 2021, 2022, 2023, 2024
#       (each: 64-band float32, EPSG:4326, dequantized)
#                |
#                v
#   stack_concat
#       output: 384-band raster
#       bands:  aef_2019_00 ... aef_2024_63
#                |
#                v
#   vector_field_similarity
#       reference_pixel: { row, col }   # selected by click
#       metric: cosine
#       output: single-band similarity in [-1, 1]
#                |
#                v
#   diverging palette overlaid on MA basemap
#
# References:
#   - .tickets/impl/temporal-embedding-change/ (parent ticket)
#   - .tickets/impl/temporal-embedding-change/01-multi-year-embeddings.md
#       (AEF coverage, chip-grid alignment invariant)
#   - .tickets/impl/temporal-embedding-change/02-trajectory-ops.md
#       (op contracts, perf budget, band naming)
#   - reference-sites.json (curated click targets + provenance)
#
# Inspired by Ahmed (Clark University, CGA) — concatenated annual
# foundation-model embeddings + click-to-search.
# ============================================================

name: "Solar-on-Forest Trajectory Search — Massachusetts"
description: >
  Click a documented forest-to-solar reference site and rank every
  Massachusetts pixel by how closely its 2019-2024 AlphaEarth Foundations
  embedding trajectory resembles the reference. All compute runs in the
  browser via @folia/compute (WASM with JS fallback). No server.

  Honest scope: cosine similarity on the stacked trajectory captures
  end-state similarity — "what places look like the clicked reference,
  averaged over 6 years." It does not isolate conversion events. The
  sub-ticket 06 substrate comparison (AEF vs Tessera on real Hansen×USPVDB
  labels) showed both substrates score at chance under this recipe on the
  forest→solar task; the surfaced ranking is best read as "trajectory
  resemblance," not "confirmed forest→solar site." For conversion-isolation
  the recipe needs a Δ-magnitude filter (||emb(2024) − emb(2019)||₂),
  formalised in sub-ticket 07 at multi-scale H3 grids.
  See docs/solutions/recipes/aef-vs-tessera-ma-solar-tree.md for numbers.

settings:
  # Massachusetts state bounding box [west, south, east, north]
  default_bbox: [-73.5, 41.2, -69.9, 42.9]
  theme: light

# ------------------------------------------------------------
# Layers
# ------------------------------------------------------------
#
# Six year-specific AEF source layers, one per trajectory year.
# Each resolves through @folia/compute's `aef://` source resolver
# (see packages/compute/src/io/aef-source.ts) and yields a
# 64-band float32 raster on the same WGS84 chip grid.
#
# The chip-grid alignment invariant (verified in sub-ticket 01)
# guarantees that pixel (row, col) refers to the same WGS84
# location across all six years, which is what makes the
# trajectory stack meaningful.
# ------------------------------------------------------------

layers:
  aef_2019:
    type: raster
    source:
      uri: "aef://v1/annual/2019"
    description: "AlphaEarth Foundations annual embedding, 2019. 64 bands."

  aef_2020:
    type: raster
    source:
      uri: "aef://v1/annual/2020"
    description: "AlphaEarth Foundations annual embedding, 2020. 64 bands."

  aef_2021:
    type: raster
    source:
      uri: "aef://v1/annual/2021"
    description: "AlphaEarth Foundations annual embedding, 2021. 64 bands."

  aef_2022:
    type: raster
    source:
      uri: "aef://v1/annual/2022"
    description: "AlphaEarth Foundations annual embedding, 2022. 64 bands."

  aef_2023:
    type: raster
    source:
      uri: "aef://v1/annual/2023"
    description: "AlphaEarth Foundations annual embedding, 2023. 64 bands."

  aef_2024:
    type: raster
    source:
      uri: "aef://v1/annual/2024"
    description: "AlphaEarth Foundations annual embedding, 2024. 64 bands."

  # ----------------------------------------------------------
  # Trajectory raster: stack the six per-year layers along a
  # new time axis and emit a single (T*D = 6*64 = 384)-band
  # raster. Output bands are named `aef_{year}_{NN}`, sorted
  # chronologically because input keys (`aef_2019` ... ) are
  # already lexicographically chronological.
  # ----------------------------------------------------------
  trajectory:
    type: raster
    compute:
      op: stack_concat
      inputs:
        aef_2019: { layer: aef_2019 }
        aef_2020: { layer: aef_2020 }
        aef_2021: { layer: aef_2021 }
        aef_2022: { layer: aef_2022 }
        aef_2023: { layer: aef_2023 }
        aef_2024: { layer: aef_2024 }
      params:
        # Optional explicit ordering; redundant here because the
        # default sort is already chronological. Listed for
        # readability and to lock the contract.
        time_keys:
          - aef_2019
          - aef_2020
          - aef_2021
          - aef_2022
          - aef_2023
          - aef_2024

  # ----------------------------------------------------------
  # Similarity surface. The reference pixel is filled in at
  # runtime by the click handler in index.html — it computes
  # (row, col) for the lat/lon of the clicked reference site
  # against the trajectory raster's transform and writes it
  # into params.reference_pixel.
  #
  # Output is a single-band raster `similarity` in [-1, 1]
  # rendered with a diverging palette so that "very similar"
  # (close to 1.0) pops against the average background.
  # ----------------------------------------------------------
  similarity:
    type: raster
    compute:
      op: vector_field_similarity
      inputs:
        trajectory: { layer: trajectory }
      params:
        # Filled in at runtime. Default is the Spencer / St.
        # Joseph's Abbey site (mature solar on former forest);
        # the click handler overrides this when the user picks
        # a different reference site.
        reference_pixel: { row: 0, col: 0 }
        metric: cosine
    style:
      palette: coolwarm
      field: similarity
      clim: [0.4, 1.0]
      fillOpacity: 0.7

# ------------------------------------------------------------
# Views
# ------------------------------------------------------------

views:
  - name: "Click a documented forest-to-solar site"
    layers: [similarity]
    interactions:
      similarity:
        click:
          type: select
          field: similarity
