# River conditions integration

Verified 2026-10-04. Public, credential-free USGS data; no purchases or account setup.

## Browser interface

Load `public/conditions.js` before app code. It exports `window.OhioRiverConditions`; Node gets CommonJS exports for tests.

- `getCatalog(routeId)` returns `{id,gauges}` synchronously. Gauge objects contain USGS ID, official station name, `[longitude,latitude]`, station URL, metadata URL, and applicability scope.
- `await getForRiver(routeId, {signal, forceRefresh})` returns current flow/stage and recent endpoint-change trends. A route object with `.id` is also accepted.
- An aborted caller receives `AbortError`. The shared request may finish for other callers, so the UI must also guard results by the current route/selection generation to prevent a late response replacing a newer section selection.
- The asynchronous result contains `riverId`, `status`, `checkedAt`, `disclaimer`, `warnings[]`, `advisories[]`, `guidance[]`, `sourceLinks[]`, and `gauges[]`.
- Top-level status is `ok`, `partial`, `unavailable`, or `unsupported`. It describes data availability only. **Never render `ok` as “safe.”**
- Each gauge status is `ok`, `partial`, `stale`, or `unavailable`, with independent `flow` and `stage` objects or `null`.
- Metric fields: `value`, `unit` (`cfs` or `ft`), `observedAt` (ISO UTC), `ageMinutes`, `stale`, `approval`, `qualifiers[]`, `lastModified`, `stationId`, `parameterCode`, `seriesId`, `trend` or `null`.
- Trend fields: `direction` (`rising`, `falling`, `unchanged`), signed `change`, `unit`, `fromTime`, `toTime`, `elapsedHours`, `sampleCount`. It is only the change between observed endpoints, not a forecast or a rapid-rise safety classifier. Label the actual interval.
- Display timestamps in America/New_York if desired, but retain ISO values. Do not replace observation time with fetch time. `checkedAt` is request start time; cache reuse keeps the previous check time.

## API: modern OGC v1

Use https://api.waterdata.usgs.gov/ogcapi/v1/collections/ . Live HTTP requests on 2026-10-04 returned `access-control-allow-origin: *`; no PHP proxy or key is needed for on-demand browser access.

- Current observations: `latest-continuous/items`
- Six-hour recent observations: `continuous/items`
- Station metadata: `monitoring-locations/items/USGS-03274000?f=json` (single-item URL). The metadata key is `id`, not `monitoring_location_id`; requesting that nonexistent filter returned `InvalidQuery`.
- Parameter 00060 is discharge in `ft^3/s`; 00065 is gauge height in `ft`.
- Queries use explicit catalog IDs in CQL2 text, with an AND restriction for the two parameters. Example filter: `monitoring_location_id IN ('USGS-03270000') AND parameter_code IN ('00060','00065')`.
- `f=json`, `filter-lang=cql2-text`, and `limit=10000` are explicit. Responses are GeoJSON FeatureCollections. The current implementation treats any `rel:next` as incomplete/unavailable rather than quietly analyzing a partial page. Bounded selected-corridor requests tested well below this limit.
- History uses absolute ISO start/end for a six-hour window. Although the docs mention ISO durations, `datetime=PT6H` returned HTTP 400 “Unknown string format” on the live service. Absolute timestamps were verified working.
- Do not build on legacy WaterServices. USGS announced intentional degradations starting November 16, 2026 and final decommissioning February 22, 2027.

Sources:
- https://api.waterdata.usgs.gov/docs/ogcapi/
- https://api.waterdata.usgs.gov/docs/ogcapi/migration/
- https://api.waterdata.usgs.gov/docs/ogcapi/complex-queries/
- https://waterdata.usgs.gov/blog/api-v1-release/
- https://waterdata.usgs.gov/blog/wdfn-waterservices-degradation/

## Freshness, rate limits, failures

- Two selected-corridor requests, current and history, in parallel. No background polling.
- Same-route in-flight requests are deduplicated. Successful results are reused in memory for 60 seconds; explicit Refresh bypasses memory reuse. No persistent localStorage cache and no stale-while-revalidate masquerading as current.
- API requests use `cache:no-store`; live USGS responses themselves specify no-store. The one-minute application state reuse avoids repeatedly fetching when the same panel is reopened.
- Timeout per request is 18 seconds. History-only failure retains current readings with missing trend; current-reading failure reports unavailable. After an unsuccessful refresh, any previous actual values are explicitly marked stale and lose their trends.
- The application marks observations older than 120 minutes stale as a conservative freshness caution. This is an app choice, **not** a USGS quality standard, official service-level promise, or kayaking cutoff. Always expose the exact measurement time. Some gauges transmit less often, so older data does not prove a sensor failure.
- `latest-continuous` can retain observations from discontinued stations many years ago. Never equate a successful API response or “latest” with fresh data.
- HTTP 429 honors numeric/date `Retry-After` with at least a one-minute cooldown; no automatic retry storm. Current docs say keys permit higher limits and headers may publish per-endpoint limits, but the observed anonymous response had no numeric allowance headers. Do not claim a guaranteed anonymous quota or create credentials for this app.
- USGS official fallback: each station page plus https://api.waterdata.usgs.gov/status/ . Failure never substitutes a different river's station or fabricated sample value.

Sources:
- https://api.waterdata.usgs.gov/docs/ogcapi/keys/
- https://api.waterdata.usgs.gov/docs/ogcapi/efficiency/
- https://api.waterdata.usgs.gov/status/
- https://www.usgs.gov/mission-areas/water-resources/science/streamgaging-basics

## Hydrologic applicability and limits

46 explicitly selected stations are grouped across 14 river corridors. Metadata was verified by USGS's Ohio stream-station collection and current readings on 2026-10-04. The map corridors span many reaches and sometimes tributary access points; these are **named-location context**, not a measured or modeled discharge at the clicked coordinate.

- Show gauge names, locations, and scope. No nearest-station algorithm, interpolation, cross-divide matching, or watershed-area scaling is performed.
- Great Miami, Mad, Stillwater, Little Miami, Maumee, Cuyahoga, Muskingum, Kokosing, Olentangy, Mahoning, Blanchard, Black, and Vermilion catalogs use stations on the named main stem. Tributary stations are excluded.
- Cuyahoga Hiram Rapids is explicitly upper river upstream of Lake Rockwell/Gorge and does not represent the lower river. The other Cuyahoga stations are Old Portage, Jaite, and Independence, which NPS specifically lists for its lower-river context.
- Black River Elyria is main stem only, not East/West Branch or the lake.
- Mad Dayton's gauge is the particular station MCD names for Mad River Run. It is not a Buck Creek observation.
- Mohican main-stem Greer exists in USGS metadata but returned no current flow/stage series. Return unavailable rather than substitute Clear Fork/Black Fork. A future exact-reach implementation can add operator-verified gauges only with their branch/dam limitations.
- Lake Erie and East Sandusky Bay return unsupported river-gauge conditions. They need appropriate marine wind/wave forecasts, temperature, and advisories.
- For truly reach-specific safety guidance, the product needs explicit launch/takeout endpoints plus a reviewed gauge-to-reach relationship and authoritative operator thresholds. Same-river membership alone does not establish representativeness above/below dams or major inflows.

## Physical meaning and safety

USGS already calculates discharge using site-specific measurements and rating relationships. The app does not invent a pressure, velocity, or safety calculation from cfs. Stage is the elevation above a station-specific datum, not water depth; stage numbers should not be compared across stations. Hamilton's recorded 63.50 ft is therefore not “63 feet deep.” Flood stage measures local flood impacts and is not a paddling go/no-go criterion. A low/normal reading does not remove dams, strainers, cold-water risks, pollution, or closures.

Sources:
- https://www.usgs.gov/faqs/why-doesnt-usgs-measure-gage-height-bottom-stream
- https://waterdata.usgs.gov/blog/gage_height/
- https://www.usgs.gov/faqs/how-a-rating-curve-used-convert-gage-height-streamflow

## Official local guidance and dated hazards

No universal thresholds or green “safe” state are provided.

1. Mad River Run only, Eastwood MetroPark river mile 3.7: MCD's currently linked 2018 map names USGS 03270000 and describes 300–1,200 cfs as optimal for this feature; beginners may prefer at/below 800 cfs. Shown as historical published guidance with its precise applicability, never automatically extrapolated to the trail.
   https://www.mcdwater.org/uploads/2024-01-08/659b87862389c837568/2018-Mad-River-map-combined.pdf
   https://www.mcdwater.org/river-fun/river-trails
2. Kokosing: Knox County Park District describes 100–200 cfs as preferred float conditions; its actual station link was verified to be **03136500 Mount Vernon**, not Millwood. The page does not specify a publication date. Shown with caveats, not as a blanket safety guarantee.
   https://knoxcountyparks.org/trails/water-trails/kokosing-river-water-trail/
3. Cuyahoga: on review 2026-10-04, NPS lists a river-wide construction closure near mile 17, with signed portage upstream of Fitzwater Road (exit west/river left, relaunch east side). It also advises against paddling the woody-debris obstruction upstream of Lock 39, and identifies the Lock 29 shelf/dam remnants. These advisories are dated research, not a realtime closures feed. UI must display the review date and link to recheck; gauge freshness is independent.
   https://www.nps.gov/cuva/planyourvisit/conditions.htm
   https://www.nps.gov/cuva/planyourvisit/paddling-the-river.htm
4. NPS now links the 2026 CUYAC official river conditions tool; open for full lower/upper Cuyahoga risk context rather than copying its colors without provenance.
   https://experience.arcgis.com/experience/1a2975a451d640468c2c76de356bac99
5. MCD's operator conditions and seasonal bacteria maps are separate links. Their own guidance says users must still judge skill, weather, water temperature, current, and debris even in green/yellow zones.
   https://greatmiamiriverway.com/water-data/trail-conditions-map
   https://greatmiamiriverway.com/water-data/bacteria-map

## Validation

`node --test test-conditions.cjs`: 17 tests cover strict values/units, stale readings, source-series isolation, timestamps, multi-series ambiguity, API shapes/errors/incomplete pages, signal cancellation, 429 cooldown, independent history failure, failed-refresh fallback, lake unsupported state, explicit mapping, dated hazards, and actual recorded official response fixtures.

Live HTTP adapter checks on 2026-10-04 around 01:22 UTC:
- Mad: five stations produced actual flow/stage/trends. Dayton 03270000: 586 cfs and 3.16 ft at 00:30 UTC, provisional, with observed falling endpoint change.
- Cuyahoga: Hiram Rapids/Old Portage recent; Jaite/Independence correctly marked older than two hours, no current trend.
- Mohican Greer: no data, correctly unavailable.

`test-fixtures` are historical automated-test inputs, never used to populate the live UI.
