Skip to content

frs_wsg_drainage: replace single-ltree closure with measure-aware fwa_downstream; drop public.wsg_outlet dependency #214

Description

@NewGraphEnvironment

Problem

frs_wsg_drainage() computes drainage closure with a single-column ltree ancestry test:

WHERE f.outlet <@ w.outlet

against public.wsg_outlet — a table that no repo builds. It was materialized ad-hoc in a psql session on 2026-05-24 (link#175); the CREATE statement was never committed and survives only in a session transcript. Consumers that connect to a database without it fail hard (relation "public.wsg_outlet" does not exist), which is NewGraphEnvironment/link#227.

Worse, the predicate is wrong, and the missing table was hiding it.

Why the predicate is wrong

A single ltree per group cannot express where along a stem a group sits. Its wscode_ltree names the stream; it does not say whether you are above or below a given confluence. Consequences, measured against the local FWA load (246 groups):

  • 25% of groups (62/246) resolve to an outlet shallower than their own polygon's wscode_ltree, because the ad-hoc derivation took nlevel(wscode_ltree) ASC — the shallowest code appearing anywhere in the group. A group that merely clips a larger river inherits that river's code and becomes a false ancestor of everything upstream on it.
  • Only 167 distinct outlets for 246 groups. The largest false-ancestor cluster is 14 Fraser groups all sharing outlet 100 (BBAR CHWK COTR DOGC FRCN HARR LFRA MFRA MORK NARC SETN TABR TWAC UFRA). Because <@ is inclusive, each is an "ancestor" of every other, so any single Fraser focal pulls in all 14.
  • Ordering hazard. Equal outlets give equal nlevel, so the query falls through to its w.wsg ASC tiebreak — alphabetical. In that Fraser cluster, LFRA (the most downstream group) sorts seventh, so six groups upstream of it order first. Downstream-first ordering is load-bearing for cross-WSG ;DAM propagation in link, so this is a correctness hazard, not just wasted compute.

Concrete example — Morice and Bulkley. The Morice (Wedzin Kwa) is hydrologically the mainstem, but FWA carried the "Bulkley" name down through the confluence, so the Morice is filed as a tributary of the thing it feeds. Under the current predicate MORR resolves to outlet 400.431358identical to BULK — so the Bulkley appears to drain through the Morice. Backwards.

Fix

Use FWA's own topology function and the polygon table that ships with fwapg:

WHERE whse_basemapping.fwa_downstream(a.wscode_ltree, a.localcode_ltree,
                                      b.wscode_ltree, b.localcode_ltree)

localcode_ltree supplies the along-stem position the single code lacks, and fwa_downstream is the same primitive family as the fwa_upstream already used across fresh/link.

This also removes the public.wsg_outlet dependency entirelywhse_basemapping.fwa_watershed_groups_poly is standard FWA infrastructure, verified present with 246 rows and fwa_downstream available in both the local docker fwapg and the tunnelled bcfishpass database. Nothing to build, nothing to ship, no reproducibility hole.

Province-wide run: 0.3 s, 1032 edges, 208 of 246 groups with at least one downstream group (the remaining 38 are coastal outlets).

Behaviour changes

  • Default table becomes whse_basemapping.fwa_watershed_groups_poly; the expected columns change from (wsg, outlet) to (watershed_group_code, wscode_ltree, localcode_ltree). Breaking for any caller passing a custom table.
  • Closures get tighter. frs_wsg_drainage(conn, c("PARS","BULK")) goes from 15 groups to 10, correctly dropping LKEL (separate tributary), MSKE / USKE (upstream of the confluence), LBTN (sibling), MORR (upstream).
  • fwa_downstream excludes self, so the focal group must be unioned in explicitly — inclusive <@ gave that for free.
  • The roxygen @examples block and the acceptance criterion in frs_wsg_drainage: FWA WSG drainage-closure primitive #211 both record the old 15-group answer. Both need updating; that 15-group result is not a correct oracle.

Acceptance

Hydrology fixtures, all verified passing against the proposed predicate:

  • MORR → BULK TRUE, BULK → MORR FALSE
  • BULK → LSKE TRUE, BULK → KISP TRUE (the Skeena does flow through the Kispiox group)
  • BULK → MSKE FALSE, BULK → USKE FALSE (upstream of the confluence)
  • BULK → LKEL FALSE, PARS → LBTN FALSE (siblings)
  • Focal group present in its own closure
  • DS-first ordering derived from topology, not an alphabetical tiebreak — LFRA first in any Fraser closure
  • Closure invariant to focal ordering
  • Runnable @example

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions