Repository navigation
Validate modelled habitat against fish observations per species and watershed group #283
Description
If we do it: "is this threshold set better?" gets a number per species and watershed group, so candidate CH/BT thresholds can be compared rather than eyeballed. If we never do: threshold changes are judged against bcfishpass parity, which measures agreement with a model rather than with fish.
Problem
link compares its output with bcfishpass (compare_*, wsg_compare.R, parity_crosssection.R) but never with fish observations. The only observation-vs-model comparison is the Babine SK work in research/default_vs_bcfishpass.md §6-7, done by hand for one species against user_habitat_classification.
Proposed Solution
A function (working name lnk_habitat_validate()) and a driver that, for a run schema, species set and WSG set, report:
- Capture: share of observations (after
observation_exclusions, releases removed) on or within a buffer of modelled spawning / rearing / accessible segments, split by life stage where known. - Cost: km of modelled spawning and rearing, so capture can't be raised by just calling everything habitat.
- Misses: gradient, channel width and edge type of the segments under observations that fell outside modelled habitat. This is the direct evidence for which threshold is binding.
- Absences (optional input): sites sampled with no fish captured (FISS no-fish-captured records), used as a false-positive check.
Output is a tidy table per bundle × species × WSG, so two bundles can be diffed.
Corrected 2026-09-26: this said to use the per-WSG schemas because fresh.streams_habitat_* is duplicated per group (fresh#218). It is not: fresh.streams_habitat_ch is 2,329,201 rows and 2,329,201 distinct (id_segment, watershed_group_code). Naver Creek joins 187 rows on the full key and 4,301 on bare id_segment, because id_segment repeats across groups (#203). The persist schemas are correct when joined on the full key, so the validator reads them directly.
Known biases to report alongside the numbers, not correct silently:
- observation points often sit at the downstream end of a sampling site;
- sampling clusters near road access;
- observations exist only where fish have access, so observed gradients are cut off at the access limit.
Scope decided (2026-09-26)
lnk_habitat_validate()returnslist(summary, observations).summaryhas one row per WSG × species × stage (any/spawn/rear), with capture counts and shares, cost km fromlnk_rollup_wsg(), and absence columns when absences are supplied.observationshas one row per retained location, with its segment's attributes and a diagnosticmiss_reason.- Any observation source (added during review):
observationstakes bcfishobs, another table, or a data frame withspecies_code,watershed_group_code,blue_line_keyanddownstream_route_measure. The bcfishobs filters (match_types,source_exclude) default on, and a filter whose column the source lacks is an error rather than a skip. Snapping points to the network stays outside the validator; a bulk snap in fresh is proposed separately. - CH spawn-staged capture is mostly the overlay: 237 of 245 locations sit in
user_habitat_classificationspawning reaches, so the result reports capture outside those reaches (*_outside_uhc). - Accessible =
streams_access.access_<sp> IN (1, 2), the definitionlnk_rollup_wsg()'saccessible_kmuses, so capture and cost agree. Research: calibrate CH and BT gradient and channel-width thresholds from observations #284 usedstreams_habitat.accessible. - Accessible capture is partly circular. Observations are pipeline inputs: they lift barriers (
observation_threshold), they are break points, anduser_habitat_classificationforces habitat. Spawning and rearing capture is the score; accessible capture is a sanity check, and observations on inaccessible segments are reported as access misses. - Buffer: capture can extend upstream along the same stream (
buffer_m), since observation points often sit at the downstream end of a site. The driver reports 0 and 100 m. - This PR runs read-only against
fresh_defaultandfresh(51 shared WSGs). Scoringdefault_tunedis Research: calibrate CH and BT gradient and channel-width thresholds from observations #284 step 5, with its own run decisions stated at launch.
Result: method, caveats and the default vs bcfishpass baseline are in research/habitat_validation.md (on main once the PR merges).
Activity
Accept observations from any source (#283)
Merge pull request #289 from NewGraphEnvironment/283-validate-modelle…
Keep the unfiled fresh bulk-snap issue draft with the #283 archive
Regenerate #284, #283 and pooling-scenario evidence under the Hazelto…