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 inCASE WHEN <pred> THEN TRUE ELSE FALSE END. Extracted fromfrs_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 aknown = NULLparameter — when supplied, callsfrs_habitat_overlay()automatically as a post-step. Rule-based classification + observation overlay can now be a single call.- Breaking:
frs_habitat_known()renamed tofrs_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.
- New
frs_habitat_overlay()— stitches known-habitat boolean flags from a wide-format lookup table INTO an existingstreams_habitatclassified byfrs_habitat_classify(). Mirrors the bcfishpass blend of model output (habitat_linear_<sp>) with manual / observation-based knowns (streams_habitat_known) into the publishedstreams_habitat_linearinteger encoding. Purely additive (FALSE → TRUEonly). Per-species columns{habitat}_{species_lower}matched against the species code instreams_habitat; missing per-species columns skip with a verbose message rather than erroring. Compound join key parameterisable viaby(defaultc("blue_line_key", "downstream_route_measure")). Surfaced as a need in link#55 — link's pipeline loadeduser_habitat_classification.csvfor break-points + barrier overrides but never propagated its flags intofresh.streams_habitat. After this lands, link'slnk_pipeline_classifycan callfrs_habitat_overlay()as a post-step.
- Params validator accepts
wetland_ha_minpredicate on rear rules withwaterbody_type: W— mirrors the existinglake_ha_min/waterbody_type: Lrule. The classifier in 0.17.0 already readwetland_ha_minfrom 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 whenlnk_rules_build()started emittingwaterbody_type: Wrules with the threshold.
frs_habitat_classify()now honours thewaterbody_type: L/waterbody_type: Wentries under a species'rear:rules in the rules YAML. Previously thelake_rearing/wetland_rearingbooleans were set whenever a segment fell in the species' rear channel-width window and matched a lake/wetlandwaterbody_key— regardless of whether the species had a lake/wetland rear rule declared. Now the flag isFALSEunless the species has the matching rule, and the rule's optionallake_ha_min/wetland_ha_minthreshold 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-identicallake_rearing_hatotals despite the bundles declaring different rear rules.
frs_habitat_classify()output schema gains awetland_rearingboolean column, mirroringlake_rearingbut joined towhse_basemapping.fwa_wetlands_polyonwaterbody_key(#164). Additive schema change — existing callers are unaffected;lake_rearingsemantics unchanged. Prerequisite for link's compound rearing rollup (link#51).
- Remove unused
frs_fish_habitat()andfrs_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_overridesdocstring onfrs_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.
- Add
frs_barriers_minimal()— reduce barrier points to the downstream-most per flow path viafwa_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)
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) replacesfwa_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
Apply bridge gradient along downstream path in frs_cluster.
frs_cluster()downstream check now appliesbridge_gradientandbridge_distancesegment-by-segment along thefwa_downstreamtracepath. Upstream check remains boolean —FWA_Upstreamreturns tributaries that interleave with mainstem inrow_number()ordering, making path gradient unreliable on branching networks (#153)
Support spawn_connected rules for waterbody-adjacent spawning.
- Parse
spawn_connectedblock 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 meetingspawn_connectedthresholds getspawning = TRUEeven if they failed standard classification. BULK SK spawning -9.6% to -0.7% vs bcfishpass (#154)
Fix lake outlet ordering and extract reusable downstream trace.
- Fix multi-BLK lake outlet selection: use
wscode_ltreenetwork topology instead ofdownstream_route_measure(meaningless across BLKs). BULK SK spawning -22.6% to +0.1% vs bcfishpass (#147) - Fix cumulative distance partitioning: partition by
waterbody_keyinstead ofblue_line_keyso 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: Lnow includes reservoirs (fwa_manmade_waterbodies_poly) via shared.frs_waterbody_tables()helper — the FWA table split is digitization origin, not ecology (#147)
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 (bypassingfrs_habitat) (#150).frs_index_working()is now idempotent withIF NOT EXISTS— safe to call multiple times on the same table (#150)
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 includewaterbody_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)
Preserve multiple labels per break position.
- Multiple labels at the same
(blue_line_key, downstream_route_measure)now survive instreams_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)
Persist gradient barriers and downstream-only distance fix.
to_barriersparameter onfrs_habitat()— persist gradient barriers table with ltree enrichment for link'slnk_barrier_overrides()(#143)connected_distance_maxdistance filter now downstream-only with correctfwa_upstreamdirection (#133)- Fix
aoi+wsginteraction — character aoi ANDed with WSG filter instead of replacing it (#141)
Distance filter, aoi fix, and measure rounding.
connected_distance_maxdistance 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
aoireplacingwsgfilter instead of being additive —frs_habitat(wsg = "ADMS", aoi = "edge_type != 6010")now correctly scopes to ADMS, not province-wide (#141) measure_precisionparameter onfrs_network_segment()andfrs_habitat()— controls break measure rounding. Default0(integer, matching bcfishpass). Breaks table rounded and deduped before splitting (#135)- Auto-skip gradient/channel_width inheritance on
waterbody_type: L/Wrules (#131)
Post-cluster distance filter, measure rounding, and lake threshold auto-skip.
connected_distance_maxnow 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_precisionparameter onfrs_network_segment()andfrs_habitat()— controls decimal places for break measure rounding. Default0(integer, matching bcfishpass). Breaks table rounded and deduped before splitting (#135)- Auto-skip gradient/channel_width inheritance on
waterbody_type: L/Wrules — lake/wetland flow lines are routing lines (#131)
Lake/wetland threshold auto-skip and connected distance cap.
- Auto-skip gradient/channel_width CSV inheritance on
waterbody_type: Landwaterbody_type: Wrules — lake/wetland flow lines are routing lines, channel dimensions are meaningless (#131) connected_distance_maxpredicate in rules YAML — caps network distance from connected habitat whenrequires_connectedis present. SK/KO spawning capped at 3km from rearing lake (#133)
Replace observations with barrier_overrides.
barrier_overridesparameter replacesobservationsonfrs_habitat()andfrs_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 fromparameters_fresh.csv— fish passage interpretation belongs in link, not the network engine
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 callsfrs_break_find()once with all gradient classes instead of looping per threshold. Simpler, faster, more barriers detected.
Observation-based access override.
observationsparameter onfrs_habitat()andfrs_habitat_classify()— fish observations upstream of gradient/falls barriers override access gating per species. Thresholds fromparameters_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
Post-segmentation gradient control.
gradient_recomputeparameter onfrs_network_segment()andfrs_habitat()—TRUE(default) recomputes gradient from DEM vertices after splitting;FALSEinherits parent segment gradient, matching bcfishpass v0.5.0 behavior (#124)frs_col_generate(exclude =)— skip named columns from regeneration (generic, reusable)
SK/KO spawning requires connected rearing lake.
requires_connectedpredicate in rules YAML — spawning segments must be spatially connected to rearing viafrs_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 = TRUEwith 3km bridge distance. KO added toparameters_fresh.csv - ADMS sub-basin: SK spawning 15 → 0 (correct — no lakes >= 200 ha)
Fix short barrier detection.
frs_break_find()gainsmin_lengthparameter (default0) — 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. Passmin_length = 100to restore pre-0.12.2 behavior (#118)- ADMS sub-basin at 15%: 137 barriers (was 98, +39 recovered)
Per-rule threshold overrides in habitat rules YAML.
- Rules can now specify
gradient: [min, max]andchannel_width: [min, max]that override CSV inheritance per rule. Missing fields still inherit from CSV whenthresholds: true(#116) - Bundled YAML updated: all
waterbody_type: Rrules now havechannel_width: [0, 9999]— skips channel_width_min on river polygons (matching bcfishpass v0.5.0 pattern where river polygon widths are unreliable)
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.yamlwith NGE defaults for 11 species (synced fromlink/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: falseper 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. Passrules = FALSEto restore pre-0.12.0 behavior. - Phase 2 MAD support deferred to #114
Gradient barrier label format precision fix.
- New
gradient_NNNNlabel format — 4-digit zero-padded basis points (threshold × 10000) preserves precision for fractional thresholds.0.05→gradient_0500,0.0549→gradient_0549,0.15→gradient_1500. Resolution: 1 basis point (#110) - Fixes silent precision loss in 0.11.0 where
as.integer(thr * 100)collapsed0.05and0.0549both togradient_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 legacygradient_Nfor backward compat with user-supplied labels viafrs_break_find(label = "gradient_15")
Sub-segment gradient resolution via auto-derived breaks.
breaks_gradientparameter onfrs_habitat()— generates gradient breaks at biologically meaningful thresholds derived fromspawn_gradient_max+rear_gradient_maxinparameters_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 givefrs_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)
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. UsesST_ClusterDBSCANfor spatial clustering andfwa_downstreamtrace()for downstream trace with gradient bridge and distance cap (#107)- Per-species cluster config in
parameters_fresh.csv—cluster_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
Flexible AOI, configurable access gating, and feature indexing.
frs_habitat()accepts any AOI — sf polygons, WHERE clauses, ltree filters — not just WSG codes. Addspeciesandlabelparams for custom runs (#96)gateparameter onfrs_habitat_classify()— setFALSEto classify raw habitat potential without access restrictions (#98)blocking_labelsparameter — configurable which labels restrict access. Default"blocked". Setc("blocked", "potential")for conservative analysis. Only"blocked"andgradient_Nblock by default; everything else passes throughfrs_feature_find()— locate any point features on the network (crossings, observations, stations) withcol_idfor 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 tofrs_feature_find()- Province-wide run completed: 229 WSGs, 5.98M segments
- Classify growth penalty fixed: constant-time DELETE by
watershed_group_code(#91)
Unified pipeline, domain-agnostic network segmentation, and major performance improvements.
frs_network_segment()— domain-agnostic network segmentation. Extracts, enriches, breaks at any point sources, assignsid_segment. One table, one copy of geometry.frs_habitat_classify()— long-format habitat classification. One row per segment x species withaccessible,spawning,rearing,lake_rearing. Species-specific accessibility via break label filtering.frs_feature_find()— locate any point features on the network (crossings, observations, stations). Replacesfrs_break_find()points/table modes.frs_feature_index()— index upstream/downstream relationships between segments and features. Stores feature ID arrays per segment.
frs_habitat()rewritten to wrapfrs_network_segment()+frs_habitat_classify(). Auto-generates gradient barriers per WSG from species parameters.to_streams+to_habitatparams replaceto_prefixfor persistent output tables.- mirai replaces furrr for parallel execution — lighter daemons, foundation for crew.aws.batch.
frs_break_find()slimmed to gradient-only (island detection). Point/table modes moved tofrs_feature_find().
- 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
id_segmentfor unique sub-segment identity after breaking (#82)col_blk+col_measureon 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)
Local Docker fwapg instance and generic network-referenced classification via break_sources.
- Replace
fallsparameter withbreak_sourceslist across pipeline (frs_habitat(),frs_habitat_partition(),frs_habitat_access()) — accepts any number of point tables withlabel,label_col, andlabel_mapfor flexible classification (#70) - Add
labelandsourcecolumns to breaks table for provenance tracking - Ship
inst/extdata/falls.csv(3,294 barrier falls) andinst/extdata/crossings.csv(533k crossings) withdata-raw/refresh scripts - Add Docker-based local fwapg: containerized PostGIS + loader with GDAL/psql/bcdata (#63)
- Fix Phase 2 sequential mode to reuse
conninstead of callingfrs_db_conn()
Parallelized multi-WSG habitat pipeline — 2.5x speedup over sequential on BULK.
- Add
frs_habitat()— orchestrator: run the full habitat pipeline across watershed groups withfurrrparallelism (#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 sameaccess_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()whenworkers > 1 - Benchmark scripts and logs in
scripts/habitat/
Watershed-group habitat pipeline — run the full pipeline across all species in a WSG.
- Add
whereparameter tofrs_extract()for SQL predicate filtering, ANDed withaoiwhen both provided (#60) - Add
frs_wsg_species()to look up species presence and bcfishpass view names per watershed group from bundledwsg_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)
- 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
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
toparam tofrs_network()— write results to DB working tables instead of pulling to R, scales to any number of watershed groups (#53) - Add
whereparam tofrs_classify()— scope classification by SQL predicate for edge-type-aware and accessibility-filtered habitat modelling - Add
whereandappendparams tofrs_break_find()table mode — filter points table and combine multiple break sources (gradient + falls) in one breaks table (#38) - Add
exclude_edge_typesparam tofrs_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
- Add
fromandextra_whereparams to waterbody specs infrs_network()for filtering waterbodies to those connected to habitat streams (#49) - Network traversal table configurable via
.frs_opt("tbl_network")(#44)
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 viaST_LocateBetweenandfwa_slopealongintervalgradient sampling (#38) - Add
frs_classify()for labeling features by attribute ranges, break accessibility (viafwa_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 viaoptions()— foundation for spyda compatibility (#44) - All write functions return
conninvisibly for consistent|>chaining
- Breaking: All DB-using functions now take
connas the first required parameter instead of...connection args. Create a connection once withconn <- frs_db_conn()and pass it to all calls. Enables piping:conn |> frs_break() |> frs_classify()(#35)
- Multi-blue-line-key support for
frs_watershed_at_measure()andfrs_network()viaupstream_blkparam (#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_measureparam 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, andextra_whereparams for custom table schemas - Add
frs_check_upstream()validation for cross-BLK network connectivity - Add
blue_line_keyandstream_order_minparams tofrs_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_allto 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, withclipparam onfrs_network()for inline use (#12) - Add
frs_watershed_split()for programmatic sub-basin delineation from break points — snap, delineate, subtract with stableblk/drmidentifiers (#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.