aihydro-data 0.2.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. aihydro_data-0.2.0/LICENSE +19 -0
  2. aihydro_data-0.2.0/PKG-INFO +566 -0
  3. aihydro_data-0.2.0/README.md +481 -0
  4. aihydro_data-0.2.0/aihydro_data/__init__.py +150 -0
  5. aihydro_data-0.2.0/aihydro_data/_pipeline.py +843 -0
  6. aihydro_data-0.2.0/aihydro_data/_version.py +2 -0
  7. aihydro_data-0.2.0/aihydro_data/cache/__init__.py +321 -0
  8. aihydro_data-0.2.0/aihydro_data/cache/manifest.py +162 -0
  9. aihydro_data-0.2.0/aihydro_data/cli.py +74 -0
  10. aihydro_data-0.2.0/aihydro_data/contracts.py +250 -0
  11. aihydro_data-0.2.0/aihydro_data/exceptions.py +89 -0
  12. aihydro_data-0.2.0/aihydro_data/geometry/__init__.py +161 -0
  13. aihydro_data-0.2.0/aihydro_data/geometry/batch.py +92 -0
  14. aihydro_data-0.2.0/aihydro_data/geometry/measures.py +58 -0
  15. aihydro_data-0.2.0/aihydro_data/help_topics/__init__.py +20 -0
  16. aihydro_data-0.2.0/aihydro_data/help_topics/auth.md +72 -0
  17. aihydro_data-0.2.0/aihydro_data/help_topics/batch.md +64 -0
  18. aihydro_data-0.2.0/aihydro_data/help_topics/caching.md +57 -0
  19. aihydro_data-0.2.0/aihydro_data/help_topics/deprecations.md +45 -0
  20. aihydro_data-0.2.0/aihydro_data/help_topics/errors.md +51 -0
  21. aihydro_data-0.2.0/aihydro_data/help_topics/fallback.md +57 -0
  22. aihydro_data-0.2.0/aihydro_data/help_topics/first_fetch.md +50 -0
  23. aihydro_data-0.2.0/aihydro_data/help_topics/products.md +94 -0
  24. aihydro_data-0.2.0/aihydro_data/mcp/__init__.py +815 -0
  25. aihydro_data-0.2.0/aihydro_data/products/__init__.py +92 -0
  26. aihydro_data-0.2.0/aihydro_data/products/dem.py +248 -0
  27. aihydro_data-0.2.0/aihydro_data/products/et.py +305 -0
  28. aihydro_data-0.2.0/aihydro_data/products/landcover.py +222 -0
  29. aihydro_data-0.2.0/aihydro_data/products/optical.py +313 -0
  30. aihydro_data-0.2.0/aihydro_data/products/precipitation.py +331 -0
  31. aihydro_data-0.2.0/aihydro_data/products/soil.py +124 -0
  32. aihydro_data-0.2.0/aihydro_data/products/soil_moisture.py +73 -0
  33. aihydro_data-0.2.0/aihydro_data/products/streamflow.py +256 -0
  34. aihydro_data-0.2.0/aihydro_data/products/temperature.py +332 -0
  35. aihydro_data-0.2.0/aihydro_data/products/vegetation.py +155 -0
  36. aihydro_data-0.2.0/aihydro_data/routing/__init__.py +75 -0
  37. aihydro_data-0.2.0/aihydro_data/routing/detect.py +57 -0
  38. aihydro_data-0.2.0/aihydro_data/routing/policy.py +149 -0
  39. aihydro_data-0.2.0/aihydro_data/routing/regions.py +90 -0
  40. aihydro_data-0.2.0/aihydro_data/sampling/__init__.py +29 -0
  41. aihydro_data-0.2.0/aihydro_data/sampling/catchment.py +321 -0
  42. aihydro_data-0.2.0/aihydro_data/sampling/chunked.py +200 -0
  43. aihydro_data-0.2.0/aihydro_data/skills/__init__.py +15 -0
  44. aihydro_data-0.2.0/aihydro_data/sources/__init__.py +11 -0
  45. aihydro_data-0.2.0/aihydro_data/sources/_common.py +55 -0
  46. aihydro_data-0.2.0/aihydro_data/sources/_gee_vendored/__init__.py +23 -0
  47. aihydro_data-0.2.0/aihydro_data/sources/_gee_vendored/auth.py +365 -0
  48. aihydro_data-0.2.0/aihydro_data/sources/_gee_vendored/cli.py +115 -0
  49. aihydro_data-0.2.0/aihydro_data/sources/_gee_vendored/contracts.py +259 -0
  50. aihydro_data-0.2.0/aihydro_data/sources/_gee_vendored/map_layers.py +158 -0
  51. aihydro_data-0.2.0/aihydro_data/sources/_gee_vendored/presets.py +138 -0
  52. aihydro_data-0.2.0/aihydro_data/sources/_gee_vendored/timeseries.py +143 -0
  53. aihydro_data-0.2.0/aihydro_data/sources/_retry.py +145 -0
  54. aihydro_data-0.2.0/aihydro_data/sources/base.py +102 -0
  55. aihydro_data-0.2.0/aihydro_data/sources/cds_glofas.py +448 -0
  56. aihydro_data-0.2.0/aihydro_data/sources/direct_api.py +546 -0
  57. aihydro_data-0.2.0/aihydro_data/sources/gee/__init__.py +398 -0
  58. aihydro_data-0.2.0/aihydro_data/sources/gee/_composite.py +327 -0
  59. aihydro_data-0.2.0/aihydro_data/sources/gee/_download.py +315 -0
  60. aihydro_data-0.2.0/aihydro_data/sources/geoglows_retro.py +312 -0
  61. aihydro_data-0.2.0/aihydro_data/sources/hyriver.py +402 -0
  62. aihydro_data-0.2.0/aihydro_data/sources/openmeteo_flood.py +201 -0
  63. aihydro_data-0.2.0/aihydro_data/sources/stac.py +383 -0
  64. aihydro_data-0.2.0/aihydro_data/transforms/__init__.py +53 -0
  65. aihydro_data-0.2.0/aihydro_data/transforms/cloud_mask.py +191 -0
  66. aihydro_data-0.2.0/aihydro_data/transforms/indices.py +524 -0
  67. aihydro_data-0.2.0/aihydro_data/viz/__init__.py +108 -0
  68. aihydro_data-0.2.0/aihydro_data/viz/_common.py +91 -0
  69. aihydro_data-0.2.0/aihydro_data/viz/_compare.py +200 -0
  70. aihydro_data-0.2.0/aihydro_data/viz/auto.py +198 -0
  71. aihydro_data-0.2.0/aihydro_data/viz/hydrology.py +327 -0
  72. aihydro_data-0.2.0/aihydro_data/viz/spatial.py +159 -0
  73. aihydro_data-0.2.0/aihydro_data.egg-info/PKG-INFO +566 -0
  74. aihydro_data-0.2.0/aihydro_data.egg-info/SOURCES.txt +98 -0
  75. aihydro_data-0.2.0/aihydro_data.egg-info/dependency_links.txt +1 -0
  76. aihydro_data-0.2.0/aihydro_data.egg-info/entry_points.txt +8 -0
  77. aihydro_data-0.2.0/aihydro_data.egg-info/requires.txt +72 -0
  78. aihydro_data-0.2.0/aihydro_data.egg-info/top_level.txt +1 -0
  79. aihydro_data-0.2.0/pyproject.toml +226 -0
  80. aihydro_data-0.2.0/setup.cfg +4 -0
  81. aihydro_data-0.2.0/tests/test_auto_batch.py +129 -0
  82. aihydro_data-0.2.0/tests/test_cache_and_batch.py +365 -0
  83. aihydro_data-0.2.0/tests/test_indices.py +408 -0
  84. aihydro_data-0.2.0/tests/test_layering.py +73 -0
  85. aihydro_data-0.2.0/tests/test_live_backends.py +284 -0
  86. aihydro_data-0.2.0/tests/test_live_sweep.py +228 -0
  87. aihydro_data-0.2.0/tests/test_mcp_tools.py +385 -0
  88. aihydro_data-0.2.0/tests/test_open_meteo.py +281 -0
  89. aihydro_data-0.2.0/tests/test_optical_products.py +242 -0
  90. aihydro_data-0.2.0/tests/test_phase1_fixes.py +323 -0
  91. aihydro_data-0.2.0/tests/test_phase2_integrity.py +193 -0
  92. aihydro_data-0.2.0/tests/test_phase3_products.py +253 -0
  93. aihydro_data-0.2.0/tests/test_phase3_routing_cache.py +168 -0
  94. aihydro_data-0.2.0/tests/test_phase4_products.py +225 -0
  95. aihydro_data-0.2.0/tests/test_phase_d_robustness.py +142 -0
  96. aihydro_data-0.2.0/tests/test_routing.py +246 -0
  97. aihydro_data-0.2.0/tests/test_sampling.py +357 -0
  98. aihydro_data-0.2.0/tests/test_scaffold.py +191 -0
  99. aihydro_data-0.2.0/tests/test_stac.py +125 -0
  100. aihydro_data-0.2.0/tests/test_viz.py +266 -0
@@ -0,0 +1,19 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Copyright 2026 Mohammad Galib (mgalib@purdue.edu) and AI-Hydro contributors.
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
18
+
19
+ For the full Apache-2.0 text, see https://www.apache.org/licenses/LICENSE-2.0.txt
@@ -0,0 +1,566 @@
1
+ Metadata-Version: 2.4
2
+ Name: aihydro-data
3
+ Version: 0.2.0
4
+ Summary: Global hydrology dataverse — multi-source, region-aware data fetch (GEE + STAC + HyRiver) for AI agents and humans
5
+ Author-email: Mohammad Galib <mgalib@purdue.edu>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/AI-Hydro/aihydro-data
8
+ Project-URL: Documentation, https://github.com/AI-Hydro/AI-Hydro/wiki
9
+ Project-URL: Repository, https://github.com/AI-Hydro/aihydro-data
10
+ Project-URL: Issues, https://github.com/AI-Hydro/aihydro-data/issues
11
+ Keywords: hydrology,geospatial,earth-engine,STAC,global,data-fetch,MCP,agentic,CAMELS
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Topic :: Scientific/Engineering :: Hydrology
15
+ Classifier: Topic :: Scientific/Engineering :: GIS
16
+ Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: aihydro-core<0.2,>=0.1.0
25
+ Requires-Dist: numpy>=1.24.0
26
+ Requires-Dist: pandas>=2.0.0
27
+ Requires-Dist: pydantic>=2.0.0
28
+ Requires-Dist: requests>=2.31.0
29
+ Requires-Dist: python-dateutil>=2.8.2
30
+ Requires-Dist: shapely>=2.0.0
31
+ Requires-Dist: geopandas>=0.13.0
32
+ Requires-Dist: pyproj>=3.6.0
33
+ Provides-Extra: gee
34
+ Requires-Dist: earthengine-api>=0.1.390; extra == "gee"
35
+ Provides-Extra: stac
36
+ Requires-Dist: pystac-client>=0.7.0; extra == "stac"
37
+ Requires-Dist: stackstac>=0.5.0; extra == "stac"
38
+ Requires-Dist: planetary-computer>=1.0.0; extra == "stac"
39
+ Requires-Dist: rioxarray>=0.15.0; extra == "stac"
40
+ Requires-Dist: rasterio>=1.3.0; extra == "stac"
41
+ Requires-Dist: xarray>=2023.0.0; extra == "stac"
42
+ Provides-Extra: hyriver
43
+ Requires-Dist: pynhd>=0.16.0; extra == "hyriver"
44
+ Requires-Dist: pygeohydro>=0.16.0; extra == "hyriver"
45
+ Requires-Dist: pygridmet>=0.15.0; extra == "hyriver"
46
+ Requires-Dist: py3dep>=0.15.0; extra == "hyriver"
47
+ Requires-Dist: hydrofunctions>=0.2.0; extra == "hyriver"
48
+ Requires-Dist: rioxarray>=0.15.0; extra == "hyriver"
49
+ Requires-Dist: xarray>=2023.0.0; extra == "hyriver"
50
+ Provides-Extra: direct
51
+ Requires-Dist: hydrofunctions>=0.2.0; extra == "direct"
52
+ Provides-Extra: opendap
53
+ Requires-Dist: xarray>=2023.0.0; extra == "opendap"
54
+ Requires-Dist: netCDF4>=1.6.0; extra == "opendap"
55
+ Provides-Extra: glofas
56
+ Requires-Dist: cdsapi>=0.7.0; extra == "glofas"
57
+ Requires-Dist: xarray>=2023.0.0; extra == "glofas"
58
+ Requires-Dist: netCDF4>=1.6.0; extra == "glofas"
59
+ Requires-Dist: pyproj>=3.4.0; extra == "glofas"
60
+ Requires-Dist: shapely>=2.0.0; extra == "glofas"
61
+ Provides-Extra: geoglows
62
+ Requires-Dist: geoglows>=2.2; extra == "geoglows"
63
+ Requires-Dist: s3fs>=2023.0.0; extra == "geoglows"
64
+ Requires-Dist: zarr>=2.14.0; extra == "geoglows"
65
+ Requires-Dist: pyproj>=3.4.0; extra == "geoglows"
66
+ Requires-Dist: shapely>=2.0.0; extra == "geoglows"
67
+ Provides-Extra: cache
68
+ Requires-Dist: diskcache>=5.6.0; extra == "cache"
69
+ Provides-Extra: mcp
70
+ Requires-Dist: fastmcp>=2.0.0; extra == "mcp"
71
+ Provides-Extra: viz
72
+ Requires-Dist: matplotlib>=3.7.0; extra == "viz"
73
+ Requires-Dist: folium>=0.15.0; extra == "viz"
74
+ Provides-Extra: all
75
+ Requires-Dist: aihydro-data[cache,direct,gee,geoglows,glofas,hyriver,mcp,opendap,stac,viz]; extra == "all"
76
+ Provides-Extra: dev
77
+ Requires-Dist: pytest>=7.4.0; extra == "dev"
78
+ Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
79
+ Requires-Dist: pytest-mock>=3.12.0; extra == "dev"
80
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
81
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
82
+ Requires-Dist: build>=1.0.0; extra == "dev"
83
+ Requires-Dist: twine>=4.0.0; extra == "dev"
84
+ Dynamic: license-file
85
+
86
+ # aihydro-data
87
+
88
+ **Global hydrology dataverse — fetch any variable, anywhere, from the best available source.**
89
+
90
+ `aihydro-data` is a variable-centric, multi-source, region-aware Python library that unifies Google Earth Engine (GEE), HyRiver, and direct HTTP APIs behind a single `fetch()` call. It is the data backbone of the AI-Hydro toolchain.
91
+
92
+ ```python
93
+ from aihydro_data import fetch
94
+
95
+ # Auto mode — router picks the best product for the geometry's region
96
+ result = fetch(
97
+ variable="precipitation",
98
+ geometry=watershed_gdf, # GeoDataFrame, GeoJSON, shapely, (lat, lon), or bbox
99
+ start="2010-01-01",
100
+ end="2020-12-31",
101
+ )
102
+ print(result.product) # "CHIRPS" (auto-selected)
103
+ print(result.source) # "gee"
104
+ print(result.citation) # full bibliographic reference
105
+ result.data # pd.DataFrame or xr.DataArray
106
+ result.next_steps # agent-facing hints: what to do with this data
107
+ ```
108
+
109
+ [![PyPI](https://img.shields.io/pypi/v/aihydro-data)](https://pypi.org/project/aihydro-data/)
110
+ [![Python](https://img.shields.io/pypi/pyversions/aihydro-data)](https://pypi.org/project/aihydro-data/)
111
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)
112
+
113
+ ---
114
+
115
+ ## Table of Contents
116
+
117
+ - [Why](#why)
118
+ - [Install](#install)
119
+ - [Quick Start](#quick-start)
120
+ - [Products (32 total)](#products)
121
+ - [Routing System](#routing-system)
122
+ - [Auth Setup](#auth-setup)
123
+ - [MCP Tools](#mcp-tools)
124
+ - [Architecture](#architecture)
125
+ - [Contributing](#contributing)
126
+
127
+ ---
128
+
129
+ ## Why
130
+
131
+ Before `aihydro-data`, fetching hydrology data meant hard-coding a single CONUS-only source per variable (GridMET for precip, NWIS for streamflow, POLARIS for soil). Anything outside CONUS meant writing a new fetcher from scratch.
132
+
133
+ `aihydro-data` turns this into a single declarative routing problem: **one call, every region, every variable, documented fallbacks**.
134
+
135
+ | Variable | CONUS (primary) | Global (primary) | Fallback chain |
136
+ |---|---|---|---|
137
+ | Precipitation | GridMET | CHIRPS (GEE) | IMERG, ERA5-Land, CHIRPS-IRI* |
138
+ | Tmax / Tmin | GridMET, Daymet | ERA5-Land | — |
139
+ | ET (actual) | MOD16 | MOD16 | TerraClimate, ERA5-Land |
140
+ | ET (potential) | GridMET | ERA5-Land | MOD16 |
141
+ | Soil moisture | SMAP | SMAP | — |
142
+ | Land cover | NLCD | ESA WorldCover | Dynamic World |
143
+ | Soil properties | POLARIS | SoilGrids | — |
144
+ | DEM | 3DEP (10 m) | Copernicus GLO-30 | SRTM, MERIT-DEM |
145
+ | Streamflow | USGS NWIS | — | — |
146
+ | NDVI | MODIS (250 m) | MODIS (250 m) | Sentinel-2 (10 m) |
147
+ | LAI | MODIS | MODIS | — |
148
+
149
+ \* `CHIRPS_IRI` — auth-free OPeNDAP fallback, no GEE account required.
150
+
151
+ ---
152
+
153
+ ## Install
154
+
155
+ ```bash
156
+ # Full install (all backends)
157
+ pip install aihydro-data[all]
158
+
159
+ # Per-backend
160
+ pip install aihydro-data[gee] # Google Earth Engine (20 products)
161
+ pip install aihydro-data[hyriver] # CONUS via HyRiver (10 products)
162
+ pip install aihydro-data[stac] # STAC catalogues (Planetary Computer)
163
+ pip install aihydro-data[opendap] # IRI OPeNDAP (CHIRPS auth-free fallback)
164
+ ```
165
+
166
+ > **Python**: 3.10+ &nbsp;|&nbsp; **GEE auth**: required for 20 GEE products (see [Auth Setup](#auth-setup))
167
+
168
+ ---
169
+
170
+ ## Quick Start
171
+
172
+ ### 1. Auto mode — global watershed
173
+
174
+ ```python
175
+ from aihydro_data import fetch
176
+ import geopandas as gpd
177
+
178
+ # Any geometry: GeoDataFrame, shapely Point/Polygon, (lat, lon) tuple, or bbox
179
+ gdf = gpd.read_file("ganges_basin.geojson")
180
+
181
+ result = fetch("precipitation", gdf, "2015-01-01", "2015-12-31")
182
+ print(result.product) # "CHIRPS" (auto-selected for South Asia)
183
+ print(result.data)
184
+ # date precipitation
185
+ # 0 2015-01-01 1.23
186
+ # 1 2015-01-02 0.00
187
+ # ...
188
+ ```
189
+
190
+ ### 2. Auto mode — CONUS watershed
191
+
192
+ ```python
193
+ from aihydro_data import fetch
194
+ from shapely.geometry import Point
195
+
196
+ # Kansas City — routes to GridMET (no GEE auth needed)
197
+ result = fetch("precipitation", Point(-94.5, 39.1), "2020-01-01", "2020-12-31")
198
+ print(result.product) # "GRIDMET_PRECIP"
199
+ print(result.source) # "hyriver"
200
+ ```
201
+
202
+ ### 3. Manual mode — pin a specific product
203
+
204
+ ```python
205
+ result = fetch(
206
+ "et",
207
+ gdf,
208
+ "2010-01-01", "2020-12-31",
209
+ mode="manual",
210
+ product="MOD16_ET",
211
+ )
212
+ print(result.units) # "mm/month"
213
+ print(result.citation) # Running et al. 2019 ...
214
+ print(result.bibtex) # @dataset{...}
215
+ ```
216
+
217
+ ### 4. Plot the result (v0.1.2+)
218
+
219
+ ```python
220
+ result = fetch("streamflow", "03245500", "2010-01-01", "2020-12-31")
221
+
222
+ # Auto-dispatched plot — picks line/bar/imshow based on data shape
223
+ result.plot(logy=True)
224
+
225
+ # Interactive folium map preview
226
+ result.map()
227
+
228
+ # Multi-source comparison in one call
229
+ from aihydro_data.viz import compare
230
+ fig = compare(
231
+ ["GRIDMET_PRECIP", "CHIRPS", "ERA5L_PRECIP"],
232
+ watershed_gdf, "2015-01-01", "2020-12-31",
233
+ plots=["timeseries", "climatology", "scatter", "double_mass"],
234
+ )
235
+
236
+ # Research-grade hydrology plots
237
+ from aihydro_data.viz import flow_duration_curve, climatology, budyko
238
+ flow_duration_curve(streamflow_result)
239
+ budyko(precip_result, pet_result, et_result, label="My catchment")
240
+ ```
241
+
242
+ Install with `pip install aihydro-data[viz]` (matplotlib + folium).
243
+
244
+ ### 5. Discover available products
245
+
246
+ ```python
247
+ from aihydro_data import list_products, get_product
248
+
249
+ # List all precipitation products
250
+ for p in list_products(variable="precipitation"):
251
+ print(f"{p.id:20s} {p.coverage} {p.resolution_m}m {p.source}")
252
+
253
+ # Inspect one product's full spec
254
+ spec = get_product("CHIRPS")
255
+ print(spec.common_pitfalls)
256
+ print(spec.examples)
257
+ ```
258
+
259
+ ### 6. Validate before fetching
260
+
261
+ ```python
262
+ from aihydro_data.mcp import data_validate_request
263
+
264
+ check = data_validate_request(
265
+ variable="et",
266
+ geometry={"type": "Point", "coordinates": [-94.5, 39.1]},
267
+ start="1990-01-01",
268
+ end="2000-12-31",
269
+ )
270
+ # {"ok": False, "issues": [{"code": "DATE_OUT_OF_RANGE",
271
+ # "product": "MOD16_ET", "message": "MOD16 starts 2000-01-01."}], ...}
272
+ ```
273
+
274
+ ---
275
+
276
+ ## Products
277
+
278
+ 32 products across 13 variables, live-tested against real backends (v0.2.0).
279
+
280
+ ### Precipitation (6 products)
281
+
282
+ | ID | Source | Coverage | Resolution | Timestep | Notes |
283
+ |---|---|---|---|---|---|
284
+ | `CHIRPS` | GEE | Global | 5 km | Daily | Primary global; GEE auth required |
285
+ | `IMERG_PRECIP` | GEE | Global | 11 km | Daily | NASA GPM V07; GEE auth required |
286
+ | `ERA5L_PRECIP` | GEE | Global | 11 km | Daily | Reanalysis; 1950–present |
287
+ | `GRIDMET_PRECIP` | HyRiver | CONUS | 4 km | Daily | Primary CONUS; no auth |
288
+ | `DAYMET_PRECIP` | HyRiver | N. America | 1 km | Daily | High-res; no auth |
289
+ | `CHIRPS_IRI` | Direct API | Global | 5 km | Daily | **Auth-free fallback** via IRI OPeNDAP |
290
+
291
+ ### Temperature (7 products)
292
+
293
+ | ID | Source | Coverage | Resolution | Timestep |
294
+ |---|---|---|---|---|
295
+ | `ERA5L_TMAX` | GEE | Global | 11 km | Daily |
296
+ | `ERA5L_TMIN` | GEE | Global | 11 km | Daily |
297
+ | `ERA5L_TMEAN` | GEE | Global | 11 km | Daily |
298
+ | `GRIDMET_TMAX` | HyRiver | CONUS | 4 km | Daily |
299
+ | `GRIDMET_TMIN` | HyRiver | CONUS | 4 km | Daily |
300
+ | `DAYMET_TMAX` | HyRiver | N. America | 1 km | Daily |
301
+ | `DAYMET_TMIN` | HyRiver | N. America | 1 km | Daily |
302
+
303
+ ### Evapotranspiration (5 products)
304
+
305
+ | ID | Variable | Source | Coverage | Resolution | Timestep |
306
+ |---|---|---|---|---|---|
307
+ | `MOD16_ET` | ET (actual) | GEE | Global | 500 m | 8-day → monthly |
308
+ | `TERRACLIMATE_AET` | ET (actual) | GEE | Global | 4.6 km | Monthly |
309
+ | `MOD16_PET` | PET | GEE | Global | 500 m | 8-day → monthly |
310
+ | `ERA5L_PET` | PET | GEE | Global | 11 km | Daily |
311
+ | `GRIDMET_PET` | PET | HyRiver | CONUS | 4 km | Daily |
312
+
313
+ ### DEM (4 products)
314
+
315
+ | ID | Source | Coverage | Resolution | Notes |
316
+ |---|---|---|---|---|
317
+ | `GLO30` | GEE | Global | 30 m | Copernicus; primary global DEM |
318
+ | `SRTM` | GEE | 60°S–60°N | 30 m | NASA SRTM v3 |
319
+ | `MERIT_DEM` | GEE | Global | 90 m | Hydrologically conditioned |
320
+ | `DEM3DEP_10M` | HyRiver | CONUS | 10 m | USGS 3DEP; highest resolution |
321
+
322
+ ### Soil Moisture (1 product)
323
+
324
+ | ID | Source | Coverage | Resolution | Timestep |
325
+ |---|---|---|---|---|
326
+ | `SMAP_SM` | GEE | Global | 9 km | Daily (2015–present) |
327
+
328
+ ### Land Cover (3 products)
329
+
330
+ | ID | Source | Coverage | Notes |
331
+ |---|---|---|---|
332
+ | `NLCD` | HyRiver | CONUS | NLCD 2021; 30 m |
333
+ | `ESA_WORLDCOVER` | GEE | Global | 10 m; 2020 & 2021 |
334
+ | `DYNAMIC_WORLD` | GEE | Global | 10 m; Sentinel-2 derived |
335
+
336
+ ### Soil Properties (2 products)
337
+
338
+ | ID | Source | Coverage | Notes |
339
+ |---|---|---|---|
340
+ | `POLARIS` | HyRiver | CONUS | 30 m; 9 properties |
341
+ | `SOILGRIDS` | GEE | Global | 250 m; ISRIC |
342
+
343
+ ### Vegetation (3 products)
344
+
345
+ | ID | Variable | Source | Coverage | Resolution | Timestep |
346
+ |---|---|---|---|---|---|
347
+ | `MODIS_NDVI` | NDVI | GEE | Global | 250 m | 16-day composite |
348
+ | `SENTINEL2_NDVI` | NDVI | GEE | Global | 10 m | ~5 day revisit |
349
+ | `MODIS_LAI` | LAI | GEE | Global | 500 m | 8-day composite |
350
+
351
+ ### Streamflow (1 product)
352
+
353
+ | ID | Source | Coverage | Notes |
354
+ |---|---|---|---|
355
+ | `NWIS_STREAMFLOW` | Direct API | CONUS | USGS daily values; pass gauge ID as geometry |
356
+
357
+ ---
358
+
359
+ ## Routing System
360
+
361
+ The router is a **declarative policy table** in `routing/policy.py` — no if/else chains, no source-specific logic. Adding a new product means adding one row.
362
+
363
+ ```
364
+ fetch(variable, geometry, start, end)
365
+ │
366
+ ▼
367
+ detect_region(geometry) ← CONUS? S_ASIA? EUROPE? global?
368
+ │
369
+ ▼
370
+ PRODUCT_POLICY[(variable, region)] ← ordered list [primary, fallback1, fallback2, ...]
371
+ │
372
+ ▼
373
+ resolve_product(spec) ← ProductSpec with backend_config
374
+ │
375
+ ▼
376
+ Backend.fetch_timeseries() ← gee / hyriver / direct_api
377
+ │
378
+ ▼
379
+ FetchResult(data, product, source, citation, units, next_steps)
380
+ ```
381
+
382
+ **Region detection** uses bounding-box math: if the geometry's centroid is inside the CONUS rectangle (−125° to −66°W, 24° to 50°N), the region is `"CONUS"`. Otherwise a Pfafstetter level-2 table resolves to `"S_ASIA"`, `"EUROPE"`, `"AFRICA"`, etc. Unknown regions fall to `"global"`.
383
+
384
+ **Fallback chain**: if the primary product raises `SourceUnavailable` or times out, the pipeline walks down the policy list automatically.
385
+
386
+ ---
387
+
388
+ ## Auth Setup
389
+
390
+ ### Google Earth Engine (20 products)
391
+
392
+ ```bash
393
+ # 1. Install
394
+ pip install aihydro-data[gee]
395
+
396
+ # 2. Authenticate (one time — writes ~/.config/earthengine/credentials)
397
+ python -c "import ee; ee.Authenticate()"
398
+
399
+ # 3. Verify
400
+ python -c "from aihydro_data.mcp import data_doctor; print(data_doctor())"
401
+ ```
402
+
403
+ > GEE requires a [Google account registered for Earth Engine](https://earthengine.google.com/signup/). Academic / research use is free.
404
+
405
+ ### HyRiver (10 products)
406
+
407
+ No auth required. Just install:
408
+
409
+ ```bash
410
+ pip install aihydro-data[hyriver]
411
+ ```
412
+
413
+ ### CHIRPS IRI / NWIS (2 products)
414
+
415
+ No auth required:
416
+
417
+ ```bash
418
+ pip install aihydro-data[opendap] # CHIRPS_IRI — needs xarray + netCDF4
419
+ # NWIS_STREAMFLOW works with [hyriver] — no extra auth
420
+ ```
421
+
422
+ ---
423
+
424
+ ## MCP Tools
425
+
426
+ `aihydro-data` ships 9 MCP tools that expose the full API to AI agents (Claude, etc.) via the AI-Hydro MCP server:
427
+
428
+ | Tool | Description |
429
+ |---|---|
430
+ | `data_fetch` | Fetch a variable for a geometry and date range |
431
+ | `data_batch_fetch` | Fetch multiple geometries in one call |
432
+ | `data_list_products` | Discover products by variable / region / source |
433
+ | `data_describe_product` | Full spec for one product (citation, pitfalls, examples) |
434
+ | `data_validate_request` | Dry-run validation before fetching (size estimate, date checks) |
435
+ | `data_get_cache_status` | Inspect the disk cache |
436
+ | `data_invalidate_cache` | Clear cached entries |
437
+ | `data_doctor` | Environment check: auth, backends, cache, missing extras |
438
+ | `data_help` | Built-in onboarding guide (topics: auth, first_fetch, caching, …) |
439
+
440
+ The tools are auto-registered when `aihydro-data[mcp]` is installed, via the `aihydro.tools` entry-point group.
441
+
442
+ ---
443
+
444
+ ## Architecture
445
+
446
+ ```
447
+ aihydro_data/
448
+ ├── __init__.py ← Public API: fetch(), list_products(), get_product()
449
+ ├── fetch.py ← Unified entry point
450
+ ├── _pipeline.py ← Routing → product resolution → backend dispatch → cache
451
+ ├── contracts.py ← ProductSpec, FetchRequest, FetchResult (Pydantic)
452
+ ├── exceptions.py ← Typed exceptions with agent-friendly .to_dict()
453
+ │
454
+ ├── products/ ← Declarative variable registry (one file per variable)
455
+ │ ├── precipitation.py 6 products
456
+ │ ├── temperature.py 7 products
457
+ │ ├── et.py 5 products
458
+ │ ├── dem.py 4 products
459
+ │ ├── soil_moisture.py 1 product
460
+ │ ├── landcover.py 3 products
461
+ │ ├── soil.py 2 products
462
+ │ ├── vegetation.py 3 products
463
+ │ └── streamflow.py 1 product
464
+ │
465
+ ├── sources/ ← Backend adapters (lazy imports — safe without extras)
466
+ │ ├── base.py SourceBackend ABC
467
+ │ ├── gee.py GEE backend (20 products)
468
+ │ ├── hyriver.py HyRiver backend (10 products)
469
+ │ ├── direct_api.py NWIS + CHIRPS IRI OPeNDAP (2 products)
470
+ │ ├── stac.py STAC/Planetary Computer (future)
471
+ │ └── _gee_vendored/ GEE auth + timeseries helpers (vendored from aihydro-tools)
472
+ │
473
+ ├── routing/
474
+ │ ├── regions.py CONUS bbox, Pfafstetter region table
475
+ │ ├── detect.py detect_region(geometry) → str
476
+ │ └── policy.py PRODUCT_POLICY: (variable, region) → [product_ids]
477
+ │
478
+ ├── geometry/
479
+ │ └── __init__.py coerce_geometry() — normalises all input types
480
+ │
481
+ ├── cache/
482
+ │ └── __init__.py Disk cache at ~/.aihydro/cache/data/
483
+ │
484
+ ├── mcp/
485
+ │ └── __init__.py 9 MCP tools (data_fetch, data_doctor, …)
486
+ │
487
+ └── help_topics/ Bundled markdown help (version-pinned to install)
488
+ ├── first_fetch.md
489
+ ├── auth.md
490
+ ├── caching.md
491
+ └── ...
492
+ ```
493
+
494
+ ### Three orthogonal axes
495
+
496
+ | Axis | What it is | Where it lives |
497
+ |---|---|---|
498
+ | **Variable** | *what* — precipitation, tmax, et, ndvi, dem, … | `products/<variable>.py` |
499
+ | **Source/Product** | *where from* — GridMET, CHIRPS, ERA5-Land, MOD16, … | `ProductSpec.backend_config` |
500
+ | **Region** | *where to* — CONUS, S_ASIA, EUROPE, global, … | `routing/policy.py` |
501
+
502
+ A user asks for "precipitation over this watershed" — the router resolves all three axes automatically, or the user pins any/all of them manually.
503
+
504
+ ### Agent-friendly design
505
+
506
+ Every failure returns a structured envelope:
507
+
508
+ ```python
509
+ {
510
+ "error": True,
511
+ "code": "GEE_AUTH_MISSING",
512
+ "message": "Google Earth Engine credentials not found.",
513
+ "recovery": "Run `python -c \"import ee; ee.Authenticate()\"`",
514
+ "next_tools": ["data_doctor", "data_help"],
515
+ "docs_anchor": "auth#gee"
516
+ }
517
+ ```
518
+
519
+ Every success carries `citation`, `bibtex`, `units`, `license`, and `next_steps` so agents can chain downstream tools without re-planning.
520
+
521
+ ---
522
+
523
+ ## Contributing
524
+
525
+ ### Adding a new product
526
+
527
+ 1. Add a `ProductSpec` to the relevant `products/<variable>.py` (or create a new file).
528
+ 2. Add a row to `routing/policy.py`.
529
+ 3. If it's a new backend, add a `Backend` subclass to `sources/`.
530
+ 4. Run `pytest -m "not live"` — no live credentials needed for the offline suite.
531
+
532
+ ### Running tests
533
+
534
+ ```bash
535
+ # Offline suite (no network, no auth — ~7 seconds)
536
+ pytest -m "not live"
537
+
538
+ # Live sweep — tests all 32 products against real backends (~13 minutes)
539
+ # Requires GEE auth + internet
540
+ pytest tests/test_live_sweep.py -v
541
+ ```
542
+
543
+ ---
544
+
545
+ ## Status
546
+
547
+ **v0.1.5** — Pre-PyPI development. 34 products live-tested; visualization layer (auto-plot, hydrology plots, multi-source compare, folium map preview); STAC backend with auth-free DEM + landcover; cookbook notebook with 10 recipes; multi-OS CI. Versioning stays on `0.1.x` until the first public PyPI release.
548
+
549
+ See **[ROADMAP.md](ROADMAP.md)** for what's planned next, and **[examples/cookbook.ipynb](examples/cookbook.ipynb)** for 10 working recipes.
550
+
551
+ | Phase | Status | Description |
552
+ |---|---|---|
553
+ | 1: Scaffold | ✅ | Package structure, contracts, registry |
554
+ | 2: Precipitation vertical | ✅ | 6 products, routing, fallback chain |
555
+ | 3: CONUS migration | ✅ | Streamflow, temperature, landcover, soil |
556
+ | 4: Global gap-filling | ✅ | ET, DEM, soil moisture, vegetation |
557
+ | 5: Cache + provenance | ✅ | Disk cache, manifest, license tracking |
558
+ | 6: Batch fetching | ✅ | Multi-geometry parallel dispatch |
559
+ | 7: MCP tools | ✅ | 9 tools, help topics, doctor |
560
+ | 8: PyPI publish | 🔲 | v0.2.0 release |
561
+
562
+ ---
563
+
564
+ ## License
565
+
566
+ Apache-2.0. Data products carry their own licenses — always check `result.license` or `data_describe_product(id)`.