Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package: link
Title: Stream Network Habitat Interpretation (Experimental)
Version: 0.42.0
Date: 2026-06-01
Version: 0.43.0
Date: 2026-06-08
Authors@R: c(
person("Allan", "Irvine", , "airvine@newgraphenvironment.com",
role = c("aut", "cre"),
Expand Down
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# link 0.43.0

`lnk_pipeline_run()` now builds and persists `<persist_schema>.streams_access` (and `access_<sp>`) regardless of the `mapping_code` flag ([#218](https://github.com/NewGraphEnvironment/link/issues/218)). Access is foundational — `mapping_code` depends on it, not the reverse — so the access build (`lnk_presence` → pre-persist → `lnk_barriers_views` → `lnk_pipeline_access`) moves out of the `if (isTRUE(mapping_code))` block to run unconditionally; only the `mapping_code` **token assembly** (`lnk_mapping_code`) stays gated. Habitat-only callers (`mapping_code = FALSE` — `data-raw/wsg_pipeline_run.R`, `lnk_compare_wsg(mapping_code = FALSE)`) now also emit `streams_access`. No persist change required: `lnk_pipeline_persist()` already probes for `streams_access` and `streams_mapping_code` independently and copies whichever working tables exist. The `mapping_code = TRUE` execution order (pre-persist → barriers_views → access → mapping_code → final persist) is byte-identical to v0.42.0, so the cached vignette parity (99.04% BT) is unchanged.

# link 0.42.0

First package vignette: `vignettes/pars-habitat-connectivity.Rmd` — bull trout and Arctic grayling habitat and connectivity classification for the Parsnip River Watershed Group (`PARS`, FWCP Peace), rehearsed end-to-end so it can transfer into the Fish Passage Peace 2025 report appendix ([#215](https://github.com/NewGraphEnvironment/link/issues/215)). Two analyses: (1) **parity** — link's `bcfishpass` config reproduces bcfishpass's per-segment `mapping_code` for bull trout at 99.04%; (2) **extension** — link's `default` config models Arctic grayling, which bcfishpass does not model at all. Map symbology reuses the bcfishpass symbology registry bundled in `gq` (`gq::gq_reg_main()` + `gq_tmap_classes()`, the same recipe `fresh` uses), so stream colours match a bcfishpass QGIS project exactly. The vignette is tunnel-free: the model run + comparison run once locally in `data-raw/wsg_vignette_data.R`, which caches artifacts to `inst/vignette-data/` (`pars.gpkg`, `pars_parity.rds`); the vignette only loads those, so pkgdown CI builds it with no Postgres and no bcfishpass snapshot. New Suggests (`bookdown`, `gq`, `knitr`, `rmarkdown`) + `VignetteBuilder: knitr` + `gq` Remote.
Expand Down
179 changes: 91 additions & 88 deletions R/lnk_pipeline_run.R
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,18 @@
#' 10. [lnk_barriers_unify()] — unify per-source barriers into a single
#' working-schema table (always; promotes the mapping_code-only
#' flag in `lnk_compare_wsg()` to canonical PG state).
#' 10b. *Optional* mapping_code phase — gated by `mapping_code = TRUE`.
#' Runs between barriers_unify and persist:
#' [lnk_barriers_views()] over working `<schema>.barriers` (tunnel-
#' free, link-canonical per-species views), [lnk_pipeline_access()]
#' (writes working `streams_access`), [lnk_mapping_code()] (writes
#' working `streams_mapping_code`). Persist phase copies both to
#' `<persist_schema>`. See link#187.
#' 10a. **Access phase (always-on, link#218).** Runs between barriers_unify
#' and persist: a pre-persist of the current WSG (so the next two steps
#' see cross-WSG barriers), [lnk_barriers_views()] (tunnel-free,
#' link-canonical per-species views over `<persist_schema>.barriers`),
#' and [lnk_pipeline_access()] (writes working `streams_access`). Access
#' is foundational — mapping_code depends on it — so it builds regardless
#' of `mapping_code`. See link#187, #196.
#' 10b. *Optional* mapping_code token assembly — gated by `mapping_code = TRUE`.
#' [lnk_mapping_code()] consumes `streams_access` + `streams_habitat`
#' and writes working `streams_mapping_code`.
#' 11. [lnk_pipeline_persist()] — copy per-WSG streams + per-species
#' habitat + barriers (+ optional streams_access + streams_mapping_code)
#' habitat + barriers + streams_access (+ optional streams_mapping_code)
#' into `<persist_schema>` (idempotent DELETE-WHERE-WSG + INSERT).
#'
#' @param conn DBI connection to the local pipeline database (typically
Expand All @@ -59,15 +62,16 @@
#' @param cleanup_working Logical. When `TRUE` (default), drop the
#' `<schema>` working schema at the end. Pass `FALSE` for interactive
#' debug / manual inspection.
#' @param mapping_code Logical. When `TRUE`, additionally runs the
#' tunnel-free mapping_code build phase (10b above) — produces
#' `<persist_schema>.streams_access` and
#' `<persist_schema>.streams_mapping_code` for the WSG, consumed
#' downstream by `data-raw/build_species_views.R --bcfp` (QGIS bcfp-
#' shape symbology). Default `FALSE`. Methodology shift from pre-#187
#' compare_wsg: access uses link's own per-species barriers (via
#' `blocks_species` predicate on `<schema>.barriers`), not bcfp's
#' tunnel-staged tables.
#' @param mapping_code Logical. Gates only the mapping_code **token
#' assembly** (phase 10b) — when `TRUE`, [lnk_mapping_code()] runs and
#' `<persist_schema>.streams_mapping_code` is produced for the WSG,
#' consumed downstream by `data-raw/build_species_views.R --bcfp` (QGIS
#' bcfp-shape symbology). Default `FALSE`. **`streams_access` is built
#' and persisted regardless of this flag** (phase 10a, link#218) —
#' access is foundational, not mapping_code-specific. Methodology shift
#' from pre-#187 compare_wsg: access uses link's own per-species
#' barriers (via `blocks_species` predicate on `<schema>.barriers`),
#' not bcfp's tunnel-staged tables.
#'
#' @return `conn`, invisibly. Side effects are the writes into
#' `<persist_schema>.streams`, `streams_habitat_<sp>`, and `barriers`.
Expand Down Expand Up @@ -162,82 +166,81 @@ lnk_pipeline_run <- function(conn, aoi, cfg, loaded,
lnk_barriers_unify(conn, aoi = aoi, cfg = cfg, # nolint: object_usage_linter
loaded = loaded, schema = schema)

# Optional mapping_code phase (link#187). Tunnel-free build of
# streams_access + streams_mapping_code in the working schema. Persist
# phase (next) copies both to <persist_schema>. Methodology shift vs
# pre-#187 compare_wsg: ACCESS now uses link's own per-species barriers
# (derived from <schema>.barriers's `blocks_species` predicate via
# lnk_barriers_views) instead of bcfp's barriers tables staged via the
# tunnel. Yields a more meaningful diff vs bcfp's streams_mapping_code
# (the prior compare_wsg path used bcfp access semantics, artificially
# suppressing real link-vs-bcfp divergence). See NEWS v0.40.0.
if (isTRUE(mapping_code)) {
pres <- lnk_presence(loaded$wsg_species_presence, aoi) # nolint: object_usage_linter
# Access phase (link#187, always-on per link#218). Tunnel-free build of
# streams_access in the working schema; the persist phase (below) copies it
# to <persist_schema>. Access is foundational — mapping_code depends on it,
# not the reverse — so it runs regardless of `mapping_code`. Methodology vs
# pre-#187 compare_wsg: ACCESS uses link's own per-species barriers (derived
# from <schema>.barriers's `blocks_species` predicate via lnk_barriers_views)
# instead of bcfp's barriers tables staged via the tunnel. See NEWS v0.40.0.
# Only the mapping_code token assembly (step 3) stays gated by `mapping_code`.
pres <- lnk_presence(loaded$wsg_species_presence, aoi) # nolint: object_usage_linter

# 0. Pre-persist current WSG's barriers + streams + habitat into
# <persist_schema> BEFORE building views. lnk_barriers_views defaults
# to reading the persist barriers table — necessary for cross-WSG
# access lookups (e.g. PARS BT through Bennett dams in PCEA/UPCE,
# link#152). My #187 Phase 4 worked around an ordering issue by
# pointing views at working <schema>.barriers, but that loses the
# cross-WSG visibility. Fix: persist current WSG's barriers FIRST,
# so persist holds current + all previously-persisted WSGs by the
# time the views are built. Second persist call below re-runs
# idempotently and adds streams_access + streams_mapping_code.
# link#196.
lnk_pipeline_persist(conn, aoi = aoi, cfg = cfg, # nolint: object_usage_linter
species = active_species, schema = schema)
# 0. Pre-persist current WSG's barriers + streams + habitat into
# <persist_schema> BEFORE building views. lnk_barriers_views defaults
# to reading the persist barriers table — necessary for cross-WSG
# access lookups (e.g. PARS BT through Bennett dams in PCEA/UPCE,
# link#152). My #187 Phase 4 worked around an ordering issue by
# pointing views at working <schema>.barriers, but that loses the
# cross-WSG visibility. Fix: persist current WSG's barriers FIRST,
# so persist holds current + all previously-persisted WSGs by the
# time the views are built. Second persist call below re-runs
# idempotently and adds streams_access (+ streams_mapping_code when
# mapping_code = TRUE). link#196.
lnk_pipeline_persist(conn, aoi = aoi, cfg = cfg, # nolint: object_usage_linter
species = active_species, schema = schema)

# 1. Per-species + per-source barrier views over PERSIST
# <persist_schema>.barriers (default — see #196). Province-wide,
# tunnel-free, link-canonical. Sees current WSG (just persisted) +
# all previously-persisted WSGs (cross-WSG dam visibility).
# `species = active_species` so the view set matches the bundle
# (default config = bt/gr/ko/rb — not the bcfp 8).
lnk_barriers_views(conn, schema = schema, cfg = cfg, # nolint: object_usage_linter
species = active_species)
# 1. Per-species + per-source barrier views over PERSIST
# <persist_schema>.barriers (default — see #196). Province-wide,
# tunnel-free, link-canonical. Sees current WSG (just persisted) +
# all previously-persisted WSGs (cross-WSG dam visibility).
# `species = active_species` so the view set matches the bundle
# (default config = bt/gr/ko/rb — not the bcfp 8).
lnk_barriers_views(conn, schema = schema, cfg = cfg, # nolint: object_usage_linter
species = active_species)

# 2. Per-segment access. barriers_per_sp drives `accessible` (token1
# ACCESS + token2 gate). Uses the per-species `barriers_<sp>_access`
# views (link#200) — these reproduce bcfp's per-species access set
# (`barriers_<sp>`, smnorris/bcfishpass model/01_access/sql/
# model_access_bt.sql): NATURAL barriers only (gradient at the species
# threshold + falls + subsurface), MINUS the observation/habitat
# override, ∪ user_definite (override-exempt). Dams/PSCIS/modelled are
# NOT in the access set — they annotate access via token2
# (barrier_sources below), never block it. So a dam-downstream segment
# that is otherwise accessible stays accessible and emits `;DAM`. All
# inputs are province-wide-persisted (barriers + barrier_overrides via
# the pre-persist above), so the cross-WSG downstream walk is correct in
# every WSG, not just the run's own. See RUNBOOK.md §5.
sp_set <- tolower(active_species)
barriers_per_sp <- setNames(
lapply(sp_set, function(sp) paste0(schema, ".barriers_", sp, "_access")),
sp_set)
# 2. Per-segment access. barriers_per_sp drives `accessible` (token1
# ACCESS + token2 gate). Uses the per-species `barriers_<sp>_access`
# views (link#200) — these reproduce bcfp's per-species access set
# (`barriers_<sp>`, smnorris/bcfishpass model/01_access/sql/
# model_access_bt.sql): NATURAL barriers only (gradient at the species
# threshold + falls + subsurface), MINUS the observation/habitat
# override, ∪ user_definite (override-exempt). Dams/PSCIS/modelled are
# NOT in the access set — they annotate access via token2
# (barrier_sources below), never block it. So a dam-downstream segment
# that is otherwise accessible stays accessible and emits `;DAM`. All
# inputs are province-wide-persisted (barriers + barrier_overrides via
# the pre-persist above), so the cross-WSG downstream walk is correct in
# every WSG, not just the run's own. See RUNBOOK.md §5.
sp_set <- tolower(active_species)
barriers_per_sp <- setNames(
lapply(sp_set, function(sp) paste0(schema, ".barriers_", sp, "_access")),
sp_set)

lnk_pipeline_access(conn, # nolint: object_usage_linter
segments = paste0(schema, ".streams"),
aoi = aoi,
to = paste0(schema, ".streams_access"),
barriers_per_sp = barriers_per_sp,
observations = paste0(schema, ".observations"),
presence = pres,
barrier_sources = list(
anthropogenic = paste0(schema, ".barriers_anthropogenic_unified"),
pscis = paste0(schema, ".barriers_pscis"),
dams = paste0(schema, ".barriers_dams_unified"),
remediations = paste0(schema, ".barriers_remediations")),
crossings_table = paste0(schema, ".crossings"))
lnk_pipeline_access(conn, # nolint: object_usage_linter
segments = paste0(schema, ".streams"),
aoi = aoi,
to = paste0(schema, ".streams_access"),
barriers_per_sp = barriers_per_sp,
observations = paste0(schema, ".observations"),
presence = pres,
barrier_sources = list(
anthropogenic = paste0(schema, ".barriers_anthropogenic_unified"),
pscis = paste0(schema, ".barriers_pscis"),
dams = paste0(schema, ".barriers_dams_unified"),
remediations = paste0(schema, ".barriers_remediations")),
crossings_table = paste0(schema, ".crossings"))

# 3. Per-segment per-species mapping_code tokens. Schema-aware wrapper
# over lnk_pipeline_mapping_code — delegates the pure data transform.
# Species pass-through: intersect bcfp residence defaults with
# active_species so mapping_code only computes for species the bundle
# actually models. Species in active_species but NOT in any bcfp
# residence category (e.g. GR/KO/RB in the default bundle) get
# assigned to species_resident by fallback — GR/KO/RB are all resident
# salmonids; treat as resident until link#189 data-drives this from
# dimensions.csv.
# 3. Per-segment per-species mapping_code tokens — gated by `mapping_code`
# (link#218). Schema-aware wrapper over lnk_pipeline_mapping_code —
# delegates the pure data transform. Species pass-through: intersect bcfp
# residence defaults with active_species so mapping_code only computes for
# species the bundle actually models. Species in active_species but NOT in
# any bcfp residence category (e.g. GR/KO/RB in the default bundle) get
# assigned to species_resident by fallback — GR/KO/RB are all resident
# salmonids; treat as resident until link#189 data-drives this from
# dimensions.csv.
if (isTRUE(mapping_code)) {
sp_resident_bcfp <- c("bt", "wct")
sp_anadromous_bcfp <- c("ch", "cm", "co", "pk", "sk", "st")
sp_spawn_only_bcfp <- c("cm", "pk")
Expand Down
38 changes: 21 additions & 17 deletions man/lnk_pipeline_run.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading