forcingkit 0.3.0__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. {forcingkit-0.3.0 → forcingkit-0.5.0}/.agents/forcingkit.md +4 -4
  2. forcingkit-0.5.0/.env.template +20 -0
  3. {forcingkit-0.3.0 → forcingkit-0.5.0}/AGENTS.md +4 -3
  4. {forcingkit-0.3.0 → forcingkit-0.5.0}/PKG-INFO +1 -1
  5. {forcingkit-0.3.0 → forcingkit-0.5.0}/docs/source/atmospheric_forcing.rst +10 -5
  6. {forcingkit-0.3.0 → forcingkit-0.5.0}/docs/source/conf.py +1 -1
  7. {forcingkit-0.3.0 → forcingkit-0.5.0}/docs/source/fetchers.rst +19 -10
  8. {forcingkit-0.3.0 → forcingkit-0.5.0}/docs/source/hycom.rst +76 -16
  9. {forcingkit-0.3.0 → forcingkit-0.5.0}/docs/source/index.rst +13 -5
  10. {forcingkit-0.3.0 → forcingkit-0.5.0}/docs/source/necofs.rst +2 -1
  11. {forcingkit-0.3.0 → forcingkit-0.5.0}/docs/source/nyofs.rst +4 -2
  12. forcingkit-0.5.0/docs/source/rivers.rst +177 -0
  13. {forcingkit-0.3.0 → forcingkit-0.5.0}/docs/source/roadmap.rst +61 -15
  14. {forcingkit-0.3.0 → forcingkit-0.5.0}/docs/source/sources.rst +23 -1
  15. {forcingkit-0.3.0 → forcingkit-0.5.0}/pyproject.toml +1 -1
  16. {forcingkit-0.3.0 → forcingkit-0.5.0}/service/forcingkit_serve/main.py +220 -0
  17. forcingkit-0.5.0/src/forcingkit/data/rivers/northeast_us.json +29 -0
  18. {forcingkit-0.3.0 → forcingkit-0.5.0}/src/forcingkit/dispatcher.py +186 -3
  19. forcingkit-0.5.0/src/forcingkit/fetchers/coastwatch.py +487 -0
  20. {forcingkit-0.3.0 → forcingkit-0.5.0}/src/forcingkit/fetchers/erddap.py +3 -2
  21. {forcingkit-0.3.0 → forcingkit-0.5.0}/src/forcingkit/fetchers/hycom.py +44 -11
  22. {forcingkit-0.3.0 → forcingkit-0.5.0}/src/forcingkit/fetchers/necofs.py +118 -1
  23. {forcingkit-0.3.0 → forcingkit-0.5.0}/src/forcingkit/fetchers/noaa.py +1 -1
  24. forcingkit-0.5.0/src/forcingkit/fetchers/usgs.py +347 -0
  25. forcingkit-0.5.0/src/forcingkit/fetchers/usgs_rivers.py +625 -0
  26. {forcingkit-0.3.0 → forcingkit-0.5.0}/src/forcingkit/settings.py +2 -3
  27. forcingkit-0.3.0/tests/integration/test_obc_mab.py → forcingkit-0.5.0/tests/integration/test_obc_offshore_nj.py +4 -4
  28. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/scripts/inspect_dem.py +12 -5
  29. forcingkit-0.5.0/tests/unit/test_coastwatch.py +222 -0
  30. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/unit/test_hycom.py +111 -6
  31. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/unit/test_necofs_parent.py +9 -0
  32. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/unit/test_settings.py +4 -7
  33. forcingkit-0.5.0/tests/unit/test_usgs.py +110 -0
  34. forcingkit-0.5.0/tests/unit/test_usgs_rivers.py +490 -0
  35. {forcingkit-0.3.0 → forcingkit-0.5.0}/uv.lock +1 -1
  36. forcingkit-0.3.0/.env.template +0 -17
  37. {forcingkit-0.3.0 → forcingkit-0.5.0}/.claude/CLAUDE.md +0 -0
  38. {forcingkit-0.3.0 → forcingkit-0.5.0}/.github/copilot-instructions.md +0 -0
  39. {forcingkit-0.3.0 → forcingkit-0.5.0}/.github/workflows/docs.yml +0 -0
  40. {forcingkit-0.3.0 → forcingkit-0.5.0}/.github/workflows/publish.yml +0 -0
  41. {forcingkit-0.3.0 → forcingkit-0.5.0}/.github/workflows/tests.yml +0 -0
  42. {forcingkit-0.3.0 → forcingkit-0.5.0}/.gitignore +0 -0
  43. {forcingkit-0.3.0 → forcingkit-0.5.0}/.markdownlint.yaml +0 -0
  44. {forcingkit-0.3.0 → forcingkit-0.5.0}/.pre-commit-config.yaml +0 -0
  45. {forcingkit-0.3.0 → forcingkit-0.5.0}/.python-version +0 -0
  46. {forcingkit-0.3.0 → forcingkit-0.5.0}/CONTRIBUTING.md +0 -0
  47. {forcingkit-0.3.0 → forcingkit-0.5.0}/Dockerfile +0 -0
  48. {forcingkit-0.3.0 → forcingkit-0.5.0}/LICENSE +0 -0
  49. {forcingkit-0.3.0 → forcingkit-0.5.0}/README.md +0 -0
  50. {forcingkit-0.3.0 → forcingkit-0.5.0}/docker-compose.yml +0 -0
  51. {forcingkit-0.3.0 → forcingkit-0.5.0}/docs/Makefile +0 -0
  52. {forcingkit-0.3.0 → forcingkit-0.5.0}/docs/make.bat +0 -0
  53. {forcingkit-0.3.0 → forcingkit-0.5.0}/docs/requirements-docs.txt +0 -0
  54. {forcingkit-0.3.0 → forcingkit-0.5.0}/docs/source/_extra/CNAME +0 -0
  55. {forcingkit-0.3.0 → forcingkit-0.5.0}/docs/source/_static/custom.css +0 -0
  56. {forcingkit-0.3.0 → forcingkit-0.5.0}/docs/source/_static/forcingkit-noreaster-wind.gif +0 -0
  57. {forcingkit-0.3.0 → forcingkit-0.5.0}/main.py +0 -0
  58. {forcingkit-0.3.0 → forcingkit-0.5.0}/service/__init__.py +0 -0
  59. {forcingkit-0.3.0 → forcingkit-0.5.0}/service/forcingkit_serve/__init__.py +0 -0
  60. {forcingkit-0.3.0 → forcingkit-0.5.0}/service/forcingkit_serve/routers/bathymetry.py +0 -0
  61. {forcingkit-0.3.0 → forcingkit-0.5.0}/service/forcingkit_serve/routers/plotly_api.py +0 -0
  62. {forcingkit-0.3.0 → forcingkit-0.5.0}/service/forcingkit_serve/routers/removed.py +0 -0
  63. {forcingkit-0.3.0 → forcingkit-0.5.0}/service/forcingkit_serve/routers/viewer.py +0 -0
  64. {forcingkit-0.3.0 → forcingkit-0.5.0}/service/run_server.py +0 -0
  65. {forcingkit-0.3.0 → forcingkit-0.5.0}/src/forcingkit/__init__.py +0 -0
  66. {forcingkit-0.3.0 → forcingkit-0.5.0}/src/forcingkit/fetchers/dbofs.py +0 -0
  67. {forcingkit-0.3.0 → forcingkit-0.5.0}/src/forcingkit/fetchers/hrrr.py +0 -0
  68. {forcingkit-0.3.0 → forcingkit-0.5.0}/src/forcingkit/fetchers/hrrr_atmosphere.py +0 -0
  69. {forcingkit-0.3.0 → forcingkit-0.5.0}/src/forcingkit/fetchers/hydrography.py +0 -0
  70. {forcingkit-0.3.0 → forcingkit-0.5.0}/src/forcingkit/fetchers/ndbc.py +0 -0
  71. {forcingkit-0.3.0 → forcingkit-0.5.0}/src/forcingkit/fetchers/nyofs.py +0 -0
  72. {forcingkit-0.3.0 → forcingkit-0.5.0}/src/forcingkit/zarr_stream.py +0 -0
  73. {forcingkit-0.3.0 → forcingkit-0.5.0}/static/app.js +0 -0
  74. {forcingkit-0.3.0 → forcingkit-0.5.0}/static/favicon.ico +0 -0
  75. {forcingkit-0.3.0 → forcingkit-0.5.0}/static/favicon.svg +0 -0
  76. {forcingkit-0.3.0 → forcingkit-0.5.0}/static/index.html +0 -0
  77. {forcingkit-0.3.0 → forcingkit-0.5.0}/static/logo.svg +0 -0
  78. {forcingkit-0.3.0 → forcingkit-0.5.0}/static/preview3d.js +0 -0
  79. {forcingkit-0.3.0 → forcingkit-0.5.0}/static/styles.css +0 -0
  80. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/__init__.py +0 -0
  81. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/integration/test_auth.py +0 -0
  82. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/integration/test_erddap_fetch.py +0 -0
  83. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/integration/test_nyofs_obc_fetch.py +0 -0
  84. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/integration/test_ofs_archive_paths.py +0 -0
  85. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/integration/test_s3_roms_fetchers.py +0 -0
  86. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/scripts/inspect_grib.py +0 -0
  87. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/scripts/inspect_zarr.py +0 -0
  88. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/unit/test_dbofs.py +0 -0
  89. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/unit/test_dispatcher.py +0 -0
  90. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/unit/test_hrrr_atm.py +0 -0
  91. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/unit/test_hrrr_idx.py +0 -0
  92. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/unit/test_main.py +0 -0
  93. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/unit/test_ndbc.py +0 -0
  94. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/unit/test_noaa_currents.py +0 -0
  95. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/unit/test_noaa_datum.py +0 -0
  96. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/unit/test_nyofs.py +0 -0
  97. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/unit/test_obc_donor_report.py +0 -0
  98. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/unit/test_obc_pipeline.py +0 -0
  99. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/unit/test_removed_routes.py +0 -0
  100. {forcingkit-0.3.0 → forcingkit-0.5.0}/tests/unit/test_zarr_stream.py +0 -0
@@ -3,8 +3,8 @@
3
3
  ## Mission
4
4
 
5
5
  Python microservice providing real-time and historical forcing (the parent ocean and
6
- the HRRR atmosphere) and validation observations to the `coastal-sim` Julia physics
7
- engine.
6
+ the HRRR atmosphere) and validation observations to downstream ocean models, for
7
+ example ones built on Oceananigans.jl or NumericalEarth.
8
8
 
9
9
  ## Environment
10
10
 
@@ -41,7 +41,7 @@ HTTP Request → service/forcingkit_serve/main.py
41
41
  *Note: Because the architecture is modular, a future roadmap item includes the integration of TPXO (via `tpxo.py`) given usage rights.*
42
42
 
43
43
  Atmospheric forcing: HRRR from 2014-07-30 (`hrrr_atmosphere.py`, `/api/v1/atmosphere`).
44
- Earlier runs use ERA5 through NumericalEarth in coastal-sim, not this service.
44
+ Earlier runs use ERA5 through NumericalEarth in the model, not this service.
45
45
 
46
46
  ## Testing
47
47
 
@@ -89,7 +89,7 @@ uv run python -c "from forcingkit.fetchers import nyofs; print(nyofs.get_metadat
89
89
  - **Mocking strategy**: Dispatcher unit tests check tuple/dict return
90
90
  boundaries carefully. When modifying mocked fetchers, track
91
91
  keyword-argument vs positional argument boundaries.
92
- - **Grid normalization**: `coastal-sim` expects elevations positive
92
+ - **Grid normalization**: downstream models expect elevations positive
93
93
  up (LMSL/NAVD88). Normalize any inverted datasets in the fetcher
94
94
  tier before the dispatcher sees them.
95
95
  - **Cache keys**: Cache Zarr keys are deterministic hashes of bbox
@@ -0,0 +1,20 @@
1
+ # forcingkit environment variables
2
+ # Copy this file to .env and fill in your credentials.
3
+ # A docker-compose service can load it with env_file.
4
+
5
+ # No credentials are required: HRRR, NOAA OFS, NECOFS, HYCOM, NDBC, CO-OPS and the
6
+ # USGS Water Data APIs are all served without authentication.
7
+
8
+ # Optional USGS Water Data API key (free from https://api.waterdata.usgs.gov/signup/).
9
+ # Without it, river discharge requests still work at a lower hourly rate limit.
10
+ # USGS_API_KEY=
11
+
12
+ # Cache root for every store (default ~/.cache/forcingkit). The old name
13
+ # ECODATA_CACHE_CACHE_DIR is still read, with a warning, until the next release.
14
+ # FORCINGKIT_CACHE_DIR=~/.cache/forcingkit
15
+
16
+ # Threads for parallel OPeNDAP reads (default 4; was ECODATA_CACHE_MAX_WORKERS).
17
+ # FORCINGKIT_MAX_WORKERS=4
18
+
19
+ # The topobathykit elevation service (default http://localhost:9595; was TOPOBATHYSIM_URL).
20
+ # TOPOBATHYKIT_URL=http://localhost:9595
@@ -22,9 +22,10 @@ guidance in this repository.
22
22
  ## Repository Focus
23
23
 
24
24
  forcingkit is a Python microservice that fetches, harmonizes, regrids, and serves
25
- real-time and historical forcing to the `coastal-sim` Julia physics engine: the parent
26
- ocean (`/api/v1/obc`, schema z-v2), the HRRR atmosphere (`/api/v1/atmosphere`), tides,
27
- and station telemetry and NDBC observations for validation.
25
+ real-time and historical forcing to downstream ocean models (for example ones built on
26
+ Oceananigans.jl or NumericalEarth): the parent ocean (`/api/v1/obc`, schema z-v3), the
27
+ HRRR atmosphere (`/api/v1/atmosphere`), tides, and station telemetry and NDBC
28
+ observations for validation.
28
29
 
29
30
  ## Core Rules
30
31
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: forcingkit
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: Spatiotemporal forcing for computational Earth-system models: selects, regrids and serves model-ready time series with provenance.
5
5
  Project-URL: Documentation, https://forcingkit.docs.lhzn.io
6
6
  Project-URL: Issues, https://github.com/lhzn-io/forcingkit/issues
@@ -2,7 +2,8 @@ Atmospheric Forcing Datasets
2
2
  ============================
3
3
 
4
4
  As of 2026-10-04. Which atmospheric datasets can force a coastal ocean model through this service
5
- or through `NumericalEarth <https://github.com/NumericalEarth/NumericalEarth.jl>`__, how they compare, and which to choose.
5
+ or through `NumericalEarth <https://github.com/NumericalEarth/NumericalEarth.jl>`__, how they
6
+ compare, and which to choose.
6
7
 
7
8
  An ocean model's surface fluxes need, at every hour of the run: 10 m wind (eastward and
8
9
  northward), 2 m air temperature and specific humidity, surface pressure, precipitation, and
@@ -151,9 +152,11 @@ ERA5 (fallback)
151
152
 
152
153
  *Provenance.* `ECMWF <https://www.ecmwf.int/>`__'s fifth-generation global reanalysis for the
153
154
  `Copernicus Climate Change Service <https://climate.copernicus.eu/>`__ (`Hersbach et al., 2020
154
- <https://doi.org/10.1002/qj.3803>`__): 0.25 degree grid (about 31 km), hourly, 1940 to the present. ERA5T, the initial release,
155
+ <https://doi.org/10.1002/qj.3803>`__): 0.25 degree grid (about 31 km), hourly, 1940 to the present.
156
+ ERA5T, the initial release,
155
157
  appears about five days behind real time and is overwritten by the final ERA5 two to three months
156
- later. Requires a free `Copernicus Climate Data Store <https://cds.climate.copernicus.eu/>`__ account (credentials in ``~/.cdsapirc``);
158
+ later. Requires a free `Copernicus Climate Data Store <https://cds.climate.copernicus.eu/>`__
159
+ account (credentials in ``~/.cdsapirc``);
157
160
  Copernicus licence, attribution required.
158
161
 
159
162
  *How it is used.* A model reads it through NumericalEarth's ``ERA5PrescribedAtmosphere`` and
@@ -167,7 +170,8 @@ standard against which forcing biases are judged.
167
170
 
168
171
  *Limits.* At 31 km a 15 km coastal domain spans one or two ERA5 cells, so the forcing is nearly
169
172
  uniform and smears the coast: in the first hours of 2026-04-02 over such a domain off New Jersey,
170
- the ERA5 box (which includes New Jersey land) was about 3 K warmer at 2 m and had about half HRRR's wind speed. Latency rules out
173
+ the ERA5 box (which includes New Jersey land) was about 3 K warmer at 2 m and had about half HRRR's
174
+ wind speed. Latency rules out
171
175
  anything closer than five days to the present.
172
176
 
173
177
  Choosing
@@ -220,6 +224,7 @@ Links
220
224
  - NOAA HRRR on AWS: https://registry.opendata.aws/noaa-hrrr-pds/
221
225
  - NOAA RRFS: https://gsl.noaa.gov/rrfs/ ; operational date and retirements:
222
226
  https://gribstream.com/blog/noaa-rrfs-refs-operational-august-2026
223
- - ERA5T latency: https://climate.copernicus.eu/key-update-climate-dataset-brings-data-five-days-behind-real-time
227
+ - ERA5T latency:
228
+ https://climate.copernicus.eu/key-update-climate-dataset-brings-data-five-days-behind-real-time
224
229
  - ECMWF open data: https://www.ecmwf.int/en/forecasts/datasets/open-data
225
230
  - JRA55-do: https://climate.mri-jma.go.jp/pub/ocean/JRA55-do/
@@ -9,7 +9,7 @@ sys.path.insert(0, os.path.abspath("../../src"))
9
9
  project = "forcingkit"
10
10
  copyright = "2026, Long Horizon Observatory"
11
11
  author = "Daniel Fry"
12
- release = "0.3.0"
12
+ release = "0.5.0"
13
13
 
14
14
  extensions = [
15
15
  "sphinx.ext.autodoc",
@@ -19,11 +19,12 @@ Atmospheric forcing (``/api/v1/atmosphere``)
19
19
  - NOAA `High-Resolution Rapid Refresh <https://rapidrefresh.noaa.gov/hrrr/>`__ (developed by
20
20
  the `NOAA Global Systems Laboratory <https://gsl.noaa.gov/>`__, run operationally by
21
21
  `NCEP <https://www.nco.ncep.noaa.gov/>`__), 3 km, from the anonymous AWS S3 bucket
22
- `noaa-hrrr-bdp-pds <https://registry.opendata.aws/noaa-hrrr-pds/>`__; the archive starts 2014-07-30. Surface fields are interpolated to a
23
- regular 0.03 degree grid and streamed hour by hour (schema ``hrrr-atm-v1``). Earlier dates
24
- use `ERA5 <https://cds.climate.copernicus.eu/>`__ through
25
- `NumericalEarth <https://github.com/NumericalEarth/NumericalEarth.jl>`__ in the model, outside this service. See
26
- :doc:`atmospheric_forcing`.
22
+ `noaa-hrrr-bdp-pds <https://registry.opendata.aws/noaa-hrrr-pds/>`__; the archive starts
23
+ 2014-07-30. Surface fields are interpolated to a regular 0.03 degree grid and streamed hour
24
+ by hour (schema ``hrrr-atm-v1``). Earlier dates use `ERA5
25
+ <https://cds.climate.copernicus.eu/>`__ through `NumericalEarth
26
+ <https://github.com/NumericalEarth/NumericalEarth.jl>`__ in the model, outside this
27
+ service. See :doc:`atmospheric_forcing`.
27
28
 
28
29
  Parent ocean (``/api/v1/obc``)
29
30
  ------------------------------
@@ -37,7 +38,8 @@ Parent ocean (``/api/v1/obc``)
37
38
  * - **NECOFS**
38
39
  - `Northeast Coastal Ocean Forecast System <https://fvcom.smast.umassd.edu/?p=20>`__, `FVCOM
39
40
  <https://fvcom.smast.umassd.edu/>`__ GOM7 (`UMass Dartmouth SMAST
40
- <https://www.umassd.edu/smast/>`__ and `WHOI <https://www.whoi.edu/>`__), unstructured mesh with 45 sigma layers. Daily history files from 2025-01-01, then the rolling forecast.
41
+ <https://www.umassd.edu/smast/>`__ and `WHOI <https://www.whoi.edu/>`__), unstructured mesh
42
+ with 45 sigma layers. Daily history files from 2025-01-01, then the rolling forecast.
41
43
  The only donor that streams the ``z-v3`` parent store. See :doc:`necofs`.
42
44
  * - **NYOFS**
43
45
  - `NOAA New York/New Jersey Operational Forecast System
@@ -58,12 +60,16 @@ Parent ocean (``/api/v1/obc``)
58
60
  the present. The experiment follows the date: GLBv0.08 ``expt_53.X`` (reanalysis) to
59
61
  2015, a chain of GLBv0.08 analysis experiments to 2018-12-04, GLBy0.08 ``expt_93.0`` to
60
62
  2024-09-05, then ESPC-D-V02. A window that spans a switch is stitched; if any piece fails,
61
- the whole window fails. Last-resort fallback; legacy output layout.
63
+ the whole window fails. Tidal only from 2024-09-05, recorded in the store's ``tides``
64
+ attribute (``"none"``, ``"included"`` or ``"mixed"``). Last-resort fallback; legacy
65
+ output layout.
62
66
 
63
67
  `TPXO10 <https://www.tpxo.net/>`__ tidal harmonics are a roadmap item, not integrated; the
64
68
  `pyTMD <https://github.com/pyTMD/pyTMD>`__ dependency was removed on
65
69
  2026-10-05. The ``include_tides`` and ``tidal_model`` request fields
66
70
  are still accepted and form part of the cache key, but no tide is added to the parent store.
71
+ A parent that already contains the tide (a HYCOM store with ``tides = "included"``) should not
72
+ have tides added again downstream; see :doc:`hycom`.
67
73
 
68
74
  Donor selection
69
75
  ~~~~~~~~~~~~~~~
@@ -149,8 +155,9 @@ As checked on 2026-10-06, NYOFS and DBOFS output is available from three places:
149
155
  - From 2024-11-19, one directory per day, ``<ofs>/netcdf/{yyyy}/{mm}/{dd}/``; the first day
150
156
  holds only its last cycle. Before that, one flat directory per month,
151
157
  ``<ofs>/netcdf/{yyyymm}/``, with a mix of file names (NYOFS 2024-07 uses the older names,
152
- DBOFS 2024-07 the newer ones); several of those months are partial and some are absent (NYOFS lacks 2024-01 to 2024-03, 2024-05
153
- and 2024-06; DBOFS lacks 2024-02 and starts 2024-01 on the 29th).
158
+ DBOFS 2024-07 the newer ones); several of those months are partial and some are absent
159
+ (NYOFS lacks 2024-01 to 2024-03, 2024-05 and 2024-06; DBOFS lacks 2024-02 and starts
160
+ 2024-01 on the 29th).
154
161
 
155
162
  .. note::
156
163
  An earlier version of this page said that NCEI stopped archiving at the end of November 2023,
@@ -181,7 +188,9 @@ Common processing
181
188
  -----------------
182
189
 
183
190
  - **Hourly time axis.** Non-streaming donors are resampled to a strict hourly index by linear
184
- interpolation. Streaming donors deliver hourly records directly.
191
+ interpolation. Streaming donors deliver hourly records directly. HYCOM steps are 3 hours apart
192
+ with occasional gaps of up to 51 hours, which this bridges with a straight line; a store does
193
+ not yet record the longest gap it filled (:doc:`hycom`).
185
194
  - **Float32 fields, full-precision coordinates.** Data variables are cast to Float32 for the
186
195
  model; ``lat``, ``lon``, ``z`` and ``z_face`` keep full precision, since Float32 longitudes near
187
196
  -74 resolve only about 8e-6 degrees.
@@ -8,7 +8,9 @@ live against it on the same day.
8
8
  HYCOM is forcingkit's global parent ocean and the dispatcher's last-resort donor: it accepts any
9
9
  box and is tried after NYOFS, DBOFS and NECOFS (:doc:`fetchers`). It covers 1994 to the present
10
10
  by switching between several experiments, and delivers the legacy output layout rather than the
11
- ``z-v3`` parent store.
11
+ ``z-v3`` parent store. Its fields are already on a regular longitude/latitude grid at fixed
12
+ depths in metres, with temperature and salinity, so it is the donor closest to ``z-v3``
13
+ (:doc:`roadmap`).
12
14
 
13
15
  System overview
14
16
  ---------------
@@ -73,6 +75,7 @@ Experiments forcingkit reads
73
75
  Each experiment serves from the time in the third column until the next one starts. Where two
74
76
  overlap, the switch is at the newer one's first time step, with one exception: GLBy0.08
75
77
  ``expt_93.0`` is kept to its last step on 2024-09-05 although ESPC-D-V02 begins on 2024-08-10.
78
+ The GLB experiments carry no tides; ESPC-D-V02 does (see `Tides`_).
76
79
  Times before 1994-01-01 are not served.
77
80
 
78
81
  Grids
@@ -105,6 +108,48 @@ experiments carry all five in one dataset, 3-hourly, plus ``tau`` (analysis time
105
108
  field: ``u3z``, ``v3z``, ``t3z`` and ``s3z`` are 3-hourly; ``ssh`` is hourly. All use the time
106
109
  unit "hours since 2000-01-01 00:00:00".
107
110
 
111
+ Tides
112
+ ~~~~~
113
+
114
+ The record changes character at 2024-09-05. Sea surface height sampled on 2026-10-06 at
115
+ 40.40 N, 72.48 W (open water south of Long Island) shows:
116
+
117
+ - **ESPC-D-V02** (hourly ``ssh``): a semidiurnal tide of about 0.43 m amplitude with a period
118
+ near 12.4 hours.
119
+ - **GLBy0.08** ``expt_93.0`` (3-hourly): variations within about 5 cm and no tidal period. The
120
+ GLB experiments are run without tidal forcing.
121
+
122
+ So a window before 2024-09-05 is a non-tidal ocean and a window after it is a tidal one, and a
123
+ window across the date changes from one to the other. The fetcher records which in the
124
+ ``tides`` attribute of the output, which the dispatcher carries into the store:
125
+
126
+ .. list-table::
127
+ :header-rows: 1
128
+ :widths: 20 80
129
+
130
+ * - ``tides``
131
+ - Window
132
+ * - ``"none"``
133
+ - Entirely before 2024-09-05 (GLB experiments).
134
+ * - ``"included"``
135
+ - Entirely from 2024-09-05 (ESPC-D-V02).
136
+ * - ``"mixed"``
137
+ - Spans 2024-09-05: non-tidal before the switch, tidal after it. The fetcher also logs a
138
+ warning.
139
+
140
+ Two consequences:
141
+
142
+ - **Do not add tides twice.** A tidal parent (``tides = "included"``) already carries the tide
143
+ in ``zeta`` and in the currents. A downstream model that adds its own tidal boundary forcing
144
+ (harmonic constituents from TPXO, FES or similar) should do so only when ``tides`` is
145
+ ``"none"``; adding it to a tidal parent doubles the tide. A ``"mixed"`` window has no single
146
+ correct treatment and is best split at 2024-09-05 or avoided.
147
+ - **Tidal sampling.** ESPC-D-V02's ``zeta`` is kept at its native hourly steps (step 3 below),
148
+ and the dispatcher's hourly resample leaves those values unchanged. Velocities, temperature
149
+ and salinity are published only 3-hourly, so the tidal currents are sampled four times per
150
+ semidiurnal cycle and linearly interpolated to hourly in between, which flattens their peaks
151
+ by up to about 30 percent.
152
+
108
153
  Where the data lives
109
154
  --------------------
110
155
 
@@ -127,9 +172,13 @@ How forcingkit reads it
127
172
  dataset's own convention (-180..180 or 0..360, detected per dataset); a box that crosses the
128
173
  dataset's seam is read in two parts. Only the five fields above are downloaded.
129
174
  3. **ESPC-D-V02 merge.** The five per-field datasets are read separately and merged on the
130
- times they share, so hourly ``zeta`` is reduced to the 3-hourly steps.
175
+ union of their times, so ``zeta`` keeps its hourly steps and ``u``, ``v``, ``temp`` and
176
+ ``salt`` are NaN at the two hours between their 3-hourly steps. Times outside the span that
177
+ all five fields share are dropped, so no field begins or ends on a step it has no data for.
131
178
  4. **Clean-up.** Longitudes are returned on 0..360 for every experiment. Times are sorted,
132
- duplicates dropped, and decoded to datetimes.
179
+ duplicates dropped, decoded to datetimes and rounded to the second (ESPC-D-V02 ``ssh``
180
+ decodes a few hundred nanoseconds off the hour, which would otherwise keep its steps apart
181
+ from those of the other fields in step 3).
133
182
  5. **Stitch.** Pieces from different experiments are concatenated in time. A piece on a
134
183
  different grid is interpolated (bilinear) onto the grid of the first piece. The step at a
135
184
  switch is read from both experiments; the newer one's copy is kept.
@@ -139,9 +188,12 @@ How forcingkit reads it
139
188
  data in a dataset's time range is not retried.
140
189
 
141
190
  Output is a Dataset with ``u``, ``v``, ``temp``, ``salt`` (``time, depth, lat, lon``) and
142
- ``zeta`` (``time, lat, lon``). Land and cells below the sea floor are NaN. The dispatcher then
143
- resamples to hourly by linear interpolation, casts to Float32, and writes the store with
144
- ``schema = "legacy"``.
191
+ ``zeta`` (``time, lat, lon``), and the ``tides`` attribute (see `Tides`_). Land and cells below
192
+ the sea floor are NaN. The time axis is 3-hourly for GLB experiments and hourly for ESPC-D-V02,
193
+ where only ``zeta`` has data at every step. The dispatcher then resamples to hourly by linear
194
+ interpolation, each variable from the steps at which it has data (so the 3-hourly fields are
195
+ bridged between their own steps and hourly ``zeta`` is kept as is), casts to Float32, and
196
+ writes the store with ``schema = "legacy"``.
145
197
 
146
198
  Known issues
147
199
  ~~~~~~~~~~~~
@@ -161,8 +213,8 @@ Known issues
161
213
  - **GLBv0.08 ``expt_93.0`` steps back.** Its time axis goes from 2018-06-21 09:00 to 06:00,
162
214
  repeating 06:00. The fetcher selects by position and drops the duplicate.
163
215
  - **Dead path for ``expt_53.X``.** ``GLBy0.08/expt_53.X`` answers HTTP 200 with an empty
164
- dataset description; the reanalysis lives under ``GLBv0.08``. forcingkit read the GLBy0.08
165
- path before 2026-10-06, so HYCOM served nothing before 2018-12-04 until then.
216
+ dataset description; the reanalysis lives under ``GLBv0.08``, which is the path forcingkit
217
+ reads.
166
218
  - **GLBy0.08 ``expt_93.0`` starts at 12:00.** Its first step is 2018-12-04 12:00, not the
167
219
  midnight that the catalogue date suggests; the switch from GLBv0.08 ``expt_93.0`` is at
168
220
  12:00.
@@ -186,27 +238,35 @@ these returned all five fields on 10 x 5 cells and 40 depths:
186
238
  * - 2018-12-03 18:00, 12 h
187
239
  - GLBv0.08 ``expt_93.0``
188
240
  - 5 steps, in 12 s.
189
- * - 2026-10-01 00:00, 6 h
241
+ * - 2026-10-01 00:00, 24 h
190
242
  - ESPC-D-V02 (five datasets)
191
- - 3 steps, in 216 s; ``zeta`` reduced from hourly to 3-hourly.
243
+ - 25 hourly steps, ``zeta`` at all 25 and the other fields at 9, in 547 s (one read
244
+ retried); ``tides = "included"``. After the dispatcher's resample, ``zeta`` was
245
+ unchanged (a tidal range of about 0.8 m at one cell) and the surface ``u`` had no
246
+ gaps.
192
247
  * - 2018-12-04 06:00, 12 h
193
248
  - GLBv0.08 then GLBy0.08 ``expt_93.0``
194
249
  - 5 steps, in 367 s; 12:00 read from both, kept once.
195
250
  * - 2024-09-04 18:00, 12 h
196
251
  - GLBy0.08 ``expt_93.0`` then ESPC-D-V02
197
- - 5 steps, in 334 s. ESPC-D-V02 marks a few more cells as land or below the sea floor
198
- than GLBy0.08 in this box.
252
+ - 9 steps (3-hourly to 2024-09-05 00:00, then hourly), 13 after the dispatcher's
253
+ resample, in 623 s (one read retried); ``tides = "mixed"`` and the crossing warning
254
+ logged. An earlier attempt failed after two timeouts on ``v3z``. ESPC-D-V02 marks a
255
+ few more cells as land or below the sea floor than GLBy0.08 in this box.
199
256
 
200
- Before the per-dataset retry was added, five earlier attempts at the two stitched windows each
201
- stopped on one read timeout (see `Known issues`_).
257
+ The times are for windows of 6 to 12 hours; multi-day windows have not been timed. Most of the
258
+ time goes to per-request latency on the server rather than to data volume, so a longer window
259
+ need not take proportionally longer, but plan for minutes per fetch and see `Known issues`_.
202
260
 
203
261
  Tests
204
262
  -----
205
263
 
206
264
  - ``tests/unit/test_hycom.py``: experiment resolution and window splitting at every switch,
207
265
  the URLs for GLBv0.08 and the ESPC-D-V02 fields, stitching across 2018-12-04 (regridding and
208
- the duplicated switch step) and across 2024-09-05 (merged ESPC-D-V02 fields), the all or
209
- nothing rule, the retry after a failed read (and none for an empty time range), both
266
+ the duplicated switch step) and across 2024-09-05 (merged ESPC-D-V02 fields), hourly ``zeta``
267
+ in the ESPC-D-V02 merge and through the dispatcher's hourly resample, trimming to the span
268
+ all fields share, rounding decoded times to the second, the ``tides`` attribute and the warning for a window across 2024-09-05, the
269
+ all or nothing rule, the retry after a failed read (and none for an empty time range), both
210
270
  longitude conventions and the seam, and the non-monotonic time axis. No network.
211
271
 
212
272
  .. code-block:: bash
@@ -17,17 +17,20 @@ and serves model-ready time series, with a record of where every value came from
17
17
  What it does
18
18
  ------------
19
19
 
20
- A regional ocean model needs two things from outside its own domain: the state of a larger
21
- "parent" ocean along its open boundaries, and the atmosphere above its surface. Both come from
20
+ A regional ocean model needs three things from outside its own domain: the state of a larger
21
+ "parent" ocean along its open boundaries, the atmosphere above its surface, and the rivers that
22
+ flow into it. The first two come from
22
23
  operational and archive systems (NOAA's `HRRR <https://rapidrefresh.noaa.gov/hrrr/>`__, the NOAA
23
24
  `Operational Forecast Systems <https://tidesandcurrents.noaa.gov/models.html>`__,
24
25
  `NECOFS <https://fvcom.smast.umassd.edu/?p=20>`__, `HYCOM <https://www.hycom.org/>`__) that
25
26
  differ in grid type, vertical coordinate, time convention, file layout and access protocol, and
26
- that change those details without notice.
27
+ that change those details without notice. River discharge comes from stream gauges, which
28
+ measure flow upstream of the head of tide rather than at the mouth.
27
29
 
28
30
  forcingkit is a FastAPI service that sits between those providers and model codes such as
29
31
  `Oceananigans.jl <https://github.com/CliMA/Oceananigans.jl>`__ (for example through
30
- `NumericalEarth <https://github.com/NumericalEarth/NumericalEarth.jl>`__). For a bounding box and a time window it:
32
+ `NumericalEarth <https://github.com/NumericalEarth/NumericalEarth.jl>`__). For a bounding box and a
33
+ time window it:
31
34
 
32
35
  1. picks the source (the "donor") best suited to the box, and falls back to the next one if the
33
36
  first cannot deliver;
@@ -60,6 +63,10 @@ What it serves
60
63
  - HRRR surface fields (10 m wind, 2 m temperature and humidity, pressure, radiation,
61
64
  precipitation) on a regular 0.03 degree grid, hourly, as schema ``hrrr-atm-v1``. See
62
65
  :doc:`atmospheric_forcing`.
66
+ * - ``POST /api/v1/rivers``
67
+ - Freshwater discharge at each listed river mouth in the box, hourly, from the tide-free
68
+ `USGS <https://waterdata.usgs.gov/>`__ stream gauges upstream (chosen per request) scaled
69
+ to the drainage area at the mouth, as schema ``river-v1``. See :doc:`rivers`.
63
70
  * - ``POST /api/v1/tide``
64
71
  - Observed water level at a `NOAA CO-OPS <https://tidesandcurrents.noaa.gov/>`__ station, for
65
72
  validation.
@@ -107,7 +114,7 @@ Parent-ocean donors at a glance
107
114
  - HYCOM GLBv0.08, GLBy0.08 and ESPC-D-V02, regular
108
115
  - 1/12 degree (about 9 km)
109
116
  - Global; 1994 to the present
110
- - Legacy output; last-resort fallback
117
+ - Legacy output; last-resort fallback; tidal only from 2024-09-05
111
118
 
112
119
  Quick start
113
120
  -----------
@@ -153,6 +160,7 @@ PyPI as ``forcingkit``.
153
160
  nyofs
154
161
  hycom
155
162
  atmospheric_forcing
163
+ rivers
156
164
 
157
165
  .. toctree::
158
166
  :maxdepth: 1
@@ -20,7 +20,8 @@ Regional Association of Coastal Ocean Observing Systems <https://neracoos.org/>`
20
20
  It is built on `FVCOM <https://fvcom.smast.umassd.edu/>`__, the unstructured-grid, finite-volume,
21
21
  free-surface, primitive-equation coastal ocean model introduced by `Chen, Liu and Beardsley
22
22
  (2003) <https://doi.org/10.1175/1520-0426(2003)020%3C0159:AUGFVT%3E2.0.CO;2>`__ and developed by
23
- Changsheng Chen's group at SMAST with Robert C. Beardsley at WHOI. forcingkit reads its **GOM7** configuration, which
23
+ Changsheng Chen's group at SMAST with Robert C. Beardsley at WHOI. forcingkit reads its **GOM7**
24
+ configuration, which
24
25
  spans the Gulf of Maine, Georges Bank, southern New England, Long Island Sound, New York Harbor
25
26
  and the Mid-Atlantic Bight shelf.
26
27
 
@@ -321,7 +321,9 @@ Institutions and data services
321
321
  <https://opendap.co-ops.nos.noaa.gov/thredds/catalog/catalog.html>`__
322
322
  - `NOAA NCEI <https://www.ncei.noaa.gov/>`__: `NYOFS files
323
323
  <https://www.ncei.noaa.gov/thredds/catalog/model-nyofs-files/catalog.html>`__
324
- - `NOAA Open Data Dissemination <https://www.noaa.gov/information-technology/open-data-dissemination>`__:
324
+ - `NOAA Open Data Dissemination
325
+ <https://www.noaa.gov/information-technology/open-data-dissemination>`__:
325
326
  `OFS on AWS <https://github.com/NOAA-Big-Data-Program/nodd-data-docs/blob/main/OFS/README.md>`__
326
- - `Stevens Institute of Technology, Davidson Laboratory <https://www.stevens.edu/davidson-laboratory>`__:
327
+ - `Stevens Institute of Technology, Davidson Laboratory
328
+ <https://www.stevens.edu/davidson-laboratory>`__:
327
329
  `NYHOPS <https://hudson.dl.stevens-tech.edu/maritimeforecast/>`__
@@ -0,0 +1,177 @@
1
+ River Discharge
2
+ ===============
3
+
4
+ ``POST /api/v1/rivers`` delivers hourly freshwater discharge at the mouth of every listed river
5
+ in a box (or at mouths passed with the request), for point or surface sources in an ocean model. A nested regional model otherwise
6
+ receives river water only through its open boundaries and initial state, so a river that enters
7
+ inside the domain is missing as a source and its freshwater decays over the run.
8
+
9
+ Method
10
+ ------
11
+
12
+ The method is common practice for gauge-driven river forcing. The NOAA Operational Forecast
13
+ Systems map US Geological Survey real-time gauges onto model cells with a per-river scale factor
14
+ and fall back to climatology. Regional estuary models scale gauged flow to ungauged area by the
15
+ ratio of drainage areas.
16
+
17
+ 1. **Gauges, derived for every request.** Only a river's mouth is listed. From its NHDPlus
18
+ outlet flowline, forcingkit asks the USGS `NLDI <https://waterdata.usgs.gov/blog/nldi-intro/>`__
19
+ for every USGS site upstream, then keeps a site if:
20
+
21
+ - USGS classes it as a stream site (``ST``). Tidal streams (``ST-TS``) and estuaries (``ES``)
22
+ are left out, because their flow reverses with the tide; on the Connecticut River that rules
23
+ out Hartford and Middle Haddam;
24
+ - it has a drainage area, and a discharge record (15-minute or daily) covering the window;
25
+ - it drains at least ``min_share`` of the mouth's area (default 0.01), so headwater brooks
26
+ drop out;
27
+ - no other kept site lies downstream of it, which leaves the lowest gauge on each branch.
28
+
29
+ The kept gauges then measure disjoint parts of the basin. The selection follows the window: a
30
+ gauge that opened in 2010 is used from 2010 on, and one that closed is dropped from then on.
31
+ 2. **Discharge at the mouth.** The sum of the river's gauges, scaled by the drainage area at the
32
+ mouth over the gauged area:
33
+
34
+ .. math::
35
+
36
+ Q_\text{mouth} = \frac{A_\text{mouth}}{\sum_k A_k} \sum_k Q_k
37
+
38
+ Gauge areas come from the USGS monitoring-location records; the mouth's area is that of the
39
+ outlet's NLDI basin.
40
+ 3. **Time base.** The 15-minute record from the USGS Water Data APIs (``continuous``), averaged to
41
+ hourly values centred on the hour. The store holds ``hours + 1`` records, from ``start_time``
42
+ to ``start_time + hours``.
43
+ 4. **Gaps.** A gap of up to ``max_gap_hours`` (default 6) with values on both sides is
44
+ interpolated. A longer gap takes that day's mean from the ``daily`` record. A day with neither
45
+ is an error, unless ``allow_climatology`` is set; then it takes the USGS day-of-year median of
46
+ daily flow. Every filled hour is flagged in ``fill`` (0 measured, 1 interpolated, 2 daily
47
+ mean, 3 climatology median), and the store's ``provenance`` attribute counts them per river.
48
+ 5. **Water properties.** Salinity is zero. Water temperature (``temperature``) comes from the first
49
+ gauge on the river that measures it (USGS parameter 00010), and is NaN where none does.
50
+ 6. **Provisional data.** USGS values are provisional until approved, typically months later, and
51
+ provisional values are revised. A store containing any provisional value is marked
52
+ ``provisional = True`` and is served from the cache for one day only; after that a request
53
+ rebuilds it.
54
+ 7. **Travel time.** None by default. A mouth entry may set ``lag_hours``; gauge values are then
55
+ taken that many hours earlier.
56
+
57
+ Mouths
58
+ ------
59
+
60
+ Mouths are listed in ``forcingkit/data/rivers/*.json`` and in any ``*.json`` file in the directory
61
+ named by ``FORCINGKIT_RIVER_MOUTHS_DIR``; a later entry with the same name replaces an earlier one.
62
+ A request may instead pass its own ``mouths``. Adding a river needs a mouth entry and no code or
63
+ release:
64
+
65
+ .. code-block:: json
66
+
67
+ {"mouths": [
68
+ {"name": "Connecticut", "lon": -72.3305, "lat": 41.2726, "comid": 7703046},
69
+ {"name": "Saugatuck", "lon": -73.36, "lat": 41.10, "trace_from": "USGS-01208990"}
70
+ ]}
71
+
72
+ - ``name``, ``lon``, ``lat`` (required): the point places the mouth for the bbox test.
73
+ - ``comid``: the NHDPlus outlet flowline. Otherwise ``trace_from`` names any NLDI feature on the
74
+ river (a USGS site such as ``USGS-01184000``) and the outlet is the end of its downstream trace.
75
+ Failing both, the point is snapped to the nearest flowline, with a warning: at an estuary the
76
+ nearest flowline can be a short coastal one rather than the river.
77
+ - ``include`` and ``exclude``: USGS site numbers to pin or rule out where judgement beats the rule
78
+ (for example a gauge below a dam whose releases should not drive the forcing). A pinned set that
79
+ drains more than the mouth's area is refused as double counting.
80
+ - ``lag_hours``: travel time from the gauges to the mouth, applied to the gauge window.
81
+
82
+ The id of a store includes the mouth entries and ``min_share``, but not the derived gauges, which
83
+ are recorded in the store instead. NLDI facts about a mouth (its outlet, basin area, upstream sites
84
+ and which sites lie downstream of which) are cached for 30 days under ``usgs/derivations`` in the
85
+ cache directory, so only the first request for a mouth takes long (about a minute for the
86
+ Connecticut, whose basin holds 1,375 USGS sites).
87
+
88
+ The packaged mouths are the rivers into Long Island Sound. For a request over the Sound in April
89
+ 2026 the derivation chose:
90
+
91
+ .. list-table::
92
+ :header-rows: 1
93
+ :widths: 16 46 13 13 12
94
+
95
+ * - River
96
+ - USGS gauges kept
97
+ - Gauged (mi²)
98
+ - Mouth (mi²)
99
+ - Scale
100
+ * - Housatonic
101
+ - 01205500 at Stevenson; 01208500 Naugatuck at Beacon Falls
102
+ - 1,804
103
+ - 1,944
104
+ - 1.08
105
+ * - Quinnipiac
106
+ - 01196500 at Wallingford; 01196620 Mill River near Hamden; 01196561 Muddy River near East
107
+ Wallingford
108
+ - 148
109
+ - 204
110
+ - 1.37
111
+ * - Connecticut
112
+ - 01184000 at Thompsonville; 01189995 Farmington at Tariffville
113
+ - 10,237
114
+ - 11,242
115
+ - 1.10
116
+ * - Thames
117
+ - 01127000 Quinebaug at Jewett City; 011230695 Shetucket at Taftville; 01127500 Yantic at
118
+ Yantic
119
+ - 1,314
120
+ - 1,470
121
+ - 1.12
122
+
123
+ The Quinnipiac's outlet basin is New Haven Harbor's, so its scaled flow also stands for the
124
+ harbour's other streams. The Housatonic at Stevenson is below a hydroelectric dam, so its hourly
125
+ flow follows the dam's releases. The Bronx River gauge (01302020) has no record before the 2000s,
126
+ so a request for an earlier window fails unless ``skip_ungauged`` is set, which leaves such a
127
+ river out and lists it in the store's ``skipped_rivers``.
128
+
129
+ Request and store
130
+ -----------------
131
+
132
+ .. code-block:: bash
133
+
134
+ curl -s -X POST localhost:9598/api/v1/rivers -H 'Content-Type: application/json' -d '{
135
+ "bbox": {"min_lon": -73.855, "min_lat": 40.75, "max_lon": -71.9, "max_lat": 41.45},
136
+ "start_time": "2026-04-02T00:00:00Z", "hours": 48}'
137
+
138
+ Optional fields: ``mouths``, ``min_share`` (default 0.01), ``max_gap_hours`` (default 6),
139
+ ``allow_climatology`` and ``skip_ungauged`` (both default false), and ``cache_bust``.
140
+
141
+ For that request (four rivers, 49 records), the mean discharges over the 48 hours were 1,553 m³/s
142
+ for the Connecticut, 147 for the Housatonic, 148 for the Thames and 13 for the Quinnipiac.
143
+
144
+ The store (schema ``river-v1``) has a ``river`` dimension:
145
+
146
+ - ``discharge`` (time, river): m³ s⁻¹, ``water_volume_transport_in_river_channel``.
147
+ - ``temperature`` (time, river): °C.
148
+ - ``fill`` (time, river): the flags above.
149
+ - ``mouth_lon``, ``mouth_lat``, ``scale_factor``, ``salinity`` (river).
150
+ - Attributes: ``river_names``; ``provenance`` (JSON: per river, the gauges kept, the areas, the
151
+ scale factor, the outlet, and the dropped candidates counted by reason); ``derivation_dropped``
152
+ (JSON: every dropped candidate with its reason); ``skipped_rivers``; ``provisional``; and
153
+ ``built_utc``.
154
+
155
+ A JSON sidecar next to the store repeats the request, the mouths and the derivation. ``GET /api/v1/rivers/download/{zarr_id}``
156
+ returns the store as a zip.
157
+
158
+ Credentials
159
+ -----------
160
+
161
+ None are required. A free USGS Water Data API key (``USGS_API_KEY``, from
162
+ `api.waterdata.usgs.gov/signup <https://api.waterdata.usgs.gov/signup/>`__) raises the hourly rate
163
+ limit; it is sent in the ``X-Api-Key`` header. forcingkit uses the modernized APIs, not the
164
+ legacy ``waterservices.usgs.gov``, which USGS plans to retire in 2027.
165
+
166
+ Limits
167
+ ------
168
+
169
+ - Mouths are not found automatically within a box: each river needs a mouth entry. Finding
170
+ coastal outlets from NHDPlus would remove that step.
171
+ - The selection rule cannot tell a regulated gauge from a natural one; use ``exclude`` where dam
172
+ operations should not reach the forcing.
173
+ - Gauges measure surface runoff. Groundwater discharge (most of Long Island's freshwater input) is
174
+ not represented.
175
+ - A model that applies discharge as a surface freshwater flux on fixed levels dilutes salinity
176
+ but adds no volume. Whether the volume is added depends on the model's vertical coordinate, not
177
+ on this store.