Summary
The reactive recalculation system (reactive.py) has gaps where derived attributes don't auto-update when their dependencies change.
Gaps Found
| Derived Attribute |
Depends On |
Auto-Recalculates? |
stream_order |
path_freq |
❌ NO |
path_segs |
topology |
❌ NO |
path_order |
dist_out |
❌ NO |
main_side |
path_freq |
✅ YES |
Impact
If topology is modified (e.g., fixing flow direction), these attributes become stale:
stream_order won't reflect new path_freq
path_segs won't reflect new segment boundaries
path_order won't reflect new distance ordering
Evidence
From reactive.py analysis:
_recalc_reach_main_side() exists and uses path_freq
- No equivalent
_recalc_reach_stream_order() or _recalc_reach_path_segs()
Recommendation
- Add
stream_order to reactive dependency graph with trigger on path_freq change
- Add
path_segs to reactive dependency graph with trigger on topology change
- Add
path_order to reactive dependency graph with trigger on dist_out change
- Or document these as "static after initial computation" if intentional
Related
Design Notes (2026-02-17 brainstorming session)
Rescoped to v18. This is bigger than patching reactive.py — the real fix is a PostGIS trigger-based reactive system.
What we learned
- The Python-side
reactive.py (DuckDB) is obsolete for this purpose. Production data lives in PostgreSQL, edited via QGIS.
- Geometry edits cascade: geometry → reach_id, reach_len → topology (rch_id_up/dn) → dist_out → path_freq → stream_order, path_order, path_segs. Too many dependencies to patch incrementally.
path_segs in reconstruction.py is also broken (0.1% match with v17b). Needs a rewrite regardless.
Target architecture (v18)
- Pure PL/pgSQL trigger functions on
sword_reaches_v17b (or v18 table)
CONSTRAINT TRIGGER ... INITIALLY DEFERRED so bulk QGIS edits recompute once at transaction commit
- Scope recalculation to
network column (subnetwork ID). If an edit merges two networks, recompute both.
path_freq: recursive CTE from outlets (n_rch_dn=0) upstream
stream_order: round(ln(path_freq)) + 1, side channels → -9999
path_order: ROW_NUMBER() OVER (PARTITION BY path_freq ORDER BY dist_out)
path_segs: needs correct junction-based segmentation algorithm (separate sub-task)
Schema reference (sword_reaches_v17b)
rch_id_up / rch_id_dn: space-delimited text
network: integer subnetwork ID (247 networks, largest is 125K reaches)
strm_order, path_freq, path_order, path_segs: bigint columns
Summary
The reactive recalculation system (
reactive.py) has gaps where derived attributes don't auto-update when their dependencies change.Gaps Found
stream_orderpath_freqpath_segspath_orderdist_outmain_sidepath_freqImpact
If topology is modified (e.g., fixing flow direction), these attributes become stale:
stream_orderwon't reflect newpath_freqpath_segswon't reflect new segment boundariespath_orderwon't reflect new distance orderingEvidence
From
reactive.pyanalysis:_recalc_reach_main_side()exists and usespath_freq_recalc_reach_stream_order()or_recalc_reach_path_segs()Recommendation
stream_orderto reactive dependency graph with trigger onpath_freqchangepath_segsto reactive dependency graph with trigger on topology changepath_orderto reactive dependency graph with trigger ondist_outchangeRelated
docs/validation_specs/stream_order_path_segs_validation_spec.mdDesign Notes (2026-02-17 brainstorming session)
Rescoped to v18. This is bigger than patching
reactive.py— the real fix is a PostGIS trigger-based reactive system.What we learned
reactive.py(DuckDB) is obsolete for this purpose. Production data lives in PostgreSQL, edited via QGIS.path_segsinreconstruction.pyis also broken (0.1% match with v17b). Needs a rewrite regardless.Target architecture (v18)
sword_reaches_v17b(or v18 table)CONSTRAINT TRIGGER ... INITIALLY DEFERREDso bulk QGIS edits recompute once at transaction commitnetworkcolumn (subnetwork ID). If an edit merges two networks, recompute both.path_freq: recursive CTE from outlets (n_rch_dn=0) upstreamstream_order:round(ln(path_freq)) + 1, side channels → -9999path_order:ROW_NUMBER() OVER (PARTITION BY path_freq ORDER BY dist_out)path_segs: needs correct junction-based segmentation algorithm (separate sub-task)Schema reference (sword_reaches_v17b)
rch_id_up/rch_id_dn: space-delimited textnetwork: integer subnetwork ID (247 networks, largest is 125K reaches)strm_order,path_freq,path_order,path_segs: bigint columns