# catalog/operations/hydrology/tiered_catchment_network.yaml

id: hydrology_tiered_catchment_network
name: Tiered Catchment Network
description: >
  Build a two-tier hierarchical catchment network with constraints learned from
  the data/drainages/ pipeline analysis. Creates Z1 (major drainages from HUC12)
  and Z2 (sub-drainages from NHDPlus catchments) with limits on catchment count,
  upstream distance, and name cleaning to prevent over-extension.

  Key improvements over catchment_network:
  - Catchment count limits (max 200) to prevent outliers like South Fork Weber (1,424)
  - Upstream distance limits (20km) for spatial constraints
  - Name cleaning to extract base names from combined forms
  - HUC12 merging by normalized name
  - Explicit river exclusions for major regional rivers
version: 1.0.0
category: hydrology
type: composite
default_implementation: native

# External data sources required
sources:
  gnis:
    connector: usgs_gnis
    params:
      feature_classes: [valley, basin, stream, flat, arroyo, channel, creek, river, fork, branch]
      include_historical: false
    description: GNIS point features for drainage names

  huc12:
    connector: usgs_wbd
    params:
      level: 12
    description: HUC12 watershed boundaries

  huc10:
    connector: usgs_wbd
    params:
      level: 10
    description: HUC10 watershed boundaries (optional, for context)
    required: false

  catchments:
    connector: usgs_nhd
    params:
      layer: catchment
      resolution: high
    description: NHDPlus HR catchment polygons

  flowlines:
    connector: usgs_nhd
    params:
      layer: flowline
      resolution: high
    description: NHDPlus HR flowlines for upstream tracing

inputs:
  - name: gnis
    type: vector
    format: geojson
    geometry: Point
    description: GNIS features for drainage names and label placement
    required: true

  - name: huc12
    type: vector
    format: geoparquet
    geometry: Polygon
    description: HUC12 watershed boundaries
    required: true

  - name: catchments
    type: vector
    format: geoparquet
    geometry: Polygon
    description: NHDPlus catchment polygons
    required: true

  - name: flowlines
    type: vector
    format: geoparquet
    geometry: LineString
    description: NHDPlus flowlines for network tracing
    required: true

# requires: omitted — this is a pure vector op over HUC12/NHD/GNIS/flowlines.
# No raster/elevation concept is consumed. The "catchment" / "drainage" concept
# does not yet exist in registry/concepts/; output concept omitted pending it.

outputs:
  - name: major_drainages
    type: vector
    format: geoparquet
    geometry: Polygon
    description: Z1 major drainage polygons from merged HUC12s
    properties:
      id:
        type: string
        description: Unique drainage identifier
      name:
        type: string
        description: Official drainage name from GNIS
      display_name:
        type: string
        description: Cleaned display name (e.g., "Big Cottonwood Canyon")
      area_km2:
        type: number
        description: Total drainage area in square kilometers
      huc12_count:
        type: integer
        description: Number of HUC12s merged into this drainage
      centroid:
        type: point
        description: Geographic centroid for label placement
      pole_of_inaccessibility:
        type: point
        description: Visual center for label placement

  - name: sub_drainages
    type: vector
    format: geoparquet
    geometry: Polygon
    description: Z2 sub-drainage polygons from NHDPlus catchments
    properties:
      id:
        type: string
        description: Unique sub-drainage identifier
      name:
        type: string
        description: Sub-drainage name from GNIS or flowline
      display_name:
        type: string
        description: Cleaned display name
      parent_id:
        type: string
        description: ID of containing Z1 drainage
      parent_name:
        type: string
        description: Name of containing Z1 drainage
      area_km2:
        type: number
        description: Sub-drainage area in square kilometers
      catchment_count:
        type: integer
        description: Number of catchments in this sub-drainage
      constrained:
        type: boolean
        description: True if catchment count was limited by max_catchments

  - name: hierarchy_edges
    type: vector
    format: geoparquet
    geometry: null
    description: Z1-Z2 parent-child relationships
    properties:
      child_id:
        type: string
      parent_id:
        type: string
      relationship:
        type: string
        enum: [contains, mostly_contains]

  - name: drainage_labels
    type: vector
    format: geojson
    geometry: Point
    description: GNIS geometries optimized for label placement

params:
  # Z1 Configuration (Major Drainages from HUC12)
  huc12_prefixes_to_strip:
    type: array
    default: ["Headwaters ", "Outlet ", "Upper ", "Lower ", "Middle "]
    description: Prefixes to strip when grouping HUC12s by base name

  huc12_exclude_patterns:
    type: array
    default: ["Frontal", "Reservoir", "Jordan River", "Great Salt Lake", "Utah Lake", "Antelope Island"]
    description: HUC12 name patterns to exclude from Z1 (valley floors, lakes)

  min_huc12s_per_canyon:
    type: integer
    default: 1
    min: 1
    max: 10
    description: Minimum number of HUC12s required to form a Z1 drainage

  # Z2 Configuration (Sub-drainages from NHDPlus)
  max_catchments_per_feature:
    type: integer
    default: 200
    min: 10
    max: 5000
    description: >
      Maximum catchments per sub-drainage. Features exceeding this are
      excluded as over-extended (e.g., South Fork Weber had 1,424).

  max_upstream_distance_km:
    type: number
    default: 20.0
    min: 1.0
    max: 100.0
    description: Maximum upstream trace distance in kilometers

  exclude_rivers:
    type: array
    default: ["Provo River", "Weber River", "Ogden River", "Jordan River", "South Fork Weber River"]
    description: Major rivers to exclude from Z2 (regional, not canyon-specific)

  # Name Cleaning
  clean_names:
    type: boolean
    default: true
    description: Apply name cleaning transformations for display

  display_name_overrides:
    type: string
    default: '{"Mill Creek": "Millcreek Canyon", "East Canyon Creek": "East Canyon"}'
    description: JSON string of explicit display name mappings (raw -> display)

  canyon_suffix_mode:
    type: boolean
    default: true
    description: Replace "Creek" with "Canyon" for Z1 display names

  # General
  min_area_km2:
    type: number
    default: 0.5
    min: 0.01
    max: 100
    description: Minimum area for sub-drainages (filters tiny features)

# DAG definition
dag:
  # ============================================================================
  # Z1: Major Drainages from HUC12
  # ============================================================================

  - id: filter_huc12
    op: vector_filter
    inputs: [huc12]
    params:
      expression: "NOT any(pattern in name for pattern in ${huc12_exclude_patterns})"
    description: Remove HUC12s matching exclude patterns (valleys, lakes, reservoirs)

  - id: clean_huc12_names
    op: vector_name_clean
    inputs: [filter_huc12]
    params:
      name_column: name
      output_column: display_name
      prefixes_to_strip: ${huc12_prefixes_to_strip}
      split_on_connector: true
      name_overrides: ${display_name_overrides}
      canyon_suffix: ${canyon_suffix_mode}
    description: Clean HUC12 names for display and grouping

  - id: group_huc12_by_name
    op: vector_group_by_name
    inputs: [clean_huc12_names]
    params:
      name_column: name
      normalize_prefixes: ${huc12_prefixes_to_strip}
      min_group_size: ${min_huc12s_per_canyon}
    description: Group HUC12s by normalized base name

  - id: merge_huc12_groups
    op: vector_merge_by_rules
    inputs: [group_huc12_by_name]
    params:
      merge_by_group: true
      require_adjacency: true
      aggregation:
        area_km2: sum
        huc12_count: count
    description: Merge grouped HUC12s into Z1 drainage polygons

  - id: compute_z1_centroids
    op: vector_centroid
    inputs: [merge_huc12_groups]
    params:
      method: pole_of_inaccessibility
      include_pole_of_inaccessibility: true
    description: Compute visual centers for Z1 label placement

  # ============================================================================
  # Z2: Sub-drainages from NHDPlus
  # ============================================================================

  - id: filter_flowlines
    op: vector_filter
    inputs: [flowlines]
    params:
      expression: "name IS NOT NULL AND name NOT IN ${exclude_rivers}"
    description: Filter to named flowlines, excluding major regional rivers

  - id: trace_upstream
    op: vector_upstream_trace
    inputs: [filter_flowlines, flowlines, catchments]
    params:
      max_distance_km: ${max_upstream_distance_km}
      max_features: ${max_catchments_per_feature}
      dissolve_output: true
    description: Trace upstream for each named flowline with constraints

  - id: filter_z2_by_area
    op: vector_filter
    inputs: [trace_upstream]
    params:
      expression: "area_km2 >= ${min_area_km2}"
    description: Remove sub-drainages below minimum area threshold

  - id: clean_z2_names
    op: vector_name_clean
    inputs: [filter_z2_by_area]
    params:
      name_column: name
      output_column: display_name
      split_on_connector: true
      name_overrides: ${display_name_overrides}
      canyon_suffix: false
    description: Clean sub-drainage names for display

  # ============================================================================
  # Hierarchy: Link Z2 to Z1
  # ============================================================================

  - id: build_hierarchy
    op: vector_spatial_containment
    inputs: [compute_z1_centroids, clean_z2_names]
    params:
      parent_id_column: id
      parent_name_column: display_name
      min_containment_ratio: 0.5
      allow_multiple_parents: false
    description: Build Z1->Z2 parent-child relationships

  # ============================================================================
  # Labels: Extract GNIS geometries for label placement
  # ============================================================================

  - id: filter_gnis_for_labels
    op: vector_spatial_join
    inputs: [gnis, compute_z1_centroids]
    params:
      how: inner
      predicate: intersects
    description: Filter GNIS to features within Z1 drainages

  - id: clean_label_names
    op: vector_name_clean
    inputs: [filter_gnis_for_labels]
    params:
      name_column: name
      output_column: display_name
      split_on_connector: true
      name_overrides: ${display_name_overrides}
    description: Clean GNIS names for label display

  # ============================================================================
  # Final Outputs
  # ============================================================================

  - id: finalize_z1
    op: vector_select
    inputs: [compute_z1_centroids]
    params:
      columns:
        - id
        - name
        - display_name
        - area_km2
        - huc12_count
        - centroid
        - pole_of_inaccessibility
        - geom
    description: Select final Z1 output columns

  - id: finalize_z2
    op: vector_select
    inputs: [build_hierarchy]
    params:
      columns:
        - id
        - name
        - display_name
        - parent_id
        - parent_name
        - area_km2
        - catchment_count
        - constrained
        - geom
    description: Select final Z2 output columns

output: finalize_z2

# Multiple outputs
outputs_map:
  major_drainages: finalize_z1
  sub_drainages: finalize_z2
  hierarchy_edges: build_hierarchy.hierarchy_edges
  drainage_labels: clean_label_names

# Execution hints
execution:
  realtime_max_km2: null  # Always batch (requires full region processing)
  cost_per_km2: 0.0002
  time_per_km2_sec: 0.02
  memory_profile: high  # Large vector processing
  gpu_required: false
  parallelizable: true  # Can split by HUC regions

cache_policy:
  regional_precompute: [wasatch_range, western_us]
  ttl_days: 365
  invalidate_on: [source_update, param_change]

# backends: audited 2026-08-14 (defect 50). NOTHING dispatches this op: no arm in folia-engine
# `dispatch_op`, no `registerOp`/OP_TABLE entry in packages/compute, no `_BUILTIN_OP_MAP`
# key in folia/compute.py, no backend manifest. Declared EMPTY on purpose — an absent
# block would be indistinguishable from one nobody ever wrote.
# this record composes other ops in its `dag:` block rather than naming a kernel — but nothing reads an op record's `dag:` (no expander exists in folia/, packages/compute or folia-engine), so the composition is documentation, not a run path.
backends: {}

display_hints:
  map:
    renderer: maplibre
    type: fill
    paint:
      fill-color: "rgba(30, 144, 255, 0.3)"
      fill-outline-color: "#1E90FF"
  table:
    renderer: tanstack-table

ui:
  icon: layers
  color: "#1E90FF"
  preview:
    type: vector
    layers:
      - output: major_drainages
        style:
          fill: "rgba(30, 144, 255, 0.3)"
          stroke: "#1E90FF"
          stroke_width: 2
      - output: sub_drainages
        style:
          fill: "rgba(100, 149, 237, 0.2)"
          stroke: "#6495ED"
          stroke_width: 1
