WEPP Output Scope Contract
Authoritative contract for report
output_scopeselection and baseline-vs-roads output isolation. See also:docs/schemas/rq-response-contract.md,docs/schemas/weppcloud-browse-parquet-filter-contract.md,wepppy/nodb/mods/roads/specification.md
Normative Status
- This document is normative and authoritative for
output_scopebehavior. - Requirement keywords
MUST,MUST NOT,SHOULD, andMAYare interpreted per RFC 2119. - If implementation and this contract diverge, either:
- implementation MUST be corrected, or
- this contract MUST be updated in the same change set.
Scope
- Defines canonical
output_scopevalues and normalization. - Defines dataset path mapping and run-output isolation rules.
- Defines route-level
output_scopesupport and invalid-input behavior. - Defines Runs-page link discovery rules for Roads run results.
- Does not replace auth/capability requirements for those routes.
Canonical Values
- Allowed values:
baseline | roads. - Default when query parameter is missing or empty:
baseline. - Value matching is case-insensitive (
ROADSnormalizes toroads). - Invalid values MUST fail explicitly; silent fallback is forbidden.
Implementation reference:
wepppy/wepp/reports/output_scope.py::normalize_output_scope
Path Mapping Contract
- Baseline output root:
wepp/output - Roads output root:
wepp/roads/output - Interchange root:
<output_root>/interchange
scoped_dataset_path(...) rules:
- For
baseline, return the input path unchanged. - For
roads, rewrite only paths rooted atwepp/outputtowepp/roads/output. - Paths outside
wepp/outputMUST remain unchanged.
Examples:
wepp/output/interchange/H.wat.parquet+roads->wepp/roads/output/interchange/H.wat.parquetwatershed/hillslopes.parquet+roads-> unchanged
Watershed-table schema note:
output_scoperewrites do not alter watershed table schemas.watershed/hillslopes.parquetmay include additive Peridot provenance columns such aslength_estimate_mode,length_area_over_channel, andlength_edge_median; scoped path behavior remains unchanged. Direct Peridot output behavior is documented in the Peridot watershed output contract.
Implementation references:
wepppy/wepp/reports/output_scope.py::resolve_output_scope_pathswepppy/wepp/reports/output_scope.py::scoped_dataset_path
Storage Isolation Contract
- Roads-scoped report resource regeneration MUST write only under
wepp/roads/output/interchange/*. - Roads flows MUST NOT mutate baseline
wepp/output/*. - Required Roads resources MUST be checked after regeneration; missing required resources MUST fail explicitly with
FileNotFoundError. - Successful regeneration persists
roads_report_resources.output_scope = "roads".
Implementation reference:
wepppy/nodb/mods/roads/roads.py::_regenerate_roads_report_resources
AgFields routing-suite boundary
AgFields routing results are independently composable experimental trees under
wepp/ag_fields/watershed/{concept-1,concept-2,hybrid}/. They are not canonical
output_scope values and MUST NOT be selected by passing their identifiers to a
standard WEPP report route. Their runs, outputs, manifests, and interchange files
remain isolated from both wepp/output and wepp/roads/output; current UI support
is authenticated execution/state plus direct artifact browsing. Making a standard
report scheme-aware requires an explicit additive contract and route tests in a
future change.
Route Support Matrix
| Route | output_scope Query Param |
Default | Invalid Value Behavior | Notes |
|---|---|---|---|---|
/runs/<runid>/<config>/report/wepp/summary |
Yes | baseline |
400 + JSON error payload |
Scope passed to outlet/hill/channel summary reports. |
/runs/<runid>/<config>/report/wepp/yearly_watbal |
Yes | baseline |
400 + JSON error payload |
Scope passed to TotalWatbalReport. |
/runs/<runid>/<config>/report/wepp/avg_annual_watbal |
Yes | baseline |
400 + JSON error payload |
Scope passed to hillslope/channel watbal reports. |
/runs/<runid>/<config>/plot/wepp/streamflow |
Yes | baseline |
400 + JSON error payload |
Uses scope-aware totalwatsed3.parquet relpath. |
/runs/<runid>/<config>/report/wepp/return_periods |
Yes | baseline |
400 + JSON error payload |
Scope passed to return-period postprocess service. |
/runs/<runid>/<config>/gl-dashboard |
Yes | baseline |
400 + JSON error payload |
Scope-aware WEPP dataset relpaths injected into template context. |
/runs/<runid>/<config>/storm-event-analyzer |
Yes | baseline |
400 + JSON error payload |
Scope-aware WEPP dataset relpaths injected into template context. |
/runs/<runid>/<config>/report/wepp/results |
No | N/A | N/A | Baseline Run Results panel. |
/runs/<runid>/<config>/report/roads/results |
No | N/A | N/A | Roads Run Results panel; links are generated as roads-scoped where applicable. |
Runs-Page Trigger and Discovery Contract
Baseline WEPP Run Results panel
- Served by
/report/wepp/results. - Link set is template-driven (
controls/wepp_reports.htm) with conditional visibility for baseline artifacts:totalwatsed3.parquetandtotalwatsed2.parquetdownload links appear only when those files exist.- interchange README link appears only when
wepp/output/interchange/README.mdexists.
Roads Run Results panel
- Served by
/report/roads/resultsand rendered fromcontrols/roads_reports.htm. - Panel visibility condition:
run_summary_state == "completed"roads_report_resources.status == "ready"roads_report_resources.missing_relpathsis empty
- Links are discovered from
roads_report_resourcesand emitted by_roads_run_results_report_links(...).
Roads link gating by required resources:
Watershed Loss Summary: requiresloss_pw0.out.parquet,loss_pw0.hill.parquet,loss_pw0.chn.parquet.Return Periods: requiresebe_pw0.parquet,totalwatsed3.parquet.Yearly Water Balance: requirestotalwatsed3.parquet.Daily Streamflow: requirestotalwatsed3.parquet.Average Annual Water Balance: requiresH.wat.parquet.GL DashboardandStorm Event Analyzer: included in Roads link set when the panel is ready.Road Segment Loss Summary (Parquet/CSV): requires non-emptyroads_segment_loss_summary_relpath.
Client trigger behavior:
- Roads controller JS fetches
/report/roads/results/on initialization and after prepare/run completion to refresh the panel in place.
Implementation references:
wepppy/weppcloud/routes/nodb_api/wepp_bp.py::report_wepp_resultswepppy/weppcloud/routes/nodb_api/roads_bp.py::_roads_run_results_report_linkswepppy/weppcloud/templates/controls/wepp_reports.htmwepppy/weppcloud/templates/controls/roads_reports.htmwepppy/weppcloud/controllers_js/roads.js::renderRoadsResultsPanel
Format Contract
- Canonical persisted report resources in scoped output directories SHOULD be Parquet.
roads_segment_loss_summaryMUST be persisted only as:wepp/roads/output/interchange/roads_segment_loss_summary.parquet
- CSV export MUST be runtime conversion from Parquet via:
/download/{subpath}?as_csv=1
- No on-disk CSV shadow artifact is allowed for roads segment loss summary.
Error Contract
- Library-level scope validation (
normalize_output_scope) raises:ValueError("Invalid output_scope '<value>'. Expected one of: baseline|roads.")
- Route-level invalid scope handling MUST return:
- HTTP
400 - JSON payload containing
error.messagewith the invalid-scope text.
- HTTP
- Roads resource regeneration missing required artifacts MUST raise
FileNotFoundErrorwith missing relpaths listed.
Compliance Checklist
When adding or modifying scope-aware report surfaces:
- Validate
output_scopeusingnormalize_output_scope. - Use
scoped_dataset_pathorresolve_output_scope_pathsfor all scoped dataset paths. - Preserve
output_scopein rendered template links/forms/selectors. - Add/maintain route tests for:
- default scope behavior,
output_scope=roads,- invalid scope (
400).
- Keep roads-only writes under
wepp/roads/output/*; never mutatewepp/output/*in roads flows. - Update this contract in the same change set when scope behavior changes.