Small command-line tool that fetches current Snoqualmie River USGS gauge readings and prints a simple status table.
- Python 3.10 or newer
- Internet access to query the USGS Instantaneous Values API
From a clean system:
git clone https://github.com/your-user/streamvis.git
cd streamvis
# Create a virtual environment (optional but recommended)
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Install dependencies and CLI entry point
pip install .
# Run via installed console script
streamvis
# Or run the script directly without installing
python streamvis.py- Calls the USGS Instantaneous Values service for the gauges defined in
config.toml(or the built-in defaults if no config is present). - Extracts the most recent stage (ft) and flow (cfs).
- Classifies each gauge as
NORMAL,ACTION,MINOR FLOOD,MOD FLOOD, orMAJOR FLOODusingFLOOD_THRESHOLDS. - Prints a compact table to standard output with the observation time and the per-gauge expected next update.
If the USGS service is unreachable or returns unexpected data, streamvis exits with a message and non-zero status.
To minimize needless polling while keeping latency low, run in adaptive mode:
streamvis --mode adaptiveBehavior:
- Fetches once immediately, then learns the typical update cadence per gauge (EWMA of observed intervals) starting from a strong 15‑minute‑multiple prior (15/30/60 min, etc.) and snapping to the best‑fitting multiple once enough intervals are observed or backfilled.
- Schedules the next multi-gauge request just before the next expected update (shared single call for all gauges).
- If the prediction was early (no new timestamps), it widens the interval slightly and does a short retry (default 60s) so it converges toward ~1 call per new update.
- When all tracked gauges are on ≤1h cadences, it includes USGS IV
modifiedSince(duration) to omit unchanged stations and reduce payload on early polls. - Persisted state lives at
~/.streamvis_state.json(override with--state-file PATH). Only the last timestamps, learned intervals, and last values are stored—no heavy history. A single-writer lock prevents twostreamvisinstances from using the same state file concurrently; start a second instance with a different--state-fileif needed. - Learning has sensible floors/ceilings: sub-60-second deltas are ignored when learning cadence, and learned intervals are clamped to a reasonable range before scheduling the next fetch.
- About every 6 hours,
streamvisre-fetches a small recent history window to detect missed updates or cadence shifts and re-align the learner without adding noticeable load.
Latency-aware scheduling:
- For each station,
streamvistracks:- Observation cadence (seconds between gauge timestamps).
- Observation→API latency as a window (lower/upper bounds) and robust stats (Tukey biweight location/scale).
- A cadence multiple confidence score (
cadence_fit) used to decide when to trust the 15‑minute‑grid snap.
- The scheduler uses a two-regime strategy:
- Coarse polling while far from the expected next update (fraction of the learned interval, scaling with cadence bounds).
- Short bursts of finer polling inside a narrow latency window for stations whose latency is stable (small robust scale), to converge on update timing at second-level resolution without hammering the API.
Options:
--debug: emit per-poll control summaries (cadence, latency, calls/update) to stderr for tuning.--min-retry-seconds(default 60): retry delay if the prediction was early.--max-retry-seconds(default 300): ceiling when backing off on errors.--backfill-hours(default 6): on startup, backfill this many hours of recent history from USGS IV to seed the cadence learner and charts (0 disables).--community-base URL: optional base URL for shared “community” priors. If set,streamvisfetches{URL}/summary.jsonat most once per day to seed cold starts with observed cadence/latency for each station.--community-publish: when used with--community-base, publish per‑update latency samples to{URL}/sample(native + browser when enabled).--user-lat/--user-lon: optional manual location for the Nearby gauges section in native TUI mode.- Nearby mode (
nin TUI): when enabled and a location is available,streamvisqueries the USGS Site Service for active IV stream gauges near you, adds the three closest to the session if they aren’t already tracked, and groups the “nearby” gauges at the bottom of the main table under a divider. When you toggle Nearby off, any gauges that were newly added by Nearby are evicted (no longer tracked/polled). --ui-tick-sec(default 0.15): UI refresh tick in TUI mode; raise this on slow devices/browsers to reduce CPU.--no-update-alert: in TUI mode, disable the bell/flash alert when new data is fetched.
streamvis reads station metadata and USGS/NWPS base URLs from config.toml in the project/module directory when present:
[global.usgs]defines the USGS IV base URL(s); if omitted, the tool falls back tohttps://waterservices.usgs.gov/nwis/iv/.[stations.<GAUGE_ID>]blocks define per-station metadata, includingusgs_site_noused to build IV queries.[global.noaa_nwps]and per-stationforecast_endpointfields can supply forecast URL templates; these are only used when non-empty.
If config.toml is missing or incomplete, streamvis uses its built-in Snoqualmie defaults so it remains runnable out of the box.
streamvis can optionally overlay official river forecasts from NOAA’s National Water Prediction Service (NWPS) when configured with an appropriate API endpoint.
CLI options:
--forecast-base: URL template for the forecast API. It may contain{gauge_id}and{site_no}placeholders fromSITE_MAP.- Example template (you must align this with NOAA’s current API docs before use):
--forecast-base 'https://api.water.noaa.gov/.../stations/{gauge_id}/forecast'
- Example template (you must align this with NOAA’s current API docs before use):
--forecast-hours(default 72): forecast horizon (in hours) considered when computing peak summaries.
You can also configure a default forecast template and/or per-station forecast_endpoint values in config.toml. When both are present, precedence is:
- CLI
--forecast-base - Per-station
forecast_endpointinconfig.toml [global.noaa_nwps].default_forecast_templateinconfig.toml
Behavior when enabled:
- Forecasts are refreshed for all gauges at most once per 60 minutes (
FORECAST_REFRESH_MIN). - For each station,
streamvis:- Stores a forecast time series (time, stage, flow) in local state.
- Computes 3-hour, 24-hour, and full-horizon maxima for stage and flow.
- Compares the latest observation to the nearest forecast point to estimate amplitude bias (delta and ratio).
- Compares observed vs forecast peak times to estimate a simple phase shift (peak earlier/later than forecast).
- In the TUI detail view (select station +
Enter):- Shows a table of the last few updates and trends.
- If forecast data is available:
- Displays forecast peak summaries: 3h / 24h / full (stage/flow).
- Shows “vs forecast now” deltas and ratios for stage and flow.
- Shows an estimated peak timing offset (hours earlier/later than forecast).
Important:
- This repository is developed offline, so the exact NOAA/NWPS endpoint and JSON shape are not hard-coded as authoritative.
- The forecast parsing logic in
streamvis.pyis intentionally conservative and documented as making shape assumptions; you should adapt the URL template and field mapping to match NOAA’s current NWPS API documentation for your gauges.
-
Observed data – USGS NWIS Instantaneous Values (IV):
https://waterservices.usgs.gov/nwis/iv/- Official USGS hydrologic observations (stage and flow).
- Near-real-time, typically 15-minute resolution.
- Supports multi-station, multi-parameter JSON responses in a single call; ideal for polite, batched polling.
-
Forecast data – NOAA National Water Prediction Service (NWPS):
water.noaa.govAPIs.- Official river stage/flow forecasts produced by River Forecast Centers.
- Designed for low-latency access once products are issued.
- Integrated here via the configurable
--forecast-baseURL template so operators can target the right low-latency endpoint for their stations.
-
Cross-check data (optional) – NW RFC text flowplots:
https://www.nwrfc.noaa.gov/station/flowplot/textPlot.cgi- Text hydrologic summaries (observed + forecast) for selected stations.
- When
--nwrfc-textis enabled,streamvisperiodically fetches the text plot for supported stations (currentlyGARW1andCONW1) and:- Parses observed stage/flow time series in local time (PST/PDT) and converts to UTC.
- Stores the series alongside USGS history in state.
- Computes a simple per-timestamp difference vs the latest USGS observation when timestamps align (Δstage, Δflow).
- In the TUI detail view, if NW RFC data is available for the selected station, a compact “NW RFC vs USGS (last)” line shows the latest deltas so you can see whether the downstream RFC view agrees with raw USGS IV.
- USGS IV remains the authoritative source; NW RFC is treated strictly as a secondary cross-check.
Lightweight batching/caching:
- All gauges are fetched in a single USGS call to avoid per-station chatter.
- Only minimal state (last timestamps + EWMA interval + last values, plus a small rolling history for charts) is persisted to keep I/O small and avoid historical bloat.
Launch the full-screen TUI that shows current time/date, observation times, per-station next-ETA, and interactive detail with a text-mode chart:
streamvis --mode tui # optional: --chart-metric flow- Arrow keys /
j/kto select a station; selected row highlights. ctoggles the chart metric between stage and flow;btoggles the bell/flash alert when new data is fetched;rrequests an immediate refresh;fforces an immediate refetch even if the last observation time has not advanced;qquits.- Detail pane shows the selected station’s last reading, when it was observed, per-gauge next expected update, and a sparkline of recent history (lightweight, persisted in state).
- The loop reuses the adaptive cadence learner to keep requests near 1 call per new update.
- State also records last fetch/success/failure timestamps and learned intervals across runs so the TUI stays conservative even after restart.
Note: TUI mode uses Python curses (available on macOS/Linux; Windows users may need WSL or an environment with curses support).
streamvis also ships a small browser harness that runs the existing curses TUI inside a web page using Pyodide (Python → WebAssembly) and a minimal web_curses shim:
- The core logic lives in the
streamvis/package; only HTTP and curses are abstracted behindhttp_client.pyandweb_curses.py. - In the browser, HTTP calls use
pyodide.http.open_url, so requests go directly from the page to USGS/NWPS/NWRFC with CORS. - The TUI draws into a
<div id="terminal">usingweb_curses, and key events (q, arrows,c,r,f,Enter) are forwarded from JS intogetch(). - State is persisted between browser sessions by mapping the
--state-file streamvis_state.jsonused by the web entrypoint tolocalStorage(streamvis_state_json) and syncing it on every save, so even a mid‑run reload retains learned cadence/latency. If the full state ever exceeds browser storage quotas, streamvis falls back to persisting a slimmer state that preserves scheduling/latency learning but drops bulky overlays. - The browser shim adapts the virtual terminal size to the viewport and lets you tap/click on a station row to select it and toggle its detail/table view, which works well on devices like an iPhone in landscape mode.
Community config in the browser (optional):
- Add
?community=...to the page URL to set--community-base(cached in browser localStorage asstreamvis_community_base). - Add
&publish=1to also enable--community-publish(cached asstreamvis_community_publish). - Your community endpoint must serve CORS headers (e.g.,
Access-Control-Allow-Origin: *) for the browser to POST samples.
To host this on GitHub Pages:
- Ensure a
.nojekyllfile exists at the site root so GitHub Pages publishes Python package files like__init__.py. - Publish
web/index.htmlandweb/main.jsalongside the Python sources:http_client.py,web_curses.py,web_entrypoint.py,config.toml- the full
streamvis/package directory (includingstreamvis/__init__.py) - If GitHub Pages serves the repo root, keep the Python sources at repo root and open
/web/. - If GitHub Pages serves
web/as the Pages root, copy the Python sources intoweb/as well. The loader will try both./...and../....
- Configure GitHub Pages to serve either the repo root or the
web/directory, matching the layout above. - Open the Pages URL on your phone or desktop; the page will load Pyodide from a CDN and start
streamvisin TUI mode inside the browser.
See BACKLOG.md for future work ideas.