Problem
Habitat eligibility (which segments qualify as spawning/rearing per species) varies by species in ways the current parameters_habitat_thresholds.csv (fresh 0.11.0) can't elegantly express. Each "special case" tempts a one-off code path. We want one mechanism that handles every species without code changes.
bcfishpass v0.5.0 special cases that don't fit one CSV row
Each species spawn/rear can require multiple rules combined with OR. The CSV format with spawn_edge_types / rear_edge_types columns assumes a single rule per habitat type.
| Species |
Habitat |
Rules in bcfishpass v0.5.0 |
| BT |
spawn |
(edge_types: stream,canal AND gradient/cw thresholds) OR (waterbody_type=R AND gradient/cw thresholds) |
| CO |
spawn |
same OR pattern with R rivers |
| CO |
rear |
(stream,canal AND gradient/cw thresholds) OR (edge_type IN 1050,1150 with NO threshold checks) |
| SK |
rear |
waterbody_type=L AND area_ha >= 200 (lake-only, no stream rearing at all) |
| BT |
rear |
any edge type connected to spawning (post-cluster, but no edge filter at classify time) |
The pattern: a species' habitat-type qualifies on ANY of N rules. Each rule is a (segment predicate, attribute thresholds) pair.
Proposed Solution: Path 1 — Hybrid CSV + rules YAML
Keep parameters_habitat_thresholds.csv for the 80% case (species with one simple rule). Add an optional rules file for species that need multiple rules.
CSV stays unchanged for simple species
BT, CH, ST, WCT, RB, GR, CT, DV — each can be expressed as a single rule from the existing CSV columns.
New file: parameters_habitat_rules.yaml (or .json)
# Special-case species. Listed species REPLACE their CSV row.
# Species not listed here use the CSV row as-is.
SK:
spawn:
- edge_types: [stream, canal]
gradient: [0, 0.0249]
channel_width: [2, 9999]
mad: [0.175, 9999]
rear:
- waterbody_type: L
lake_ha_min: 200
channel_width: [1.5, 9999]
# no gradient — lake-only
CO:
spawn:
- edge_types: [stream, canal]
gradient: [0, 0.0549]
channel_width: [2, 9999]
mad: [0.164, 9999]
- waterbody_type: R # double-line rivers
gradient: [0, 0.0549]
channel_width: [2, 9999]
mad: [0.164, 9999]
rear:
- edge_types: [stream, canal]
gradient: [0, 0.0549]
channel_width: [1.5, 9999]
mad: [0.03, 40]
- edge_types_explicit: [1050, 1150] # wetland-flow streams, no thresholds
Rule format
Each rule is a list of optional predicates joined by AND. Multiple rules per habitat type are joined by OR.
| Predicate |
Type |
Maps to SQL |
edge_types |
list of fresh categories |
edge_type IN (...) via frs_edge_types(category) |
edge_types_explicit |
list of integer codes |
edge_type IN (...) direct codes |
waterbody_type |
character (L, R, W) |
waterbody_key IN (SELECT ... WHERE waterbody_type = '...') |
lake_ha_min |
numeric |
additional filter on lake join |
gradient |
[min, max] |
s.gradient BETWEEN ... AND ... |
channel_width |
[min, max] |
s.channel_width BETWEEN ... AND ... |
mad |
[min, max] |
requires mad_m3s on streams (currently missing) |
frs_habitat_classify() changes
Add rules parameter (default NULL). When provided:
- For each species, check if rules file has an entry. If yes, use rules instead of CSV row. If no, use CSV row as today.
- Build
spawn_cond / rear_cond SQL by joining rule predicates with AND, then joining rules with OR.
- Lake / waterbody joins added as needed per rule.
frs_habitat() changes
Pass rules arg through to frs_habitat_classify().
What does NOT change
parameters_habitat_thresholds.csv schema and format
parameters_fresh.csv schema and format
frs_classify(), frs_aggregate(), frs_network_* (unaffected)
- Existing examples and tests for species with single-rule habitat
Implementation scope
Changes:
R/frs_params.R — load + parse rules file, merge with CSV
R/frs_habitat_classify.R — replace single spawn_cond/rear_cond build with rule-list evaluator
R/frs_habitat.R — pass rules arg through
New file:
inst/extdata/parameters_habitat_rules.yaml — default rules (or empty/missing — CSV-only behavior)
Test approach
Use ADMS sub-basin 100.190442.999098.995997.058910.432966:
- SK rearing with default (no rules): currently 21.55 km on streams. With rules file specifying SK lake-only: 0 km on streams, lake_rearing filtered to ≥ 200 ha. Matches bcfishpass v0.5.0 (0).
- CO rearing with rules adding wetland-flow carve-out: matches bcfishpass v0.5.0.
- Species without rules entries (BT, CH): unchanged from CSV behavior.
Relates to
Problem
Habitat eligibility (which segments qualify as spawning/rearing per species) varies by species in ways the current
parameters_habitat_thresholds.csv(fresh 0.11.0) can't elegantly express. Each "special case" tempts a one-off code path. We want one mechanism that handles every species without code changes.bcfishpass v0.5.0 special cases that don't fit one CSV row
Each species spawn/rear can require multiple rules combined with OR. The CSV format with
spawn_edge_types/rear_edge_typescolumns assumes a single rule per habitat type.The pattern: a species' habitat-type qualifies on ANY of N rules. Each rule is a (segment predicate, attribute thresholds) pair.
Proposed Solution: Path 1 — Hybrid CSV + rules YAML
Keep
parameters_habitat_thresholds.csvfor the 80% case (species with one simple rule). Add an optional rules file for species that need multiple rules.CSV stays unchanged for simple species
BT, CH, ST, WCT, RB, GR, CT, DV — each can be expressed as a single rule from the existing CSV columns.
New file:
parameters_habitat_rules.yaml(or .json)Rule format
Each rule is a list of optional predicates joined by AND. Multiple rules per habitat type are joined by OR.
edge_typesedge_type IN (...)viafrs_edge_types(category)edge_types_explicitedge_type IN (...)direct codeswaterbody_typewaterbody_key IN (SELECT ... WHERE waterbody_type = '...')lake_ha_mingradient[min, max]s.gradient BETWEEN ... AND ...channel_width[min, max]s.channel_width BETWEEN ... AND ...mad[min, max]frs_habitat_classify()changesAdd
rulesparameter (defaultNULL). When provided:spawn_cond/rear_condSQL by joining rule predicates with AND, then joining rules with OR.frs_habitat()changesPass
rulesarg through tofrs_habitat_classify().What does NOT change
parameters_habitat_thresholds.csvschema and formatparameters_fresh.csvschema and formatfrs_classify(),frs_aggregate(),frs_network_*(unaffected)Implementation scope
Changes:
R/frs_params.R— load + parse rules file, merge with CSVR/frs_habitat_classify.R— replace singlespawn_cond/rear_condbuild with rule-list evaluatorR/frs_habitat.R— passrulesarg throughNew file:
inst/extdata/parameters_habitat_rules.yaml— default rules (or empty/missing — CSV-only behavior)Test approach
Use ADMS sub-basin
100.190442.999098.995997.058910.432966:Relates to