Skip to contents

fresh 0.27.5

channel_width_min_bypass block schema change: replace single stream_order key with stream_order_min + stream_order_max to express child-order ranges. The single key was modeling only stream_order = 1 (bcfp parity), but frs_order_child always supported a range — child_order_min / child_order_max parameters. The new pair maps directly. Validator rejects the legacy stream_order key with unknown keys to flag stale rules.yaml files for regeneration.

Unblocks link’s dimensions.csv::rear_stream_order_child_min / rear_stream_order_child_max columns. No behaviour change for frs_order_child itself; this is a YAML schema rename only.

fresh 0.27.4

Allow optional distance_max key inside channel_width_min_bypass block in rules YAML — link uses it to cap the bypass to the lower N metres of each direct-trib BLK (i.e. frs_order_child(distance_max = N)). Validator accepts a positive numeric scalar; rejects zero / negative / non-numeric values.

fresh 0.27.3

Restore stream_order_max predicate in frs_order_child() — the previous patch (0.27.2) removed the s.stream_order = s.stream_order_max filter because the column doesn’t exist on fresh.streams. That was the wrong fix: the predicate is load-bearing for direct-child semantics. Without it, multi-order BLKs like a named creek that grows from order-1 headwaters to order-3 mouth (e.g. Divan Creek, BLK 356353593 in HORS) get their order-1 headwater segments credited as direct trib mouths of large rivers — they are not.

Fix: derive stream_order_max per BLK on the fly via MAX(stream_order) OVER (PARTITION BY blue_line_key) in a CTE, then apply s.stream_order = s.stream_order_max to bound the bypass to mouth-side reaches only. Same answer as bcfishpass.streams.stream_order_max (which is a stored column there). Verified against HORS BT — eliminates ~95 km of spurious credit on multi-order BLK headwaters.

fresh 0.27.2

Fix frs_order_child() SQL referencing nonexistent column s.stream_order_max. fresh.streams has stream_order and stream_order_parent from FWA but no stream_order_max — the 0.27.0 SQL failed at execution against any real database (only the SQL-shape unit tests passed, and they asserted the broken predicate). Drop the broken predicate; default both child_order_min and child_order_max to 1L when neither is set, which matches bcfishpass’s hardcoded stream_order = 1 predicate exactly. Caller-passed bounds still apply unchanged.

Adds a regression test that scans the emitted SQL across every parameter combination for stream_order_max and fails if it reappears.

fresh 0.27.1

Patch follow-up to 0.27.0. .frs_load_rules validator now accepts the channel_width_min_bypass predicate that link emits to drive frs_order_child() post-classify (link#96 wiring). Validates that the field is a named mapping with stream_order and stream_order_parent_min integer scalars. Without this patch, link’s emitted rules.yaml fails fast at frs_params() with unknown predicates: channel_width_min_bypass before any classification runs.

fresh 0.27.0

Closes #158. New frs_order_child(): post-classification UPDATE that credits direct order-1 (or other) tributaries of order-N+ rivers with <label> = TRUE. Captures the bcfishpass rearing bypass currently hard-coded in model/02_habitat_linear/sql/load_habitat_linear_<sp>.sql for BT/CH/CO/ST/WCT — predicate cw.channel_width >= rear_channel_width_min OR (s.stream_order_parent >= 5 AND s.stream_order = 1).

  • Parametric: parent_order_min (default 5L matches bcfp), child_order_min/max, distance_max. Generic on label column (rearing / lake_rearing / wetland_rearing).
  • Idempotent + additive: <label> IS NOT TRUE guard. Never adds rearing above an inaccessible barrier: accessible = TRUE guard.
  • No existing-caller behaviour change. Wiring into link’s pipeline is a follow-up PR (link side: lnk_pipeline_classify calls frs_order_child post-classify per-species, gated on dimensions.csv::rear_stream_order_bypass).

fresh 0.26.0

Closes #191. .frs_connected_waterbody Phase 2 (upstream spawn) cluster + lake-adjacency gate is now opt-out via a lake_adjacent parameter on the rule. Default TRUE keeps bcfishpass-parity behaviour byte-identical for any caller not setting the knob; passing lake_adjacent: no in <sp>.spawn_connected.lake_adjacent runs the relaxed Phase 2 that credits any spawn-eligible segment upstream of and accessible from a qualifying rearing waterbody. Used by callers that need to credit upstream tributary reaches not in lake-adjacent clusters (e.g. NewGraph default-bundle SK methodology — link#87). .frs_load_rules validator updated to accept the new key.

Cross-host testing pattern (m4 ↔︎ m1 over Tailscale, with Sys.setenv() overrides for PG_*_SHARE to point fresh tests at a host’s local Docker fwapg when the bcfp tunnel is unavailable) documented in CLAUDE.md “Testing on alternate hosts.”

fresh 0.25.0

Two bcfishpass-parity fixes surfaced during link’s WSG-coverage expansion (link’s MORR + KISP runs, 2026-04-30).

frs_cluster phase-1 + confluence-boost interaction (#186). .frs_cluster_both previously excluded on-spawning rearing segments from the clustering CTE. Removing those segments could shift cluster_minimums into the confluence-boost zone (DRM < confluence_m) and validate clusters that bridge_gradient should deny. bcfishpass’s path-2 keeps on-spawning segments in clusters, so its cluster_min stays high and the confluence boost doesn’t fire. Phase-1 protection now lives only in the final UPDATE step — on-spawning segments retain label_cluster = TRUE regardless of cluster outcome, but the cluster boundaries used for phase-2 / phase-3 testing are unchanged. Reproduction case: MORR ST cluster 502 (Nado Creek + tributary 360704379, 12.58% gradient between trib confluence and downstream spawning) — bcfp denies the trib’s rearing-eligible segments; fresh now matches.

.frs_trace_downstream averaged FWA gradient (#187). .frs_trace_downstream previously used whse_basemapping.fwa_downstreamtrace, an iterator returning FWA-original linear_feature_id rows with feature-averaged gradients. Localized barriers on a sub-piece of a long FWA feature (e.g. a 7 m, 84% lake-outlet drop inside an otherwise flat 3 km feature) are invisible to that approach. Switched to a FWA_Downstream predicate join against the broken streams table arg — same pattern .frs_cluster_both phase-3 already uses. Localized gradients produced by frs_network_segment() are now visible to the trace. New required columns in origins_sql: wscode_ltree, localcode_ltree (.frs_connected_waterbody is the only caller; updated). Reproduction case: KISP SK at Kitwancool Lake — bcfp’s model/02_habitat_linear/sql/load_habitat_linear_sk.sql correctly stops at the lake-outlet drop; fresh now matches.

Both fixes tighten link’s bcfishpass-config parity rollup. No API changes for end users; .frs_trace_downstream is an internal helper. NEWS bump from 0.24.1 → 0.25.0 because behaviour changes are observable in frs_habitat_classify / frs_cluster / frs_habitat outputs for any caller running connectivity or cluster-aware classifications.

fresh 0.24.1

Refresh bundled inst/extdata/parameters_habitat_rules.yaml to match link’s current default-bundle output. The bundled sample had drifted to pre-in_waterbody / pre-area_only shape (last synced 2026-04-12) while link’s bundles evolved through three #69 phases. Sync recovers the canonical sample for any frs_habitat() caller using fresh’s bundled defaults.

Reframe vignettes/habitat-pipeline.Rmd as a primitives walkthrough. Header note up front directs production users to frs_habitat() and link’s rule-based pipeline; vignette body unchanged — primitives are still useful for debugging, custom pipelines, and understanding what frs_habitat() does internally.

No behaviour change to any exported function.

fresh 0.24.0

Add area_only: true flag to rule grammar — decouples bucket-flag derivation from the main rear predicate (#182).

Today, a waterbody_type: L or W rule placed in a species’ rear: list does two things at once: triggers fresh to derive a lake_rearing / wetland_rearing predicate (the per-segment flag that drives lake_rearing_ha / wetland_rearing_ha area rollups), AND OR’s into the main rear predicate (the per-segment rearing flag, which drives linear rearing_km). The two effects are coupled, so a user can’t say “credit the polygon area but exclude polygon-mainlines from linear km.”

This release adds area_only: true on a rule. When set, fresh uses the rule’s predicate to derive the corresponding bucket flag (lake_rearing or wetland_rearing) but does not include the rule in the OR-chain that builds the main rear predicate. Stream-edge rules (with in_waterbody: false from #180) decide what counts as linear; L/W rules with area_only: true decide what polygons contribute area without double-counting the polygon-mainline as linear.

  • area_only: true on a waterbody_type: L|W rule → rule contributes to lake_rear / wetland_rear predicate derivation only, excluded from main rear predicate.
  • area_only: false or absent → today’s behaviour (rule contributes to both). Backward compatible.
  • Validator: requires waterbody_type: L or W (no bucket flag to drive on stream-edge rules or waterbody_type: R); rejects non-logical / NA / length>1 values.
  • 13 new tests across test-frs_params.R (validator) and test-frs_habitat_predicates.R (decoupling). 167 PASS in those files (was 154). Full suite green.

Coordinates with link#69 phase 2lnk_rules_build() will emit area_only: true on L/W polygon rule blocks driven by per-species rear_lake_area_only / rear_wetland_area_only columns in dimensions.csv. Combined with the polygon-rule edge_types_explicit: [1000, 1100] filter (mainlines only), this expresses the use-case-2 model: linear excludes polygon-mainlines, area still rolls up.

fresh 0.23.1

Hotfix on top of 0.23.0 — register in_waterbody with the rules-YAML validator so emitted rules pass loading.

.frs_load_rules() validates each rule entry’s keys against an allowlist of known predicates (valid_predicates in R/frs_params.R). 0.23.0 added the predicate at the SQL emission layer (.frs_rule_to_sql()) but missed adding it to the allowlist, so a rules YAML carrying in_waterbody: was rejected by .frs_load_rules() before SQL emission ever ran. This release adds in_waterbody to valid_predicates.

  • 1 new test: .frs_load_rules accepts a YAML with in_waterbody: true|false on edge-type and waterbody-type rules (113 PASS in test-frs_params.R, was 112).
  • No behaviour change beyond unblocking the predicate that 0.23.0 introduced.

fresh 0.23.0

Add in_waterbody boolean predicate to the rule grammar (#180).

The rule grammar in parameters_habitat_rules.yaml already had positive predicates for selecting segments inside polygon waterbodies (waterbody_type: R/L/W) but no symmetric way to restrict a rule to segments outside any waterbody. That left the stream-edge rule families silent on polygon membership, so a stream rule like edge_types_explicit: [1000, 1100, 2000, 2300] matched both true streams and the same-coded centerlines that thread through wetland/lake/river polygons — overlapping with the polygon rules on those segments and double-classifying.

This release adds in_waterbody: false | true as the natural complement to waterbody_type:. Stream-edge rules can now express “this classification only applies outside polygon footprints,” which is the semantic complement of the existing polygon rules. Together the two predicates partition the network into stream-edge classifications and polygon-classifications with no overlap.

  • in_waterbody: false → adds s.waterbody_key IS NULL to the rule’s AND chain.
  • in_waterbody: true → adds s.waterbody_key IS NOT NULL.
  • absent → no constraint (pre-in_waterbody behaviour, backward compatible).
  • Composes with waterbody_type: <letter> — the positive predicate already implies IS NOT NULL, so the two together are redundant rather than contradictory.
  • Validation: non-logical / NA / length>1 values raise an error at .frs_rule_to_sql() time.
  • 5 new tests under test-frs_params.R (112 in that file, was 107). Full suite green.

Coordinates with link#69lnk_rules_build() will emit in_waterbody: false on stream-edge rule blocks once this lands.

fresh 0.22.0

frs_habitat_overlay() simplified — drop format and long_value_col parameters; accept only the canonical source-table shape (#177).

  • Canonical shape: one row per (segment × species), with join keys in by, the species code in species_col (default "species_code"), and one indicator column per habitat type. Indicator coercion accepts integer 1, text 'true'/'t'/'1' (case + whitespace insensitive), boolean.
  • Dropped paths (breaking, pre-1.0): format = "wide" per-species-suffix layout (spawning_sk, rearing_sk) and format = "long" (habitat_type rows + habitat_ind indicator). Neither had current production consumers — the wide-suffix layout was scoped for direct reads of bcfishpass.streams_habitat_known (never integrated); the long format was link’s read of bcfishpass’s pre-2026-04-26 CSV (bcfishpass moved to a different shape on 2026-04-26).
  • New parameter: species_col (default "species_code"). Was added in PR #176’s first attempt as an additive bolt-on; this release lands it as the only path.
  • Non-canonical sources: transform first via a SQL view, R pivot, or upstream adapter (e.g., link’s forthcoming lnk_ingest_bcfishpass()), then call overlay against the canonical-shape view. Shape-translation lives with the consumer; fresh stays a thin SQL adapter.
  • Bridge mode (bridge = NULL) unchanged — orthogonal to source shape.
  • Tests: dropped wide-suffix and long-format paths; canonical-shape integration tests exercise integer + text + boolean indicators, additive guard, custom species_col, custom by, and bridge mode.

Coordinated link release (0.12.0) updates the call site in lnk_pipeline_classify.

fresh 0.21.0

frs_habitat_overlay() rename + 3-way bridge join. Pre-1.0 cleanup driven by review of v0.20.0; no deprecation alias.

  • Param renames (breaking):
    • tableto (matches fresh’s “destination” convention)
    • knownfrom (matches “source” convention; also detaches from the “known habitat” provenance framing — function is the abstract overlay mechanism)
  • New bridge = NULL parameter for 3-way joins. When supplied, the SQL becomes to ← bridge ← from with range containment: bridge.downstream_route_measure >= from.downstream_route_measure AND bridge.upstream_route_measure <= from.upstream_route_measure. Lets to tables keyed by id_segment (e.g. fresh.streams_habitat) overlay from sources keyed by (blue_line_key, drm) ranges.
  • Range columns auto-stripped from by in bridge modedownstream_route_measure / upstream_route_measure are handled by the range predicates, so they don’t need to be (and shouldn’t be) in the equality by clause.
  • frs_habitat_classify()’s known = NULL shortcut dropped (also breaking). Convenience hid configurability now that overlay has format + bridge knobs. Caller pattern is now explicitly two-step: frs_habitat_classify() then frs_habitat_overlay().
  • Reads as a sentence: frs_habitat_overlay(from = X, to = Y, bridge = Z) — “overlay flags from X to Y via this bridge.” Domain-agnostic — bridge isn’t required to be fresh.streams; any segments table providing id_segment + range columns works (lake centerlines, wetland shorelines, future cottonwood-pinned segmentations).

Surfaced in link#55 when fresh.streams_habitat (keyed by id_segment only) needed overlay from user_habitat_classification (keyed by (blue_line_key, drm) with range [drm, urm]).

Tests: 52 (was 43 in v0.20.0) — added bridge schema validation, SQL shape verification, range-containment integration test on a 3-segment fixture.

fresh 0.20.0

frs_habitat_overlay() accepts long-format known-habitat tables in addition to the original wide-format. Backward-compatible: the new format arg defaults to "wide".

  • New args: format = c("wide", "long") and long_value_col = "habitat_ind".
  • Long-format expects: join keys (per by) + species_code + habitat_type + a boolean / text indicator column. Each row is one (segment × species × habitat_type) tuple. Matches link’s user_habitat_classification.csv shape, which is the bcfishpass user_habitat_classification.csv source format (before bcfishpass pivots it to wide for streams_habitat_known).
  • Indicator accepts boolean OR text — 'TRUE' / 't' / 'true' all qualify. Non-matching values (e.g. 'FALSE', NULL) skip.
  • Up-front validation: long-format known table must have all required columns (join keys + species_code + habitat_type + indicator). Errors clearly if missing.
  • Wide-format path unchanged. 11 new tests covering long-format unit + integration; previous wide tests all still pass (42 total on frs_habitat_overlay).

Surfaced in link#55 — link’s user_habitat_classification table is loaded long-format by lnk_pipeline_prepare. Forcing a pivot in link to satisfy the wide-format assumption was special-case overhead. Now fresh handles either shape so callers use whichever they have.

fresh 0.19.0

Decompose frs_habitat_classify() into composable pieces; rename frs_habitat_known()frs_habitat_overlay(). Pre-1.0, breaking changes accepted to set the right shape before external use.

  • New exported frs_habitat_predicates(sp_params) — pure-R helper that takes a single species’ params and returns a named list of SQL boolean predicates (spawn, rear, lake_rear, wetland_rear) ready to embed in CASE WHEN <pred> THEN TRUE ELSE FALSE END. Extracted from frs_habitat_classify()’s per-species loop. Testable without a DB; 33 unit tests cover both rules-YAML and CSV-ranges paths, edge_type filters, and the lake/wetland W-rule gating.
  • frs_habitat_classify() shrinks from 551 → ~447 lines and gains a known = NULL parameter — when supplied, calls frs_habitat_overlay() automatically as a post-step. Rule-based classification + observation overlay can now be a single call.
  • Breaking: frs_habitat_known() renamed to frs_habitat_overlay(). The mechanism is overlay (additive OR-in); “known” presupposed provenance the function doesn’t enforce. No deprecation alias — pre-1.0, no external users.

fresh 0.18.0

  • New frs_habitat_overlay() — stitches known-habitat boolean flags from a wide-format lookup table INTO an existing streams_habitat classified by frs_habitat_classify(). Mirrors the bcfishpass blend of model output (habitat_linear_<sp>) with manual / observation-based knowns (streams_habitat_known) into the published streams_habitat_linear integer encoding. Purely additive (FALSE → TRUE only). Per-species columns {habitat}_{species_lower} matched against the species code in streams_habitat; missing per-species columns skip with a verbose message rather than erroring. Compound join key parameterisable via by (default c("blue_line_key", "downstream_route_measure")). Surfaced as a need in link#55 — link’s pipeline loaded user_habitat_classification.csv for break-points + barrier overrides but never propagated its flags into fresh.streams_habitat. After this lands, link’s lnk_pipeline_classify can call frs_habitat_overlay() as a post-step.

fresh 0.17.1

  • Params validator accepts wetland_ha_min predicate on rear rules with waterbody_type: W — mirrors the existing lake_ha_min / waterbody_type: L rule. The classifier in 0.17.0 already read wetland_ha_min from rear rules to filter the fwa_wetlands_poly join; the validator predicate allowlist hadn’t been extended so rules YAML with the key was rejected. Surfaced in link#51 when lnk_rules_build() started emitting waterbody_type: W rules with the threshold.

fresh 0.17.0

  • frs_habitat_classify() now honours the waterbody_type: L / waterbody_type: W entries under a species’ rear: rules in the rules YAML. Previously the lake_rearing / wetland_rearing booleans were set whenever a segment fell in the species’ rear channel-width window and matched a lake/wetland waterbody_key — regardless of whether the species had a lake/wetland rear rule declared. Now the flag is FALSE unless the species has the matching rule, and the rule’s optional lake_ha_min / wetland_ha_min threshold filters the polygon join to exclude waterbodies below that area (#165). Surfaced in link#51 when every species in link’s bcfishpass and default bundles produced bit-identical lake_rearing_ha totals despite the bundles declaring different rear rules.

fresh 0.16.0

  • frs_habitat_classify() output schema gains a wetland_rearing boolean column, mirroring lake_rearing but joined to whse_basemapping.fwa_wetlands_poly on waterbody_key (#164). Additive schema change — existing callers are unaffected; lake_rearing semantics unchanged. Prerequisite for link’s compound rearing rollup (link#51).

fresh 0.15.0

  • Remove unused frs_fish_habitat() and frs_fish_obs(). Both were BC-specific fetchers hard-coded to bcfishpass/bcfishobs table names and had no callers in fresh or link. If BC-specific fetchers become useful later they belong in link where the domain context lives (#162)
  • Exported function count: 41 → 39.
  • Add missing @param barrier_overrides docstring on frs_habitat_classify() — the parameter was already in the signature but absent from roxygen so the rendered help didn’t describe link’s main integration point.
  • Docs role-clarity refresh: README + CLAUDE.md ecosystem tables updated to describe link’s full scope; pipeline wording split into fish-habitat (link → fresh) and land-cover-change (fresh → flooded → drift); CLAUDE.md version + architecture file list brought current.

fresh 0.14.0

  • Add frs_barriers_minimal() — reduce barrier points to the downstream-most per flow path via fwa_upstream() self-join. Extracts the bcfishpass “non-minimal removal” pattern from link’s compare pipeline into a reusable fresh function. Typical reduction ~27,000 raw gradient barriers → ~700 minimal on a full watershed group (#160)

fresh 0.13.8

Three-phase cluster connectivity for rearing.

  • Phase 1: on-spawning segments (both rearing AND spawning) excluded from clustering — always valid. Prevents over-removal of rearing on spawning streams (#153)
  • Phase 3: FWA_Downstream() on the broken streams table (mainstem only) replaces fwa_downstreamtrace() on raw FWA. Finds spawning downstream of rearing clusters with path gradient + distance constraints (#153)
  • BULK CH rearing +6.0% → +2.6%, BT rearing +1.3% → -2.2%. All species within 5% across 4 WSGs

fresh 0.13.7

Apply bridge gradient along downstream path in frs_cluster.

  • frs_cluster() downstream check now applies bridge_gradient and bridge_distance segment-by-segment along the fwa_downstreamtrace path. Upstream check remains boolean — FWA_Upstream returns tributaries that interleave with mainstem in row_number() ordering, making path gradient unreliable on branching networks (#153)

fresh 0.13.6

Support spawn_connected rules for waterbody-adjacent spawning.

  • Parse spawn_connected block in rules YAML — permissive spawn thresholds for segments in the downstream trace from waterbody outlets. Own validation separate from spawn/rear rules (#154)
  • Additive step in .frs_connected_waterbody(): accessible segments in the trace meeting spawn_connected thresholds get spawning = TRUE even if they failed standard classification. BULK SK spawning -9.6% to -0.7% vs bcfishpass (#154)

fresh 0.13.5

Fix lake outlet ordering and extract reusable downstream trace.

  • Fix multi-BLK lake outlet selection: use wscode_ltree network topology instead of downstream_route_measure (meaningless across BLKs). BULK SK spawning -22.6% to +0.1% vs bcfishpass (#147)
  • Fix cumulative distance partitioning: partition by waterbody_key instead of blue_line_key so distance accumulates per lake, not per BLK (#147)
  • Extract .frs_trace_downstream() — reusable downstream trace with distance cap and gradient stop (#147)
  • Rename .frs_connected_spawning() to .frs_connected_waterbody() — parameterized for any waterbody type (#147)
  • waterbody_type: L now includes reservoirs (fwa_manmade_waterbodies_poly) via shared .frs_waterbody_tables() helper — the FWA table split is digitization origin, not ecology (#147)

fresh 0.13.4

Index input tables for standalone frs_habitat_classify performance.

  • frs_habitat_classify() now indexes input tables before the access gating loop — 35x speedup when called directly (bypassing frs_habitat) (#150)
  • .frs_index_working() is now idempotent with IF NOT EXISTS — safe to call multiple times on the same table (#150)

fresh 0.13.3

Two-phase connected spawning and multi-label breaks.

  • Two-phase connected spawning for lake-rearing species (SK, KO) matching bcfishpass v0.5.0: downstream trace from lake outlets (3km cap, gradient stop) + upstream cluster with lake polygon proximity (ST_DWithin). Auto-dispatched when rearing rules include waterbody_type: L (#147)
  • Multiple labels per break position preserved — a gradient_15 and a falls at the same measure both survive in streams_breaks (#145)

fresh 0.13.2

Preserve multiple labels per break position.

  • Multiple labels at the same (blue_line_key, downstream_route_measure) now survive in streams_breaks — a gradient_15 and a falls at the same measure are both preserved. Enables per-barrier overrides, access reporting, and habitat reporting by barrier type (#145)

fresh 0.13.1

Persist gradient barriers and downstream-only distance fix.

  • to_barriers parameter on frs_habitat() — persist gradient barriers table with ltree enrichment for link’s lnk_barrier_overrides() (#143)
  • connected_distance_max distance filter now downstream-only with correct fwa_upstream direction (#133)
  • Fix aoi + wsg interaction — character aoi ANDed with WSG filter instead of replacing it (#141)

fresh 0.13.0

Distance filter, aoi fix, and measure rounding.

  • connected_distance_max distance filter now downstream-only — upstream spawning has no distance cap, matching bcfishpass v0.5.0 SK spawning logic. Uses DRM difference (same-BLK) and ST_Distance Euclidean (cross-BLK) (#133)
  • Fix aoi replacing wsg filter instead of being additive — frs_habitat(wsg = "ADMS", aoi = "edge_type != 6010") now correctly scopes to ADMS, not province-wide (#141)
  • measure_precision parameter on frs_network_segment() and frs_habitat() — controls break measure rounding. Default 0 (integer, matching bcfishpass). Breaks table rounded and deduped before splitting (#135)
  • Auto-skip gradient/channel_width inheritance on waterbody_type: L/W rules (#131)

fresh 0.12.9

Post-cluster distance filter, measure rounding, and lake threshold auto-skip.

  • connected_distance_max now caps habitat EXTENT from connected segments (not just search distance). Post-cluster filter removes individual segments beyond the cap. SK spawning 112 km → ~74 km (#133)
  • measure_precision parameter on frs_network_segment() and frs_habitat() — controls decimal places for break measure rounding. Default 0 (integer, matching bcfishpass). Breaks table rounded and deduped before splitting (#135)
  • Auto-skip gradient/channel_width inheritance on waterbody_type: L/W rules — lake/wetland flow lines are routing lines (#131)

fresh 0.12.8

Lake/wetland threshold auto-skip and connected distance cap.

  • Auto-skip gradient/channel_width CSV inheritance on waterbody_type: L and waterbody_type: W rules — lake/wetland flow lines are routing lines, channel dimensions are meaningless (#131)
  • connected_distance_max predicate in rules YAML — caps network distance from connected habitat when requires_connected is present. SK/KO spawning capped at 3km from rearing lake (#133)

fresh 0.12.7

Replace observations with barrier_overrides.

  • barrier_overrides parameter replaces observations on frs_habitat() and frs_habitat_classify() — accepts a pre-computed table of (blue_line_key, downstream_route_measure, species_code) from link. Fresh skips matched barriers without counting, thresholds, or date filters (#129)
  • Remove observation counting SQL and observation_* columns from parameters_fresh.csv — fish passage interpretation belongs in link, not the network engine

fresh 0.12.6

Multi-class gradient barrier detection.

  • frs_break_find(classes =) — single-pass multi-class gradient detection matching bcfishpass v0.5.0. Tags every vertex with its gradient class, groups consecutive same-class vertices into islands, places one barrier at each class transition. Catches transitions WITHIN steep sections that boolean above/below missed (#127)
  • frs_break_find(blk_filter =) — restrict to main flow lines (blue_line_key = watershed_key). Default TRUE (matches bcfishpass)
  • frs_habitat() now calls frs_break_find() once with all gradient classes instead of looping per threshold. Simpler, faster, more barriers detected.

fresh 0.12.5

Observation-based access override.

  • observations parameter on frs_habitat() and frs_habitat_classify() — fish observations upstream of gradient/falls barriers override access gating per species. Thresholds from parameters_fresh.csv: observation_threshold, observation_date_min, observation_buffer_m, observation_species (#69)
  • Defaults match bcfishpass v0.5.0: BT >= 1 obs (all salmonids count), CH/CO/SK/PK/CM/ST >= 5 obs (species-specific), since 1990, 20m buffer
  • ADMS: BT accessible +103%, CH/CO +10% with bcfishobs.observations

fresh 0.12.4

Post-segmentation gradient control.

  • gradient_recompute parameter on frs_network_segment() and frs_habitat()TRUE (default) recomputes gradient from DEM vertices after splitting; FALSE inherits parent segment gradient, matching bcfishpass v0.5.0 behavior (#124)
  • frs_col_generate(exclude =) — skip named columns from regeneration (generic, reusable)

fresh 0.12.3

SK/KO spawning requires connected rearing lake.

  • requires_connected predicate in rules YAML — spawning segments must be spatially connected to rearing via frs_cluster(). No rearing lake = no spawning (#120)
  • New .frs_run_connectivity() orchestrates both rearing cluster checks (cluster_rearing) and spawning connectivity checks (requires_connected + cluster_spawning) after classification
  • SK/KO: cluster_spawning = TRUE with 3km bridge distance. KO added to parameters_fresh.csv
  • ADMS sub-basin: SK spawning 15 → 0 (correct — no lakes >= 200 ha)

fresh 0.12.2

Fix short barrier detection.

  • frs_break_find() gains min_length parameter (default 0) — keeps all gradient islands regardless of length. A 30m waterfall at 20% gradient is a real barrier but was silently dropped by the old 100m minimum. Pass min_length = 100 to restore pre-0.12.2 behavior (#118)
  • ADMS sub-basin at 15%: 137 barriers (was 98, +39 recovered)

fresh 0.12.1

Per-rule threshold overrides in habitat rules YAML.

  • Rules can now specify gradient: [min, max] and channel_width: [min, max] that override CSV inheritance per rule. Missing fields still inherit from CSV when thresholds: true (#116)
  • Bundled YAML updated: all waterbody_type: R rules now have channel_width: [0, 9999] — skips channel_width_min on river polygons (matching bcfishpass v0.5.0 pattern where river polygon widths are unreliable)

fresh 0.12.0

Habitat eligibility rules format (Phase 1).

  • YAML-based habitat rules for multi-rule species — each species gets a list of rules per habitat type joined by OR, each rule is AND of predicates. Replaces the single-rule-per-CSV-row limitation (#113)
  • Bundled inst/extdata/parameters_habitat_rules.yaml with NGE defaults for 11 species (synced from link/inst/extdata/parameters_habitat_dimensions.csv)
  • frs_params(rules_yaml =) loads rules YAML (default = bundled, NULL = skip rules for backward compat)
  • frs_habitat(rules =) pass-through (NULL = bundled, FALSE = disable, string path = custom file)
  • thresholds: false per rule — wetland-flow carve-out pattern where a rule bypasses CSV gradient/channel_width inheritance
  • Default behavior change: SK rearing now lake-only (area >= 200 ha); PK/CM rearing = 0 (explicit rear: []); CO/BT/CH/ST/WCT/RB rearing expanded to include river polygons + wetland-flow segments. Pass rules = FALSE to restore pre-0.12.0 behavior.
  • Phase 2 MAD support deferred to #114

fresh 0.11.1

Gradient barrier label format precision fix.

  • New gradient_NNNN label format — 4-digit zero-padded basis points (threshold × 10000) preserves precision for fractional thresholds. 0.05gradient_0500, 0.0549gradient_0549, 0.15gradient_1500. Resolution: 1 basis point (#110)
  • Fixes silent precision loss in 0.11.0 where as.integer(thr * 100) collapsed 0.05 and 0.0549 both to gradient_5, causing distinct biological thresholds to share a single break
  • New .frs_validate_gradient_thresholds() errors on out-of-range, excess precision, NA, or label-collision inputs (catches the bug class going forward)
  • Parser .frs_access_label_filter() accepts BOTH new format and legacy gradient_N for backward compat with user-supplied labels via frs_break_find(label = "gradient_15")

fresh 0.11.0

Sub-segment gradient resolution via auto-derived breaks.

  • breaks_gradient parameter on frs_habitat() — generates gradient breaks at biologically meaningful thresholds derived from spawn_gradient_max + rear_gradient_max in parameters_habitat_thresholds.csv. Three modes: NULL (default auto-derive), numeric vector (explicit override), numeric(0) (disable, 0.10.0 behavior) (#101)
  • Default behavior change: frs_habitat() now produces ~30% more segments on typical sub-basins (ADMS test: 312 → 409). The added breaks are at biologically meaningful gradient thresholds and give frs_cluster() the resolution it needs to detect within-segment steep sections that would otherwise be hidden by averaging
  • Pass breaks_gradient = numeric(0) to restore 0.10.0 behavior (only species access thresholds)

fresh 0.10.0

Network cluster connectivity analysis.

  • frs_cluster() — generic cluster connectivity validation. Groups adjacent segments sharing one label, checks if another label exists upstream, downstream, or both on the network. Disconnected clusters set to FALSE. Uses ST_ClusterDBSCAN for spatial clustering and fwa_downstreamtrace() for downstream trace with gradient bridge and distance cap (#107)
  • Per-species cluster config in parameters_fresh.csvcluster_rearing, cluster_direction, cluster_bridge_gradient, cluster_bridge_distance, cluster_confluence_m. Anadromous species opt in; resident species do not
  • direction = "both" evaluates upstream and downstream independently, keeps clusters valid in either direction

fresh 0.9.0

Flexible AOI, configurable access gating, and feature indexing.

  • frs_habitat() accepts any AOI — sf polygons, WHERE clauses, ltree filters — not just WSG codes. Add species and label params for custom runs (#96)
  • gate parameter on frs_habitat_classify() — set FALSE to classify raw habitat potential without access restrictions (#98)
  • blocking_labels parameter — configurable which labels restrict access. Default "blocked". Set c("blocked", "potential") for conservative analysis. Only "blocked" and gradient_N block by default; everything else passes through
  • frs_feature_find() — locate any point features on the network (crossings, observations, stations) with col_id for feature identity (#92)
  • frs_feature_index() — index upstream/downstream relationships between segments and features as ID arrays (#93)
  • frs_break_find() slimmed to gradient-only. Point/table modes moved to frs_feature_find()
  • Province-wide run completed: 229 WSGs, 5.98M segments
  • Classify growth penalty fixed: constant-time DELETE by watershed_group_code (#91)

fresh 0.8.0

Unified pipeline, domain-agnostic network segmentation, and major performance improvements.

New functions

  • frs_network_segment() — domain-agnostic network segmentation. Extracts, enriches, breaks at any point sources, assigns id_segment. One table, one copy of geometry.
  • frs_habitat_classify() — long-format habitat classification. One row per segment x species with accessible, spawning, rearing, lake_rearing. Species-specific accessibility via break label filtering.
  • frs_feature_find() — locate any point features on the network (crossings, observations, stations). Replaces frs_break_find() points/table modes.
  • frs_feature_index() — index upstream/downstream relationships between segments and features. Stores feature ID arrays per segment.

Pipeline changes

Performance

  • Island-based gradient barriers: 85% fewer barriers than interval method (#86)
  • Accessibility recycled by threshold group: 5 queries → 2 for species sharing thresholds (#89)
  • Classify growth penalty fixed: constant time DELETE by watershed_group_code (#91)
  • Breaks table indexed with ltree GIST for cross-BLK queries: 15x speedup
  • Province-wide: 229 WSGs, 5.98M segments completed

Other

  • id_segment for unique sub-segment identity after breaking (#82)
  • col_blk + col_measure on break sources for configurable column names (#80)
  • .frs_sql_num() for locale-safe numeric SQL literals
  • Docker-based local fwapg tuned for M4 Max Pro (128GB/16 cores)

fresh 0.7.0

Local Docker fwapg instance and generic network-referenced classification via break_sources.

  • Replace falls parameter with break_sources list across pipeline (frs_habitat(), frs_habitat_partition(), frs_habitat_access()) — accepts any number of point tables with label, label_col, and label_map for flexible classification (#70)
  • Add label and source columns to breaks table for provenance tracking
  • Ship inst/extdata/falls.csv (3,294 barrier falls) and inst/extdata/crossings.csv (533k crossings) with data-raw/ refresh scripts
  • Add Docker-based local fwapg: containerized PostGIS + loader with GDAL/psql/bcdata (#63)
  • Fix Phase 2 sequential mode to reuse conn instead of calling frs_db_conn()

fresh 0.6.0

Parallelized multi-WSG habitat pipeline — 2.5x speedup over sequential on BULK.

  • Add frs_habitat() — orchestrator: run the full habitat pipeline across watershed groups with furrr parallelism (#61)
  • Add frs_habitat_partition() — generic partition prep (extract, enrich, pre-compute breaks). Accepts any AOI, not just WSG codes
  • Add frs_habitat_access() — gradient + falls barrier computation at a threshold, deduplicated across species sharing the same access_gradient_max
  • Add frs_habitat_species() — classify one species with pre-computed access and habitat breaks
  • Both Phase 1 (partition prep across WSGs) and Phase 2 (species classification) parallelize via furrr::future_map() when workers > 1
  • Benchmark scripts and logs in scripts/habitat/

fresh 0.5.0

Watershed-group habitat pipeline — run the full pipeline across all species in a WSG.

  • Add where parameter to frs_extract() for SQL predicate filtering, ANDed with aoi when both provided (#60)
  • Add frs_wsg_species() to look up species presence and bcfishpass view names per watershed group from bundled wsg_species_presence.csv
  • Add data-raw/pipeline_wsg.R — runs full habitat pipeline per species per WSG with timing (baseline: 20 min for BULK, 7 species, 32K segments)

fresh 0.4.1

  • Add frs_categorize() for priority-ordered boolean-to-category collapse — mapping codes for QGIS, reporting, gq style registry (#57)
  • Refine habitat pipeline vignette: code below each narrative section with function links, 2x2 scenario grid, access barriers on habitat map

fresh 0.4.0

Coho habitat pipeline — classify habitat on the database at any scale. New vignette proves the workflow on the Byman-Ailport subbasin of the Neexdzii Kwa.

  • Add frs_col_join() for generic lookup table enrichment — channel width, MAD, upstream area, any key (#51)
  • Add frs_edge_types() lookup helper with bundled FWA edge type CSV (41 codes from GeoBC User Guide)
  • Add to param to frs_network() — write results to DB working tables instead of pulling to R, scales to any number of watershed groups (#53)
  • Add where param to frs_classify() — scope classification by SQL predicate for edge-type-aware and accessibility-filtered habitat modelling
  • Add where and append params to frs_break_find() table mode — filter points table and combine multiple break sources (gradient + falls) in one breaks table (#38)
  • Add exclude_edge_types param to frs_point_snap() — only exclude subsurface flow (1425) by default, not wetland connectors (1410) (#52)
  • Bundle bcfishpass habitat parameter CSVs and fresh-specific access gradient thresholds (#54)
  • Add coho habitat pipeline vignette: extract → enrich → break (gradient + falls) → classify (accessible, spawning, rearing, lake rearing) → aggregate → scenario comparison

fresh 0.3.1

  • Add from and extra_where params to waterbody specs in frs_network() for filtering waterbodies to those connected to habitat streams (#49)
  • Network traversal table configurable via .frs_opt("tbl_network") (#44)

fresh 0.3.0

Server-side habitat model pipeline — replaces ~34 bcfishpass SQL scripts with 4 composable functions. See the function reference for details.

  • Add frs_extract() for staging read-only data to writable working schema (#36)
  • Add frs_break() family (find, validate, apply, wrapper) for network geometry splitting via ST_LocateBetween and fwa_slopealonginterval gradient sampling (#38)
  • Add frs_classify() for labeling features by attribute ranges, break accessibility (via fwa_upstream), and manual overrides — pipeable for multi-label classification (#39)
  • Add frs_aggregate() for network-directed feature summarization from points (#40)
  • Add frs_col_generate() to convert gradient/measures/length to PostgreSQL generated columns — auto-recompute after geometry changes (#45)
  • Add .frs_opt() for configurable column names via options() — foundation for spyda compatibility (#44)
  • All write functions return conn invisibly for consistent |> chaining

fresh 0.2.0

CRAN release: 2020-05-29

  • Breaking: All DB-using functions now take conn as the first required parameter instead of ... connection args. Create a connection once with conn <- frs_db_conn() and pass it to all calls. Enables piping: conn |> frs_break() |> frs_classify() (#35)

fresh 0.1.0

CRAN release: 2019-10-21

  • Multi-blue-line-key support for frs_watershed_at_measure() and frs_network() via upstream_blk param (#20)
  • Add frs_watershed_at_measure() for watershed polygon delineation with subbasin subtraction
  • Add frs_network() unified multi-table traversal function replacing per-type fetch functions
  • Add frs_default_cols() with sensible column defaults for streams, lakes, crossings, fish obs, and falls
  • Add upstream_measure param for network subtraction between two points on the same stream
  • Add frs_waterbody_network() for upstream/downstream lake and wetland queries via waterbody key bridge
  • Add wscode_col, localcode_col, and extra_where params for custom table schemas
  • Add frs_check_upstream() validation for cross-BLK network connectivity
  • Add blue_line_key and stream_order_min params to frs_point_snap() for targeted snapping via KNN (#16, #17, #7, #18)
  • Add stream filtering guards: exclude placeholder streams (999 wscode) and unmapped tributaries (NULL localcode) from network queries; include_all to bypass. Subsurface flow (edge_type 1410/1425) kept in network results (real connectivity) but excluded from KNN snap candidates (#15)
  • Add frs_clip() for clipping sf results to an AOI polygon, with clip param on frs_network() for inline use (#12)
  • Add frs_watershed_split() for programmatic sub-basin delineation from break points — snap, delineate, subtract with stable blk/drm identifiers (#31)
  • Security hardening: quote string values in SQL, validate table/column identifiers, clear error on missing PG env vars, gitignore credential files (#19)
  • Input type validation on all numeric params
  • Add subbasin query vignette with tmap v4 composition
  • Fix ref CTE to always query stream network, not target table

Initial release. Stream network-aware spatial operations via direct SQL against fwapg and bcfishpass. See the function reference for details.