mangrovedigital/tide-engine-api
Tides — Wind-Corrected Tide Engine (10,000 Islands / Everglades City, FL)
Outputs an accurate corrected water level (NAVD88, metres) for a point/time in the 10,000 Islands backcountry, accounting for wind and pressure rather than the astronomical tide alone. Pure-Python (stdlib + requests), no model files.
corrected(t) = astronomical_tide(t) + correction(t)The product: wind-corrected tide forecast
Standard tide chart + a wind-corrected curve, carried 3 days on the wind forecast, anchored to the current observed level. Built for the case the chart fails: a multi-day N/NE/E blow after a winter front that turns a "mid incoming" into a dead low. Validated on held-out winter fronts: the plain chart is off ~1.2 ft; the product cuts that ~62% (0.46 ft), and beats persistence ~20% during fronts.
from engine.forecast import TideForecaster
from datetime import datetime, timezone
fc = TideForecaster.build("255327081275900", 25.89539, -81.4555) # East River
out = fc.forecast(datetime.now(tz=timezone.utc).timestamp(), horizon_h=72)
for p in out["series"]: # each: tide_ft, corrected_ft, lead_h, confidence_ft
print(p["t"], p["tide_ft"], p["corrected_ft"])See it: python scripts/demo_forecast.py (live 3-day, two curves) · python scripts/demo_front_replay.py (chart vs actual vs corrected through a real front) · python scripts/validate_forecast.py (held-out accuracy numbers).
Quick start (corrected level / navigability engine)
from engine import core
from datetime import datetime, timezone
eng = core.build_engine(
site="255327081275900", lat=25.89538889, lon=-81.4555, # East River
train=("2025-09-01", "2026-03-31"),
neighbor_site="255432081303900", # Faka-Union (nearest)
neighbor_train=("2025-09-01", "2026-03-31"),
hp_win=120, lags=tuple(range(10)),
)
t = datetime(2026, 6, 12, 12, tzinfo=timezone.utc).timestamp()
print(eng.corrected_at(t)) # -> dict with corrected_m, layer, provenance, statusEvery output carries provenance and a status of OK or HOLD: <reason> (it never emits a silent zero when data is missing).
How it works (layered, most-accurate-available)
- PRIMARY (nowcast): astronomical + nearest independent neighbour gauge's full non-tidal residual (regionally coherent, corr ≈ 0.99). ~0.03 m RMSE.
- FORECAST: astronomical + persisted regional slow offset + learned wind/pressure correction (uses forecast met). ~0.08 m RMSE.
- MET-ONLY: astronomical + wind/pressure correction (no neighbour).
- HOLD: no forcing and no neighbour.
The wind→water response is learned from data and direction-dependent; it independently reproduces the real bay geometry (drains on N/NE wind, fills on S/SW wind).
Data sources (official / managed APIs)
- USGS NWIS instantaneous values (NAVD88 water level) — ground truth + regional offset
- Open-Meteo ERA5 archive + forecast — wind speed/direction + surface pressure
Verify it yourself
python -m engine.harmonic # math selftest (synthetic, known answer)
python -m engine.residual # math selftest (synthetic, known answer)
python scripts/stage3_smoke.py # corrected beats baseline on held-out data
python scripts/stage4_full.py # optimization ladder + learned physics
python scripts/final_run.py # full coverage gate; writes out/final_report.jsonAccuracy (held-out test 2026-05-01…2026-06-15)
See out/FINAL_REPORT.md for the full write-up and out/*.json for raw results.
Scope
In: corrected water-level output + provenance + self-verification + accuracy maximization. Out: UI, hardware. The engine is ready for a UI to call core.build_engine(...) then engine.corrected_at(epoch).
