Scenario layers: deferred census values + flex areas#382
Draft
drewda wants to merge 1 commit into
Draft
Conversation
Extends the progressive-loading pattern from the buffer-demographics work to the two remaining loosely-coupled scenario layers. The base query now loads only the core (stops/routes/departures); census values and flex areas auto-load on demand the first time a user action needs them, via standalone NDJSON endpoints sharing the inline pass logic. - runCensusValuesPass + /api/census-values (area resolved server-side via the extracted resolveScenarioArea helper shared with fetchFeedVersions) - runFlexAreasPass + /api/flex-areas (FlexStopTimesQueryVars, FlexDepartureTuple, getSelectedDateRange move to flex-areas-pass.ts) - ScenarioData.censusGeographies becomes a per-layer cache (censusGeographiesByLayer); the receiver routes entries by their .layer and applyScenarioResultFilter selects the current aggregate layer, so UI consumers are unchanged and layer switches A->B->A hit the cache. Post-load aggregate-layer changes now fetch the new layer's values (fixes stale re-slicing of old data). - includeCensusValues / includeFlexAreas config flags use !== false semantics; CLI/WSDOT (undefined) keep inline behavior. The SPA defers both by default; the Advanced Settings flex checkbox flips to default-off and means "load with scenario". - useBufferRefetch generalizes to useScenarioRefetch: ensureCensusValues (per-layer dedupe + abort), ensureFlexAreas, resetLayerState; buffer loadNow() also ensures census values (aggregation row seeding) with single modal ownership. - Triggers: Stops (Aggregated) tab (report emits need-census-values), Show Agg. Areas toggle, aggregate-layer watch, Flex Services toggle, plus post-fetch nudges for shared links arriving with toggles on. - docs/data-flow.md gains a "Scenario layers" section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deploying calact-network-analysis-tool with
|
| Latest commit: |
be3efb3
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://3466434d.calact-network-analysis-tool.pages.dev |
| Branch Preview URL: | https://scenario-layers.calact-network-analysis-tool.pages.dev |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Extends the progressive-loading pattern established in #381 to the two remaining loosely-coupled scenario layers: census values and flex areas. The scenario pipeline has a tightly-coupled core (stops → routes → departures, which stays combined in
/api/scenario) and independent layers that each need only small client-known inputs. Each layer now follows one pattern: a pure pass function shared by the inline pipeline and a standalone NDJSON streaming endpoint, a config flag with!== falsesemantics for CLI/WSDOT parity, and idempotent client-sideensureX()loaders. Census values and flex areas are deferred by default and auto-load implicitly the first time a user action needs them — no new buttons. Census values are additionally cached per aggregate layer, which also fixes a pre-existing stale-data gap where changing the aggregate layer after load re-sliced the old layer's values.User-facing changes
Faster base queries
Implicit on-demand loading
showAggAreas=trueorflexServicesEnabled=trueload their layers automatically after the scenario arrives.Per-layer census caching + stale-data fix
Implementation details
Shared pass functions + endpoints
src/scenario/census-values-pass.ts:runCensusValuesPass()(extracted fromfetchCensusValues, bbox padding still tied to the stop buffer radius) andresolveScenarioArea()(extracted fromfetchFeedVersions; resolves bbox/withinfrom either an explicit bbox or admin-boundary geographyIds — single source of truth for the pipeline and the endpoint).src/scenario/flex-areas-pass.ts:runFlexAreasPass()with per-feed-version error isolation via the same TaskQueue pattern;FlexStopTimesQueryVars,FlexDepartureTuple,getSelectedDateRange, andMAX_FLEX_LOCATIONS_PER_FEED_VERSIONmove here (re-exported through thesrc/scenariobarrel, so external imports are unchanged).server/api/census-values.post.tsandserver/api/flex-areas.post.tsmirrorbuffer-geographies.post.ts: validate, stream NDJSON viastreamCensusValues/streamFlexAreasinto the same wire format.Per-layer census cache
ScenarioData.censusGeographies→censusGeographiesByLayer: Map<layer, Map<geoid, CensusGeographyData>>. The receiver routes each streamed entry by its.layer(already on the wire — examples and pre-feature captures replay correctly), andapplyScenarioResultFilterselects the currentconfig.aggregateLayer's map intoScenarioFilterResult.censusGeographies, so every UI consumer (report tables, choropleth, census modal, CSV) is untouched.aggregateLayeras a dep so cached-layer switches re-run the filter without a data change.Config flags
includeCensusValues?: booleanjoinsincludeStopBufferDemographics; both plusincludeFlexAreasuse!== falsesemantics, so CLI/WSDOT callers (which never set them) keep inline behavior. The SPA sendsincludeCensusValues: includeStopBufferDemographics(demographics-with-scenario needs census values inline for aggregation row seeding) — derived, no new URL param.getGeographyData; the skipped inline census fetch is the duplicate flagged in the TODO atsrc/analysis/wsdot/index.ts).wsdot-clioutput is unchanged.Client composable
useBufferRefetchgeneralizes touseScenarioRefetch: addsensureCensusValues()(per-layer in-flight dedupe, abort-and-reissue on layer change mid-flight),ensureFlexAreas()(one-shot with a loaded flag so legitimately-empty results aren't refetched), andresetLayerState()(called at fetch start and completion; seeds loaded-layer bookkeeping from the data itself, covering inline loads, examples, and pre-feature captures uniformly).loadNow()runs census + buffer concurrently into the shared receiver (disjoint accumulator fields) and owns the modal around the pair so the faster stream doesn't hide it early.Docs
docs/data-flow.mdgains a "Scenario layers" section: the core-vs-layers dependency graph, the pass/endpoint/flag/ensure pattern, the trigger table, and the caching semantics.Tests
census-values-pass.test.ts: emit shape, bbox padded iff radius > 0, layer stamping,resolveScenarioAreabbox/geographyIds/MultiPolygon paths.flex-areas-pass.test.ts: empty-input no-op, per-feed-version error isolation.scenario.test.ts: census pass runs when the flag is undefined (CLI parity) and skips when false; receiver per-layer merge independence; filter layer selection.User test plan
/api/scenariostream contains no census or flex data and completes faster./api/census-valuesrequest fires, and the table populates including stop-less geographies. Then enable Show Agg. Areas: the choropleth renders with no additional request./api/census-valuesand/api/buffer-geographies; the aggregation table seeds full rows with apportioned columns./api/flex-areasfires once, areas render on the map and the Flex Areas report tab; toggling off and on again does not refetch.showAggAreas=true: census values load automatically once the scenario arrives.🤖 Generated with Claude Code