# sensor-gap-finder

Find the nearest **upstream sensor gap** on any river reach and get a suggested
placement for a new monitoring station. Built for
[owockibot.xyz bounty #478](https://owockibot.xyz) — uses **USGS NWIS** (via the
Network-Linked Data Index) and **EPA Water Quality Portal** data.

No dependencies. Node.js ≥ 18 (uses the built-in `fetch`). No API keys.

## Quick start

```bash
node sgf.js --lat 39.6 --lng -110.8 --distance-km 40 --gap-km 8 --pretty
```

Output is a JSON report on stdout:

- `snap` — the NHDPlus reach (`comid`) the point snapped to and the total
  upstream main-stem length analyzed
- `sensors_upstream` — every monitoring station snapped to the upstream path
  (USGS NWIS sites + EPA WQP surface-water stations), each with its
  along-channel distance `path_km` from your point
- `gaps` — every stretch of the upstream path longer than `--gap-km` with no
  monitoring station
- `nearest_upstream_gap` — the first gap upstream of the query point
- `proposed_station` — lat/lng snapped to the flowline at the gap midpoint,
  with a rationale

## How it works

1. **Snap** — the query point is snapped to the nearest NHDPlus flowline via
   NLDI `comid/position` (`POINT(lng lat)`).
2. **Walk upstream** — NLDI navigation `UM/flowlines?distance=N` returns the
   upstream main-stem reaches, already ordered downstream→upstream. The tool
   concatenates them into a single path and builds a cumulative
   along-channel distance axis (km).
3. **Collect sensors** —
   - NWIS sites: NLDI navigation `UT/nwissite` (all upstream tributaries).
   - EPA WQP stations: `Station/search?mimeType=geojson&within=3` sampled at
     points along the path (the WQP `bbox` search is unreliable — circles are
     fast), filtered to surface-water location types and snapped onto the
     path if within `--wqp-radius-km`.
4. **Find gaps** — consecutive sensors more than `--gap-km` apart along the
   channel mark a sensor gap. The query point and the upstream path head are
   treated as bounds, so an unsensed headwater also counts.
5. **Propose placement** — the midpoint of the *nearest* upstream gap,
   snapped back onto the flowline.

## Options

| flag | default | meaning |
|---|---|---|
| `--lat`, `--lng` | *(required)* | query point, WGS84 degrees |
| `--distance-km` | 50 | how far upstream to analyze |
| `--gap-km` | 5 | minimum length to count as a sensor gap |
| `--wqp-radius-km` | 1.0 | max off-channel distance for WQP stations |
| `--emit-450` | off | also emit discovered sensors' latest readings as [bounty-#450-schema](schema/ecological-sensor-reading.schema.json)-conformant objects (NWIS temperature / dissolved oxygen / pH / turbidity → `water_quality` readings) |
| `--geojson` | off | embed the upstream path as a GeoJSON LineString |
| `--pretty` | off | indent output |

## Schema conformance (bounty #450)

`schema/ecological-sensor-reading.schema.json` is vendored from the #450
submission it references. With `--emit-450`, the tool fetches the latest
NWIS instantaneous values for upstream sites that report the four
`water_quality` parameters (`00010` temperature, `00300` dissolved oxygen,
`00400` pH, `63680` turbidity) and emits them as #450-conformant reading
objects (`sensor_type`, `parameter`, `value`, `unit`, `timestamp`,
`location`, `sensor_id`, `source`, `quality_flag`). Each emitted object is
checked by a built-in validator (`validate450`) — readings that fail are
tagged `_invalid` rather than silently dropped. NWIS no-data sentinels
(`-999999`) are skipped.

## Examples

`examples/cordingley-creek.json` — a sparse rural reach (Cordingley Canyon
Creek near Price, UT): one upstream WQP station, an 11.4 km gap from the
query point, proposed station at the 5.7 km midpoint.

`examples/green-river-450.json` — a well-monitored reach (Green River near
Jensen, UT): 56 upstream sensors, no gap ≥ 10 km, and live #450-conformant
temperature/dissolved-oxygen readings from the NWIS sites found.

## Honest scope notes

- "Gap" means *no monitoring station within `path_km` distance along the
  main stem* — it does not account for what each sensor measures, sampling
  frequency, data recency, or tributary branches (main-stem `UM` navigation
  for the path; tributary `UT` sites still count if they snap near the path).
- The proposed placement is a geometric midpoint heuristic — a real siting
  decision also needs land access, power/comms, flood stage, and permitting.
- EPA WQP station positions are not guaranteed to be on the flowline; the
  `--wqp-radius-km` snap threshold controls inclusion.
- This tool was built and operated by an autonomous agent (ARION) under
  human supervision; output should be sanity-checked before siting real
  equipment.

## Data sources

- USGS NLDI — `https://api.water.usgs.gov/nldi/linked-data`
- USGS NWIS instantaneous values — `https://waterservices.usgs.gov/nwis/iv/`
- EPA/USGS Water Quality Portal — `https://www.waterqualitydata.us`
