From b330672a9aec84c660822a824680f1b2305f9d08 Mon Sep 17 00:00:00 2001 From: almac2022 Date: Thu, 23 Apr 2026 07:57:20 -0700 Subject: [PATCH 01/11] Vignette: fwapg prerequisites, user-definite barriers break bullet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vignette additions: - New "Prerequisites" section names fwapg as the source of the stream-network tables (ltree-typed watershed codes) and the traversal SQL functions (fwa_upstream, fwa_downstream, fwa_watershedatmeasure) the pipeline reads. bcfishobs marked as optional-but-recommended for observation overrides. Comparison tunnel called out as a validation convenience, not a runtime requirement. - DAG first line changed from "FWA streams (raw)" to "FWA stream network (via fwapg, ltree-enriched)" to match the prerequisites note. - Observations bullet now explicitly says overrides apply to gradient barriers, falls, AND user-definite barriers — plus an inline link to fwa_upstream since that's the SQL function driving the count. - New "User-identified definite barriers" bullet in the break-positions list, with inline links to user_barriers_definite.csv in bcfishpass and the mirror in link. Treated the same as falls: always-blocking, always a break position, eligible for per-species override via lnk_barrier_overrides. --- vignettes/reproducing-bcfishpass.Rmd | 47 ++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/vignettes/reproducing-bcfishpass.Rmd b/vignettes/reproducing-bcfishpass.Rmd index 715a86b..30ffa32 100644 --- a/vignettes/reproducing-bcfishpass.Rmd +++ b/vignettes/reproducing-bcfishpass.Rmd @@ -31,6 +31,27 @@ to run it, and how the output compares to bcfishpass reference tables. Full per-phase pipeline detail lives in [`research/bcfishpass_comparison.md`](https://github.com/NewGraphEnvironment/link/blob/main/research/bcfishpass_comparison.md). +## Prerequisites + +The pipeline reads from a PostgreSQL database with +[fwapg](https://github.com/smnorris/fwapg) loaded. fwapg is the +processed form of the BC Freshwater Atlas — it adds `wscode_ltree` +and `localcode_ltree` columns to the stream-network tables (PostgreSQL +`ltree` types encoding watershed topology) and provides the SQL +functions the pipeline uses to traverse the network: +[`fwa_upstream`](https://github.com/smnorris/fwapg/blob/main/sql/functions/FWA_Upstream.sql), +[`fwa_downstream`](https://github.com/smnorris/fwapg/blob/main/sql/functions/FWA_Downstream.sql), +[`fwa_watershedatmeasure`](https://github.com/smnorris/fwapg/blob/main/sql/functions/FWA_WatershedAtMeasure.sql), +and others. See fwapg's repository for installation. + +[bcfishobs](https://github.com/smnorris/bcfishobs) is optional but +recommended — it populates `bcfishobs.observations`, the table that +drives per-species overrides of natural barriers below. + +The comparison layer in the map at the end of this vignette reads +from a read-only tunnel to the bcfishpass reference database. That is +a validation convenience, not a requirement for running link. + ## How the bcfishpass configuration works The rollup measures **intrinsic habitat potential conditioned on @@ -45,7 +66,7 @@ suitable *and* accessible — accessibility and intrinsic potential are separable in general, and a fuller treatment would report both. ``` -FWA streams (raw) +FWA stream network (via fwapg, ltree-enriched) │ │ gradient thresholds detect barriers @ 15 / 20 / 25 / 30 % ▼ @@ -78,16 +99,19 @@ segment is one classification unit. Breaks therefore fall at positions where the decision can change: - **Observations.** bcfishpass's per-species access models flip a - natural-barrier reach to accessible when the count of fish - observations on the upstream flow path meets a threshold. Thresholds - and species filters vary per model (see the SQL under + natural-barrier reach (gradient barrier, falls, or user-definite + barrier) to accessible when the count of upstream fish + observations meets a threshold. Thresholds and species filters + vary per model (see the SQL under [`model/access/`](https://github.com/smnorris/bcfishpass/tree/ea3c5d8/model)). Per-species parameters used by link live in the bundled `"bcfishpass"` config's [`parameters_fresh.csv`](https://github.com/NewGraphEnvironment/link/blob/main/inst/extdata/configs/bcfishpass/parameters_fresh.csv) (`observation_threshold`, `observation_date_min`, - `observation_buffer_m`, `observation_species`). For BULK (bcfishpass - commit `ea3c5d8`): + `observation_buffer_m`, `observation_species`). Override counting + is done in SQL via + [`fwa_upstream`](https://github.com/smnorris/fwapg/blob/main/sql/functions/FWA_Upstream.sql) + by `lnk_barrier_overrides`. For BULK (bcfishpass commit `ea3c5d8`): - BT — ≥ 1 observation of BT, CH, CM, CO, PK, SK, or ST; any date - CH / CM / CO / PK / SK — ≥ 5 observations in that salmon set, @@ -104,6 +128,17 @@ where the decision can change: so segmentation doesn't split reaches that would end up in the same access state. +- **User-identified definite barriers** — positions listed in + bcfishpass's + [`user_barriers_definite.csv`](https://github.com/smnorris/bcfishpass/blob/ea3c5d8/data/user_barriers_definite.csv) + (mirrored at + [`inst/extdata/configs/bcfishpass/overrides/user_barriers_definite.csv`](https://github.com/NewGraphEnvironment/link/blob/main/inst/extdata/configs/bcfishpass/overrides/user_barriers_definite.csv)). + Each row specifies `blue_line_key` and `downstream_route_measure` + for a barrier that always blocks access. Treated the same as falls + — always-blocking, always a break position, eligible for + per-species override via `lnk_barrier_overrides` when enough + upstream observations clear the threshold. + - **Habitat classification endpoints** — manual spawning / rearing delineations from bcfishpass's [`user_habitat_classification.csv`](https://github.com/smnorris/bcfishpass/blob/ea3c5d8/data/user_habitat_classification.csv) From 3bb229e1bdf8b6ce62380c16bb91866afd0d6d3a Mon Sep 17 00:00:00 2001 From: almac2022 Date: Thu, 23 Apr 2026 10:43:43 -0700 Subject: [PATCH 02/11] Archive #38 PWF, init #44 PWF for barriers_definite_control wiring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - planning/archive/2026-04-23-targets-pipeline/ — three-PR arc closed (PRs #41/#42/#43 shipping 0.3.0 → 0.5.0). Bit-identical rollups across three consecutive tar_make runs. - planning/active/ — new PWF for #44. Approach per /Users/airvine/.claude/plans/stateful-hopping-feather.md: fix latent ctrl_filter bug in lnk_barrier_overrides (current filter treats any control row as blocking; docstring says only barrier_ind = TRUE blocks) + wire control through from .lnk_pipeline_prep_overrides via manifest-driven gating (cfg$overrides$barriers_definite_control, not information_schema probe). Follow-up issue scoped to migrate remaining probes to manifest-driven gating. Relates to #44 --- planning/active/findings.md | 106 ++++++------------ planning/active/progress.md | 50 +-------- planning/active/task_plan.md | 101 ++++++++--------- .../2026-04-23-targets-pipeline/README.md | 22 ++++ .../2026-04-23-targets-pipeline/findings.md | 96 ++++++++++++++++ .../2026-04-23-targets-pipeline/progress.md | 47 ++++++++ .../2026-04-23-targets-pipeline/task_plan.md | 73 ++++++++++++ 7 files changed, 326 insertions(+), 169 deletions(-) create mode 100644 planning/archive/2026-04-23-targets-pipeline/README.md create mode 100644 planning/archive/2026-04-23-targets-pipeline/findings.md create mode 100644 planning/archive/2026-04-23-targets-pipeline/progress.md create mode 100644 planning/archive/2026-04-23-targets-pipeline/task_plan.md diff --git a/planning/active/findings.md b/planning/active/findings.md index 6877e8b..3ee943e 100644 --- a/planning/active/findings.md +++ b/planning/active/findings.md @@ -1,96 +1,60 @@ -# Findings: _targets.R pipeline (#38) +# Findings: Wire barriers_definite_control (#44) -## Why targets (and not a monolithic lnk_habitat) +## Where the gap lives -Earlier session considered a big `lnk_habitat(conn, aoi, config)` wrapper that orchestrates the whole pipeline. Rejected: +Three places where `"barriers_definite_control"` is referenced in link today: -- Hides the DAG that rtj is trying to parallelize -- Duplicates what `tar_make()` already provides (caching, skipping, parallelism) -- Turns pipeline variants into if/else branches inside one function rather than separate target graphs -- Every DAG node collapsed to one black-box call — inspection, debugging, partial reruns all harder +1. **`R/lnk_pipeline_prepare.R` → `.lnk_pipeline_prep_load_aux`** — loads the per-AOI filtered CSV rows into `.barriers_definite_control` when `cfg$overrides$barriers_definite_control` is non-NULL. Already correct. +2. **`R/lnk_pipeline_prepare.R` → `.lnk_pipeline_prep_gradient`** — `information_schema` probe for the table, then `DELETE FROM gradient_barriers_raw g USING barriers_definite_control c WHERE ... c.barrier_ind::boolean = false`. Already correct. Removes **passable** positions from gradient set before minimal reduction. +3. **`R/lnk_pipeline_prepare.R` → `.lnk_pipeline_prep_overrides`** — does NOT pass `control` to `lnk_barrier_overrides`. This is the gap. `lnk_barrier_overrides` accepts a `control` parameter but is called without one from this site. -Targets solves these natively. `_targets.R` IS the pipeline definition. Each target is a named node. `tar_make()` runs, `tar_visnetwork()` / `tar_mermaid()` visualize, `tar_skip` inherits cache invalidation, parallelism via crew controllers. +## Latent bug in lnk_barrier_overrides control filter -link still owns interpretation helpers (the `R/lnk_habitat_*.R` phase functions). Those are called BY targets, not instead of it. +Read `R/lnk_barrier_overrides.R` lines 140–153. The implementation: -## Architectural constraints from rtj - -From `rtj/docs/distributed-fwapg.md` (cross-referenced; byte-identical fwapg restored on M1 as of 2026-04-22): - -1. **localhost DB per worker** — every worker creates its own `lnk_db_conn()` to localhost. No remote DB chatter over tailnet (latency blows up on hundreds of `dbGetQuery` calls). -2. **Small returns from `map()` targets** — KB-scale data frames only. No geometry, no raster, no wkb shipped over SSH. Our `compare_bcfishpass_wsg()` returns ~10 rows per WSG. -3. **M1 is optional** — `crew_controller_group` handles graceful degradation. Target graph has no M1 awareness. -4. **WSG is the parallelization unit** — ~220 WSGs province-wide, naturally independent. We start with 4 (ADMS, BULK, BABL, ELKR). -5. **Schema namespacing** — `working_` per rtj contract. Prevents parallel workers on the same host from colliding on `working.*`. - -## Design decisions - -### Per-phase helpers, not one wrapper -Six `lnk_pipeline_*.R` functions, one per DAG phase. Each is a clear unit; each can be targeted independently. Phase names read as verbs: setup → load → prepare → break → classify → connect. +```r +ctrl_where <- sprintf("LEFT JOIN %s c ON b.blue_line_key = c.blue_line_key + AND abs(b.downstream_route_measure - c.downstream_route_measure) < 1", control) +ctrl_filter <- "AND c.blue_line_key IS NULL" +``` -### `aoi` not `wsg` for the partition param -`wsg` hardcodes the bcfishpass WSG partition scheme. Fresh already uses `aoi` as the generic spatial filter (accepts WSG code, ltree, sf polygon). Link helpers inherit this convention. Today `aoi = "BULK"` works the same as the old `wsg = "BULK"`; tomorrow it extends to mapsheets, HUC basins, custom polygons. +Filter treats ANY control row as blocking override — including `barrier_ind = FALSE` rows. Docstring (lines 27–30) says only `barrier_ind = TRUE` rows block. -### Prefix is `lnk_pipeline_*` -Not `lnk_habitat_*` — only one of six phases (classify) is actually about habitat. The others are setup, loading, network prep, segmenting, connectivity. `lnk_pipeline_*` reads as "these are pipeline building blocks." +In practice on bcfishpass input this is masked because `.lnk_pipeline_prep_gradient`'s upstream DELETE removes `barrier_ind = FALSE` positions from `gradient_barriers_raw` before they reach the override step. But falls and user-definite positions are not pruned by control at load time — they stay in `natural_barriers`. If a control row with `barrier_ind = FALSE` exists for a fall or definite-barrier position, the current filter blocks observation overrides on it. Should not block. -### Static branching (`tar_map`) vs dynamic (`pattern = map(wsg)`) -Use `tar_map`. Static branching produces named targets (`comparison_BULK`, `comparison_ADMS`) — debuggable, inspectable, diffable. Dynamic branching hides per-element names behind indices — harder to trace. +Fix: `"AND (c.blue_line_key IS NULL OR c.barrier_ind::boolean = false)"`. -### Targets in `Suggests`, not `Imports` -Pipeline-dev dependency, not user-facing. Users who want to run the comparison can `install.packages(c("targets", "crew"))` on demand. `link` itself stays minimal. +## Manifest-driven gating decision -### Regenerate the research doc DAG -`tar_mermaid()` output replaces the hand-written Mermaid in `research/bcfishpass_comparison.md`. Single source of truth. Keep the glossary and `classDef` color-coding — those are human decoration, not pipeline structure. +`.lnk_pipeline_prep_overrides` could probe `information_schema.tables` to discover whether the control table exists (same pattern used there for habitat). Decided against: the manifest key is the direct contract. If `cfg$overrides$barriers_definite_control` is non-NULL the load step wrote the table; if it's NULL no table exists. Manifest gate, not DB probe. -### `compare_bcfishpass_wsg()` return shape -```r -tibble::tibble( - wsg = "BULK", - species = "BT", - habitat_type = c("spawning", "rearing"), - link_km = c(34.2, 71.8), - bcfishpass_km = c(33.1, 73.4), - diff_pct = c(+3.3, -2.2) -) -``` -Pulls from fresh's `streams_habitat` table joined against `bcfishpass.streams_habitat_linear_*` reference tables. Both live on the worker's localhost DB (byte-identical dumps on M4 and M1 per rtj). +Scope discipline: the existing `information_schema` probe for the habitat table in the same function, and the similar probe in `.lnk_pipeline_prep_gradient` for the control table, work correctly today. Leaving them alone in this PR; filing a follow-up issue for consistency. Using the manifest as the contract is well preferred across the package. -## PR 2 design constraint: `fresh.streams` is not per-AOI +## Tests that need to exist -`lnk_pipeline_prepare` writes base segments to `fresh.streams`, matching the legacy compare script. Fresh's downstream functions (`frs_break_apply`, `frs_habitat_classify`, `frs_cluster`) assume that table path. Every pipeline run does `DROP TABLE IF EXISTS fresh.streams CASCADE` before rebuilding, so two parallel AOI runs on the same host would overwrite each other's segments and race the `streams_habitat` output. +- `tests/testthat/test-lnk_barrier_overrides.R` does not exist. Creating new. +- `tests/testthat/test-lnk_pipeline_prepare.R` exists — extending with prep_overrides control pass-through tests. -This is tolerable for single-host single-run today — it breaks the parallel `tar_map(wsg = ...)` + `crew_controller_local(workers = 2)` design. Three ways to handle in PR 2: +## No `information_schema` probe in the new code -1. `crew_controller_local(workers = 1)` — serialize runs on each host. Simplest, defeats half the parallelism gain. -2. Per-AOI fresh table names — patch fresh to accept a `streams_table` parameter across break/classify/cluster, or write into `.streams` and follow all downstream fresh calls with that. Substantial fresh work. -3. Separate database per worker — overkill. +`.lnk_pipeline_prep_overrides`'s new control guard reads `cfg$overrides$barriers_definite_control` directly. That field is populated by `lnk_config()` when the manifest declares the key. No DB round-trip needed. -Leaning toward option 1 for the initial PR 2, with option 2 as a fresh follow-up. Document explicitly. +## Expected rollup direction -## Other accepted fragilities (from code-check on `prepare`) +Running the pipeline pre-fix vs post-fix on bcfishpass config: -- `id_segment` assignment in `prep_network` uses `row_number() OVER (ORDER BY blue_line_key, downstream_route_measure)`. Ties produce arbitrary ordering across runs. Faithful to compare script; ties are rare on FWA in practice. -- `natural_barriers` re-joins gradient barriers to FWA instead of using the already-enriched ltree columns from `gradient_barriers_raw`. Silent drop if an enrichment UPDATE left NULL ltree. Faithful to compare script; hasn't hit in 4 WSGs of real data. -- Per-model gradient class sets (bt, ch_cm_co_pk_sk, st, wct) are hardcoded in `prep_minimal`. TODO in-code to move into `cfg$pipeline$gradient_models` so variants can swap them. +- WSGs with `user_barriers_definite_control.csv` rows having `barrier_ind = TRUE` and upstream observations at those positions → rollup `link_km` shrinks for affected species (positions that were wrongly overridden are no longer overridden). Moves toward bcfishpass reference. +- WSGs with no such rows → rollup unchanged. -## Unknowns to resolve during implementation +Magnitude: unknown. Control-TRUE rows on the four validated WSGs are uncommon, so likely small. Direction matters more than magnitude. -- How cleanly does `frs_habitat_classify()` accept a `working_` schema? Does it assume `working.*`? If so, we need a `working_schema` arg in fresh. If `lnk_habitat_classify` writes to a schema name that fresh doesn't know about, classification may fail. -- Per-WSG schema cleanup contract — `on.exit(DROP SCHEMA working_ CASCADE)` inside `compare_bcfishpass_wsg()`, or let the next run drop + recreate? -- Does `frs_break_apply()` need to know the schema for the streams table, or does the input table name carry it? +## Reproducibility -Document findings as discovered. +The change is a deterministic additional filter clause on a `LEFT JOIN`. No new randomness, no schedule-dependent behaviour. Two back-to-back `tar_make()` runs must produce bit-identical rollups. Will verify with `digest::digest()`. ## Cross-refs -- rtj/docs/distributed-fwapg.md — architectural source of truth -- fresh 0.14.0 — `frs_barriers_minimal()` is prerequisite for `lnk_habitat_build_network` -- link 0.2.0 — `lnk_config()` feeds all phases - -## Versions - -- fresh: 0.14.0 -- link: main (0.2.0 → 0.3.0) -- bcfishpass: ea3c5d8 -- fwapg: Docker (FWA 20240830) +- Plan file: `/Users/airvine/.claude/plans/stateful-hopping-feather.md` +- Issue: link#44 +- Parallel cleanup issue (separate PR): link#45 (gradient classes) +- Follow-up to file at end of this PR: "Migrate remaining pipeline probes to manifest-driven gating" diff --git a/planning/active/progress.md b/planning/active/progress.md index 5fedb39..31b10bf 100644 --- a/planning/active/progress.md +++ b/planning/active/progress.md @@ -1,47 +1,9 @@ # Progress -## Session 2026-04-22 +## Session 2026-04-23 -- Archived lnk_config PWF (shipped as link 0.2.0 via PR #39) -- Starting link#38: `_targets.R` pipeline -- Dependencies cleared: fresh 0.14.0 (frs_barriers_minimal) and link 0.2.0 (lnk_config) are on main -- rtj data parity on M4 + M1 confirmed; R install on M1 (Phase 3) still pending but not blocking — single-host first -- Issue #38 updated with package-vs-pipeline split (helpers in `R/`, `_targets.R` + comparison in `data-raw/`) -- PR 1 Phase 1.1 done: `lnk_pipeline_setup()` (originally `lnk_habitat_setup_schema`, renamed before building more). Mocked tests for SQL shape + identifier validation (8 passing). Live DB test intentionally skipped — CREATE SCHEMA semantics are Postgres's, not ours to test. -- Naming decision: prefix is `lnk_pipeline_*` (not `lnk_habitat_*` — only 1 of 6 phases is actually about habitat). Phase names read as verbs: setup → load → prepare → break → classify → connect. -- Param decision: canonical `(conn, aoi, cfg, schema)`. `aoi` follows fresh convention — accepts a WSG code today; extends to ltree filters, sf polygons, mapsheets later. `setup` is the only outlier: `(conn, schema, overwrite)`. -- PR 1 Phase 1.2 done: `lnk_pipeline_load()` — loads crossings + misc crossings + applies modelled fixes (NONE/OBS → PASSABLE) + PSCIS barrier status overrides. Split into three internal `@noRd` helpers for readability. Cleaner scope than the original "load_inputs" plan: falls, definite barriers, observation exclusions, and habitat classification moved to `prepare` where they're actually consumed. 12 tests (4 input validation + 4 fixes SQL/branching + 1 apply_pscis branching + 3 structure). 169 link tests total. -- PR 1 Phase 1.3 done: `lnk_pipeline_prepare()` — thin orchestrator over 6 internal sub-helpers (prep_load_aux, prep_gradient, prep_natural, prep_overrides, prep_minimal, prep_network). First real consumer of `frs_barriers_minimal()` from fresh 0.14.0. `.lnk_quote_literal()` added to utils.R for safe SQL literal interpolation. 31 new tests (input validation + SQL shape + 4 model minimal reductions + union). Full link suite at 200 passing. -- Code-check found one genuine architectural concern for PR 2: `fresh.streams` is a shared schema, parallel WSG runs on one host would collide. Noted in findings.md with three mitigation options (leaning toward `workers = 1` for initial PR 2). -- PR 1 Phase 1.4 done: `lnk_pipeline_break()` — builds observations_breaks (species-filtered via `cfg$wsg_species` + data-error exclusions), habitat_endpoints (DRM + URM union), crossings_breaks, then sequential `frs_break_apply` respecting `cfg$pipeline$break_order` with `id_segment` reassignment between rounds. Four internal `@noRd` sub-helpers. 13 new tests (input validation + obs species derivation incl. CT expansion + SQL shape per branch + break_order honored). Full link suite at 229 passing. -- PR 1 Phase 1.5/1.6 done: `lnk_pipeline_classify()` + `lnk_pipeline_connect()` — classify builds `fresh.streams_breaks` (gradient FULL + falls + definite + crossings, WSG-filtered) then calls `frs_habitat_classify()` with rules YAML + barrier overrides. Connect wraps fresh's `.frs_run_connectivity` for per-species cluster + connected_waterbody. Both auto-derive species from `cfg$parameters_fresh` ∩ `cfg$wsg_species` presence for the AOI; both accept explicit `species =` override. 22 tests covering input validation, species derivation, access-gating breaks SQL shape, no-species error. Full link suite at 251 passing. -- **All six pipeline helpers complete.** -- PR 1 Phase 1.7 done: compare_bcfishpass.R rewritten from 635 lines to 136 lines using the six helpers. ADMS run 67s end-to-end, all species within 5%, spawning values identical to research doc, rearing within ~1% (acceptable ordering variance from id_segment tie-breaking). -- Fix along the way: added `cfg$species` (parsed from rules YAML at load) so `lnk_pipeline_classify_species` intersects against rules species (8) instead of parameters_fresh species (11). parameters_fresh has CT/DV/RB which bcfishpass doesn't model. Also added `barriers_definite` to `config.yaml` `break_order` (was missing). -- PR 1 ready to close. Remaining: NEWS/DESCRIPTION bump, final `/code-check`, PR with SRED tag. -- PR 1 MERGED as link 0.3.0 (PR #41). Branch deleted. - -## PR 2 kickoff - -- Branched `38-targets-pipeline-pr2` off main. -- Wrote `data-raw/compare_bcfishpass_wsg(wsg, config)` — wraps the six phase helpers for one WSG, returns a small tibble (wsg × species × habitat_type × link_km × bcfishpass_km × diff_pct). KB-scale return — no geometry, ships cleanly over SSH when distributed. -- Wrote `data-raw/_targets.R` — `tar_map(wsg = 4 WSGs)` over the per-WSG target, `crew_controller_local(workers = 1)`, rollup target binds all four tibbles. Serial because `fresh.streams` is a shared schema across workers on the same host (findings.md). -- Added `targets` / `crew` / `tibble` / `dplyr` to DESCRIPTION Suggests. -- Drift lesson from PR 1 → Issue #40 filed (CSV provenance + runtime stamps). Scope expands `lnk_stamp` (#24) into the lineage source. -- Next: `/code-check` on PR 2 staged diff, then `tar_make()` end-to-end, commit stamped verification log. -- Reframing (per user): the correctness bar is **bit-identical output from the same inputs**, not "within 5% of bcfishpass." The 5% comparison is parity diagnostics only. Saved to memory (`feedback_reproducibility.md`) + CLAUDE.md. Research-doc drift from earlier today (BT rearing -0.7 → -1.1) is env-state drift, not pipeline non-determinism — to be traceable once stamps/lineage ship (#40). -- tar_make end-to-end done. Three successive runs (10, 11, 12) produced bit-identical 34-row rollup tibbles — reproducibility proven. Wall clock ~8m 30s per run (serial). -- Promoted `.lnk_pipeline_classify_species` → exported `lnk_pipeline_species(cfg, aoi)` to remove duplication with the data-raw inline helper. Tests moved to `test-lnk_pipeline_species.R`. classify + connect internals updated. Compare wrapper uses `link::lnk_pipeline_species()`. -- Code-check surfaced a real connection leak (second `dbConnect` could throw before `on.exit` registered) and SQL quoting inconsistency on species list. Both fixed; 12th run confirms numbers unchanged. -- DESCRIPTION bumped to 0.4.0. NEWS entry captures the reproducibility + parity distinction. Committing and pushing PR 2 next. -- PR 2 MERGED as link 0.4.0 (PR #42). Branch deleted. - -## PR 3 kickoff - -- Branched `38-targets-pipeline-pr3` off main. -- `tar_mermaid()` reviewed — output is hashed-ID graph unsuitable as a research-doc DAG. Kept the hand-written pipeline DAG and added a clean "Targets orchestration" Mermaid beside it. -- Research doc results table refreshed with run 12 numbers (2026-04-22), correctness-bar section added at top. -- Vignette `reproducing-bcfishpass.Rmd` written — three-line entrypoint, rollup table, BULK CH habitat mapgl map. Pre-computes artifacts via `data-raw/vignette_reproducing_bcfishpass.R` → `inst/extdata/vignette-data/{rollup,bulk_ch}.rds`. Rendered clean on local test. -- Retired `data-raw/compare_bcfishpass.R`; `_targets.R` + `compare_bcfishpass_wsg.R` supersede it. -- DESCRIPTION bumped to 0.5.0; mapgl + sf added to Suggests. -- Next: `/code-check` on staged diff, commit, push, PR with SRED tag. +- Archived `2026-04-23-targets-pipeline/` — link#38 closed via PRs #41/#42/#43. Three consecutive `tar_make()` runs produced bit-identical rollups. All species within 5% of bcfishpass reference on all four WSGs. +- Branched `44-barriers-definite-control` off main. +- Plan approved. PWF initialized for #44. +- Pre-flight complete: identified the `ctrl_filter` bug in `lnk_barrier_overrides` (all rows block, not just `barrier_ind = TRUE`), and confirmed `.lnk_pipeline_prep_overrides` doesn't pass `control`. Same PR fixes both — filter semantics + missing pass-through. +- Next: Phase 1 — fix `R/lnk_barrier_overrides.R` `ctrl_filter` and add `tests/testthat/test-lnk_barrier_overrides.R`. diff --git a/planning/active/task_plan.md b/planning/active/task_plan.md index af41346..24a2c74 100644 --- a/planning/active/task_plan.md +++ b/planning/active/task_plan.md @@ -1,73 +1,66 @@ -# Task Plan: _targets.R pipeline (#38) +# Task Plan: Wire barriers_definite_control into lnk_barrier_overrides (#44) ## Goal -Replace the 635-line `data-raw/compare_bcfishpass.R` script with a targets-driven pipeline that: -- Runs each DAG node as a `tar_target()` — inspectable, cacheable, skippable -- Parallelizes across watershed groups via `tar_map(wsg = c(...))` -- Regenerates the research doc DAG from `tar_mermaid()` -- Single-host on M4 first; distributed swap to `crew_controller_group(local=M4, cluster=M1)` is a follow-up after rtj Phase 4 +Honour `user_barriers_definite_control.csv`'s `barrier_ind = TRUE` rows at the observation-override step. Positions marked as non-overridable (known fish-blocking dams, long impassable falls, diversions) must never be re-opened by historical upstream observations. Matches bcfishpass's per-species access SQL. -Uses `lnk_config("bcfishpass")` (shipped in 0.2.0) and `frs_barriers_minimal()` (fresh 0.14.0). +Bit-identical-across-reruns reproducibility preserved. Rollup direction expected: toward bcfishpass reference, not away. -## Package vs pipeline split +## Phase 1: lnk_barrier_overrides control filter fix -Helpers (`lnk_habitat_*`) go in `R/` as exported package functions — generic building blocks any caller can compose. `_targets.R` + `compare_bcfishpass_wsg()` go in `data-raw/` — this specific comparison pipeline, not part of the installed package. `data-raw/` is the canonical R-package home for "code that USES this package to produce outputs." - -## PR 1: Extract helpers to R/lnk_pipeline_*.R - -Break the 635-line script into small named functions (one per pipeline phase). Canonical signature `(conn, aoi, cfg, schema)` — `aoi` follows fresh convention (accepts a WSG code today; extends to ltree filters, sf polygons, mapsheets later). `setup` is the only outlier: `(conn, schema, overwrite)`. - -- [x] `R/lnk_pipeline_setup.R` — create working schema, ensure `fresh` schema -- [x] `R/lnk_pipeline_load.R` — crossings + modelled fixes + PSCIS status overrides. Falls, definite barriers, observation exclusions, habitat classification moved to `prepare` (load stays focused on anthropogenic crossings) -- [x] `R/lnk_pipeline_prepare.R` — loads falls + definite + control + habitat confirms; detects gradient barriers (`frs_break_find`) with control pruning + ltree enrichment; builds natural_barriers; computes barrier overrides via `lnk_barrier_overrides`; per-model non-minimal reduction via `frs_barriers_minimal` (fresh 0.14.0); loads fresh.streams with channel_width + stream_order_parent + GENERATED cols + id_segment. Six internal `@noRd` sub-helpers -- [x] `R/lnk_pipeline_break.R` — builds observations_breaks (species-filtered + exclusions), habitat_endpoints (DRM + URM), crossings_breaks; runs sequential `frs_break_apply` in config-defined order with `id_segment` reassignment between rounds -- [x] `R/lnk_pipeline_classify.R` — builds access-gating `fresh.streams_breaks` (gradient + falls + definite + crossings), calls `frs_habitat_classify` with rules YAML + thresholds + barrier overrides. Species default derives from `cfg$parameters_fresh` ∩ `cfg$wsg_species` presence for the AOI. -- [x] `R/lnk_pipeline_connect.R` — calls fresh's `.frs_run_connectivity` (per-species cluster + connected_waterbody driven by `cfg$parameters_fresh` flags). Fresh internal access flagged as a follow-up (export a stable API in fresh). -- [x] Update existing `data-raw/compare_bcfishpass.R` to call the helpers — verified on ADMS (635 lines → 136 lines, all species within 5%, sub-1% rearing drift from research doc acceptable) -- [ ] Tests + runnable examples for each helper (live-DB tests skip without `.lnk_db_available()`) -- [ ] pkgdown reference entries -- [ ] `/code-check` before each commit -- [ ] PR 1: SRED tag (NewGraphEnvironment/sred-2025-2026#24) — Relates to #38 - -## PR 2: Add _targets.R + per-partition target fn +- [ ] Read `R/lnk_barrier_overrides.R` control block (lines 140–153 + docstring). Confirm current `ctrl_filter = "AND c.blue_line_key IS NULL"` treats ANY control row as blocking, regardless of `barrier_ind`. Docstring says only `TRUE` rows block — fix. +- [ ] Update `ctrl_filter` to `"AND (c.blue_line_key IS NULL OR c.barrier_ind::boolean = false)"`. +- [ ] Update the inline comment (lines 140–143) to reflect the fixed semantics. +- [ ] New test file `tests/testthat/test-lnk_barrier_overrides.R`: + - Mocked SQL assertion that `ctrl_filter` produces the expected `(c.blue_line_key IS NULL OR c.barrier_ind::boolean = false)` clause when `control` is non-NULL + - Mocked SQL assertion that `ctrl_filter` is empty when `control = NULL` + - Input validation smoke tests (control as NULL, as character) +- [ ] `devtools::test(filter = "lnk_barrier_overrides")` green +- [ ] `/code-check` before commit -- [x] `data-raw/compare_bcfishpass_wsg.R` — wraps pipeline phases for one WSG, returns ~10-row tibble (wsg × species × habitat_type × link_km × bcfishpass_km × diff_pct). Creates own conn + conn_ref with fail-early on missing `PG_PASS_SHARE`, registers on.exit cleanup per-conn (no leak on second conn failure), cleans up on exit. Defensive drop of `fresh.streams*` at entry. -- [x] Pulls comparison diff against `bcfishpass.habitat_linear_*` reference over tunnel. All interpolated strings go through `DBI::dbQuoteLiteral`. -- [x] `data-raw/_targets.R` with static `tar_map(wsg = c("ADMS","BULK","BABL","ELKR"))` + synchronous execution (crew removed after the controller hung on dispatched-but-never-complete behavior; shared `fresh.streams` prevents parallel anyway). -- [x] `targets` + `tarchetypes` + `tibble` + `dplyr` → DESCRIPTION Suggests (crew dropped). -- [x] **Promote `.lnk_pipeline_classify_species` → exported `lnk_pipeline_species(cfg, aoi)`** — canonical public helper for "species this config classifies in this AOI". Used by classify + connect internally and by data-raw externally. Removes both the duplicated private helper and the inlined `.wsg_species_present` from data-raw. -- [x] Run `tar_make()` end-to-end on all 4 WSGs. Rollup = 34 rows, all within 5% of bcfishpass. Reproducibility check: runs 10 + 11 produced bit-identical rollup tibbles. -- [x] Log the run under `data-raw/logs/20260422_10_tar_make_from_dataraw.txt` + `20260422_11_tar_make_final.txt` (plus `20260422_12_*` post-fix re-verify). -- [x] `/code-check` before commit — found a real conn leak (second dbConnect could throw before on.exit registered) and a SQL quoting inconsistency on species; both fixed and re-verified. -- [x] **Correctness framing** — reframed verification from "within 5% of bcfishpass" to "bit-identical across repeated runs". Added section to CLAUDE.md + memory entry. Confirmed across three runs (10, 11, 12) — all 34 rollup rows identical. -- [ ] PR 2: SRED tag — Relates to #38 +## Phase 2: Wire control through .lnk_pipeline_prep_overrides + +- [ ] Update `R/lnk_pipeline_prepare.R` `.lnk_pipeline_prep_overrides`: + - Compute `control_arg <- if (!is.null(cfg$overrides$barriers_definite_control)) paste0(schema, ".barriers_definite_control") else NULL` + - Pass to `lnk_barrier_overrides(..., control = control_arg, ...)` + - **Manifest-driven** — not `information_schema` probe. `cfg$overrides$barriers_definite_control` is the contract. +- [ ] Add test to `tests/testthat/test-lnk_pipeline_prepare.R`: + - `.lnk_pipeline_prep_overrides` with `cfg$overrides$barriers_definite_control` non-NULL → call carries `control = ".barriers_definite_control"` + - `cfg$overrides$barriers_definite_control` NULL → call carries `control = NULL` +- [ ] `devtools::test(filter = "lnk_pipeline_prepare")` green +- [ ] Full `devtools::test()` green +- [ ] `lintr::lint_package()` — no new lints on changed files +- [ ] `/code-check` before commit -## PR 3: Retire old script + research doc refresh + vignette +## Phase 3: End-to-end verification -- [x] `tar_mermaid()` reviewed — output is hashed-ID orchestration graph, poor replacement for the hand-written pipeline-phase DAG. Kept the pipeline DAG in `research/bcfishpass_comparison.md`; added a small "Targets orchestration" Mermaid showing cfg → 4 WSGs → rollup. -- [x] Research doc refreshed with 2026-04-22 rollup numbers (was 2026-04-15) + reproducibility framing at top. -- [x] Delete `data-raw/compare_bcfishpass.R` — superseded by `_targets.R` + `compare_bcfishpass_wsg.R`. Git history preserves. -- [x] Vignette `vignettes/reproducing-bcfishpass.Rmd` — narrative, three-line entrypoint, rollup table, BULK CH habitat mapgl map, reproducibility note, pointers to future default-variant vignette. -- [x] `data-raw/vignette_reproducing_bcfishpass.R` — pre-computes `rollup.rds` + `bulk_ch.rds` into `inst/extdata/vignette-data/` so the vignette doesn't hit the DB at build time. CLAUDE.md vignette convention. -- [x] `mapgl`, `sf` added to DESCRIPTION Suggests. -- [x] NEWS entry + bump to 0.5.0. -- [ ] `/code-check` before commit -- [ ] PR 3: SRED tag — Fixes #38 +- [ ] `pak::local_install()` to pick up the pipeline changes +- [ ] First run: `cd data-raw && Rscript -e 'targets::tar_destroy(ask = FALSE); targets::tar_make()'` → log under `data-raw/logs/20260423_01_tar_make_post_44.txt` +- [ ] Inspect new rollup; compare to pre-change baseline (run 12 from 2026-04-22). Direction must be toward bcfishpass on WSGs with controlled `barrier_ind = TRUE` rows. +- [ ] Reproducibility run: immediately re-run `tar_make()` → `data-raw/logs/20260423_02_tar_make_repro.txt`. Rollup must be bit-identical. +- [ ] `digest::digest()` on the two rollup tibbles → same hash -## Follow-up (out of scope) +## Phase 4: Artifact updates -- Distributed execution — swap `crew_controller_local()` for `crew_controller_group(local=M4, cluster=M1)` after rtj Phase 4 passes the M4→M1 SSH exec check -- `configs/default/` variant wired into a second `_targets.R` or CLI arg — tracked via #19/#20/#21 biological decisions +- [ ] Regenerate vignette data: `Rscript data-raw/vignette_reproducing_bcfishpass.R`. Produces new `rollup.rds` + `sub_ch.rds` + `sub_ch_bcfp.rds`. +- [ ] Render vignette locally to verify pivot tables + map update cleanly +- [ ] Update `research/bcfishpass_comparison.md`: + - Four per-WSG parity tables with new numbers + - Short paragraph under "Key fixes during comparison" documenting the control wiring + numeric direction +- [ ] `NEWS.md` 0.6.0 entry: "Honour `user_barriers_definite_control.csv` at the observation-override step. Previously controlled positions could be re-opened by upstream observations; now they can't." +- [ ] `DESCRIPTION` version bump 0.5.0 → 0.6.0 -## Follow-up (out of scope for this PR) +## Phase 5: Ship -- Distributed execution — swap `crew_controller_local()` for `crew_controller_group(local=M4, cluster=M1)` after rtj Phase 4 passes the M4→M1 SSH exec check -- `configs/default/` variant wired into a second `_targets.R` or CLI arg — tracked via #19/#20/#21 biological decisions +- [ ] `/code-check` on full staged diff +- [ ] Commit atomically per the plan's commit layout +- [ ] Push branch +- [ ] Open PR with SRED tag `Relates to NewGraphEnvironment/sred-2025-2026#24` +- [ ] **File follow-up issue** (before closing PR 44): "Migrate remaining pipeline probes to manifest-driven gating". See `/Users/airvine/.claude/plans/stateful-hopping-feather.md` for scope. ## Versions at start - fresh: 0.14.0 -- link: main (0.2.0, target 0.3.0) +- link: main (0.5.0, target 0.6.0) - bcfishpass: ea3c5d8 - fwapg: Docker (FWA 20240830) diff --git a/planning/archive/2026-04-23-targets-pipeline/README.md b/planning/archive/2026-04-23-targets-pipeline/README.md new file mode 100644 index 0000000..9b70d03 --- /dev/null +++ b/planning/archive/2026-04-23-targets-pipeline/README.md @@ -0,0 +1,22 @@ +# Archive: targets-pipeline refactor (link#38, closed 2026-04-23) + +## Outcome + +Three-PR arc completed: + +- **PR #41 (link 0.3.0)** — six `lnk_pipeline_*` phase helpers extracted from the 635-line compare script +- **PR #42 (link 0.4.0)** — `data-raw/_targets.R` + `compare_bcfishpass_wsg()`, exported `lnk_pipeline_species()`, reproducibility framing +- **PR #43 (link 0.5.0)** — vignette `reproducing-bcfishpass.Rmd`, research doc refresh, retired legacy `compare_bcfishpass.R` + +Three consecutive `tar_make()` runs produced bit-identical 34-row rollup tibbles. All species within 5% of bcfishpass reference on all four WSGs (ADMS, BULK, BABL, ELKR). + +## What superseded it + +- New PWF cycle 2026-04-23 for #44 (wire `barriers_definite_control` into `lnk_barrier_overrides`) +- Issue #45 filed for gradient-class cleanup, parallel-safe +- Issue #40 filed for config CSV provenance + pipeline run stamps (supersedes narrow scope of #24) + +## Key lessons captured + +- `feedback_verification_logs.md` — always stamp env state in pipeline verification logs +- `feedback_reproducibility.md` — correctness bar is bit-identical output, not "within 5% of bcfishpass" diff --git a/planning/archive/2026-04-23-targets-pipeline/findings.md b/planning/archive/2026-04-23-targets-pipeline/findings.md new file mode 100644 index 0000000..6877e8b --- /dev/null +++ b/planning/archive/2026-04-23-targets-pipeline/findings.md @@ -0,0 +1,96 @@ +# Findings: _targets.R pipeline (#38) + +## Why targets (and not a monolithic lnk_habitat) + +Earlier session considered a big `lnk_habitat(conn, aoi, config)` wrapper that orchestrates the whole pipeline. Rejected: + +- Hides the DAG that rtj is trying to parallelize +- Duplicates what `tar_make()` already provides (caching, skipping, parallelism) +- Turns pipeline variants into if/else branches inside one function rather than separate target graphs +- Every DAG node collapsed to one black-box call — inspection, debugging, partial reruns all harder + +Targets solves these natively. `_targets.R` IS the pipeline definition. Each target is a named node. `tar_make()` runs, `tar_visnetwork()` / `tar_mermaid()` visualize, `tar_skip` inherits cache invalidation, parallelism via crew controllers. + +link still owns interpretation helpers (the `R/lnk_habitat_*.R` phase functions). Those are called BY targets, not instead of it. + +## Architectural constraints from rtj + +From `rtj/docs/distributed-fwapg.md` (cross-referenced; byte-identical fwapg restored on M1 as of 2026-04-22): + +1. **localhost DB per worker** — every worker creates its own `lnk_db_conn()` to localhost. No remote DB chatter over tailnet (latency blows up on hundreds of `dbGetQuery` calls). +2. **Small returns from `map()` targets** — KB-scale data frames only. No geometry, no raster, no wkb shipped over SSH. Our `compare_bcfishpass_wsg()` returns ~10 rows per WSG. +3. **M1 is optional** — `crew_controller_group` handles graceful degradation. Target graph has no M1 awareness. +4. **WSG is the parallelization unit** — ~220 WSGs province-wide, naturally independent. We start with 4 (ADMS, BULK, BABL, ELKR). +5. **Schema namespacing** — `working_` per rtj contract. Prevents parallel workers on the same host from colliding on `working.*`. + +## Design decisions + +### Per-phase helpers, not one wrapper +Six `lnk_pipeline_*.R` functions, one per DAG phase. Each is a clear unit; each can be targeted independently. Phase names read as verbs: setup → load → prepare → break → classify → connect. + +### `aoi` not `wsg` for the partition param +`wsg` hardcodes the bcfishpass WSG partition scheme. Fresh already uses `aoi` as the generic spatial filter (accepts WSG code, ltree, sf polygon). Link helpers inherit this convention. Today `aoi = "BULK"` works the same as the old `wsg = "BULK"`; tomorrow it extends to mapsheets, HUC basins, custom polygons. + +### Prefix is `lnk_pipeline_*` +Not `lnk_habitat_*` — only one of six phases (classify) is actually about habitat. The others are setup, loading, network prep, segmenting, connectivity. `lnk_pipeline_*` reads as "these are pipeline building blocks." + +### Static branching (`tar_map`) vs dynamic (`pattern = map(wsg)`) +Use `tar_map`. Static branching produces named targets (`comparison_BULK`, `comparison_ADMS`) — debuggable, inspectable, diffable. Dynamic branching hides per-element names behind indices — harder to trace. + +### Targets in `Suggests`, not `Imports` +Pipeline-dev dependency, not user-facing. Users who want to run the comparison can `install.packages(c("targets", "crew"))` on demand. `link` itself stays minimal. + +### Regenerate the research doc DAG +`tar_mermaid()` output replaces the hand-written Mermaid in `research/bcfishpass_comparison.md`. Single source of truth. Keep the glossary and `classDef` color-coding — those are human decoration, not pipeline structure. + +### `compare_bcfishpass_wsg()` return shape +```r +tibble::tibble( + wsg = "BULK", + species = "BT", + habitat_type = c("spawning", "rearing"), + link_km = c(34.2, 71.8), + bcfishpass_km = c(33.1, 73.4), + diff_pct = c(+3.3, -2.2) +) +``` +Pulls from fresh's `streams_habitat` table joined against `bcfishpass.streams_habitat_linear_*` reference tables. Both live on the worker's localhost DB (byte-identical dumps on M4 and M1 per rtj). + +## PR 2 design constraint: `fresh.streams` is not per-AOI + +`lnk_pipeline_prepare` writes base segments to `fresh.streams`, matching the legacy compare script. Fresh's downstream functions (`frs_break_apply`, `frs_habitat_classify`, `frs_cluster`) assume that table path. Every pipeline run does `DROP TABLE IF EXISTS fresh.streams CASCADE` before rebuilding, so two parallel AOI runs on the same host would overwrite each other's segments and race the `streams_habitat` output. + +This is tolerable for single-host single-run today — it breaks the parallel `tar_map(wsg = ...)` + `crew_controller_local(workers = 2)` design. Three ways to handle in PR 2: + +1. `crew_controller_local(workers = 1)` — serialize runs on each host. Simplest, defeats half the parallelism gain. +2. Per-AOI fresh table names — patch fresh to accept a `streams_table` parameter across break/classify/cluster, or write into `.streams` and follow all downstream fresh calls with that. Substantial fresh work. +3. Separate database per worker — overkill. + +Leaning toward option 1 for the initial PR 2, with option 2 as a fresh follow-up. Document explicitly. + +## Other accepted fragilities (from code-check on `prepare`) + +- `id_segment` assignment in `prep_network` uses `row_number() OVER (ORDER BY blue_line_key, downstream_route_measure)`. Ties produce arbitrary ordering across runs. Faithful to compare script; ties are rare on FWA in practice. +- `natural_barriers` re-joins gradient barriers to FWA instead of using the already-enriched ltree columns from `gradient_barriers_raw`. Silent drop if an enrichment UPDATE left NULL ltree. Faithful to compare script; hasn't hit in 4 WSGs of real data. +- Per-model gradient class sets (bt, ch_cm_co_pk_sk, st, wct) are hardcoded in `prep_minimal`. TODO in-code to move into `cfg$pipeline$gradient_models` so variants can swap them. + +## Unknowns to resolve during implementation + +- How cleanly does `frs_habitat_classify()` accept a `working_` schema? Does it assume `working.*`? If so, we need a `working_schema` arg in fresh. If `lnk_habitat_classify` writes to a schema name that fresh doesn't know about, classification may fail. +- Per-WSG schema cleanup contract — `on.exit(DROP SCHEMA working_ CASCADE)` inside `compare_bcfishpass_wsg()`, or let the next run drop + recreate? +- Does `frs_break_apply()` need to know the schema for the streams table, or does the input table name carry it? + +Document findings as discovered. + +## Cross-refs + +- rtj/docs/distributed-fwapg.md — architectural source of truth +- fresh 0.14.0 — `frs_barriers_minimal()` is prerequisite for `lnk_habitat_build_network` +- link 0.2.0 — `lnk_config()` feeds all phases + +## Versions + +- fresh: 0.14.0 +- link: main (0.2.0 → 0.3.0) +- bcfishpass: ea3c5d8 +- fwapg: Docker (FWA 20240830) diff --git a/planning/archive/2026-04-23-targets-pipeline/progress.md b/planning/archive/2026-04-23-targets-pipeline/progress.md new file mode 100644 index 0000000..5fedb39 --- /dev/null +++ b/planning/archive/2026-04-23-targets-pipeline/progress.md @@ -0,0 +1,47 @@ +# Progress + +## Session 2026-04-22 + +- Archived lnk_config PWF (shipped as link 0.2.0 via PR #39) +- Starting link#38: `_targets.R` pipeline +- Dependencies cleared: fresh 0.14.0 (frs_barriers_minimal) and link 0.2.0 (lnk_config) are on main +- rtj data parity on M4 + M1 confirmed; R install on M1 (Phase 3) still pending but not blocking — single-host first +- Issue #38 updated with package-vs-pipeline split (helpers in `R/`, `_targets.R` + comparison in `data-raw/`) +- PR 1 Phase 1.1 done: `lnk_pipeline_setup()` (originally `lnk_habitat_setup_schema`, renamed before building more). Mocked tests for SQL shape + identifier validation (8 passing). Live DB test intentionally skipped — CREATE SCHEMA semantics are Postgres's, not ours to test. +- Naming decision: prefix is `lnk_pipeline_*` (not `lnk_habitat_*` — only 1 of 6 phases is actually about habitat). Phase names read as verbs: setup → load → prepare → break → classify → connect. +- Param decision: canonical `(conn, aoi, cfg, schema)`. `aoi` follows fresh convention — accepts a WSG code today; extends to ltree filters, sf polygons, mapsheets later. `setup` is the only outlier: `(conn, schema, overwrite)`. +- PR 1 Phase 1.2 done: `lnk_pipeline_load()` — loads crossings + misc crossings + applies modelled fixes (NONE/OBS → PASSABLE) + PSCIS barrier status overrides. Split into three internal `@noRd` helpers for readability. Cleaner scope than the original "load_inputs" plan: falls, definite barriers, observation exclusions, and habitat classification moved to `prepare` where they're actually consumed. 12 tests (4 input validation + 4 fixes SQL/branching + 1 apply_pscis branching + 3 structure). 169 link tests total. +- PR 1 Phase 1.3 done: `lnk_pipeline_prepare()` — thin orchestrator over 6 internal sub-helpers (prep_load_aux, prep_gradient, prep_natural, prep_overrides, prep_minimal, prep_network). First real consumer of `frs_barriers_minimal()` from fresh 0.14.0. `.lnk_quote_literal()` added to utils.R for safe SQL literal interpolation. 31 new tests (input validation + SQL shape + 4 model minimal reductions + union). Full link suite at 200 passing. +- Code-check found one genuine architectural concern for PR 2: `fresh.streams` is a shared schema, parallel WSG runs on one host would collide. Noted in findings.md with three mitigation options (leaning toward `workers = 1` for initial PR 2). +- PR 1 Phase 1.4 done: `lnk_pipeline_break()` — builds observations_breaks (species-filtered via `cfg$wsg_species` + data-error exclusions), habitat_endpoints (DRM + URM union), crossings_breaks, then sequential `frs_break_apply` respecting `cfg$pipeline$break_order` with `id_segment` reassignment between rounds. Four internal `@noRd` sub-helpers. 13 new tests (input validation + obs species derivation incl. CT expansion + SQL shape per branch + break_order honored). Full link suite at 229 passing. +- PR 1 Phase 1.5/1.6 done: `lnk_pipeline_classify()` + `lnk_pipeline_connect()` — classify builds `fresh.streams_breaks` (gradient FULL + falls + definite + crossings, WSG-filtered) then calls `frs_habitat_classify()` with rules YAML + barrier overrides. Connect wraps fresh's `.frs_run_connectivity` for per-species cluster + connected_waterbody. Both auto-derive species from `cfg$parameters_fresh` ∩ `cfg$wsg_species` presence for the AOI; both accept explicit `species =` override. 22 tests covering input validation, species derivation, access-gating breaks SQL shape, no-species error. Full link suite at 251 passing. +- **All six pipeline helpers complete.** +- PR 1 Phase 1.7 done: compare_bcfishpass.R rewritten from 635 lines to 136 lines using the six helpers. ADMS run 67s end-to-end, all species within 5%, spawning values identical to research doc, rearing within ~1% (acceptable ordering variance from id_segment tie-breaking). +- Fix along the way: added `cfg$species` (parsed from rules YAML at load) so `lnk_pipeline_classify_species` intersects against rules species (8) instead of parameters_fresh species (11). parameters_fresh has CT/DV/RB which bcfishpass doesn't model. Also added `barriers_definite` to `config.yaml` `break_order` (was missing). +- PR 1 ready to close. Remaining: NEWS/DESCRIPTION bump, final `/code-check`, PR with SRED tag. +- PR 1 MERGED as link 0.3.0 (PR #41). Branch deleted. + +## PR 2 kickoff + +- Branched `38-targets-pipeline-pr2` off main. +- Wrote `data-raw/compare_bcfishpass_wsg(wsg, config)` — wraps the six phase helpers for one WSG, returns a small tibble (wsg × species × habitat_type × link_km × bcfishpass_km × diff_pct). KB-scale return — no geometry, ships cleanly over SSH when distributed. +- Wrote `data-raw/_targets.R` — `tar_map(wsg = 4 WSGs)` over the per-WSG target, `crew_controller_local(workers = 1)`, rollup target binds all four tibbles. Serial because `fresh.streams` is a shared schema across workers on the same host (findings.md). +- Added `targets` / `crew` / `tibble` / `dplyr` to DESCRIPTION Suggests. +- Drift lesson from PR 1 → Issue #40 filed (CSV provenance + runtime stamps). Scope expands `lnk_stamp` (#24) into the lineage source. +- Next: `/code-check` on PR 2 staged diff, then `tar_make()` end-to-end, commit stamped verification log. +- Reframing (per user): the correctness bar is **bit-identical output from the same inputs**, not "within 5% of bcfishpass." The 5% comparison is parity diagnostics only. Saved to memory (`feedback_reproducibility.md`) + CLAUDE.md. Research-doc drift from earlier today (BT rearing -0.7 → -1.1) is env-state drift, not pipeline non-determinism — to be traceable once stamps/lineage ship (#40). +- tar_make end-to-end done. Three successive runs (10, 11, 12) produced bit-identical 34-row rollup tibbles — reproducibility proven. Wall clock ~8m 30s per run (serial). +- Promoted `.lnk_pipeline_classify_species` → exported `lnk_pipeline_species(cfg, aoi)` to remove duplication with the data-raw inline helper. Tests moved to `test-lnk_pipeline_species.R`. classify + connect internals updated. Compare wrapper uses `link::lnk_pipeline_species()`. +- Code-check surfaced a real connection leak (second `dbConnect` could throw before `on.exit` registered) and SQL quoting inconsistency on species list. Both fixed; 12th run confirms numbers unchanged. +- DESCRIPTION bumped to 0.4.0. NEWS entry captures the reproducibility + parity distinction. Committing and pushing PR 2 next. +- PR 2 MERGED as link 0.4.0 (PR #42). Branch deleted. + +## PR 3 kickoff + +- Branched `38-targets-pipeline-pr3` off main. +- `tar_mermaid()` reviewed — output is hashed-ID graph unsuitable as a research-doc DAG. Kept the hand-written pipeline DAG and added a clean "Targets orchestration" Mermaid beside it. +- Research doc results table refreshed with run 12 numbers (2026-04-22), correctness-bar section added at top. +- Vignette `reproducing-bcfishpass.Rmd` written — three-line entrypoint, rollup table, BULK CH habitat mapgl map. Pre-computes artifacts via `data-raw/vignette_reproducing_bcfishpass.R` → `inst/extdata/vignette-data/{rollup,bulk_ch}.rds`. Rendered clean on local test. +- Retired `data-raw/compare_bcfishpass.R`; `_targets.R` + `compare_bcfishpass_wsg.R` supersede it. +- DESCRIPTION bumped to 0.5.0; mapgl + sf added to Suggests. +- Next: `/code-check` on staged diff, commit, push, PR with SRED tag. diff --git a/planning/archive/2026-04-23-targets-pipeline/task_plan.md b/planning/archive/2026-04-23-targets-pipeline/task_plan.md new file mode 100644 index 0000000..af41346 --- /dev/null +++ b/planning/archive/2026-04-23-targets-pipeline/task_plan.md @@ -0,0 +1,73 @@ +# Task Plan: _targets.R pipeline (#38) + +## Goal + +Replace the 635-line `data-raw/compare_bcfishpass.R` script with a targets-driven pipeline that: +- Runs each DAG node as a `tar_target()` — inspectable, cacheable, skippable +- Parallelizes across watershed groups via `tar_map(wsg = c(...))` +- Regenerates the research doc DAG from `tar_mermaid()` +- Single-host on M4 first; distributed swap to `crew_controller_group(local=M4, cluster=M1)` is a follow-up after rtj Phase 4 + +Uses `lnk_config("bcfishpass")` (shipped in 0.2.0) and `frs_barriers_minimal()` (fresh 0.14.0). + +## Package vs pipeline split + +Helpers (`lnk_habitat_*`) go in `R/` as exported package functions — generic building blocks any caller can compose. `_targets.R` + `compare_bcfishpass_wsg()` go in `data-raw/` — this specific comparison pipeline, not part of the installed package. `data-raw/` is the canonical R-package home for "code that USES this package to produce outputs." + +## PR 1: Extract helpers to R/lnk_pipeline_*.R + +Break the 635-line script into small named functions (one per pipeline phase). Canonical signature `(conn, aoi, cfg, schema)` — `aoi` follows fresh convention (accepts a WSG code today; extends to ltree filters, sf polygons, mapsheets later). `setup` is the only outlier: `(conn, schema, overwrite)`. + +- [x] `R/lnk_pipeline_setup.R` — create working schema, ensure `fresh` schema +- [x] `R/lnk_pipeline_load.R` — crossings + modelled fixes + PSCIS status overrides. Falls, definite barriers, observation exclusions, habitat classification moved to `prepare` (load stays focused on anthropogenic crossings) +- [x] `R/lnk_pipeline_prepare.R` — loads falls + definite + control + habitat confirms; detects gradient barriers (`frs_break_find`) with control pruning + ltree enrichment; builds natural_barriers; computes barrier overrides via `lnk_barrier_overrides`; per-model non-minimal reduction via `frs_barriers_minimal` (fresh 0.14.0); loads fresh.streams with channel_width + stream_order_parent + GENERATED cols + id_segment. Six internal `@noRd` sub-helpers +- [x] `R/lnk_pipeline_break.R` — builds observations_breaks (species-filtered + exclusions), habitat_endpoints (DRM + URM), crossings_breaks; runs sequential `frs_break_apply` in config-defined order with `id_segment` reassignment between rounds +- [x] `R/lnk_pipeline_classify.R` — builds access-gating `fresh.streams_breaks` (gradient + falls + definite + crossings), calls `frs_habitat_classify` with rules YAML + thresholds + barrier overrides. Species default derives from `cfg$parameters_fresh` ∩ `cfg$wsg_species` presence for the AOI. +- [x] `R/lnk_pipeline_connect.R` — calls fresh's `.frs_run_connectivity` (per-species cluster + connected_waterbody driven by `cfg$parameters_fresh` flags). Fresh internal access flagged as a follow-up (export a stable API in fresh). +- [x] Update existing `data-raw/compare_bcfishpass.R` to call the helpers — verified on ADMS (635 lines → 136 lines, all species within 5%, sub-1% rearing drift from research doc acceptable) +- [ ] Tests + runnable examples for each helper (live-DB tests skip without `.lnk_db_available()`) +- [ ] pkgdown reference entries +- [ ] `/code-check` before each commit +- [ ] PR 1: SRED tag (NewGraphEnvironment/sred-2025-2026#24) — Relates to #38 + +## PR 2: Add _targets.R + per-partition target fn + +- [x] `data-raw/compare_bcfishpass_wsg.R` — wraps pipeline phases for one WSG, returns ~10-row tibble (wsg × species × habitat_type × link_km × bcfishpass_km × diff_pct). Creates own conn + conn_ref with fail-early on missing `PG_PASS_SHARE`, registers on.exit cleanup per-conn (no leak on second conn failure), cleans up on exit. Defensive drop of `fresh.streams*` at entry. +- [x] Pulls comparison diff against `bcfishpass.habitat_linear_*` reference over tunnel. All interpolated strings go through `DBI::dbQuoteLiteral`. +- [x] `data-raw/_targets.R` with static `tar_map(wsg = c("ADMS","BULK","BABL","ELKR"))` + synchronous execution (crew removed after the controller hung on dispatched-but-never-complete behavior; shared `fresh.streams` prevents parallel anyway). +- [x] `targets` + `tarchetypes` + `tibble` + `dplyr` → DESCRIPTION Suggests (crew dropped). +- [x] **Promote `.lnk_pipeline_classify_species` → exported `lnk_pipeline_species(cfg, aoi)`** — canonical public helper for "species this config classifies in this AOI". Used by classify + connect internally and by data-raw externally. Removes both the duplicated private helper and the inlined `.wsg_species_present` from data-raw. +- [x] Run `tar_make()` end-to-end on all 4 WSGs. Rollup = 34 rows, all within 5% of bcfishpass. Reproducibility check: runs 10 + 11 produced bit-identical rollup tibbles. +- [x] Log the run under `data-raw/logs/20260422_10_tar_make_from_dataraw.txt` + `20260422_11_tar_make_final.txt` (plus `20260422_12_*` post-fix re-verify). +- [x] `/code-check` before commit — found a real conn leak (second dbConnect could throw before on.exit registered) and a SQL quoting inconsistency on species; both fixed and re-verified. +- [x] **Correctness framing** — reframed verification from "within 5% of bcfishpass" to "bit-identical across repeated runs". Added section to CLAUDE.md + memory entry. Confirmed across three runs (10, 11, 12) — all 34 rollup rows identical. +- [ ] PR 2: SRED tag — Relates to #38 + +## PR 3: Retire old script + research doc refresh + vignette + +- [x] `tar_mermaid()` reviewed — output is hashed-ID orchestration graph, poor replacement for the hand-written pipeline-phase DAG. Kept the pipeline DAG in `research/bcfishpass_comparison.md`; added a small "Targets orchestration" Mermaid showing cfg → 4 WSGs → rollup. +- [x] Research doc refreshed with 2026-04-22 rollup numbers (was 2026-04-15) + reproducibility framing at top. +- [x] Delete `data-raw/compare_bcfishpass.R` — superseded by `_targets.R` + `compare_bcfishpass_wsg.R`. Git history preserves. +- [x] Vignette `vignettes/reproducing-bcfishpass.Rmd` — narrative, three-line entrypoint, rollup table, BULK CH habitat mapgl map, reproducibility note, pointers to future default-variant vignette. +- [x] `data-raw/vignette_reproducing_bcfishpass.R` — pre-computes `rollup.rds` + `bulk_ch.rds` into `inst/extdata/vignette-data/` so the vignette doesn't hit the DB at build time. CLAUDE.md vignette convention. +- [x] `mapgl`, `sf` added to DESCRIPTION Suggests. +- [x] NEWS entry + bump to 0.5.0. +- [ ] `/code-check` before commit +- [ ] PR 3: SRED tag — Fixes #38 + +## Follow-up (out of scope) + +- Distributed execution — swap `crew_controller_local()` for `crew_controller_group(local=M4, cluster=M1)` after rtj Phase 4 passes the M4→M1 SSH exec check +- `configs/default/` variant wired into a second `_targets.R` or CLI arg — tracked via #19/#20/#21 biological decisions + +## Follow-up (out of scope for this PR) + +- Distributed execution — swap `crew_controller_local()` for `crew_controller_group(local=M4, cluster=M1)` after rtj Phase 4 passes the M4→M1 SSH exec check +- `configs/default/` variant wired into a second `_targets.R` or CLI arg — tracked via #19/#20/#21 biological decisions + +## Versions at start + +- fresh: 0.14.0 +- link: main (0.2.0, target 0.3.0) +- bcfishpass: ea3c5d8 +- fwapg: Docker (FWA 20240830) From d1a71090d49123d732e04737b7557a67d9d45869 Mon Sep 17 00:00:00 2001 From: almac2022 Date: Thu, 23 Apr 2026 10:48:00 -0700 Subject: [PATCH 03/11] Fix lnk_barrier_overrides control filter semantics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `control` parameter's documented contract is "barriers in this table with `barrier_ind = TRUE` cannot be overridden." Previous implementation used `LEFT JOIN control c + WHERE c.blue_line_key IS NULL`, which treated ANY control row as blocking — including `barrier_ind = FALSE` rows (which mark "not actually a barrier" positions, not "do not override" positions). Replaces the LEFT-JOIN/IS-NULL pattern with a `NOT EXISTS` subquery filtered to `barrier_ind::boolean = true`. Fix: - Correctly blocks override only when at least one matching control row has `barrier_ind = TRUE`. Mixed TRUE/FALSE within the 1 m position tolerance resolves to "blocked" (TRUE wins). - Removes a latent row-multiplication issue in the observation-count aggregation: the prior LEFT JOIN could multiply rows before `GROUP BY ... HAVING count(observation_key) >= threshold`, under-counting the threshold when multiple control rows matched one barrier. `NOT EXISTS` is a WHERE-clause subquery — no multiplication. Applies identically to the observation and habitat override paths (same `ctrl_where` / `ctrl_filter` pair used in both INSERTs). `.lnk_pipeline_prep_overrides` still calls `lnk_barrier_overrides()` with `control = NULL` — behavioural wiring lands in Phase 2 of this PR. New `tests/testthat/test-lnk_barrier_overrides.R` — 11 mocked SQL tests covering: - NOT EXISTS + `= true` clauses appear when `control` is non-NULL (observation path) - Same when habitat path is taken (observation path disabled) - Neither clause appears when `control = NULL` Full suite: 269 PASS. Relates to #44 --- R/lnk_barrier_overrides.R | 24 ++-- planning/active/task_plan.md | 14 +- tests/testthat/test-lnk_barrier_overrides.R | 138 ++++++++++++++++++++ 3 files changed, 159 insertions(+), 17 deletions(-) create mode 100644 tests/testthat/test-lnk_barrier_overrides.R diff --git a/R/lnk_barrier_overrides.R b/R/lnk_barrier_overrides.R index cb36b92..d2ef67f 100644 --- a/R/lnk_barrier_overrides.R +++ b/R/lnk_barrier_overrides.R @@ -137,20 +137,26 @@ lnk_barrier_overrides <- function(conn, overrides_found <- 0L - # Control table: any matching control row prevents the override. - # barrier_ind is used separately in barrier loading (true = keep, false = remove). - # Here we only care about presence — if a control row exists for this barrier - # position, observations/habitat don't override it. - ctrl_where <- if (!is.null(control)) { + # Control table: a matching control row with barrier_ind = TRUE + # blocks the override. `NOT EXISTS` (rather than a LEFT JOIN + filter) + # keeps two things right in one shot — the barrier is blocked only + # when at least one TRUE control row matches (mixed TRUE/FALSE within + # the 1 m tolerance resolves to "blocked"), and the outer GROUP BY / + # HAVING count(...) aggregation does not get row-multiplied by a join + # to control. + ctrl_where <- "" + ctrl_filter <- if (!is.null(control)) { sprintf( - "LEFT JOIN %s c - ON b.blue_line_key = c.blue_line_key - AND abs(b.downstream_route_measure - c.downstream_route_measure) < 1", + "AND NOT EXISTS ( + SELECT 1 FROM %s c + WHERE c.blue_line_key = b.blue_line_key + AND abs(b.downstream_route_measure - c.downstream_route_measure) < 1 + AND c.barrier_ind::boolean = true + )", control) } else { "" } - ctrl_filter <- if (!is.null(control)) "AND c.blue_line_key IS NULL" else "" # --- Observation-based overrides (JOIN pattern, not correlated subquery) --- if (!is.null(observations) && threshold > 0) { diff --git a/planning/active/task_plan.md b/planning/active/task_plan.md index 24a2c74..8b5133b 100644 --- a/planning/active/task_plan.md +++ b/planning/active/task_plan.md @@ -8,14 +8,12 @@ Bit-identical-across-reruns reproducibility preserved. Rollup direction expected ## Phase 1: lnk_barrier_overrides control filter fix -- [ ] Read `R/lnk_barrier_overrides.R` control block (lines 140–153 + docstring). Confirm current `ctrl_filter = "AND c.blue_line_key IS NULL"` treats ANY control row as blocking, regardless of `barrier_ind`. Docstring says only `TRUE` rows block — fix. -- [ ] Update `ctrl_filter` to `"AND (c.blue_line_key IS NULL OR c.barrier_ind::boolean = false)"`. -- [ ] Update the inline comment (lines 140–143) to reflect the fixed semantics. -- [ ] New test file `tests/testthat/test-lnk_barrier_overrides.R`: - - Mocked SQL assertion that `ctrl_filter` produces the expected `(c.blue_line_key IS NULL OR c.barrier_ind::boolean = false)` clause when `control` is non-NULL - - Mocked SQL assertion that `ctrl_filter` is empty when `control = NULL` - - Input validation smoke tests (control as NULL, as character) -- [ ] `devtools::test(filter = "lnk_barrier_overrides")` green +- [x] Read `R/lnk_barrier_overrides.R` control block. Confirmed: current filter treated ANY control row as blocking; docstring said only `barrier_ind = TRUE` rows block. +- [x] Updated `ctrl_filter` to `"AND (c.blue_line_key IS NULL OR c.barrier_ind::boolean = false)"`. +- [x] Updated the inline comment to describe the fixed semantics. +- [x] New test file `tests/testthat/test-lnk_barrier_overrides.R` with mocked SQL assertions — 7 tests covering observation-path control filter, NULL-control path, habitat-path control filter. +- [x] `devtools::test()` green: 265 PASS. +- [x] lintr clean on changed R/test files (only pre-existing indentation style notes, consistent with the rest of the codebase). - [ ] `/code-check` before commit ## Phase 2: Wire control through .lnk_pipeline_prep_overrides diff --git a/tests/testthat/test-lnk_barrier_overrides.R b/tests/testthat/test-lnk_barrier_overrides.R new file mode 100644 index 0000000..7970f4a --- /dev/null +++ b/tests/testthat/test-lnk_barrier_overrides.R @@ -0,0 +1,138 @@ +# -- ctrl_filter honours barrier_ind (mocked SQL) ----------------------------- + +# The ctrl_where / ctrl_filter pattern appears inside both the +# observation-based override SQL and the habitat-based override SQL. These +# tests capture the rendered SQL and assert the filter shape. + +.stub_params <- function() { + data.frame( + species_code = "BT", + observation_threshold = 1L, + observation_date_min = "2000-01-01", + observation_buffer_m = 20, + observation_species = "BT", + stringsAsFactors = FALSE + ) +} + +test_that("lnk_barrier_overrides honours barrier_ind = true via NOT EXISTS", { + captured <- character(0) + local_mocked_bindings( + dbExecute = function(conn, sql, ...) { + captured <<- c(captured, sql) + 1L + }, + dbGetQuery = function(conn, sql, ...) { + if (grepl("information_schema.columns", sql)) { + return(data.frame( + column_name = c("blue_line_key", "wscode_ltree", "localcode_ltree"), + stringsAsFactors = FALSE)) + } + if (grepl("SELECT count", sql, ignore.case = TRUE)) { + return(data.frame(count = 0L)) + } + data.frame() + }, + .package = "DBI" + ) + + lnk_barrier_overrides( + conn = "mock", + barriers = "working.natural_barriers", + observations = "bcfishobs.observations", + control = "working.barriers_definite_control", + params = .stub_params(), + to = "working.barrier_overrides", + verbose = FALSE + ) + + joined <- paste(captured, collapse = "\n") + # NOT EXISTS subquery in WHERE, not a LEFT JOIN in FROM + expect_match(joined, "AND NOT EXISTS", fixed = TRUE) + expect_match(joined, "FROM working.barriers_definite_control c", + fixed = TRUE) + expect_match(joined, "c.barrier_ind::boolean = true", + fixed = TRUE) + expect_no_match(joined, "LEFT JOIN.*barriers_definite_control") +}) + +test_that("lnk_barrier_overrides omits ctrl_filter when control is NULL", { + captured <- character(0) + local_mocked_bindings( + dbExecute = function(conn, sql, ...) { + captured <<- c(captured, sql) + 1L + }, + dbGetQuery = function(conn, sql, ...) { + if (grepl("information_schema.columns", sql)) { + return(data.frame( + column_name = c("blue_line_key", "wscode_ltree", "localcode_ltree"), + stringsAsFactors = FALSE)) + } + if (grepl("SELECT count", sql, ignore.case = TRUE)) { + return(data.frame(count = 0L)) + } + data.frame() + }, + .package = "DBI" + ) + + lnk_barrier_overrides( + conn = "mock", + barriers = "working.natural_barriers", + observations = "bcfishobs.observations", + control = NULL, + params = .stub_params(), + to = "working.barrier_overrides", + verbose = FALSE + ) + + joined <- paste(captured, collapse = "\n") + expect_no_match(joined, "NOT EXISTS.*barriers_definite_control") + expect_no_match(joined, "LEFT JOIN.*barriers_definite_control") + expect_no_match(joined, "c\\.barrier_ind::boolean") +}) + +test_that("lnk_barrier_overrides control filter applies to habitat overrides too", { + captured <- character(0) + local_mocked_bindings( + dbExecute = function(conn, sql, ...) { + captured <<- c(captured, sql) + 1L + }, + dbGetQuery = function(conn, sql, ...) { + if (grepl("information_schema.columns", sql)) { + return(data.frame( + column_name = c("blue_line_key", "wscode_ltree", "localcode_ltree"), + stringsAsFactors = FALSE)) + } + if (grepl("SELECT count", sql, ignore.case = TRUE)) { + return(data.frame(count = 0L)) + } + data.frame() + }, + .package = "DBI" + ) + + # No observations; habitat path only + lnk_barrier_overrides( + conn = "mock", + barriers = "working.natural_barriers", + observations = NULL, + habitat = "working.user_habitat_classification", + control = "working.barriers_definite_control", + params = .stub_params(), + to = "working.barrier_overrides", + verbose = FALSE + ) + + joined <- paste(captured, collapse = "\n") + expect_match(joined, "AND NOT EXISTS", fixed = TRUE) + expect_match(joined, + "FROM working.barriers_definite_control c", fixed = TRUE) + expect_match(joined, "c.barrier_ind::boolean = true", fixed = TRUE) + # habitat-only path uses habitat-specific SQL (confirm we're in that + # branch by checking for the habitat join pattern) + expect_match(joined, + "working.user_habitat_classification h", fixed = TRUE) +}) From 53bedbde1a25198fe1f34d543db71958f38165f5 Mon Sep 17 00:00:00 2001 From: almac2022 Date: Thu, 23 Apr 2026 10:51:37 -0700 Subject: [PATCH 04/11] Wire barriers_definite_control through prep_overrides (manifest-gated) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `.lnk_pipeline_prep_overrides` now passes `.barriers_definite_control` to `lnk_barrier_overrides` as the `control` argument whenever `cfg$overrides$barriers_definite_control` is declared by the config manifest. The manifest key is the direct contract — same pattern used for the other override roles in the pipeline. Phase 1's NOT EXISTS filter is what honours it at the SQL layer. Also fixes an asymmetric-gating bug caught in code review: `.lnk_pipeline_prep_load_aux` previously only wrote `.barriers_definite_control` when both the manifest declared the key AND the current AOI had matching rows. With Phase 2's manifest-driven gate, that meant AOIs where the manifest was declared but no rows matched would reference a table that was never created, and `lnk_barrier_overrides`'s NOT EXISTS subquery would raise "relation does not exist." Mirrored the `barriers_definite` pattern — whenever the manifest declares the key, write a schema-valid table (empty or populated). The NOT EXISTS against an empty table is always TRUE, so a zero-row AOI correctly blocks nothing. Two new tests in `test-lnk_pipeline_prepare.R` mock `lnk_barrier_overrides` and assert the resolved `control` arg: - manifest declares key → `control = ".barriers_definite_control"` - manifest omits key → `control = NULL` Full suite: 271 PASS. End-to-end verification via `tar_make()` follows in the next commits (rollup regeneration, research doc + NEWS updates, vignette artifact regen). Relates to #44 --- R/lnk_pipeline_prepare.R | 26 +++++++- planning/active/task_plan.md | 16 ++--- tests/testthat/test-lnk_pipeline_prepare.R | 75 ++++++++++++++++++++++ 3 files changed, 105 insertions(+), 12 deletions(-) diff --git a/R/lnk_pipeline_prepare.R b/R/lnk_pipeline_prepare.R index 2c4c922..4979e6a 100644 --- a/R/lnk_pipeline_prepare.R +++ b/R/lnk_pipeline_prepare.R @@ -125,7 +125,12 @@ lnk_pipeline_prepare <- function(conn, aoi, cfg, schema, downstream_route_measure double precision)", schema)) } - # --- Barriers-definite control (per-WSG, used to prune gradient barriers) --- + # --- Barriers-definite control (per-WSG, used to prune gradient barriers + # AND to lock positions against observation-based overrides). Mirror the + # barriers_definite pattern above — whenever the manifest declares the + # key, ensure a schema-valid table exists even if this AOI has zero rows, + # so downstream steps can gate on the manifest field rather than probing + # the DB. ctrl_all <- cfg$overrides$barriers_definite_control if (!is.null(ctrl_all)) { ctrl <- ctrl_all[ctrl_all$watershed_group_code == aoi, ] @@ -133,6 +138,14 @@ lnk_pipeline_prepare <- function(conn, aoi, cfg, schema, DBI::dbWriteTable(conn, DBI::Id(schema = schema, table = "barriers_definite_control"), ctrl, overwrite = TRUE) + } else { + .lnk_db_execute(conn, sprintf( + "DROP TABLE IF EXISTS %s.barriers_definite_control", schema)) + .lnk_db_execute(conn, sprintf( + "CREATE TABLE %s.barriers_definite_control ( + blue_line_key integer, + downstream_route_measure double precision, + barrier_ind text)", schema)) } } @@ -252,10 +265,21 @@ lnk_pipeline_prepare <- function(conn, aoi, cfg, schema, .lnk_quote_literal(schema))) habitat_arg <- if (nrow(habitat_exists) > 0) habitat_tbl else NULL + # Manifest-driven gating. `.lnk_pipeline_prep_load_aux` writes + # `.barriers_definite_control` exactly when this manifest key + # is declared on the config bundle, so the config field itself is the + # direct contract for whether control is in play — no DB probe needed. + control_arg <- if (!is.null(cfg$overrides$barriers_definite_control)) { + paste0(schema, ".barriers_definite_control") + } else { + NULL + } + lnk_barrier_overrides(conn, barriers = paste0(schema, ".natural_barriers"), observations = observations, habitat = habitat_arg, + control = control_arg, params = cfg$parameters_fresh, to = paste0(schema, ".barrier_overrides"), verbose = FALSE) diff --git a/planning/active/task_plan.md b/planning/active/task_plan.md index 8b5133b..190a493 100644 --- a/planning/active/task_plan.md +++ b/planning/active/task_plan.md @@ -18,17 +18,11 @@ Bit-identical-across-reruns reproducibility preserved. Rollup direction expected ## Phase 2: Wire control through .lnk_pipeline_prep_overrides -- [ ] Update `R/lnk_pipeline_prepare.R` `.lnk_pipeline_prep_overrides`: - - Compute `control_arg <- if (!is.null(cfg$overrides$barriers_definite_control)) paste0(schema, ".barriers_definite_control") else NULL` - - Pass to `lnk_barrier_overrides(..., control = control_arg, ...)` - - **Manifest-driven** — not `information_schema` probe. `cfg$overrides$barriers_definite_control` is the contract. -- [ ] Add test to `tests/testthat/test-lnk_pipeline_prepare.R`: - - `.lnk_pipeline_prep_overrides` with `cfg$overrides$barriers_definite_control` non-NULL → call carries `control = ".barriers_definite_control"` - - `cfg$overrides$barriers_definite_control` NULL → call carries `control = NULL` -- [ ] `devtools::test(filter = "lnk_pipeline_prepare")` green -- [ ] Full `devtools::test()` green -- [ ] `lintr::lint_package()` — no new lints on changed files -- [ ] `/code-check` before commit +- [x] Updated `.lnk_pipeline_prep_overrides` with manifest-gated `control_arg` computation; passes `control = control_arg` to `lnk_barrier_overrides`. +- [x] Fixed asymmetric gating — `.lnk_pipeline_prep_load_aux` now always creates a schema-valid (possibly empty) `.barriers_definite_control` table when the manifest declares the key, even if the AOI has zero control rows. Mirrors the `barriers_definite` pattern above. Lets `.lnk_pipeline_prep_overrides` gate on the manifest without worrying about the per-AOI row count. +- [x] Two new `.lnk_pipeline_prep_overrides` tests in `test-lnk_pipeline_prepare.R` — manifest present → `control = ".barriers_definite_control"`; manifest absent → `control = NULL`. +- [x] `devtools::test()` green: 271 PASS. +- [x] `/code-check` surfaced the asymmetric-gating bug — fixed and re-verified before commit. ## Phase 3: End-to-end verification diff --git a/tests/testthat/test-lnk_pipeline_prepare.R b/tests/testthat/test-lnk_pipeline_prepare.R index db34d3f..49f42ed 100644 --- a/tests/testthat/test-lnk_pipeline_prepare.R +++ b/tests/testthat/test-lnk_pipeline_prepare.R @@ -187,3 +187,78 @@ test_that(".lnk_pipeline_prep_network loads fresh.streams with FWA filters", { expect_match(joined, "wscode_ltree <@ '999'::ltree IS FALSE") expect_match(joined, "ADD COLUMN id_segment integer") }) + +# -- prep_overrides control pass-through (manifest-driven) ------------------- + +test_that(".lnk_pipeline_prep_overrides passes control when manifest declares it", { + cfg_stub <- structure(list( + parameters_fresh = data.frame( + species_code = "BT", + observation_threshold = 1L, + observation_date_min = "2000-01-01", + observation_buffer_m = 20, + observation_species = "BT", + stringsAsFactors = FALSE + ), + overrides = list( + barriers_definite_control = data.frame( + blue_line_key = 360873822L, + downstream_route_measure = 1000, + barrier_ind = "t", + stringsAsFactors = FALSE + ) + ) + ), class = c("lnk_config", "list")) + + captured <- list() + local_mocked_bindings( + lnk_barrier_overrides = function(conn, ...) { + captured[["args"]] <<- list(...) + invisible(NULL) + } + ) + local_mocked_bindings( + dbGetQuery = function(conn, sql, ...) { + data.frame() # no habitat table + }, + .package = "DBI" + ) + + .lnk_pipeline_prep_overrides("mock-conn", cfg = cfg_stub, + schema = "working_bulk", observations = "bcfishobs.observations") + + expect_equal(captured$args$control, "working_bulk.barriers_definite_control") +}) + +test_that(".lnk_pipeline_prep_overrides passes control = NULL when manifest omits it", { + cfg_stub <- structure(list( + parameters_fresh = data.frame( + species_code = "BT", + observation_threshold = 1L, + observation_date_min = "2000-01-01", + observation_buffer_m = 20, + observation_species = "BT", + stringsAsFactors = FALSE + ), + overrides = list() # no barriers_definite_control key + ), class = c("lnk_config", "list")) + + captured <- list() + local_mocked_bindings( + lnk_barrier_overrides = function(conn, ...) { + captured[["args"]] <<- list(...) + invisible(NULL) + } + ) + local_mocked_bindings( + dbGetQuery = function(conn, sql, ...) { + data.frame() + }, + .package = "DBI" + ) + + .lnk_pipeline_prep_overrides("mock-conn", cfg = cfg_stub, + schema = "working_bulk", observations = "bcfishobs.observations") + + expect_null(captured$args$control) +}) From 2fda0b1bc3e713e31722f0b57bc75c576891dfe1 Mon Sep 17 00:00:00 2001 From: almac2022 Date: Thu, 23 Apr 2026 11:35:03 -0700 Subject: [PATCH 05/11] Gate barriers_definite_control per-species via observation_control_apply MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 2 applied the NOT EXISTS control filter across every species in the `params` loop. A post-Phase-2 `tar_make()` drifted 11–22pp *away* from bcfishpass on ADMS and BABL (BULK and ELKR unchanged — their TRUE control rows have no upstream observations). Root cause: bcfishpass applies the control filter only in `model_access_ch_cm_co_pk_sk.sql` and `model_access_st.sql`. The BT, WCT, and CT/DV/RB access models don't reference `user_barriers_definite_control` at all. Their observations are allowed to override anadromous-blocking falls because residents routinely inhabit reaches upstream of such falls — post-glacial headwater connectivity populated many upper basins before the present channel dropped below its late-Pleistocene profile, and residents don't require ocean return. Species scope as a parameter, not a hard-coded list: - New column `observation_control_apply` in `inst/extdata/configs/bcfishpass/parameters_fresh.csv`. Logical. TRUE for CH, CM, CO, PK, SK, ST; FALSE for BT, WCT; NA for CT, DV, RB (which have no `observation_threshold` either — the flag is inapplicable). - `lnk_barrier_overrides()` reads `params$observation_control_apply[i]` inside the per-species loop. `isTRUE(as.logical(...))` normalises NA, missing-column, character, and unexpected inputs to FALSE — the resident-safe default. When FALSE, the NOT EXISTS clause is omitted from both the observation and habitat override paths. Two concerns, two locations: `cfg$overrides$barriers_definite_control` remains the table-level contract (is the control CSV declared for this config?); the new column is the application-level contract (does this species honour it?). Rules YAML stays focused on habitat classification. Tests (test-lnk_barrier_overrides.R, +3 cases): - `observation_control_apply = FALSE` → no NOT EXISTS / no `c.barrier_ind::boolean` clause in the rendered SQL. - `observation_control_apply = NA` → same — resident-safe default. - Mixed params (BT = FALSE, CH = TRUE) → per-species gating confirmed by inspecting the two emitted INSERT statements. Full suite: 279 PASS. Relates to #44 --- R/lnk_barrier_overrides.R | 25 +++- .../configs/bcfishpass/parameters_fresh.csv | 24 ++-- planning/active/progress.md | 5 + planning/active/task_plan.md | 12 ++ tests/testthat/test-lnk_barrier_overrides.R | 132 +++++++++++++++++- 5 files changed, 180 insertions(+), 18 deletions(-) diff --git a/R/lnk_barrier_overrides.R b/R/lnk_barrier_overrides.R index d2ef67f..10d2640 100644 --- a/R/lnk_barrier_overrides.R +++ b/R/lnk_barrier_overrides.R @@ -27,11 +27,18 @@ #' @param control Character or `NULL`. Schema-qualified table of barrier #' controls with columns: `blue_line_key`, `downstream_route_measure`, #' `barrier_ind`. Barriers in this table with `barrier_ind = TRUE` cannot -#' be overridden. +#' be overridden — but only for species where +#' `params$observation_control_apply` is TRUE. Resident species routinely +#' inhabit reaches upstream of anadromous-blocking falls (post-glacial +#' connectivity, no ocean-return requirement), so their observations still +#' count unless this flag says otherwise. #' @param params Data frame with per-species parameters. Must have columns: #' `species_code`, `observation_threshold`, `observation_date_min`, -#' `observation_buffer_m`, `observation_species`. See -#' `configs/bcfishpass/parameters_fresh.csv` for format. +#' `observation_buffer_m`, `observation_species`. Optional column +#' `observation_control_apply` (logical) — when TRUE, the `control` table +#' blocks overrides for this species; when FALSE/NA/missing, the species +#' ignores control. Bcfishpass defaults: TRUE for CH/CM/CO/PK/SK/ST, +#' FALSE for BT/WCT. See `configs/bcfishpass/parameters_fresh.csv`. #' @param cols_index Character vector. Column names to index on the #' barriers table for `fwa_upstream()` performance. Indexes are created #' `IF NOT EXISTS`. Default `c("blue_line_key", "wscode_ltree", @@ -135,6 +142,14 @@ lnk_barrier_overrides <- function(conn, obs_sp_list <- if (is.na(obs_sp_str)) sp else trimws(strsplit(obs_sp_str, ";")[[1]]) obs_sp_sql <- paste0("'", obs_sp_list, "'", collapse = ", ") + # Species-level opt-in for the control filter. bcfishpass applies control + # only in the anadromous access models (CH/CM/CO/PK/SK, ST) — residents + # (BT, WCT) and sub-CT species routinely live upstream of anadromous + # barriers (post-glacial headwater connectivity, no ocean-return + # requirement), so their observations should still override. + ctrl_apply_col <- species_to_process$observation_control_apply[i] + ctrl_apply <- isTRUE(as.logical(ctrl_apply_col)) + overrides_found <- 0L # Control table: a matching control row with barrier_ind = TRUE @@ -143,9 +158,9 @@ lnk_barrier_overrides <- function(conn, # when at least one TRUE control row matches (mixed TRUE/FALSE within # the 1 m tolerance resolves to "blocked"), and the outer GROUP BY / # HAVING count(...) aggregation does not get row-multiplied by a join - # to control. + # to control. Gated per-species by `observation_control_apply`. ctrl_where <- "" - ctrl_filter <- if (!is.null(control)) { + ctrl_filter <- if (!is.null(control) && ctrl_apply) { sprintf( "AND NOT EXISTS ( SELECT 1 FROM %s c diff --git a/inst/extdata/configs/bcfishpass/parameters_fresh.csv b/inst/extdata/configs/bcfishpass/parameters_fresh.csv index 10cbc2d..9158358 100644 --- a/inst/extdata/configs/bcfishpass/parameters_fresh.csv +++ b/inst/extdata/configs/bcfishpass/parameters_fresh.csv @@ -1,12 +1,12 @@ -"species_code","access_gradient_max","spawn_gradient_min","rear_gradient_min","cluster_rearing","cluster_direction","cluster_bridge_gradient","cluster_bridge_distance","cluster_confluence_m","cluster_spawning","cluster_spawn_direction","cluster_spawn_bridge_gradient","cluster_spawn_bridge_distance","cluster_spawn_confluence_m","observation_threshold","observation_date_min","observation_buffer_m","observation_species" -"BT",0.25,0,0,TRUE,"both",0.05,10000,10,FALSE,"",NA,NA,NA,1,"1990-01-01",20,"BT;CH;CO;SK;PK;CM;ST" -"CH",0.15,0,0,TRUE,"both",0.05,10000,10,FALSE,"",NA,NA,NA,5,"1990-01-01",20,"CH;CM;CO;PK;SK" -"CM",0.15,0,0,FALSE,"",NA,NA,NA,FALSE,"",NA,NA,NA,5,"1990-01-01",20,"CH;CM;CO;PK;SK" -"CO",0.15,0,0,TRUE,"both",0.05,10000,10,FALSE,"",NA,NA,NA,5,"1990-01-01",20,"CH;CM;CO;PK;SK" -"CT",0.25,0,0,FALSE,"",NA,NA,NA,FALSE,"",NA,NA,NA,NA,NA,NA,NA -"DV",0.25,0,0,FALSE,"",NA,NA,NA,FALSE,"",NA,NA,NA,NA,NA,NA,NA -"PK",0.15,0,0,FALSE,"",NA,NA,NA,FALSE,"",NA,NA,NA,5,"1990-01-01",20,"CH;CM;CO;PK;SK" -"RB",0.25,0,0,FALSE,"",NA,NA,NA,FALSE,"",NA,NA,NA,NA,NA,NA,NA -"SK",0.15,0,0,TRUE,"both",0.05,10000,10,TRUE,"both",0.05,3000,10,5,"1990-01-01",20,"CH;CM;CO;PK;SK" -"ST",0.2,0,0,TRUE,"both",0.05,10000,10,FALSE,"",NA,NA,NA,5,"1990-01-01",20,"CH;CM;CO;PK;SK;ST" -"WCT",0.2,0,0,TRUE,"both",0.05,10000,10,FALSE,"",NA,NA,NA,1,"1900-01-01",20,"WCT" +"species_code","access_gradient_max","spawn_gradient_min","rear_gradient_min","cluster_rearing","cluster_direction","cluster_bridge_gradient","cluster_bridge_distance","cluster_confluence_m","cluster_spawning","cluster_spawn_direction","cluster_spawn_bridge_gradient","cluster_spawn_bridge_distance","cluster_spawn_confluence_m","observation_threshold","observation_date_min","observation_buffer_m","observation_species","observation_control_apply" +"BT",0.25,0,0,TRUE,"both",0.05,10000,10,FALSE,"",NA,NA,NA,1,"1990-01-01",20,"BT;CH;CO;SK;PK;CM;ST",FALSE +"CH",0.15,0,0,TRUE,"both",0.05,10000,10,FALSE,"",NA,NA,NA,5,"1990-01-01",20,"CH;CM;CO;PK;SK",TRUE +"CM",0.15,0,0,FALSE,"",NA,NA,NA,FALSE,"",NA,NA,NA,5,"1990-01-01",20,"CH;CM;CO;PK;SK",TRUE +"CO",0.15,0,0,TRUE,"both",0.05,10000,10,FALSE,"",NA,NA,NA,5,"1990-01-01",20,"CH;CM;CO;PK;SK",TRUE +"CT",0.25,0,0,FALSE,"",NA,NA,NA,FALSE,"",NA,NA,NA,NA,NA,NA,NA,NA +"DV",0.25,0,0,FALSE,"",NA,NA,NA,FALSE,"",NA,NA,NA,NA,NA,NA,NA,NA +"PK",0.15,0,0,FALSE,"",NA,NA,NA,FALSE,"",NA,NA,NA,5,"1990-01-01",20,"CH;CM;CO;PK;SK",TRUE +"RB",0.25,0,0,FALSE,"",NA,NA,NA,FALSE,"",NA,NA,NA,NA,NA,NA,NA,NA +"SK",0.15,0,0,TRUE,"both",0.05,10000,10,TRUE,"both",0.05,3000,10,5,"1990-01-01",20,"CH;CM;CO;PK;SK",TRUE +"ST",0.2,0,0,TRUE,"both",0.05,10000,10,FALSE,"",NA,NA,NA,5,"1990-01-01",20,"CH;CM;CO;PK;SK;ST",TRUE +"WCT",0.2,0,0,TRUE,"both",0.05,10000,10,FALSE,"",NA,NA,NA,1,"1900-01-01",20,"WCT",FALSE diff --git a/planning/active/progress.md b/planning/active/progress.md index 31b10bf..9898289 100644 --- a/planning/active/progress.md +++ b/planning/active/progress.md @@ -7,3 +7,8 @@ - Plan approved. PWF initialized for #44. - Pre-flight complete: identified the `ctrl_filter` bug in `lnk_barrier_overrides` (all rows block, not just `barrier_ind = TRUE`), and confirmed `.lnk_pipeline_prep_overrides` doesn't pass `control`. Same PR fixes both — filter semantics + missing pass-through. - Next: Phase 1 — fix `R/lnk_barrier_overrides.R` `ctrl_filter` and add `tests/testthat/test-lnk_barrier_overrides.R`. +- Phase 1 committed (d1a7109) — `NOT EXISTS` control filter, 11 tests, 269 PASS. +- Phase 2 committed (53bedbd) — manifest-gated `control` pass-through in `.lnk_pipeline_prep_overrides`, fixed asymmetric load_aux (schema-valid empty table), 271 PASS. +- Post-Phase-2 `tar_make()` (log: `data-raw/logs/20260423_01_tar_make_post_44.txt`) showed 11–22pp drift AWAY from bcfishpass on ADMS/BABL; BULK/ELKR unchanged. Root cause: bcfishpass applies control filter only in CH/CM/CO/PK/SK and ST models (not BT/WCT/CT/DV/RB). My implementation applied it across all species in the `params` loop. +- Phase 2a: new `observation_control_apply` column in `parameters_fresh.csv` (TRUE for CH/CM/CO/PK/SK/ST; FALSE for BT/WCT; NA for CT/DV/RB), per-species NOT EXISTS gate in `lnk_barrier_overrides()`, three new tests. 279 PASS. Amendment pushed to issue #44 documenting the species-scoped approach and biological rationale. +- Next: Phase 3 — `pak::local_install()`, `tar_make()`, compare rollup to bcfishpass; expected direction — BT/WCT/ST on ADMS/BABL recover to near pre-fix, CH/CM/CO/PK/SK slightly closer to bcfishpass. diff --git a/planning/active/task_plan.md b/planning/active/task_plan.md index 190a493..0902319 100644 --- a/planning/active/task_plan.md +++ b/planning/active/task_plan.md @@ -24,6 +24,18 @@ Bit-identical-across-reruns reproducibility preserved. Rollup direction expected - [x] `devtools::test()` green: 271 PASS. - [x] `/code-check` surfaced the asymmetric-gating bug — fixed and re-verified before commit. +## Phase 2a: Per-species control gate (observation_control_apply) + +Post-Phase-2 `tar_make()` drifted 11–22pp *away* from bcfishpass on ADMS/BABL because bcfishpass applies the control filter per-species (CH/CM/CO/PK/SK and ST only), while my implementation applied it across all species. Residents (BT, WCT) inhabit reaches upstream of anadromous-blocking falls — their observations should still override. + +- [x] Add `observation_control_apply` column to `inst/extdata/configs/bcfishpass/parameters_fresh.csv`. TRUE for CH/CM/CO/PK/SK/ST; FALSE for BT/WCT; NA for CT/DV/RB. +- [x] `lnk_barrier_overrides()` gates the NOT EXISTS clause per-species on `params$observation_control_apply[i]`. Missing column or NA ⇒ no filter (resident default). +- [x] Updated `@param control` / `@param params` roxygen to document the gate. +- [x] Extended `.stub_params()` in `test-lnk_barrier_overrides.R` with optional `control_apply`. Three new tests: FALSE ⇒ no clause, NA ⇒ no clause, mixed-species params ⇒ per-species gating. +- [x] `devtools::test()`: 279 PASS. +- [x] Amend issue #44 body with Phase 2a scope and biological rationale. +- [x] `/code-check` before commit — two rounds, both Clean. + ## Phase 3: End-to-end verification - [ ] `pak::local_install()` to pick up the pipeline changes diff --git a/tests/testthat/test-lnk_barrier_overrides.R b/tests/testthat/test-lnk_barrier_overrides.R index 7970f4a..a1e692e 100644 --- a/tests/testthat/test-lnk_barrier_overrides.R +++ b/tests/testthat/test-lnk_barrier_overrides.R @@ -4,13 +4,14 @@ # observation-based override SQL and the habitat-based override SQL. These # tests capture the rendered SQL and assert the filter shape. -.stub_params <- function() { +.stub_params <- function(control_apply = TRUE) { data.frame( species_code = "BT", observation_threshold = 1L, observation_date_min = "2000-01-01", observation_buffer_m = 20, observation_species = "BT", + observation_control_apply = control_apply, stringsAsFactors = FALSE ) } @@ -136,3 +137,132 @@ test_that("lnk_barrier_overrides control filter applies to habitat overrides too expect_match(joined, "working.user_habitat_classification h", fixed = TRUE) }) + +# -- per-species control gate (observation_control_apply) -------------------- + +test_that("ctrl_filter omitted when observation_control_apply = FALSE", { + captured <- character(0) + local_mocked_bindings( + dbExecute = function(conn, sql, ...) { + captured <<- c(captured, sql) + 1L + }, + dbGetQuery = function(conn, sql, ...) { + if (grepl("information_schema.columns", sql)) { + return(data.frame( + column_name = c("blue_line_key", "wscode_ltree", "localcode_ltree"), + stringsAsFactors = FALSE)) + } + if (grepl("SELECT count", sql, ignore.case = TRUE)) { + return(data.frame(count = 0L)) + } + data.frame() + }, + .package = "DBI" + ) + + lnk_barrier_overrides( + conn = "mock", + barriers = "working.natural_barriers", + observations = "bcfishobs.observations", + habitat = "working.user_habitat_classification", + control = "working.barriers_definite_control", + params = .stub_params(control_apply = FALSE), + to = "working.barrier_overrides", + verbose = FALSE + ) + + joined <- paste(captured, collapse = "\n") + # Control is declared at call site, but this species opts out. + expect_no_match(joined, "NOT EXISTS.*barriers_definite_control") + expect_no_match(joined, "c\\.barrier_ind::boolean") +}) + +test_that("ctrl_filter omitted when observation_control_apply = NA", { + captured <- character(0) + local_mocked_bindings( + dbExecute = function(conn, sql, ...) { + captured <<- c(captured, sql) + 1L + }, + dbGetQuery = function(conn, sql, ...) { + if (grepl("information_schema.columns", sql)) { + return(data.frame( + column_name = c("blue_line_key", "wscode_ltree", "localcode_ltree"), + stringsAsFactors = FALSE)) + } + if (grepl("SELECT count", sql, ignore.case = TRUE)) { + return(data.frame(count = 0L)) + } + data.frame() + }, + .package = "DBI" + ) + + lnk_barrier_overrides( + conn = "mock", + barriers = "working.natural_barriers", + observations = "bcfishobs.observations", + control = "working.barriers_definite_control", + params = .stub_params(control_apply = NA), + to = "working.barrier_overrides", + verbose = FALSE + ) + + joined <- paste(captured, collapse = "\n") + expect_no_match(joined, "NOT EXISTS.*barriers_definite_control") + expect_no_match(joined, "c\\.barrier_ind::boolean") +}) + +test_that("ctrl_filter gated per-species across mixed params", { + captured <- character(0) + local_mocked_bindings( + dbExecute = function(conn, sql, ...) { + captured <<- c(captured, sql) + 1L + }, + dbGetQuery = function(conn, sql, ...) { + if (grepl("information_schema.columns", sql)) { + return(data.frame( + column_name = c("blue_line_key", "wscode_ltree", "localcode_ltree"), + stringsAsFactors = FALSE)) + } + if (grepl("SELECT count", sql, ignore.case = TRUE)) { + return(data.frame(count = 0L)) + } + data.frame() + }, + .package = "DBI" + ) + + mixed_params <- data.frame( + species_code = c("BT", "CH"), + observation_threshold = c(1L, 5L), + observation_date_min = c("1990-01-01", "1990-01-01"), + observation_buffer_m = c(20, 20), + observation_species = c("BT;CH", "CH;CM;CO;PK;SK"), + observation_control_apply = c(FALSE, TRUE), + stringsAsFactors = FALSE + ) + + lnk_barrier_overrides( + conn = "mock", + barriers = "working.natural_barriers", + observations = "bcfishobs.observations", + control = "working.barriers_definite_control", + params = mixed_params, + to = "working.barrier_overrides", + verbose = FALSE + ) + + # Two per-species INSERTs were emitted. BT's should have no NOT EXISTS; + # CH's should. Identify by the species-code literal in SELECT. + bt_sql <- captured[grepl("SELECT b.blue_line_key, b.downstream_route_measure, 'BT'", + captured, fixed = TRUE)] + ch_sql <- captured[grepl("SELECT b.blue_line_key, b.downstream_route_measure, 'CH'", + captured, fixed = TRUE)] + expect_true(length(bt_sql) >= 1) + expect_true(length(ch_sql) >= 1) + expect_no_match(paste(bt_sql, collapse = "\n"), "NOT EXISTS") + expect_match(paste(ch_sql, collapse = "\n"), "NOT EXISTS", fixed = TRUE) +}) From 6f3bc468baab255d86bbe8327136759fad85e88c Mon Sep 17 00:00:00 2001 From: almac2022 Date: Thu, 23 Apr 2026 12:02:00 -0700 Subject: [PATCH 06/11] Ungate habitat override path from control (bcfishpass parity) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 2a gated the control filter per-species but left a second defect: `ctrl_filter` was applied to BOTH the observation-path INSERT and the habitat-path INSERT in `lnk_barrier_overrides()`. bcfishpass's `model_access_ch_cm_co_pk_sk.sql` has separate CTEs: obs_upstr — joins observations, LEFT JOINs control, filters `bc.barrier_ind IS NULL` (control-gated) hab_upstr — joins habitat only, no control join at all The biology: expert-confirmed habitat is higher-trust than observations. By the time a reviewer has confirmed upstream habitat for a species at a given position, they have already considered the barrier's passability and the control-table designation. Observations are noisier and may be misattributed, so the control table vetoes them; habitat is a direct assertion that a species uses the reach upstream, so it stands. The old behaviour under-overrode bcfishpass: on ADMS and BABL, TRUE control positions that bcfishpass overrode via habitat stayed as barriers in link, cutting 11-22pp of accessible spawning/rearing km on CH, CM, CO, PK, SK, ST. Removing the habitat-path gate brings all four WSGs back into parity. Changes: - `lnk_barrier_overrides()`: the habitat-path INSERT no longer interpolates `ctrl_where` / `ctrl_filter`. The observation-path INSERT is unchanged (still gated by `observation_control_apply` per species). - Flipped the existing test "control filter applies to habitat overrides too" to its corrected form: "habitat override path is NOT gated by control (bcfishpass parity)". Filters captured SQL to the habitat INSERT only and asserts absence of the NOT EXISTS clause. - Roxygen on `@param control` documents that habitat confirmations bypass the control table entirely. `devtools::test()`: 279 PASS. `tar_make()` across ADMS, BULK, BABL, ELKR — all 34 rollup rows within 5% of bcfishpass reference. Exact numeric match to pre-fix baseline (no behaviour change vs pre-fix on these 4 WSGs, because habitat classifications already covered the control-table positions). Filter is now correctly applied semantically, not relying on coincidence with the habitat path. Relates to #44 --- R/lnk_barrier_overrides.R | 16 ++++++++++------ tests/testthat/test-lnk_barrier_overrides.R | 21 +++++++++++---------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/R/lnk_barrier_overrides.R b/R/lnk_barrier_overrides.R index 10d2640..b95e4c9 100644 --- a/R/lnk_barrier_overrides.R +++ b/R/lnk_barrier_overrides.R @@ -27,11 +27,13 @@ #' @param control Character or `NULL`. Schema-qualified table of barrier #' controls with columns: `blue_line_key`, `downstream_route_measure`, #' `barrier_ind`. Barriers in this table with `barrier_ind = TRUE` cannot -#' be overridden — but only for species where +#' be overridden **by observations** — but only for species where #' `params$observation_control_apply` is TRUE. Resident species routinely #' inhabit reaches upstream of anadromous-blocking falls (post-glacial #' connectivity, no ocean-return requirement), so their observations still -#' count unless this flag says otherwise. +#' count unless this flag says otherwise. Habitat confirmations +#' (`habitat` argument) are higher-trust than observations — they bypass +#' the control table entirely, for all species. #' @param params Data frame with per-species parameters. Must have columns: #' `species_code`, `observation_threshold`, `observation_date_min`, #' `observation_buffer_m`, `observation_species`. Optional column @@ -213,6 +215,11 @@ lnk_barrier_overrides <- function(conn, } # --- Habitat confirmation overrides (any confirmed habitat upstream) --- + # Control filter intentionally NOT applied here. Expert-confirmed + # habitat is a higher-trust signal than the control table — by the + # time a reviewer has marked habitat as confirmed upstream of a + # position, they have already considered the barrier's passability. + # bcfishpass does the same: `hab_upstr` CTE has no control join. if (!is.null(habitat)) { sql <- sprintf( "INSERT INTO %s (blue_line_key, downstream_route_measure, species_code) @@ -225,18 +232,15 @@ lnk_barrier_overrides <- function(conn, ON s.blue_line_key = h.blue_line_key AND round(h.upstream_route_measure::numeric) >= round(s.downstream_route_measure::numeric) AND round(h.upstream_route_measure::numeric) <= round(s.upstream_route_measure::numeric) - %s WHERE whse_basemapping.fwa_upstream( b.blue_line_key, b.downstream_route_measure, b.wscode_ltree, b.localcode_ltree, h.blue_line_key, h.upstream_route_measure, s.wscode_ltree, s.localcode_ltree, false, 200) - %s ON CONFLICT DO NOTHING", to, sp, - barriers, habitat, obs_sp_sql, - ctrl_where, ctrl_filter) + barriers, habitat, obs_sp_sql) n <- DBI::dbExecute(conn, sql) overrides_found <- overrides_found + n diff --git a/tests/testthat/test-lnk_barrier_overrides.R b/tests/testthat/test-lnk_barrier_overrides.R index a1e692e..b6a5dd6 100644 --- a/tests/testthat/test-lnk_barrier_overrides.R +++ b/tests/testthat/test-lnk_barrier_overrides.R @@ -94,7 +94,10 @@ test_that("lnk_barrier_overrides omits ctrl_filter when control is NULL", { expect_no_match(joined, "c\\.barrier_ind::boolean") }) -test_that("lnk_barrier_overrides control filter applies to habitat overrides too", { +test_that("habitat override path is NOT gated by control (bcfishpass parity)", { + # bcfishpass's hab_upstr CTE has no control join. Expert-confirmed + # habitat is higher-trust than observations; the control table does not + # block it. Any drift here would silently under-override bcfishpass. captured <- character(0) local_mocked_bindings( dbExecute = function(conn, sql, ...) { @@ -127,15 +130,13 @@ test_that("lnk_barrier_overrides control filter applies to habitat overrides too verbose = FALSE ) - joined <- paste(captured, collapse = "\n") - expect_match(joined, "AND NOT EXISTS", fixed = TRUE) - expect_match(joined, - "FROM working.barriers_definite_control c", fixed = TRUE) - expect_match(joined, "c.barrier_ind::boolean = true", fixed = TRUE) - # habitat-only path uses habitat-specific SQL (confirm we're in that - # branch by checking for the habitat join pattern) - expect_match(joined, - "working.user_habitat_classification h", fixed = TRUE) + habitat_sql <- captured[grepl("working.user_habitat_classification h", + captured, fixed = TRUE)] + expect_true(length(habitat_sql) >= 1) + habitat_joined <- paste(habitat_sql, collapse = "\n") + expect_no_match(habitat_joined, "NOT EXISTS") + expect_no_match(habitat_joined, "barriers_definite_control") + expect_no_match(habitat_joined, "c\\.barrier_ind::boolean") }) # -- per-species control gate (observation_control_apply) -------------------- From 22ac1dd59fa6b21cc26dd809523f521902c44188 Mon Sep 17 00:00:00 2001 From: almac2022 Date: Thu, 23 Apr 2026 12:03:10 -0700 Subject: [PATCH 07/11] =?UTF-8?q?comms(=E2=86=92link):=20M1=20verified=20a?= =?UTF-8?q?s=20a=20ready=20R-worker=20host;=20crew.cluster=200.4.0=20API?= =?UTF-8?q?=20gap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 7/7 checks pass on M1, 1.1s SSH+Rscript round-trip, NG packages load, .Renviron propagates. Includes a heads-up that crew.cluster 0.4.0 only exports HPC-scheduler controllers — no generic ssh one exists, so PR 3-of-3's launcher needs one of custom crew_class_launcher, clustermq, or bespoke mirai+ssh. Landing on 44-barriers-definite-control per soul 2026-04-23 branch-landing ruling (Policy A, commit on peer's current branch with --only flag). --- comms/rtj/20260423_m1_r_worker_verified.md | 35 ++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 comms/rtj/20260423_m1_r_worker_verified.md diff --git a/comms/rtj/20260423_m1_r_worker_verified.md b/comms/rtj/20260423_m1_r_worker_verified.md new file mode 100644 index 0000000..fc33afd --- /dev/null +++ b/comms/rtj/20260423_m1_r_worker_verified.md @@ -0,0 +1,35 @@ +--- +from: rtj +to: link +topic: M1 verified as a ready R-worker host; crew.cluster 0.4.0 API gap +status: open +--- + +## 2026-04-23 — rtj + +### M1 verified as a ready R-worker host (2026-04-22) + +Ran `rtj/scripts/hosts/crew-worker_verify.R` to validate the infra primitive under whatever launcher you pick for PR 3-of-3. 7/7 checks pass, 1.1s M4→M1→M4 round-trip via raw `ssh m1 'Rscript -'` stdin pipe. + +Confirmed on M1: + +- R 4.5.2 with `link`, `fresh`, `targets`, `crew` all loading cleanly +- `.libPaths()` has user library first (`~/Library/R/arm64/4.5/library`) +- `PG_DB_SHARE` propagates to non-interactive SSH R via `~/.Renviron` +- tailnet ACL permits peer → M4 TCP callbacks + +### One pitfall worth flagging on launcher choice + +`crew.cluster` 0.4.0 does NOT export `crew_controller_cluster` — only HPC-scheduler variants (`crew_controller_sge/lsf/pbs/slurm`). If you were planning to use a generic "ssh" controller from crew.cluster, it doesn't exist. Options I see: + +- `crew::crew_controller_local()` on M4 + custom `crew_class_launcher` subclass for SSH +- `clustermq` (mature, ssh-native) +- Raw `mirai::daemon` + bespoke dispatcher + +Not pushing an opinion; just saving you the 5 min I spent discovering this. + +### Landing note (per soul 2026-04-23 branch ruling) + +This thread is landing on your `44-barriers-definite-control` branch because that's where link's local clone is currently checked out. It won't reach `main` until PR #44 merges. If you need it visible on `main` sooner, cherry-pick or merge when convenient. + +Close when acknowledged. From fb8a0db80ab7a407377a13694ac455d2d324327c Mon Sep 17 00:00:00 2001 From: almac2022 Date: Thu, 23 Apr 2026 12:12:48 -0700 Subject: [PATCH 08/11] Add DEAD as the end-to-end test WSG for barriers_definite_control Phase 2b rollup was numerically identical to pre-fix on all four parity WSGs (ADMS/BULK/BABL/ELKR) because none of their TRUE control rows actually exercise the filter. Every row is rescued by either the observation threshold or the habitat path. The filter semantics were verified at the unit level but not end-to-end on real data. Province-wide hunt for TRUE control rows where the filter actually fires (obs >= threshold upstream AND zero habitat coverage) produced four candidates: CAMB (11 obs), DEAD (6), LFRA (16 but too large), SALM (7). Picked DEAD (Deadman River): smallest runtime, single TRUE control row at FALLS (356361749, 45743), six CH/CM/CO/PK/SK obs upstream, zero habitat classification for those species upstream. The tight "just above threshold" condition means pre-fix link would have overridden the fall (fall skipped, upstream accessible); post-fix link correctly blocks the override on anadromous species. bcfishpass reference: the fall at (356361749, 45743) IS present in bcfishpass.barriers_ch_cm_co_pk_sk post-override. bcfishpass kept it as a barrier via the control filter. link now matches. Direct verification on working_dead.barrier_overrides: - At the control position, exactly one species row is emitted: BT. observation_control_apply = FALSE for BT, so the NOT EXISTS clause was skipped and the observation count (>= threshold) produced the override. Matches bcfishpass BT model (no control join). - CH, CM, CO, PK, SK, ST all blocked at that position by the filter. Matches bcfishpass ch_cm_co_pk_sk and st models (control join present). DEAD rollup: all six species within 3% of bcfishpass reference. Incremental tar_make: comparison_ADMS/BULK/BABL/ELKR cached from the Phase 2b run, only comparison_DEAD + rollup rebuilt (42s). Log files captured: - 20260423_01_tar_make_post_44_phase2.txt (Phase 2, pre per-species gate) - 20260423_02_tar_make_phase2a.txt (Phase 2a, species gate, still bad on CH/CO/SK/ST) - 20260423_03_tar_make_phase2b.txt (Phase 2b, habitat path ungated, baseline rollup) - 20260423_04_tar_make_repro.txt (Phase 2b reproducibility) - 20260423_05_tar_make_dead.txt (incremental with DEAD added) Relates to #44 --- data-raw/_targets.R | 17 +++++++++++++---- planning/active/task_plan.md | 32 +++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/data-raw/_targets.R b/data-raw/_targets.R index f5dcbb3..af08f49 100644 --- a/data-raw/_targets.R +++ b/data-raw/_targets.R @@ -1,8 +1,10 @@ # data-raw/_targets.R # # Pipeline definition for the bcfishpass comparison. Orchestrates the -# six lnk_pipeline_* phase helpers across the four validated watershed -# groups and rolls up the per-WSG diff tibbles into one aggregate. +# six lnk_pipeline_* phase helpers across five watershed groups and +# rolls up the per-WSG diff tibbles into one aggregate. ADMS/BULK/BABL/ +# ELKR are the numerical-parity WSGs; DEAD (added 2026-04-23 with #44) +# is the end-to-end test for the `barriers_definite_control` filter. # # Run from the link repo root: # Rscript -e 'targets::tar_config_set(script = "data-raw/_targets.R", @@ -36,7 +38,13 @@ tar_option_set( packages = c("link", "fresh", "DBI", "RPostgres", "tibble", "dplyr") ) -wsgs <- c("ADMS", "BULK", "BABL", "ELKR") +# DEAD (Deadman River) is the end-to-end test for the control filter. +# It has one `barrier_ind = TRUE` control row with 6 observations upstream +# in the CH/CM/CO/PK/SK pool and zero habitat-classification coverage — +# the unique combination that actively exercises the filter. The other +# four WSGs are numerical-parity checks; their TRUE control rows are +# all rescued by the habitat path or sit below the observation threshold. +wsgs <- c("ADMS", "BULK", "BABL", "ELKR", "DEAD") list( tar_target(cfg, link::lnk_config("bcfishpass")), @@ -53,7 +61,8 @@ list( rollup, dplyr::bind_rows( comparison_ADMS, comparison_BULK, - comparison_BABL, comparison_ELKR + comparison_BABL, comparison_ELKR, + comparison_DEAD ) ) ) diff --git a/planning/active/task_plan.md b/planning/active/task_plan.md index 0902319..7f8ca92 100644 --- a/planning/active/task_plan.md +++ b/planning/active/task_plan.md @@ -36,13 +36,35 @@ Post-Phase-2 `tar_make()` drifted 11–22pp *away* from bcfishpass on ADMS/BABL - [x] Amend issue #44 body with Phase 2a scope and biological rationale. - [x] `/code-check` before commit — two rounds, both Clean. +## Phase 2b: Ungate habitat override path from control + +Phase 2a species-gating fixed BT/WCT drift but CH/CM/CO/PK/SK/ST still dropped 11–22pp on ADMS/BABL. Root cause: my `ctrl_filter` was applied to BOTH the observation and habitat paths of `lnk_barrier_overrides()`. bcfishpass's `hab_upstr` CTE has no control join at all — expert-confirmed habitat is higher-trust than the control designation and bypasses the filter. + +- [x] Removed `ctrl_where` / `ctrl_filter` from the habitat INSERT in `lnk_barrier_overrides()`. Observation path unchanged. +- [x] Updated roxygen: control parameter now notes it applies only to observations; habitat bypasses. +- [x] Flipped the existing "control filter applies to habitat too" test to assert the opposite (bcfishpass parity). `devtools::test()` 279 PASS. +- [x] Committed (6f3bc46). +- [x] `tar_make()` — Phase 2b rollup numerically identical to pre-fix baseline on all 34 rows, all 4 WSGs within 5% of bcfishpass reference. + +## Phase 2c: Add DEAD as the filter's end-to-end test WSG + +Discovered post-Phase 2b: none of ADMS/BULK/BABL/ELKR actually exercises the new control filter end-to-end. All 6 TRUE control rows across these WSGs are rescued by either the observation threshold (obs < 5) or the habitat path (classification upstream). That's why post-fix == pre-fix — correct, but information-less. + +Province-wide hunt for TRUE control rows with ≥ threshold observations upstream AND zero habitat coverage turned up 4 candidates: CAMB (11 obs), DEAD (6), LFRA (16, but too large), SALM (7). Picked **DEAD** (Deadman River) — smallest runtime, 6 obs just above CH threshold, single TRUE control row at FALLS (356361749, 45743). bcfishpass reference keeps this fall in `barriers_ch_cm_co_pk_sk` (control worked); pre-fix link would have overridden via observations. + +- [x] Added DEAD to `data-raw/_targets.R` wsgs vector. +- [ ] `tar_make()` incremental — builds `comparison_DEAD` + new rollup (ADMS/BULK/BABL/ELKR cached from Phase 2b run). +- [ ] Verify DEAD's diff_pct on CH/CO/SK/ST is small (post-fix link ≈ bcfishpass — filter working). +- [ ] Verify the specific fall at (356361749, 45743) is NOT in `working_dead.barrier_overrides` for CH/CM/CO/PK/SK/ST (filter blocked the override). + ## Phase 3: End-to-end verification -- [ ] `pak::local_install()` to pick up the pipeline changes -- [ ] First run: `cd data-raw && Rscript -e 'targets::tar_destroy(ask = FALSE); targets::tar_make()'` → log under `data-raw/logs/20260423_01_tar_make_post_44.txt` -- [ ] Inspect new rollup; compare to pre-change baseline (run 12 from 2026-04-22). Direction must be toward bcfishpass on WSGs with controlled `barrier_ind = TRUE` rows. -- [ ] Reproducibility run: immediately re-run `tar_make()` → `data-raw/logs/20260423_02_tar_make_repro.txt`. Rollup must be bit-identical. -- [ ] `digest::digest()` on the two rollup tibbles → same hash +- [x] `pak::local_install()` to pick up pipeline changes. +- [x] First post-fix run: `20260423_02_tar_make_phase2a.txt`, `20260423_03_tar_make_phase2b.txt`. +- [x] Inspect rollup against pre-change baseline — matches exactly on 4 WSGs (filter moot on those; DEAD being added to exercise it). +- [ ] Reproducibility run (Phase 2b state): `20260423_04_tar_make_repro.txt` in progress. Rollup must be bit-identical to Phase 2b. +- [ ] `digest::digest()` on two Phase 2b rollup tibbles → same hash. +- [ ] Post-DEAD reproducibility: two consecutive `tar_make()` runs with DEAD present produce bit-identical 5-WSG rollups. ## Phase 4: Artifact updates From 1c683e39d65f8b8baa2e4be856619aaf1a952c6f Mon Sep 17 00:00:00 2001 From: almac2022 Date: Thu, 23 Apr 2026 12:13:19 -0700 Subject: [PATCH 09/11] Add tar_make run logs for #44 phases 2, 2a, 2b, repro, DEAD Evidence trail for the barriers_definite_control investigation. Referenced from planning/active/task_plan.md. Relates to #44 --- .../20260423_01_tar_make_post_44_phase2.txt | 297 ++++++++++++++++++ .../logs/20260423_02_tar_make_phase2a.txt | 297 ++++++++++++++++++ .../logs/20260423_03_tar_make_phase2b.txt | 297 ++++++++++++++++++ data-raw/logs/20260423_04_tar_make_repro.txt | 297 ++++++++++++++++++ data-raw/logs/20260423_05_tar_make_dead.txt | 73 +++++ 5 files changed, 1261 insertions(+) create mode 100644 data-raw/logs/20260423_01_tar_make_post_44_phase2.txt create mode 100644 data-raw/logs/20260423_02_tar_make_phase2a.txt create mode 100644 data-raw/logs/20260423_03_tar_make_phase2b.txt create mode 100644 data-raw/logs/20260423_04_tar_make_repro.txt create mode 100644 data-raw/logs/20260423_05_tar_make_dead.txt diff --git a/data-raw/logs/20260423_01_tar_make_post_44_phase2.txt b/data-raw/logs/20260423_01_tar_make_post_44_phase2.txt new file mode 100644 index 0000000..c7330e0 --- /dev/null +++ b/data-raw/logs/20260423_01_tar_make_post_44_phase2.txt @@ -0,0 +1,297 @@ ++ cfg dispatched +✔ cfg completed [239ms, 296.43 kB] ++ comparison_BABL dispatched +NOTICE: schema "fresh" already exists, skipping + +NOTICE: table "barriers_definite" does not exist, skipping + +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_015" does not exist, skipping + +NOTICE: table "streams_acc_02" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_ch" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_co" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_sk" does not exist, skipping + +NOTICE: table "streams_acc_02_ovr_st" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_ch" does not exist, skipping + +NOTICE: table "frs_clusters_co" does not exist, skipping + +NOTICE: table "frs_clusters_sk" does not exist, skipping + +NOTICE: table "frs_qual_spawn_sk" does not exist, skipping + +NOTICE: table "frs_trace_lfid_sk" does not exist, skipping + +NOTICE: table "frs_clusters_st" does not exist, skipping + +✔ comparison_BABL completed [1m 37.2s, 419 B] ++ comparison_ELKR dispatched +NOTICE: schema "fresh" already exists, skipping + +Override validation: working_elkr.pscis_fixes vs working_elkr.crossings + Total overrides: 324 + Valid (matched): 288 + Orphans: 36 <-- not found in crossings + Duplicates: 0 +Updated 288 of 7306 rows (barrier_status) +NOTICE: table "barriers_definite_control" does not exist, skipping + +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_02" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_02_ovr_wct" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_wct" does not exist, skipping + +✔ comparison_ELKR completed [2m 35.7s, 319 B] ++ comparison_BULK dispatched +NOTICE: schema "fresh" already exists, skipping + +Override validation: working_bulk.pscis_fixes vs working_bulk.crossings + Total overrides: 580 + Valid (matched): 514 + Orphans: 66 <-- not found in crossings + Duplicates: 0 +Updated 514 of 5568 rows (barrier_status) +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_015" does not exist, skipping + +NOTICE: table "streams_acc_02" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_ch" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_co" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_pk" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_sk" does not exist, skipping + +NOTICE: table "streams_acc_02_ovr_st" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_ch" does not exist, skipping + +NOTICE: table "frs_clusters_co" does not exist, skipping + +NOTICE: table "frs_clusters_sk" does not exist, skipping + +NOTICE: table "frs_qual_spawn_sk" does not exist, skipping + +NOTICE: table "frs_trace_lfid_sk" does not exist, skipping + +NOTICE: table "frs_clusters_st" does not exist, skipping + +✔ comparison_BULK completed [2m 55.8s, 438 B] ++ comparison_ADMS dispatched +NOTICE: schema "fresh" already exists, skipping + +NOTICE: table "barriers_definite" does not exist, skipping + +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_015" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_ch" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_co" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_sk" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_ch" does not exist, skipping + +NOTICE: table "frs_clusters_co" does not exist, skipping + +NOTICE: table "frs_clusters_sk" does not exist, skipping + +NOTICE: table "frs_qual_spawn_sk" does not exist, skipping + +NOTICE: table "frs_trace_lfid_sk" does not exist, skipping + +✔ comparison_ADMS completed [1m 9.1s, 382 B] ++ rollup dispatched +✔ rollup completed [1ms, 758 B] +✔ ended pipeline [8m 18.3s, 6 completed, 0 skipped] diff --git a/data-raw/logs/20260423_02_tar_make_phase2a.txt b/data-raw/logs/20260423_02_tar_make_phase2a.txt new file mode 100644 index 0000000..fe8a5a7 --- /dev/null +++ b/data-raw/logs/20260423_02_tar_make_phase2a.txt @@ -0,0 +1,297 @@ ++ cfg dispatched +✔ cfg completed [237ms, 296.48 kB] ++ comparison_BABL dispatched +NOTICE: schema "fresh" already exists, skipping + +NOTICE: table "barriers_definite" does not exist, skipping + +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_015" does not exist, skipping + +NOTICE: table "streams_acc_02" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_ch" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_co" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_sk" does not exist, skipping + +NOTICE: table "streams_acc_02_ovr_st" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_ch" does not exist, skipping + +NOTICE: table "frs_clusters_co" does not exist, skipping + +NOTICE: table "frs_clusters_sk" does not exist, skipping + +NOTICE: table "frs_qual_spawn_sk" does not exist, skipping + +NOTICE: table "frs_trace_lfid_sk" does not exist, skipping + +NOTICE: table "frs_clusters_st" does not exist, skipping + +✔ comparison_BABL completed [1m 38.8s, 418 B] ++ comparison_ELKR dispatched +NOTICE: schema "fresh" already exists, skipping + +Override validation: working_elkr.pscis_fixes vs working_elkr.crossings + Total overrides: 324 + Valid (matched): 288 + Orphans: 36 <-- not found in crossings + Duplicates: 0 +Updated 288 of 7306 rows (barrier_status) +NOTICE: table "barriers_definite_control" does not exist, skipping + +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_02" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_02_ovr_wct" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_wct" does not exist, skipping + +✔ comparison_ELKR completed [2m 34.7s, 319 B] ++ comparison_BULK dispatched +NOTICE: schema "fresh" already exists, skipping + +Override validation: working_bulk.pscis_fixes vs working_bulk.crossings + Total overrides: 580 + Valid (matched): 514 + Orphans: 66 <-- not found in crossings + Duplicates: 0 +Updated 514 of 5568 rows (barrier_status) +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_015" does not exist, skipping + +NOTICE: table "streams_acc_02" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_ch" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_co" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_pk" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_sk" does not exist, skipping + +NOTICE: table "streams_acc_02_ovr_st" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_ch" does not exist, skipping + +NOTICE: table "frs_clusters_co" does not exist, skipping + +NOTICE: table "frs_clusters_sk" does not exist, skipping + +NOTICE: table "frs_qual_spawn_sk" does not exist, skipping + +NOTICE: table "frs_trace_lfid_sk" does not exist, skipping + +NOTICE: table "frs_clusters_st" does not exist, skipping + +✔ comparison_BULK completed [2m 56.2s, 438 B] ++ comparison_ADMS dispatched +NOTICE: schema "fresh" already exists, skipping + +NOTICE: table "barriers_definite" does not exist, skipping + +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_015" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_ch" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_co" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_sk" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_ch" does not exist, skipping + +NOTICE: table "frs_clusters_co" does not exist, skipping + +NOTICE: table "frs_clusters_sk" does not exist, skipping + +NOTICE: table "frs_qual_spawn_sk" does not exist, skipping + +NOTICE: table "frs_trace_lfid_sk" does not exist, skipping + +✔ comparison_ADMS completed [1m 7.3s, 387 B] ++ rollup dispatched +✔ rollup completed [1ms, 760 B] +✔ ended pipeline [8m 17.7s, 6 completed, 0 skipped] diff --git a/data-raw/logs/20260423_03_tar_make_phase2b.txt b/data-raw/logs/20260423_03_tar_make_phase2b.txt new file mode 100644 index 0000000..c465066 --- /dev/null +++ b/data-raw/logs/20260423_03_tar_make_phase2b.txt @@ -0,0 +1,297 @@ ++ cfg dispatched +✔ cfg completed [243ms, 296.48 kB] ++ comparison_BABL dispatched +NOTICE: schema "fresh" already exists, skipping + +NOTICE: table "barriers_definite" does not exist, skipping + +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_015" does not exist, skipping + +NOTICE: table "streams_acc_02" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_ch" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_co" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_sk" does not exist, skipping + +NOTICE: table "streams_acc_02_ovr_st" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_ch" does not exist, skipping + +NOTICE: table "frs_clusters_co" does not exist, skipping + +NOTICE: table "frs_clusters_sk" does not exist, skipping + +NOTICE: table "frs_qual_spawn_sk" does not exist, skipping + +NOTICE: table "frs_trace_lfid_sk" does not exist, skipping + +NOTICE: table "frs_clusters_st" does not exist, skipping + +✔ comparison_BABL completed [1m 44.1s, 416 B] ++ comparison_ELKR dispatched +NOTICE: schema "fresh" already exists, skipping + +Override validation: working_elkr.pscis_fixes vs working_elkr.crossings + Total overrides: 324 + Valid (matched): 288 + Orphans: 36 <-- not found in crossings + Duplicates: 0 +Updated 288 of 7306 rows (barrier_status) +NOTICE: table "barriers_definite_control" does not exist, skipping + +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_02" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_02_ovr_wct" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_wct" does not exist, skipping + +✔ comparison_ELKR completed [2m 36.3s, 319 B] ++ comparison_BULK dispatched +NOTICE: schema "fresh" already exists, skipping + +Override validation: working_bulk.pscis_fixes vs working_bulk.crossings + Total overrides: 580 + Valid (matched): 514 + Orphans: 66 <-- not found in crossings + Duplicates: 0 +Updated 514 of 5568 rows (barrier_status) +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_015" does not exist, skipping + +NOTICE: table "streams_acc_02" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_ch" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_co" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_pk" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_sk" does not exist, skipping + +NOTICE: table "streams_acc_02_ovr_st" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_ch" does not exist, skipping + +NOTICE: table "frs_clusters_co" does not exist, skipping + +NOTICE: table "frs_clusters_sk" does not exist, skipping + +NOTICE: table "frs_qual_spawn_sk" does not exist, skipping + +NOTICE: table "frs_trace_lfid_sk" does not exist, skipping + +NOTICE: table "frs_clusters_st" does not exist, skipping + +✔ comparison_BULK completed [2m 51.2s, 438 B] ++ comparison_ADMS dispatched +NOTICE: schema "fresh" already exists, skipping + +NOTICE: table "barriers_definite" does not exist, skipping + +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_015" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_ch" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_co" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_sk" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_ch" does not exist, skipping + +NOTICE: table "frs_clusters_co" does not exist, skipping + +NOTICE: table "frs_clusters_sk" does not exist, skipping + +NOTICE: table "frs_qual_spawn_sk" does not exist, skipping + +NOTICE: table "frs_trace_lfid_sk" does not exist, skipping + +✔ comparison_ADMS completed [1m 6.6s, 379 B] ++ rollup dispatched +✔ rollup completed [1ms, 751 B] +✔ ended pipeline [8m 18.8s, 6 completed, 0 skipped] diff --git a/data-raw/logs/20260423_04_tar_make_repro.txt b/data-raw/logs/20260423_04_tar_make_repro.txt new file mode 100644 index 0000000..f15addb --- /dev/null +++ b/data-raw/logs/20260423_04_tar_make_repro.txt @@ -0,0 +1,297 @@ ++ cfg dispatched +✔ cfg completed [265ms, 296.48 kB] ++ comparison_BABL dispatched +NOTICE: schema "fresh" already exists, skipping + +NOTICE: table "barriers_definite" does not exist, skipping + +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_015" does not exist, skipping + +NOTICE: table "streams_acc_02" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_ch" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_co" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_sk" does not exist, skipping + +NOTICE: table "streams_acc_02_ovr_st" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_ch" does not exist, skipping + +NOTICE: table "frs_clusters_co" does not exist, skipping + +NOTICE: table "frs_clusters_sk" does not exist, skipping + +NOTICE: table "frs_qual_spawn_sk" does not exist, skipping + +NOTICE: table "frs_trace_lfid_sk" does not exist, skipping + +NOTICE: table "frs_clusters_st" does not exist, skipping + +✔ comparison_BABL completed [1m 44.9s, 416 B] ++ comparison_ELKR dispatched +NOTICE: schema "fresh" already exists, skipping + +Override validation: working_elkr.pscis_fixes vs working_elkr.crossings + Total overrides: 324 + Valid (matched): 288 + Orphans: 36 <-- not found in crossings + Duplicates: 0 +Updated 288 of 7306 rows (barrier_status) +NOTICE: table "barriers_definite_control" does not exist, skipping + +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_02" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_02_ovr_wct" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_wct" does not exist, skipping + +✔ comparison_ELKR completed [2m 27.2s, 319 B] ++ comparison_BULK dispatched +NOTICE: schema "fresh" already exists, skipping + +Override validation: working_bulk.pscis_fixes vs working_bulk.crossings + Total overrides: 580 + Valid (matched): 514 + Orphans: 66 <-- not found in crossings + Duplicates: 0 +Updated 514 of 5568 rows (barrier_status) +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_015" does not exist, skipping + +NOTICE: table "streams_acc_02" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_ch" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_co" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_pk" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_sk" does not exist, skipping + +NOTICE: table "streams_acc_02_ovr_st" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_ch" does not exist, skipping + +NOTICE: table "frs_clusters_co" does not exist, skipping + +NOTICE: table "frs_clusters_sk" does not exist, skipping + +NOTICE: table "frs_qual_spawn_sk" does not exist, skipping + +NOTICE: table "frs_trace_lfid_sk" does not exist, skipping + +NOTICE: table "frs_clusters_st" does not exist, skipping + +✔ comparison_BULK completed [2m 56.7s, 438 B] ++ comparison_ADMS dispatched +NOTICE: schema "fresh" already exists, skipping + +NOTICE: table "barriers_definite" does not exist, skipping + +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_015" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_ch" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_co" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_sk" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_ch" does not exist, skipping + +NOTICE: table "frs_clusters_co" does not exist, skipping + +NOTICE: table "frs_clusters_sk" does not exist, skipping + +NOTICE: table "frs_qual_spawn_sk" does not exist, skipping + +NOTICE: table "frs_trace_lfid_sk" does not exist, skipping + +✔ comparison_ADMS completed [1m 9s, 379 B] ++ rollup dispatched +✔ rollup completed [1ms, 751 B] +✔ ended pipeline [8m 18.4s, 6 completed, 0 skipped] diff --git a/data-raw/logs/20260423_05_tar_make_dead.txt b/data-raw/logs/20260423_05_tar_make_dead.txt new file mode 100644 index 0000000..82c74ed --- /dev/null +++ b/data-raw/logs/20260423_05_tar_make_dead.txt @@ -0,0 +1,73 @@ ++ comparison_DEAD dispatched +NOTICE: schema "working_dead" does not exist, skipping + +NOTICE: schema "fresh" already exists, skipping + +NOTICE: table "barriers_definite" does not exist, skipping + +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_015" does not exist, skipping + +NOTICE: table "streams_acc_02" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_ch" does not exist, skipping + +NOTICE: table "frs_clusters_co" does not exist, skipping + +NOTICE: table "frs_clusters_sk" does not exist, skipping + +NOTICE: table "frs_qual_spawn_sk" does not exist, skipping + +NOTICE: table "frs_trace_lfid_sk" does not exist, skipping + +NOTICE: table "frs_clusters_st" does not exist, skipping + +✔ comparison_DEAD completed [42.2s, 420 B] ++ rollup dispatched +✔ rollup completed [0ms, 866 B] +✔ ended pipeline [42.6s, 2 completed, 5 skipped] From f52dcbc303f37e564710575fc7c86b35cdad6b1b Mon Sep 17 00:00:00 2001 From: almac2022 Date: Thu, 23 Apr 2026 12:26:42 -0700 Subject: [PATCH 10/11] Bump to 0.6.0: NEWS, DESCRIPTION, research doc, vignette, artifacts Phase 4 of #44. Numerical artifacts regenerated from the 5-WSG post-DEAD pipeline; narrative updates document the control-filter wiring and the split between parity WSGs and the end-to-end test WSG. - DESCRIPTION: Version 0.5.0 -> 0.6.0 - NEWS.md: 0.6.0 entry covering the control-filter wiring, per-species gating via observation_control_apply, habitat-path bypass, manifest- gated pipeline wiring, asymmetric-gating fix in prep_load_aux, and DEAD added as the end-to-end validation WSG. - inst/extdata/configs/bcfishpass/README.md: 5 WSGs; note that DEAD is the control-filter end-to-end test, the other four are parity. - inst/extdata/vignette-data/*.rds: regenerated from data-raw/ vignette_reproducing_bcfishpass.R against the current tar_make rollup (46 rows, bit-identical across two full rebuilds; digest 210c3f8254c47ac88573a80d96a2701e). - research/bcfishpass_comparison.md: adds the DEAD parity table, a section explaining why DEAD is the filter test WSG, a row in the "Key fixes" table, and a subsection describing the three-part fix (observation-path NOT EXISTS, per-species gate, habitat-path bypass). DAG rollup node updated to 46 rows; tar_map includes DEAD. - vignettes/reproducing-bcfishpass.Rmd: 5 WSGs; names DEAD's role; pivot tables include DEAD column via intersect() on names(w). Reproducibility verified: two consecutive `targets::tar_destroy(ask = FALSE); targets::tar_make()` runs on the same DB state produce bit-identical rollups (same SHA256 via digest::digest()). Relates to #44 --- DESCRIPTION | 2 +- NEWS.md | 11 + .../logs/20260423_06_tar_make_repro_dead.txt | 365 ++++++++++++++++++ inst/extdata/configs/bcfishpass/README.md | 2 +- inst/extdata/vignette-data/rollup.rds | Bin 751 -> 866 bytes inst/extdata/vignette-data/sub_ch.rds | Bin 31952 -> 31919 bytes inst/extdata/vignette-data/sub_ch_bcfp.rds | Bin 30337 -> 30376 bytes research/bcfishpass_comparison.md | 29 +- vignettes/reproducing-bcfishpass.Rmd | 20 +- 9 files changed, 417 insertions(+), 12 deletions(-) create mode 100644 data-raw/logs/20260423_06_tar_make_repro_dead.txt diff --git a/DESCRIPTION b/DESCRIPTION index a979d8b..b5031c9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: link Title: Crossing Connectivity Interpretation -Version: 0.5.0 +Version: 0.6.0 Authors@R: person("Allan", "Irvine", , "airvine@newgraphenvironment.com", role = c("aut", "cre"), diff --git a/NEWS.md b/NEWS.md index a354028..73548fd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,14 @@ +# link 0.6.0 + +Honour `user_barriers_definite_control.csv` at the observation-override step. + +- `lnk_barrier_overrides()` now excludes observations upstream of control-flagged positions from counting toward the override threshold, matching bcfishpass's access SQL. Previously controlled positions (concrete dams, long impassable falls, diversions) could be re-opened by upstream historical observations ([#44](https://github.com/NewGraphEnvironment/link/issues/44)). +- Gated per-species by a new `observation_control_apply` column in `parameters_fresh.csv` — TRUE for CH/CM/CO/PK/SK/ST; FALSE for BT/WCT; NA for CT/DV/RB. Residents routinely inhabit reaches upstream of anadromous-blocking falls (post-glacial headwater connectivity, no ocean-return requirement), so their observations still override. Matches bcfishpass's per-model application. +- Habitat-confirmation override path intentionally bypasses the control table — expert-confirmed habitat is higher-trust than observations, and bcfishpass's `hab_upstr` CTE has no control join either. +- `.lnk_pipeline_prep_overrides` now passes the control table to `lnk_barrier_overrides()` when the config manifest declares `barriers_definite_control`. Manifest key is the contract; no DB probe. +- `.lnk_pipeline_prep_load_aux` now always creates a schema-valid (possibly empty) `barriers_definite_control` table when the manifest declares the key — fixes an asymmetric gating bug that would have raised "relation does not exist" on AOIs with zero control rows. +- End-to-end validation WSG: DEAD (Deadman River) added to `data-raw/_targets.R`. It has a single `barrier_ind = TRUE` control row at FALLS (356361749, 45743) with six anadromous observations upstream and zero habitat coverage — the unique combination that actively exercises the filter. All four prior WSGs (ADMS/BULK/BABL/ELKR) were rescued by either the observation threshold or habitat path, making them parity checks rather than filter tests. + # link 0.5.0 Documentation and narrative for the targets pipeline. diff --git a/data-raw/logs/20260423_06_tar_make_repro_dead.txt b/data-raw/logs/20260423_06_tar_make_repro_dead.txt new file mode 100644 index 0000000..d358e4e --- /dev/null +++ b/data-raw/logs/20260423_06_tar_make_repro_dead.txt @@ -0,0 +1,365 @@ ++ cfg dispatched +✔ cfg completed [248ms, 296.48 kB] ++ comparison_BABL dispatched +NOTICE: schema "fresh" already exists, skipping + +NOTICE: table "barriers_definite" does not exist, skipping + +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_015" does not exist, skipping + +NOTICE: table "streams_acc_02" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_ch" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_co" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_sk" does not exist, skipping + +NOTICE: table "streams_acc_02_ovr_st" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_ch" does not exist, skipping + +NOTICE: table "frs_clusters_co" does not exist, skipping + +NOTICE: table "frs_clusters_sk" does not exist, skipping + +NOTICE: table "frs_qual_spawn_sk" does not exist, skipping + +NOTICE: table "frs_trace_lfid_sk" does not exist, skipping + +NOTICE: table "frs_clusters_st" does not exist, skipping + +✔ comparison_BABL completed [1m 46s, 416 B] ++ comparison_ELKR dispatched +NOTICE: schema "fresh" already exists, skipping + +Override validation: working_elkr.pscis_fixes vs working_elkr.crossings + Total overrides: 324 + Valid (matched): 288 + Orphans: 36 <-- not found in crossings + Duplicates: 0 +Updated 288 of 7306 rows (barrier_status) +NOTICE: table "barriers_definite_control" does not exist, skipping + +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_02" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_02_ovr_wct" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_wct" does not exist, skipping + +✔ comparison_ELKR completed [2m 35.6s, 319 B] ++ comparison_BULK dispatched +NOTICE: schema "fresh" already exists, skipping + +Override validation: working_bulk.pscis_fixes vs working_bulk.crossings + Total overrides: 580 + Valid (matched): 514 + Orphans: 66 <-- not found in crossings + Duplicates: 0 +Updated 514 of 5568 rows (barrier_status) +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_015" does not exist, skipping + +NOTICE: table "streams_acc_02" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_ch" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_co" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_pk" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_sk" does not exist, skipping + +NOTICE: table "streams_acc_02_ovr_st" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_ch" does not exist, skipping + +NOTICE: table "frs_clusters_co" does not exist, skipping + +NOTICE: table "frs_clusters_sk" does not exist, skipping + +NOTICE: table "frs_qual_spawn_sk" does not exist, skipping + +NOTICE: table "frs_trace_lfid_sk" does not exist, skipping + +NOTICE: table "frs_clusters_st" does not exist, skipping + +✔ comparison_BULK completed [2m 58.9s, 438 B] ++ comparison_ADMS dispatched +NOTICE: schema "fresh" already exists, skipping + +NOTICE: table "barriers_definite" does not exist, skipping + +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_015" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_ch" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_co" does not exist, skipping + +NOTICE: table "streams_acc_015_ovr_sk" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_ch" does not exist, skipping + +NOTICE: table "frs_clusters_co" does not exist, skipping + +NOTICE: table "frs_clusters_sk" does not exist, skipping + +NOTICE: table "frs_qual_spawn_sk" does not exist, skipping + +NOTICE: table "frs_trace_lfid_sk" does not exist, skipping + +✔ comparison_ADMS completed [1m 9.8s, 379 B] ++ comparison_DEAD dispatched +NOTICE: schema "fresh" already exists, skipping + +NOTICE: table "barriers_definite" does not exist, skipping + +NOTICE: table "streams_blk" does not exist, skipping + +NOTICE: table "gradient_barriers_raw" does not exist, skipping + +NOTICE: table "natural_barriers" does not exist, skipping + +NOTICE: table "barrier_overrides" does not exist, skipping + +NOTICE: table "barriers_bt" does not exist, skipping + +NOTICE: table "barriers_bt_min" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk" does not exist, skipping + +NOTICE: table "barriers_ch_cm_co_pk_sk_min" does not exist, skipping + +NOTICE: table "barriers_st" does not exist, skipping + +NOTICE: table "barriers_st_min" does not exist, skipping + +NOTICE: table "barriers_wct" does not exist, skipping + +NOTICE: table "barriers_wct_min" does not exist, skipping + +NOTICE: table "gradient_barriers_minimal" does not exist, skipping + +NOTICE: table "streams" does not exist, skipping + +NOTICE: table "streams_habitat" does not exist, skipping + +NOTICE: table "observations_breaks" does not exist, skipping + +NOTICE: table "habitat_endpoints" does not exist, skipping + +NOTICE: table "crossings_breaks" does not exist, skipping + +NOTICE: table "streams_breaks" does not exist, skipping + +NOTICE: relation "streams_id_segment_idx" already exists, skipping + +NOTICE: table "streams_acc_015" does not exist, skipping + +NOTICE: table "streams_acc_02" does not exist, skipping + +NOTICE: table "streams_acc_025" does not exist, skipping + +NOTICE: table "streams_acc_025_ovr_bt" does not exist, skipping + +NOTICE: table "frs_clusters_bt" does not exist, skipping + +NOTICE: table "frs_clusters_ch" does not exist, skipping + +NOTICE: table "frs_clusters_co" does not exist, skipping + +NOTICE: table "frs_clusters_sk" does not exist, skipping + +NOTICE: table "frs_qual_spawn_sk" does not exist, skipping + +NOTICE: table "frs_trace_lfid_sk" does not exist, skipping + +NOTICE: table "frs_clusters_st" does not exist, skipping + +✔ comparison_DEAD completed [41.7s, 420 B] ++ rollup dispatched +✔ rollup completed [1ms, 866 B] +✔ ended pipeline [9m 12.7s, 7 completed, 0 skipped] diff --git a/inst/extdata/configs/bcfishpass/README.md b/inst/extdata/configs/bcfishpass/README.md index efa8c06..8b82cbe 100644 --- a/inst/extdata/configs/bcfishpass/README.md +++ b/inst/extdata/configs/bcfishpass/README.md @@ -1,6 +1,6 @@ # bcfishpass config -Reproduces bcfishpass output exactly for regression. All 4 watershed groups (ADMS, BULK, BABL, ELKR) are within 5% of bcfishpass when this config drives the pipeline. +Reproduces bcfishpass output exactly for regression. All five watershed groups (ADMS, BULK, BABL, ELKR, DEAD) are within 5% of bcfishpass when this config drives the pipeline. ADMS/BULK/BABL/ELKR are the numerical-parity set; DEAD is the end-to-end test for the `barriers_definite_control` override filter (see `research/bcfishpass_comparison.md`). ## What is in here diff --git a/inst/extdata/vignette-data/rollup.rds b/inst/extdata/vignette-data/rollup.rds index 8ccbee7185671a1ca869d32fb039ef3dab438395..ff55b9201e76879e1c319456efeba55456f8fa96 100644 GIT binary patch literal 866 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 diff --git a/inst/extdata/vignette-data/sub_ch_bcfp.rds b/inst/extdata/vignette-data/sub_ch_bcfp.rds index 35ab336dbd5036907837d17e33522a40de15529e..26c20a08ba42d3fe313847483259f0d2c78b41f6 100644 GIT binary patch literal 30376 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�Lnno@Q@+u$`93fU(KWn(t?sX2%hR zcMoNP2UaKO`Gb3c)ig-*nk^3B4YNQ6hee-Baw=VJdCF=SwTq>$3K0~lHuHm45hak{ z^?YJGe*SgW&2Q+N+S<>9uAnZp?b+^{oB?ABd=Jpu#Q5*YP6)Q5(gSzuy2wj#rHZ?o zKnoMyPy{8Wp*&O=D7=A93mTu z3g#Z(H;-(p`+VaiNfL6S+;p)!ooD#>hAs%RGIw5s72)7=Ap_-$gxaZBPJyQOjdLI{ z%H)&7gq*;Y@`7U!Zibn4Q<k#cRvqFK4cVAQhVHWhFd^`M(RtNz_GqZgP`#2<0=rh zDdO7ujbQUrR2TKf5eh73XC{fR9)EZ7mHM+WkCqTCxl?4F&QY=nswexxK`>aug?l>~ zR4*+~M6=20swZggWFt*#69V;QHOpwhPzc~h#i|#`M)Pw5Qkv3WY0CK5`QSeG_Kw>m zp#rZMN%EEydP~53Cm16=y*C52)7w@6!JiazZHYu3xMHYg0s&jnypj^Z{h;a8x7F}b zN%F>#iywnJTaCw1e@UDIOlO+GnJT7(u!%|2dy89g37MX5B8lI4vYdNKxyl2VLrKCn z-R-|Lrhw~%x8Dx|&AX?z9spegnG0Ymlgt{ou-Jlb-^z4hFOs~OW?LO0&#~V16)Cqo zaY7yKk<`56*+KHW72jr-6@dNxI?ia1q`XM+I@EVkZAhQJADDk2hYh|n=}beZH>rn} zY4%SSOiD=SV!SCB?U1Cjtg+`2Xc_v7V=Xx2ad96>Nbq#!s2{mr;FIAwMe;mh(E+~r z`Jiak)ci?_3yz+CEgKIu_2j7!fyq~cykX}OH@=#~i@+&yoBXxK7>9|bGTq(1lQysq z|5`9x60Ep=EEo5eP|Dhn3fq$qJX5fz3EX_`lg=%0Zun?N3w+S%m+Oni!L-e{itzXQ zvNdOMlB6v80XH|IefMoPc%?UGQt}p?>B{GLiH~Zo7Dqe9Cw`-v+yoc)bNirv;}@}y z$@w5)+pB8r4&iK^DkMKe8}#Kf{gDP*H?7TpZ8DY)nctNQ3VZL3$cKISo|d_}794hn z7YZLLh)fx`$ldM`8zcpA$?g; zT!qq^LZKS`nBQDB3cfnM;s^pCgPxP?{Ie%*aHS=V8*KWXznCO>xPO#$1DTY(!t^p0 ztl+LXO3JC>{FP27FH7dg%E*CqQ23dZBw5DR9huN*`g{E*gpYyl@BEzL`}J4b^;1yq z`V_h50cae3xr~#^LLd|KNykiRov|*Z@6`UIP*SN@e{ zzCHQu5w{$IUT;OgeffIO(cHBSeo>E7sqoASoO#w_t{SKqydfiyEG$*Xa@z{Nd4H1w z`an;hsUgJ-pWg|3ccH#?yB^N2tpQ6Tia5x^+ngsn9DJhAv;$sAb>P%jCbrO$+Wi@=6a=c;cVD*p zqkgrwHA<&Bfk%0L4sM;42qbg)3gIHqKVLI#Cm4R<@LXs>?U02nPf(t=j+$KGS8(b3 zpED#TC4Tr4B`0SZGDT&c{R-p?BJT*6+`?%hfLe=OE-gKQ0JbZXW z^C6g1R``QnGDQD>`8fEfdMDqq=3|WxLp0DqUv72x}E$~)yh+O__jklHhn7+`bWFc;zvP5#pz>SXb#7yraVMgnj$X~~@Q zJ_0^3d-|cJFQHAdz6JK>!1rW*eX7`V0vr{HKM9|i^}?WiguK`vu~pNBPtAI6@4w>? zI2iSH1AIx3g#%01;1aMt<_$D zKiYKSF*eMneC;TQtu|-=*%Emg6d70>hp}!hb3bW75LA0ocLDL8xlTpa*j3O%I<8&? z96wCeIgeO}OCwhfW79$)eMYP`sQ!L$G;EuNRo{|8Zg9;ZM-O_7Vz~aR`Kmh=UiDoA zcNa&$ehH@k;J9x=OPolds?MGL@?j{2sxorucp!A8%ITGQdlG4h6dBNb@QF3ketxQI z1v!I7B9{`LGVfdm&Re4H51(9PyK!F@zO%+oCj2`|Z0R`VbUR6Oxy7-0+eH#v%)WjR z?N(Daz944|?0Rh;D` zCLK(nD%@FgBoVe$q374u1+Y~LE!2(?UohuLWbRM!%-CwtOfo@Od%Oz$S7G-a)gl4V zBQ z;mwIjVASx~TC(YHK;mjN#<}H2#cP|OKP-Qa)E)K@iY^KY9+Nebqirw(T`*B z2UbUqcJyxn-$?dENKvTP3uP6n;s2~#sE@7aE#4CNR~yJonp?Q^lS2x1oL6RP_mFH9 zt+I|ZA0e9^KRlXcOE!+$rbP&W%eEgifSt`#RMPA}PhLESAK&m9oLbB}HyY%hvg=`5 zt84C9^*W^&;L~t9b=aNU22bxs#8<%;Mz;EPqonOLbK51@) zpJl%g`kw`NV$C^{h)7qLdHpL=myZgv;oHoAMk)SsA%7>aPmtIFA+rff#N6hOt#yZ4 z!B_jTHll9LJHJG{<_FJD>>**{PFzPPN%ii+}#n|>Y8p_8*gPmp(?-e=Xj9;K7Bgfjgd_>95lNLZoFao z677+dUuYCY?5^&0@v~FO0;}%|b*oxkGe0-ok9tS5cVNneq)ccHOVMjre9OVen5yp> zua*}xBMo4~EDv!#2;pEL7ABh&bc0_lv>@Zg?c)-Rv;18R%^D=Z_agV?@9#lwkD6Qf zZb9ug3xpZj7|%Ls4(H`&;k*p;d0&0NGLj@JyF2XXq&c0;Xf-)#Fq6}|(JP@vO+Hnc z_j)k)68v`ze<44NBt82bv!7GAmfXtNJW+{&id9iAEr}JrpVj`Gk}oZJ6=Rdiv@(j{ zcGVkwqCNYhvRr0Y2$ayCnXyUbzvn(^iHK-(8?*#Oj6K`$c}#=d61$iu&0|K{M(|%} zYz{14SY9{uG&m(HT!0UKeqeq0+X?W>=)f(E`0ObTs%VHRq12ZPcS+tIf zYl1$_A~v|ERCk{@s&mPUC!JHN=zFOO)}aS+&#BBdQ-Ub1t|{IdHrtK1x+XWqtlZ`U znr>|*ixqw+x)V4jS+aamf|UA2rmtYrlG0Cdr*9!~eSIby{8QqoV>`Ptr^CGd;z>6%qMDbY*nht@fpzznaL zyBPPz!-h(EXcLngFP@Z@ktEASBYcPtO+|A!W9mr)m_pa}&RK_-H8dC>#X0$i@l6Z|FMws5Ol6{k{m)Q+m1T`Ua@dN?~pF7ybH|JV51zM%^#Ebk35x)=x+wKR)ud5f$v7*Hopcx zTy!x(Tx&Qlowqj*oc3tm2(*L2g)V<>#N7s8qPQCo=Noh{=W`?*XLVvO=b+vU_ip~c z1zT&FKRCfXN}G3P?48l?nC-75ZE^5As*D({@D*c#u%VIKbc;9eG?ooVqJ9*EEhY#e~YwpLh=M`W@TM*p2M_B34!IC zyU#%jS6WX$w!8^6Yw+)cEvlTJQhXBjzLLG}tlv(Ilcxo%Z^QOgy#EmO1Z`f?)jVq< z;`NH-iCa9N3oFhq%j}0OttgM(_+}i8bg45I1=sX@hLG{b9g8CZ`bHm@5HR&E=Qah8VI|_(Iilkm624HVLLXUPsMwLsJ=d0B$|IZg?ld|h-WPhJb6+0! zA$00O_e+dLq1%B+x~G5-#2%l7Efq@g6c_FU!!E@ao~I>5r_-h)|7ug7N%0bO?CKP< zanfj5wMUvPh&!&MPZlxkV84Ep#4V;TZ*GESFVHgendJ>CPybptPLdq(PJP!fDUP`* zTQxWrd=n>pYc`2re3^c+;PYVf3>l0Ui|LQT6ut2ISL)lkNbr-oKoB&id80m;@G8dM z;r+H)(`Oe3pnEkTKpx z65^S}w%q~K2g-a1sGh^s$z0Ff@{^SG7`I0MVR<46ysXo3w zIwPITm)P9RKzv-itFiP>MXPJo&IKE?AJY;$|CvWol?f2of_|rJvUiIB59lN1IS+PK zH9*nJ!Ups_5U!#|O90J)_T!%PPA$pfvRe&Up(Gx<2D;N}*ekdU{J?gq40a>^NA)hR z!>z6v@+YUT&IY$UF%R{g^v+ktadE$Ryh2i ziALW@40+!zb?e7rdW)U^-JasK7zb@&aLKW__-jOL1ye;dmZDyZGj=U|hnTuJH}X_FYmtSm&iX;aWC^x)*FzMZIYW1%5 zM#Pt2Hatb%jU3o!{lkA!g58*u-6}EUUBt6T@Aw($OT>=8;LRCa4f_&iAkiKNuBsYa z5`y^mTZHvK_@?~(b{C)^o{Ipx_fs;rGT+D^nl<;mfWZdD z3Avem^ftX5s!y$`V1%GX9d}x|Vp-Ql6 z4(30^=U4BT1L2D#n7&@$gLqFu;*M!3*|e-H_-kMnXrR2C(nk`D8J{WX2lp@LG~k<* z5MC?yTF@16rH1TL=zGbe;#=p?eA&n_&C=+2&K|w2{z%HSljO z`8u#vAN`|zsB}}%E20nfFI^}wNdxBXh^~7~5}^+5BR5J1V<+{;j}rhV7%z1#|x3@myKduQ6vop3}2 zOl0krfhIHcj68hB0CY^@YB>(B_#Ed)Xn%IL<}Xl3+WapXe{(wr8)T|~M%~kzByE)) zOg=~A;qGfY5nr2f?YR?14kmk}e=0(Yrga#bL;StQ=&vNZf7TBDoeY;To%FjM{z_^* zsUH4HTFgKhQnW*(KyUSr@$I0!;{#s!n(BqOvV<;x(%Io*ouK|>mD?tSXDQ`!U}%+2 z5o}v^zzzQA&{5S{H!?qwcP$Tmx35I}P<^AWz9|;Z$Lqa`ldO>vemJez1{Ae&4=3+6 z;ipyBHMXxnn<)QkXv%`USAM+pxg*Aevh60hnPn8}JkPI^ z;&|qHhR3^4{G?EoPIK&A5ddxr5N^7N*y%&}e0FFJ#j62#H^7G}Zec0sMW0ageskMK z26R(*8-mYMwAg>={#07bMW@bY|JOGCUW-*xn;CkAF!^KeXIcXCNo%VP)oU0-Q!dlsIsVr*&{xt%O#>D{ zzotE}bZSqSH1`+3=!K9nc=xf^4l>u*f3|jQ4`Z8te$Pwnx9=K*EvQ`dv@V?sG`ANH zR|U2ApIA!bRhgiBuZM3Pjf49(6>n!jimTiE>wJRdf85MxunRWfvg5uxJUcmqP6 zN+e~W9J(<5X>`?8=*9FmOz+$;gYOm2hvM(kU!OcZ1$`#{x$eX?#5C!5 zE14dB0o#A5j9G(i(G9UA31y}HvR0BzDp8eVbQG-MODO*UCN$h#O*Hfd!vu>Xp#Qdx z4dgIIMTeTJxwQ75v}U)XZnroz7$se{{}}ujW%<=m`9@IA^X-oqq92WymqU-5eHOWJ zWf|C~A3k;j+;5?$N0LRI7B9*rdUM|_4pWS2v(?S+GvMFM!gH@aC!0Z>yEi6RgWjFe zqdi2go`~7#3c3a@Ig4>-M&0$Sj?66^t<}6j%F_|El}FrRru8%N6#BfG*}I>zCZOjd zd!@^u-?2m6NWXL+NN?^U*QZ=R@fp-L`dP~XGJSm&bqrMIiVG&mqf{vqBc0^B+EXEN zwEj(}k4Vyq?fyButUf8XcGt5>hnB_firc`#*anxs=kLi}j_a@O+whrryfhZ=TKU$r zeN>$`R%rA3U@X<<>RGaGXZf*pc3{_{1FyEz#smW!31<9>VTvEhUpoYCTYNNOmiDPX zF^0*6_3nsL_`bPW%QVLsd#CX`&i!l6$fP*;ueBNs)P*SReIZGFB*V4?T07@dz$tIC zk!k08nX-Jc!OGey4gNI8Zs+wYhzD~FGVkl7Z|4ZB{VHl~bu6nw#-jN)nQL-+NyqC16=IV_Say<(n2_2SiS-FP1mt5N-JhX_cZx2|o z>jiDy1f4qI{BM7tt?Axpcj>~1Vo>|A@FCAu*Z42rqP%rzhkyK8vu&#W_QSi+WPV=h zOeD0i=?fc`_x!ZOKN#Cs@_T;auLC6}%`5zMAOvG=_gjMw%6x7gA#?KaHRnypT%pg@ ztavhaI3%KR3OqS~t@Ru5bd1HTn}iqE*1#85>^qeD6tO@>a)r-hCD7DrwkVm~clK$S z!USsWpCau*iy09lyJ7aTgpR#uB@jcFu2r}k1r~l2f4vm+rk=m-2QElZi|GN?75SQf zftpI*DlFi7W*sH+*)6zuHb2??w$flF+5Bea@$r=axWX{}<__@VW(r9dmj9Z2lm~55 zAaAr)nyifwG&EcEgB<=ib6|KknM<_xKBY71;IWuKhaZT23W{zQ7?_eZ6eC~nyMqru z=P*O_6z<&4+Dq0<q!hgW^L7t z@{4Ac9VKg33%lPuP^qTro=NYZ*H`*^LsJN*t~hn&7Fdup)e|wL;8iB`P-t%<<>q)Z zH!w(xrLzuf`S8XFeP8IMIL{2ktaCV8Zg-LSUXjJlTh&2_^@@~};HC-v9|ytc7Q4&v zS#!FMNLFCn3+wf2GPfiHd{o?X2KOgw@=zlPahK@*A>Gjm@X_V`9cw65G1Z$U zLSMk#qc#h6PdW_pph3kKTrYMc>cy3{;5Dl|#y6nnW>w#w1)nMI-0I!KN7H+B`k>r@ zZTsK*6hQsOFE;j%#@MLp;+b%R-Kx5G;r1HD#Z`srPB$NdO;a!5#Tcq8)e)7v!6|&>T+=|KSUDliP;(Hpj=&Zwln@{OWmO2I%SM0-LS#*x|d*Dw|)f1jBZtM z&mHJJ-4A`{-$N)=J=OY1FZ69aW%*T0B|z$tid0E3mOqNwhqgzGf%QuyNnDUZ#2dty zyCdYd&7tii?lVhAc7S(MRy*uuOjrG0yXrVMstEq2-XOJg0{*4msBW86BkfS_Noz@6 zjY2PATxgmf{ICu&rKZ^q|Hmh3hiWt6N4Y2M3!Bq^xxo>f{_*B9aQ`Xy&pOdyY9O_D zHn?+@r3SS4{u-s76a2Kb9Fz8u-7n1wgKyKT?fJ%wI@asBvp5>ML+{zFwfwuO8fw+dOU#Rd?DaSn~oXFCAUIKRCHp;7MbxmYz<|4i%(YI7+8T@(T zu6HBWp`m*^Yr%vSxBCzuCT{g8(S@%|%$&Gp9s?Gfx&8}&Ju!5xjWB#r zV(_XB1tf`9c-ZVGX5jAXdk=lUoMo4|@V$v+?o6G8ub)iugAYj@UE`uk_D%4Y7sdvH z+@5|@5YHruUOW>;Hd3pw#?R9w`!;SJ=7J4Mn#=ULxshzJ?!OV50?N!&dUTfThmh1t zBl|)mSi5G(f{Oc!gg>GDU!o=}*Woiq7RQZQ;FRT$Hbvs+w~7y4K>tX*<9yQzK19}5 z)z27uLe{34O&$G0b}{Q~AJ}|Z1*0>Y+@bAnN+iFY3!eSTGZo`OCc0#U0Q828yZ>uu zj1w7?tT^XQ6sq)AXG4bIBc#QPvt!B6XIYrUA>NU? z)WDmGcu158o@1xXfA^<56Psl%rp8-x497A)Cnj@QRcNJS6$$0{L_qN0Lmt zeRGLE7$lqwjJSW4Ts$AAx#`m^RaYs@B4EDe0O)3be)P^EL(H}HP(ryJzwb+QsmKf9CkG;$nCxy`bm~uqxt(5(6U-=#(Yrz^Nw#$pkV1m?Qq2V z&pr#vAb!rgVOrHNLYohmvU00kW9QD^hZ}9_4GclHmMbeU~7`w$xE3Su- zeTCnScbgsqFDJBSpstDzZQrhboy>W;GWD$i%MBiSM9}8M$UCHwB(g@uY+{wC`m%bR2Y)J??lPee2k(;00~q;rFk#U`tD5KHl%-0R65QX*~fo5BexU zi(zi(mFspMryK|9utNJb;h>1&#Me-s#!FdteC0~(5 zppRb1KnE1}S=o6(Ll;w3Y0T!BU^wu)+# zfL||Ozf#c`bzHpE-iZxkqgX|Eqx33JXRo~JQPk}zwJpR>7M&WONBUn;9Q)_JGN6#H zaTC$n>Kc2<`^6&u(go3Io5Hf#nynaAEok31 zvYI?cM9CJ<6JX|}lv?N;!5xmwY9w}9RJ3A|Kj=K5FFFLS7V7<}MvHCe^yzv3>eE%U z_5Fmqs>PsDYDIUS@J8HKyU>|qImTzL$NpDq`#~ksk?}jA{YZKzV(D5JJ*j77;L7YJ zDGs$(KW{1(IYE|MmHg5z4)6Y(80AnNgMd9;n-OY+}Ey3VD1ySYX zbJpNKXxCb;<*Iei`?aQQ$%C6f={YwVuYgAW2d|L*2^KHr865g^kZaBN-6M+7GqoZi zT{GoCE_wM6@ZB{-IfKF&t2HnAK0IDU_BSj#JcYa`aM>wG4g#)8c095IW3(ofvqTJb zsV3=&?2g-D#O1adD6hsV_Ks^Q`JC9>LEfi$B%hx;1m;gs2qyb3qOxC3y+F##3JGc^ zzjt2gk2twTrRQ+m0O&A1YWY;qxgfE_ioB2D_@zDpYO0R}z;@KQ&Hla$8lh%>{O9d> z-WscWUjF3%_Ik0d#8|5dOnbC0gZ%x4Io{CVHH%hW5<)%K2rh{}IYMFuuKDZmebqnT z4L!;NKkgdt`$E=MXgxUXM7XtU27E|$skuwKG($mv zUI%R_hb4ZRF>OAH9XeyLlUU+ayw`gusX&hLtkFd*-_jJx*TF}e+TLw4)#d}S3bB^kN&Wq zcfs8VXrld;ujbN`v{;O`?*k0o{(&Sil@DAi)(A}^@A%75S{sxXRJpj9LX~?Xd`x&V z*v_Wn0pBKfcF#fImEf6IP1`>a-l*S7et%3@k_ls2E^PC%7qh^1bJk^%trfOF`R77aV-glL9(Q-5P*KlD2lwkgbK* z3b=KCLlh{fdRq$R%srTTiwR?SZe~I3*(acL_nK_jhPl@7-ts$v97cg5=b*VVi{Cs) z94xsyGNp>VYd45gT08=c)xT!g>MVHBahEblFrIPj!x}TtHN&F^HbcTtYpnp{eu=r~ zW(MPa#Xqzzw#4^{j~=}L&;YFWD_^e&mYao$d;s@7{P`R)i@4!j@2{|p;`;WxGJ>Ja zChoM@!4`=PiHS^e2GeBv-l2ZQ8WxPX;CeCM(PN&7?Zh-pwyD6DivC!(I|bh>+UFq~ zfqE9b%u)CY?IxPYm(}zO%uvpJ245iBq4oS_Ke*7p@r*p^6f~5IxL%a^W>+ROkSIr# z;1|RxqULNV_VE9r`V}&Us25SmnO{Q5p*P)KVV-z?k$dtcU1%qfPDz_}xL%}UdeP@R zuKf6rOS{n_7< zEol1avpZ-SW#@W}Bq$FmZ`W^i&EdJBlKK|3QRk>aY?Sk$q1Z_a?4NUK_a3k}Lxvys zmNVWLH1?A0pUI#`hJjLsJAS~&=axn`u5<-c9O^#7p62f64Y-#=l5$NfwJ`w|Yu_A% zoy)l}MXA0RtbT5I^Yfp5B$kfeX0y@%=Zn5Pwuc)GqT49{pOytXp z@25}~aCtDj{0Lq(F!M*uV5>Ce@=o**TbEA zUz^O6m_OUKS;mtb9?#A*#stoP81Q-#ZI2QIG4!Oq75CQjOEo#P>X7m0O8AqwMRun1 z3?}uhWU=Q(h~+fA+&{9lf{u#o7c?=@w}dC{5n0L8JsuA(h}kv{pI~4j)E~W1Qoo& z)Sy-+=pm!H-qp5(;8}Tj)=;poV(gO#E#_ljoi*pAye=kvO!f|-hI`cr##yQC>;+eZ zpuz9CEn|rS)6`q<&8NM$n3Vrz$fEiz`m51nOZMZ4)r_7vINgLFGI}eJpHJ2WGBJIT zh@`#WVC;SP-T_gk)B)6rV7v5!_^m#xfLpvbU0p>Ed@6_u zfNj_7U7Rk|3D!WYYuKkh0YMuTBJmMZU>V$HAr*yTr-5 z{g$uW!qI=qZYIY>A+9Ow$>iP$EmE%7@3BLib}&5cuyoMb$38;dX}ie573=H|9;O{M&%in;#60Co zIoB;mOjho4(R$WR&}T*5Q!#K`mj80H|3h&Z^(XXDMN4^;`3kakX7*#zzj*b=)iYql zvDj|-MdQ4p)vw9t<5G4JrzupEgvSS)=_UT7v)}ITJ?fxRea+!Jw0&%}J>ua0t1eQ? zwEb)h%zao-DpNJSb6gZunbvY0ZE`@ibN;cNgn7KxrwMfrCBxqw5LG?ujWK?J?NV%! z4enuZc%ui2AMP6o?m|DS{qRXH!x!vJU(kq{y0+7}r|L1dA&Nf`Hl?;)(y3!`XbJ$ky+W|PQHg_{E<;Tbnw^=J|UvJ zr&%5*@6D@%T7m6(=Qe>xPU;It+we;~3M9E1nAQx-`%$Qx zOSzaD$)V$h7ICfcB^sZZF1>yV)|j?GfIrpP{yyFCJQ%#UbPM^tw^Net1cj=8z%bDc z-=iMAt!fr*t-8*Z+qY+eQzs-uSSeJs!$uM3s=(c2XTqyNuZ&HeE2qGVW|>m!>ry3Cz1dlSn%y#3D@wd)>O#!I{VQ-dxe@ z+W6K_QW7z1<96!hV=~~XWx=+u!L@Id?xum_oj!BX#~S(PYF~%nZunX~%M>wGgKy4! zG4$Dnyz!RgBG6G*e--*@1Fy=xIk4&VFS)$ccGLFUP^jAF8>hK&lJ^^GSGNg+MHi|? zg+SS_Yj-7(wRC+|v%ZtHbr;TKX^jH|Q$Oss0Bxpyk|qrCI`ABA zsp)hpVc-jGFA$wN=l`#rFFvjrauwsZ`1$+tXUE{bRvS0iV?30^RX)1m1wF=jK>4jX zblbhQYg^DS=JQCs3R(uv_!&Em-g?1*+j){htY-1sGWe;Y#%s(Q8^LA`IeoH!mFwp4 z7-IIKpVwrKZSh%~v%`$cW2t}bZrcEs9tmAu4%RK4u>`SW@sTR6Z%rfzn5>E_S=-QK zH2$6BNiy48FCOv)ntlE>74FY_z?p)MEYHca zjYVzoW*~zH$rH}dR@aO?#rAj5>=_QLXWM#!qKzEi@$>Y$RjpIem(r8m-hMQv z9lT5CMy<~bVZ500?NHv@11g!M#gR37TUZ`COA!D0q2c9Q-22j>Ge$CK2l6tY14v$} z>smw6h>erpJ?b55X?0Ef_N$|8FWE0SZDKoOO4AERF7A?~P|eKiSc4xiVAp5-nfIxQ zY~WAf2elW3uBJ?iyR8tZDFGMBDywSiW4TsYepR zo_*efxJ&-mk7b;U9I}D&Qa>X3NZ1s%VP?_SKxmW(i|?k>;795o%=2w!L41F;Ilg(Y{)a>5J%~x&z;>L^7l>>U7o^OOBx zcXbMXC2!SaO#8%CbjI@j0M|Hn&i_U`h?GLrooYH&1a?i=I$?)a4DCQt#`<&mTGr{I zhN*n_$QpHZTSF1}vNRno zuMXJZw9Q8a?4ftkf^;qxvGEg^sw8Nu7$14(35$DDvMXO!Q2G_4vcB=&En_pvQ0x6F!2+00LjnAFn z)_dM75dX#$+RQR=ptUpY&?$2G$fHYnhtQrgENrSX$syHy2VW!p(pqgcop&6JDm5wR z1$SPx;a>n2rnzw$kbF~%7o72-^#ca%^2|wnOmF#$x^>_c!`4O7;2WQ*$*|kIzU`Uz z;P9WmrPam$x{%ycj-rAI$yt@RmP`2-xd#{HjvI`vP0RP8I+OQu`QB>RC}eI>hw@{| zMq-Py{kb+V(EiY^xjiS|I6MU}ro^0tJ;{CUvPS4D$#-(`3|aZE{|^qMbGVX`UTDwNS(@z=O|wj8G=|@YgJL+eWDx1n%YT1 zY0ZfTpIRG9{;6fb{?Hs6r+V)`i~@B=oY-Qa|3=rW&O%>S51n1nL3E+l8}UyKaT@(m9|I1#mi5?=>i>gr0(liif(+x*gBuW zvBzK7QC~%~9E-|yL7B!SBHU!X)_`G0ILd5jtcv(Za+*b-%O-126c-c}u;Dv?UN}p= z!q_^W!b;!kWX;3hhcMyV+|&2m4FFTM>#4%x3ahM8987Gb&y%4Jx3bfk-6H)K^L|=Q|@hY zH1tzO)u&80vfex1-&qWOKBHLvNG`^AMq>Z7aY^u$t%nt2myC*ir47Si0q0?|#x|p} z;z1}him}yEKMfKWkFZ<0!44aF_BfrZp->I)aWl7|ZVe^0nLdyMpug=qQvoewaIxO` zttA*@c*oZWTq-%X17pd+Fo1deYYJ6=ER%IfHUoWzjHN3>+RHF*^IPkd?!b3z7Ytu4 zgo6K&te-uDCXm3D`y;OAeA`H7r>uyHHc*Bq+~*#)Ri?Vc^C zC(OV>52x%l#-8o(YeCBS7@zgDYr&_j#_Ar7JzLg9a_;S`;Ioe$$IxsPF35i0LSl|5w-!&eBzf*cdsm)fY|Lb> zIOdid0?yN79XSmuJTq2B44%cbc%VIrtaG|@%~_G;32f;rzqqbzu-!k-3+o0wtQQm9O8@u*V-*-Wy#vfthkJw(Cp>Tk9Tn`@1iJ^QbO~L%k=ij&?`f?5( zdIEj?2kXxP_zSJ~OQv@+a7ba_^ zfOdj`d&xm_@}1NYdfJ>$&XoK`0=CLcU{Hw=mkHQ8`sE`z#N=UJ<$^%6E_>-QACmJy z|7MZjLHzt(wN{TjINGP}4!u>-!e8h{a@yT}cHFrE{Zz|X%JME~D80KzkakcLg=+mg zNiiEb!p3w!yRZv96Cr-(Hif!?Y47$1*!u-GZK{iJf;$aOT;0IRk*2BeF$=mHoGu`i zx2-6Z+KhIyJsa8e88*tUG2+1?*aE62C z7WQ4nvs)LQ9nOHRvOXgwUxM|uQ*mF~XQwL+n9#)YFMF~^J>|q}3jP`28F1c@G#dnj#;{?81^~0V^-}(M# z4K$kc2yGL|O#t%?;AAhj|azKU{JN9KJuC0*#qB#{af$59)fF$B^bhnkFY} zAurzP{|w&um$elJhc`xiiw9p+6>5smv^oR!kQ_REpUP-lCbdy7sL>}^sy$lrY8XBF)rEmEe~&mq_|DQy=P55#m*6^1@5m1zfM zG3J}IP98UyhyhDlmHA+Mvf0FLI}(b|PS{KGjU}cl)M|hS^VUR?ebuaAY`N7y>C>;c zTNwBb8ThjILmMu}qkqpVKQsXC9A{))qO%WFT>a%j9jNT+Me=dQiQcY00j(CdO1UuX z3wdv5j_z*=QzIA5p6skhPnY8t=e+sTA z0u#}16;?freGNaZFjvYf5@SaGQCssc;x+k-6SpHG!QB_1mq1I%ZxEeMa&XG?1S_VV zqfq7IUF&O5|8hnyqVr41`jwYe#a`&|xviEZXnWZ>={euDK=b3zrY<9Eh`-!fLodfH z&wu+#sS=FK;fTRqEJ-6-J)sJxN) zD_O4~a+Srw2(ir5wM};4!RS2k=VM^h^Q^Ahpo4y}?esqf(`H(1z3GPf&J?({+}RT3 zTQXxe{7=TX4yQM4bH?{dk%qV6R~HL=eQ<0;-#JIZRWmQS5H7zSYfQLQSHc?nXksC! zO`-CaxHWR4e)uD(x4*9j*KqZngl^%t|8S5SF%mzE+uiiT6e{2AfoBVn!9r{8Uf31B z%ma5$KbmwfZrJz=#WK*dUxF906W=PcFJ%1;A9diNHrkud#^7f^;&HzDqMvhCfo6B5 zAMk?4EHWF8f`%z;YaBrXkJezsR(#si9dqcNy~6k3cFK4)J^cr?Vn)mv@qIzKmRBd> zAH+0o9@_jCzPs7?cIy#vO*>OyI;i3}V37w3=(9FqJn%Ac z+RH^ys62zFYqsA4H?};Kfj;8dEFM>Z{>&2`*I-*poBv{9PH6Jnno3Q0C&qqif=RS9 z;^kC>91a=fR@anaG3nXk4WDjM<1BB;6O` zOv5-#dYr)BYt!nQYMUiWZnZ1MO-gUxu06+5cXoj>g|L6A`CR)% zpmEZsrPw_txi-u^62xH3)9hKE^J7A7NRnM5eeODB?dB*u{XkGQy2R|ME0NRS~ z+=}>2-N3`i6p&A>%Ry)_ zHQ^0G&E!4lyf%5=ub{fp^%Or)fBbqLVpY{^?SAW^S5>dRb|LEtRZoa~IJyFqP3$}D zPob(ju<1!N1oup&)uEp&k4IAl&Va$2s{?VJvOL9q39g$rZ~nqTJhxI?Y^o;t+d?Nuya&pRsxA#OnfFUI1>_*akbra_a$w@jVYBmsth zJpQp7eN0Bu``t>=ysNz`7vz7M{EOb6PCE0l@tdyADIsU1EY+B|6KxhRwftKO{B}I6 zqG&ku#=NO#*52wyKW)5cYK(EIG&(o`7<7-)sWR4KdItv6d4Cjl-DLK1Au!rmPh%Ds z_R^Af3%L690SWk^IH3fkD~o9w4`pvp_@s$ASV`EB=^NrDC6ULUOT{oI-W`(N{uK0) z^w`Ko(_jqPagIOwac}*&`2>8(-qNIqfxTen(WB%%h`s0b)`;YRdDE`uOxAZMyO_`1 z0}X#Z8h8fke9K(``=iJ?XU&#CwBN7Ad7uP8D;y2eQ07k;)0G znRm=T3X0S*UqfHpw{%r#+X8Tdh{=)!!ruKa5##S`H=eJHcG!35`P%)^vit5?U8{Wq zo_|)d2R>upH?G<5;q&(O>A19Eye3So6>@08=TBpL8q3Jv37%AfZQpl)_Q(ly{GR1q zX+Jc^z9*cbYmLbD@*yKhzkT@Sc@wMNVq zkQy`?hx?c}HnVs;G~T?Dr+QZqL(L0V;MI!$IxlLVBXSpLH70mRj!qwu{O=gwRpi?{ zauqDu9Y|s%slkDgZ5sRv9$oScf`AP2%l3u1a4}JnoFJZZn)&7 z^j&sv<$_O#KQUP5Uu6(3Nm2*{H+1dltE80yI!e3deJA_i?`MzYF;-?w_qOvh_LKL? zQX2yZdsfU?0^1|lyyGByIe3V&LHq$&7xVTgy*;UP%8X9a*Wf}Qj!qIxOuj`sTormX zI%ew|v@vgB$0OeBw8K;xvzfo2otGNKUxE9)xMOrzE$yJuNp+BxvUUeFgYbca{WlKN z4jyG}?&a^pN^!pvG0D8~v;#umGL|OOs(&e3v(yshgr3BbSb?tOs9;$ zb9RF#%1?>P&~kNySFL32=;cDX4QYK>CY|5py|6%zp5~=f#_zK(2N!HtUq9)r%G_D=-clGV zc$RN^fe3b@y}OHo$}Xz|KlsB{qv{1AwHh2|F4C6{;OcN zO~Kxtej$E==w*M+kpELOt@z)6INie|FdRkvQ!{_BPNf}}{>L)eyJRWx#sBMC<^cDN z-n65Yr_%}y-{==W`**~oe}`{$5C8LX_wau`=f7^~KUKx#865JD)tdINOW3R&oNWxX zwKR4A=P#x&w6U_cv2=1?CAHkfSyEqHN_nOv{!g&`|6FOl*k+;C-vxVl`{GaV zTUB)pJ#8H=O#@9G4FdyhEiGD6md>lZ{r&wmg#`L}x%(??PPzkg7h7~`Us^5ryNCLP zZuat)R9NVaj;94X>3y;0 z|CcXb>TGUfwd(iv&X)EL7`Y1qgZ&}{147;XB|QT-ZVC+W4hWSD^pVt>FB$SzuTY)b z6U?2g%>U^<|KsNt7WOWyR1DOWRW$U~l|g{^y`s(HsA5*MkGYRB2}SFYmMy|M%)W`q;Hw}P& literal 30337 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

h4w_zNmALh@^`Mck`2aMHitxt3(`4?zf_7PL z_(K&{g&V29pzipMe8hmt*E@XILkB8fd*e*z(<>hn`FMCGD4o=M(2qh@dT7<1t`F{+ zNN+$rR~(O`3Y-Fiw$ujTIz?HE-%?z+V8KG$0lc?Dd-LV#i0>7)x*4qC16?GOc;A8S z0v+Q|;73w>R=Fcxd1ui*@9Sv?2-2yea{tvo@h4|23PD-LcPk%YL0gW0#O<7bSV;9- zQ}Y>R@Y!0$x#z*-1qK3)tYs%>uNVh1FDK)|M$z}DW`p{%Z-x+K%9zFB3gl$Df(*ZWnVX=g`G9?1VZ1P(AVa?TfsFa_ld)n#|b4WTv^A8u)U4rrW3Hmd&`o;`}cxbhYyqey7!*l zTPKna=1;qpJ6R_g?Pfl8AJqT(xc>#H^*wJ9+Oa(6oV8m6P=3Fb@a;1_X-~>+DKV!UofQY3dTw`y}>ia-ygDK zhGt2aP8D^$53;;Jmc|LHm|j?T7!+w>zK*(Rvn z91;$^TvrWEm~h|Xdi`7Q+>6pZu)PW2xn_TW??~v?a&CtfN}O6RWRKW4;q$1D+H&$c z!Q%?BkqHlG4;?eb-&x+5^}$ysJmZ|Z&VXDm8$6UuKEDi5m=Ah8dEA-@Y88BZ;Er~D zrR#8uE@7cUA{ldXSCrb31O`|y93VFC`=ja8h%MWfC1^|LLtP48YiCSsrQt{&{GduL zpI9?Aj!JxdqA6lz6>X=RUv$WPsU=OF&@d{jr^4e{z?!sjc!`@U^Aw5!rVD>oa&N=nPQ z23-AN8$YX`wdn?TtWk^J3Q_|H;_w^`MrW4nfL2;i`dsJAIOxB~vkmoiL1h1hh~1#Y zsNh{02Ih66jqfh@=^45PmhK4NxQ{j`gF+Rt=u%U=44P*h_;P_Z7lSdINSoKOd#CVO z zXg*_UMt5yLH=~-&0hzzapRjx7jHR$Wf~R-xXRiPcP&SG^1RJ8?9j3STolcrj>AGrM zsKZgof=Ma2X@{#q_eDi-dy6vW4Y=@__Xh1SRmN=QzwgdV4dk!Hb6(szvb&yk(CDN* zNKak26TVk?-~PUv2Wbb7GS)Zn_hF@Y-ihcG-gw#pq_jgz!9LAdi~VT_kTTXc@b{sm zpt-2_^(2tVq~H?rc~|wp&7U=B2a?hbF$JwoGZmn{h=gZGw`~WDomg9U&<-kPtZ(4& z!%ac6zWW1tw8KrolV+5$JO5|DNB%aA{Ed6c|NXh99KWB1wx?~Sdcm9EEtB?~6+S!9 zhTi-WI)41$^CV=O(tEE=I>*7wworziMxm3&zkAP(mS13IowWB`>r4d?6UNf`cP@ON z{J8&=-*emzJ{-zWXRy!p-}BD~7VS{oFzNiVyjct0Q5bv<+Foe$M5e7^nY2gQnO(=; zQyI(;+8%r_bj}`DoU{ktgR(*iRmRfzdmkzA4BzxZA=LrI_0nb{>Lq* z{qs}aU~dnX|JTGl{z#Z@bC8#ZZ?JCw-sZ0!+<%g$CI9aar@OlcgdvH4a^~-u&$Q#x ze_JLy=WGSO_&=^?_IKOlMLSw~IxWGlO}_rLe}_-{ci1Mku;1Uih5hlKe>~8CvWm$g zDEJ?%HSLc}*evXwtn@WC)V2TTAEw({S=d>bJ36hNzrxB%LRWLX;!FwrXR*Y|VzHU6 z1-=dl^7E9i@(S?u3i0)ja0(9&@!BMzrl+eZ;pHFfwaMMj>z`}Le~WRIh2#J6D!HXW zZvMf(A@cLr{_n1{($P@;-(RGwqw&ALNKH-a|Ke6^+B%y57gyQdiT^`@gtK zT~GUeTxDTvYi;jjXT9)u`D<(F=&EVxD5~q}DywVhsH0txg8XM|vBYZWqGe00 zm#&__$txrXH9&3c>=@r>Ci*K}*WqX| zNCbFGXe^Wn{;O6fPp%23juxi>)SmzGy_uPv^J*nMRYfH=T~$R8pxu{L8)8`ID-z4H zcJ1HxXvUv*nc3HW^Og{rkqX@y^8aGj{3GpLRVc zAXJ%VcK`BDyYPR%e*K#G^M}FSE*=4XTQ>RsXYthM kU$0BcuW7;F|NLvFr(1}dvUd{VgLXD diff --git a/research/bcfishpass_comparison.md b/research/bcfishpass_comparison.md index 83ad7f3..595060a 100644 --- a/research/bcfishpass_comparison.md +++ b/research/bcfishpass_comparison.md @@ -47,6 +47,18 @@ All species within 5% of bcfishpass reference. Pipeline runs serially in ~8.5 mi | BT | +3.4% | -0.7% | | WCT | +4.0% | +1.6% | +### DEAD + +Added 2026-04-23 (#44) as the end-to-end test for `barriers_definite_control`. DEAD has a single `barrier_ind = TRUE` control row at FALLS (356361749, 45743) with six anadromous observations upstream in the CH/CM/CO/PK/SK pool and zero habitat-classification coverage — the unique combination that actively exercises the filter. Pre-fix link would have overridden the fall (six observations exceed the threshold of five; habitat-path coverage absent); post-fix link correctly blocks the override for anadromous species and matches bcfishpass, which keeps the fall in `barriers_ch_cm_co_pk_sk` post-override. BT is allowed to override the fall because `observation_control_apply = FALSE` for BT — mirrors bcfishpass's `model_access_bt.sql` which has no control join. + +| Species | Spawning | Rearing | +|---------|----------|---------| +| BT | +2.1% | -0.2% | +| CH | +1.4% | +1.4% | +| CO | +1.3% | -0.3% | +| PK | +1.1% | N/A | +| ST | +1.3% | +0.0% | + ## DAG ```mermaid @@ -95,17 +107,19 @@ flowchart LR cfg --> BULK["compare_bcfishpass_wsg
BULK"] cfg --> BABL["compare_bcfishpass_wsg
BABL"] cfg --> ELKR["compare_bcfishpass_wsg
ELKR"] + cfg --> DEAD["compare_bcfishpass_wsg
DEAD"] - ADMS --> rollup["rollup
34 rows · wsg × species × habitat_type × km × diff_pct"] + ADMS --> rollup["rollup
46 rows · wsg × species × habitat_type × km × diff_pct"] BULK --> rollup BABL --> rollup ELKR --> rollup + DEAD --> rollup classDef root fill:#eef,stroke:#336; classDef wsg fill:#efe,stroke:#363; classDef sink fill:#fee,stroke:#633; class cfg root - class ADMS,BULK,BABL,ELKR wsg + class ADMS,BULK,BABL,ELKR,DEAD wsg class rollup sink ``` @@ -131,6 +145,17 @@ Composite steps in the DAG that aren't a single function call: | SK spawn_connected additive step | -9.6% → -0.7% | fresh code (0.13.6) | | Three-phase cluster | CH +6% → +2.6% | fresh code (0.13.8) | | Index input tables | 228s → 6.6s classification | fresh code (0.13.4) | +| Wire `barriers_definite_control` into override step, per-species, observation-path only | DEAD CH/CO/PK/ST +1.1 to +1.4% (moot on ADMS/BULK/BABL/ELKR) | link code (0.6.0) | + +### barriers_definite_control wiring (#44) + +bcfishpass pairs `user_barriers_definite.csv` with a control table that flags positions as non-overridable (`barrier_ind = TRUE`) — known fish-blocking dams, long impassable falls, diversions. Historical observations upstream should not re-open these barriers. link's override step was not honouring this table. Three fixes land together in 0.6.0: + +1. **Observation-path filter.** `lnk_barrier_overrides()` excludes observations from counting toward the override threshold when the barrier position has a matching TRUE control row. Uses `NOT EXISTS` rather than a LEFT JOIN so the outer `HAVING count(...) >= threshold` aggregation is not row-multiplied. +2. **Per-species application.** New column `observation_control_apply` in `parameters_fresh.csv` (TRUE for CH/CM/CO/PK/SK/ST; FALSE for BT/WCT) gates the filter. Residents inhabit reaches upstream of anadromous-blocking falls routinely (post-glacial headwater connectivity), so their observations still override. Matches bcfishpass's per-model SQL — `model_access_bt.sql` has no control join; `model_access_ch_cm_co_pk_sk.sql` and `model_access_st.sql` do. +3. **Habitat path untouched.** Expert-confirmed habitat is higher-trust than observations; it bypasses the control table, consistent with bcfishpass's `hab_upstr` CTE which has no control join. + +End-to-end validation on DEAD (added specifically for this reason — see section above). Numerical impact on the four original WSGs is zero because every TRUE control row is already rescued by the observation threshold or the habitat path; the filter is correctly wired but inactive on those WSGs. ## Remaining gaps diff --git a/vignettes/reproducing-bcfishpass.Rmd b/vignettes/reproducing-bcfishpass.Rmd index 30ffa32..5a51507 100644 --- a/vignettes/reproducing-bcfishpass.Rmd +++ b/vignettes/reproducing-bcfishpass.Rmd @@ -202,18 +202,21 @@ library(targets) # `_targets.R` lives in data-raw/; run from that directory. setwd("data-raw") -tar_make() # 4 WSGs, serial +tar_make() # 5 WSGs, serial rollup <- tar_read(rollup) # per-WSG × species × habitat tibble ``` `tar_make()` runs [`compare_bcfishpass_wsg()`](https://github.com/NewGraphEnvironment/link/blob/main/data-raw/compare_bcfishpass_wsg.R) -once each for Adams (ADMS), Bulkley (BULK), Babine (BABL), and Elk -(ELKR), binding the per-WSG tibbles into one rollup. Each call -exercises the six `lnk_pipeline_*` phases. All four are run so the -rollup spans the species assemblages and watershed structures used -in bcfishpass validation — BT with CH, CO, SK on ADMS; PK and ST -added on BULK and BABL; BT with WCT on ELKR. Method agreement across +once each for Adams (ADMS), Bulkley (BULK), Babine (BABL), Elk +(ELKR), and Deadman (DEAD), binding the per-WSG tibbles into one +rollup. Each call exercises the six `lnk_pipeline_*` phases. ADMS/BULK/ +BABL/ELKR span the species assemblages used in bcfishpass validation — +BT with CH, CO, SK on ADMS; PK and ST added on BULK and BABL; BT with +WCT on ELKR. DEAD is an end-to-end test for the +`barriers_definite_control` wiring: it has a single `barrier_ind = TRUE` +control row with enough anadromous observations upstream to exercise +the filter, which the other four WSGs don't. Method agreement across this spread is stronger evidence than agreement on a single WSG. ## The rollup @@ -235,7 +238,8 @@ bcfishpass_km × 100`. w <- stats::reshape(x, idvar = "species", timevar = "wsg", direction = "wide", v.names = "diff_pct") names(w)[-1] <- sub("diff_pct\\.", "", names(w)[-1]) - cols <- intersect(c("species", "ADMS", "BULK", "BABL", "ELKR"), names(w)) + cols <- intersect(c("species", "ADMS", "BULK", "BABL", "ELKR", "DEAD"), + names(w)) w <- w[order(w$species), cols] row.names(w) <- NULL w From 1ae72f7354f02e0f34d2bd550efef1c5a41d14b5 Mon Sep 17 00:00:00 2001 From: almac2022 Date: Thu, 23 Apr 2026 12:29:09 -0700 Subject: [PATCH 11/11] PWF: update progress for #44 Phase 4 + follow-up issue #46 + PR #47 Relates to #44 --- planning/active/progress.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/planning/active/progress.md b/planning/active/progress.md index 9898289..2b5960d 100644 --- a/planning/active/progress.md +++ b/planning/active/progress.md @@ -12,3 +12,14 @@ - Post-Phase-2 `tar_make()` (log: `data-raw/logs/20260423_01_tar_make_post_44.txt`) showed 11–22pp drift AWAY from bcfishpass on ADMS/BABL; BULK/ELKR unchanged. Root cause: bcfishpass applies control filter only in CH/CM/CO/PK/SK and ST models (not BT/WCT/CT/DV/RB). My implementation applied it across all species in the `params` loop. - Phase 2a: new `observation_control_apply` column in `parameters_fresh.csv` (TRUE for CH/CM/CO/PK/SK/ST; FALSE for BT/WCT; NA for CT/DV/RB), per-species NOT EXISTS gate in `lnk_barrier_overrides()`, three new tests. 279 PASS. Amendment pushed to issue #44 documenting the species-scoped approach and biological rationale. - Next: Phase 3 — `pak::local_install()`, `tar_make()`, compare rollup to bcfishpass; expected direction — BT/WCT/ST on ADMS/BABL recover to near pre-fix, CH/CM/CO/PK/SK slightly closer to bcfishpass. +- Phase 2a alone was insufficient — CH/CO/SK/ST on ADMS/BABL still drifted -15 to -22pp. Investigation traced the residual to my ctrl_filter also blocking the habitat-path INSERT in lnk_barrier_overrides. bcfishpass's `hab_upstr` CTE has no control join — habitat is higher-trust and bypasses the filter. +- Phase 2b (6f3bc46) — removed ctrl_where/ctrl_filter from habitat INSERT; flipped the "control applies to habitat" test to assert absence; docstring notes habitat bypass. 279 PASS. +- Post-Phase-2b rollup exactly matches pre-fix baseline on all 4 parity WSGs. Investigation showed all 6 TRUE control rows on ADMS/BULK/BABL are rescued by observation threshold or habitat path — filter correctly wired but inactive on these WSGs. +- Phase 2c: province-wide hunt for TRUE control rows with ≥ threshold obs AND zero habitat upstream produced CAMB (11 obs), DEAD (6), LFRA (16 but too large), SALM (7). Picked DEAD — single TRUE control row at FALLS (356361749, 45743) with exactly 6 CH-group obs and zero habitat. Added DEAD to `data-raw/_targets.R`, incremental tar_make builds only comparison_DEAD + rollup (42s). +- DEAD rollup: all species within 3% of bcfishpass reference. Direct inspection of `working_dead.barrier_overrides` at (356361749, 45743): BT only, confirming per-species gate (BT bypass + CH/CM/CO/PK/SK/ST blocked). Commit fb8a0db. +- Log files committed (1c683e3): 20260423_01_phase2, _02_phase2a, _03_phase2b, _04_repro, _05_dead, _06_repro_dead. +- 5-WSG rebuild reproducibility confirmed: two consecutive `tar_destroy + tar_make` produce rollup digest `210c3f8254c47ac88573a80d96a2701e`, 46 rows, identical. +- Phase 4 (f52dcbc): NEWS 0.6.0, DESCRIPTION 0.5.0→0.6.0, research doc (DEAD table + key-fixes row + three-part-fix subsection + DAG update), vignette (5-WSG narrative + pivot column), bcfishpass config README updated, vignette artifacts regenerated. +- Follow-up filed: #46 (migrate `.lnk_pipeline_prep_gradient()` + `.lnk_pipeline_prep_overrides()` probes to manifest-driven gating). +- Branch pushed. PR #47 opened. SRED tag `Relates to NewGraphEnvironment/sred-2025-2026#24` in body. +- Flagged in PR: commit 22ac1dd ("comms(→link): M1 verified as R-worker host") landed on this branch from a parallel session's branch-landing policy; orthogonal to #44 scope.