swatgenx 0.1.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.
- swatgenx-0.1.0/PKG-INFO +86 -0
- swatgenx-0.1.0/README.md +67 -0
- swatgenx-0.1.0/pyproject.toml +29 -0
- swatgenx-0.1.0/setup.cfg +4 -0
- swatgenx-0.1.0/src/swatgenx/__init__.py +40 -0
- swatgenx-0.1.0/src/swatgenx/client.py +211 -0
- swatgenx-0.1.0/src/swatgenx.egg-info/PKG-INFO +86 -0
- swatgenx-0.1.0/src/swatgenx.egg-info/SOURCES.txt +9 -0
- swatgenx-0.1.0/src/swatgenx.egg-info/dependency_links.txt +1 -0
- swatgenx-0.1.0/src/swatgenx.egg-info/requires.txt +1 -0
- swatgenx-0.1.0/src/swatgenx.egg-info/top_level.txt +1 -0
swatgenx-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: swatgenx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for SWATGenX — automated SWAT+/MODFLOW watershed models and open national water datasets for the conterminous US
|
|
5
|
+
Author-email: Vahid Rafiei <info@swatgenx.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://www.swatgenx.com
|
|
8
|
+
Project-URL: Documentation, https://www.swatgenx.com/developer-api
|
|
9
|
+
Project-URL: MCP server (agent access), https://www.swatgenx.com/mcp
|
|
10
|
+
Keywords: SWAT+,MODFLOW,hydrology,watershed,groundwater,PFAS,USGS
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Hydrology
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: requests>=2.28
|
|
19
|
+
|
|
20
|
+
# swatgenx
|
|
21
|
+
|
|
22
|
+
Python client for [SWATGenX](https://www.swatgenx.com) — automated SWAT+ / MODFLOW 6
|
|
23
|
+
watershed models and open national water datasets for the conterminous United States.
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
pip install swatgenx
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Public data — no account needed
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import swatgenx as sg
|
|
33
|
+
|
|
34
|
+
# Example-model catalog: built SWAT+ models with calibration/validation metrics
|
|
35
|
+
models = sg.catalog(state="FL", calibrated_only=True)
|
|
36
|
+
sg.calibration("01451800")
|
|
37
|
+
# {'mode': 'engineer', 'cal_daily_nse': 0.642, 'val_daily_nse': 0.748, ...}
|
|
38
|
+
|
|
39
|
+
# National groundwater inventory: 28.8M lithology intervals, 7.9M wells, 46 states
|
|
40
|
+
sg.groundwater_at(42.73, -84.55) # nearest well + lithology log
|
|
41
|
+
sg.groundwater_summary()
|
|
42
|
+
|
|
43
|
+
# National PFAS monitoring inventory
|
|
44
|
+
sg.pfas_stations(huc8="0405")
|
|
45
|
+
sg.pfas_summary()
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Order and download models — free account + API key
|
|
49
|
+
|
|
50
|
+
Sign in at [swatgenx.com](https://www.swatgenx.com) → dashboard → API keys, then:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
c = sg.Client(api_key="...") # or env SWATGENX_API_KEY
|
|
54
|
+
|
|
55
|
+
order = c.order(usgs_station="04124500") # any of 25,000+ USGS gauges
|
|
56
|
+
c.wait(order["order_id"]) # typical build: 20 min – 2 h
|
|
57
|
+
c.download("04124500", vpuid="0406", dest="model.zip") # ZIP straight to your disk
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Builds run on SWATGenX cloud infrastructure from national data (NHDPlus HR, 3DEP,
|
|
61
|
+
gSSURGO, NLCD, PRISM, USGS NWIS); delivery is pull-based — no email round-trip.
|
|
62
|
+
|
|
63
|
+
## Access ladder
|
|
64
|
+
|
|
65
|
+
| tier | requires | unlocks |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| guest | nothing | all public data functions |
|
|
68
|
+
| member | free account + API key | model orders (fair-use), downloads |
|
|
69
|
+
| extended | request via info@swatgenx.com | HUC8 whole-basin, SWAT+MODFLOW-6, HUC14 30 m site models |
|
|
70
|
+
| calibration | account credit | cloud calibration campaigns |
|
|
71
|
+
|
|
72
|
+
`sg.access_info()` returns this ladder programmatically; quota/tier errors raise
|
|
73
|
+
`SwatGenXError` with upgrade guidance.
|
|
74
|
+
|
|
75
|
+
## AI agents
|
|
76
|
+
|
|
77
|
+
The same platform is agent-native via a public MCP server:
|
|
78
|
+
**https://www.swatgenx.com/mcp** (see the site's `llms.txt`). This package and the MCP
|
|
79
|
+
server expose the same surface, enforced by the same server-side quotas.
|
|
80
|
+
|
|
81
|
+
## Data citations
|
|
82
|
+
|
|
83
|
+
- Groundwater inventory: Zenodo DOI [10.5281/zenodo.21196958](https://doi.org/10.5281/zenodo.21196958)
|
|
84
|
+
- Soil PFAS inventory: Zenodo DOI [10.5281/zenodo.21096358](https://doi.org/10.5281/zenodo.21096358)
|
|
85
|
+
|
|
86
|
+
MIT-licensed client; platform terms at swatgenx.com.
|
swatgenx-0.1.0/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# swatgenx
|
|
2
|
+
|
|
3
|
+
Python client for [SWATGenX](https://www.swatgenx.com) — automated SWAT+ / MODFLOW 6
|
|
4
|
+
watershed models and open national water datasets for the conterminous United States.
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
pip install swatgenx
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Public data — no account needed
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
import swatgenx as sg
|
|
14
|
+
|
|
15
|
+
# Example-model catalog: built SWAT+ models with calibration/validation metrics
|
|
16
|
+
models = sg.catalog(state="FL", calibrated_only=True)
|
|
17
|
+
sg.calibration("01451800")
|
|
18
|
+
# {'mode': 'engineer', 'cal_daily_nse': 0.642, 'val_daily_nse': 0.748, ...}
|
|
19
|
+
|
|
20
|
+
# National groundwater inventory: 28.8M lithology intervals, 7.9M wells, 46 states
|
|
21
|
+
sg.groundwater_at(42.73, -84.55) # nearest well + lithology log
|
|
22
|
+
sg.groundwater_summary()
|
|
23
|
+
|
|
24
|
+
# National PFAS monitoring inventory
|
|
25
|
+
sg.pfas_stations(huc8="0405")
|
|
26
|
+
sg.pfas_summary()
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Order and download models — free account + API key
|
|
30
|
+
|
|
31
|
+
Sign in at [swatgenx.com](https://www.swatgenx.com) → dashboard → API keys, then:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
c = sg.Client(api_key="...") # or env SWATGENX_API_KEY
|
|
35
|
+
|
|
36
|
+
order = c.order(usgs_station="04124500") # any of 25,000+ USGS gauges
|
|
37
|
+
c.wait(order["order_id"]) # typical build: 20 min – 2 h
|
|
38
|
+
c.download("04124500", vpuid="0406", dest="model.zip") # ZIP straight to your disk
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Builds run on SWATGenX cloud infrastructure from national data (NHDPlus HR, 3DEP,
|
|
42
|
+
gSSURGO, NLCD, PRISM, USGS NWIS); delivery is pull-based — no email round-trip.
|
|
43
|
+
|
|
44
|
+
## Access ladder
|
|
45
|
+
|
|
46
|
+
| tier | requires | unlocks |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| guest | nothing | all public data functions |
|
|
49
|
+
| member | free account + API key | model orders (fair-use), downloads |
|
|
50
|
+
| extended | request via info@swatgenx.com | HUC8 whole-basin, SWAT+MODFLOW-6, HUC14 30 m site models |
|
|
51
|
+
| calibration | account credit | cloud calibration campaigns |
|
|
52
|
+
|
|
53
|
+
`sg.access_info()` returns this ladder programmatically; quota/tier errors raise
|
|
54
|
+
`SwatGenXError` with upgrade guidance.
|
|
55
|
+
|
|
56
|
+
## AI agents
|
|
57
|
+
|
|
58
|
+
The same platform is agent-native via a public MCP server:
|
|
59
|
+
**https://www.swatgenx.com/mcp** (see the site's `llms.txt`). This package and the MCP
|
|
60
|
+
server expose the same surface, enforced by the same server-side quotas.
|
|
61
|
+
|
|
62
|
+
## Data citations
|
|
63
|
+
|
|
64
|
+
- Groundwater inventory: Zenodo DOI [10.5281/zenodo.21196958](https://doi.org/10.5281/zenodo.21196958)
|
|
65
|
+
- Soil PFAS inventory: Zenodo DOI [10.5281/zenodo.21096358](https://doi.org/10.5281/zenodo.21096358)
|
|
66
|
+
|
|
67
|
+
MIT-licensed client; platform terms at swatgenx.com.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "swatgenx"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python client for SWATGenX — automated SWAT+/MODFLOW watershed models and open national water datasets for the conterminous US"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Vahid Rafiei", email = "info@swatgenx.com" }]
|
|
13
|
+
keywords = ["SWAT+", "MODFLOW", "hydrology", "watershed", "groundwater", "PFAS", "USGS"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Science/Research",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Hydrology",
|
|
20
|
+
]
|
|
21
|
+
dependencies = ["requests>=2.28"]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://www.swatgenx.com"
|
|
25
|
+
Documentation = "https://www.swatgenx.com/developer-api"
|
|
26
|
+
"MCP server (agent access)" = "https://www.swatgenx.com/mcp"
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.packages.find]
|
|
29
|
+
where = ["src"]
|
swatgenx-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""swatgenx — Python client for SWATGenX (https://www.swatgenx.com).
|
|
2
|
+
|
|
3
|
+
Public data needs no account:
|
|
4
|
+
|
|
5
|
+
import swatgenx as sg
|
|
6
|
+
models = sg.catalog(state="FL", calibrated_only=True)
|
|
7
|
+
sg.calibration("01451800") # cal/val NSE, PBIAS, method
|
|
8
|
+
sg.groundwater_at(42.73, -84.55) # nearest well + lithology log
|
|
9
|
+
sg.pfas_stations(huc8="0405") # PFAS monitoring inventory
|
|
10
|
+
|
|
11
|
+
Ordering and downloading models needs a free account + API key
|
|
12
|
+
(sign in at swatgenx.com -> dashboard -> API keys):
|
|
13
|
+
|
|
14
|
+
c = sg.Client(api_key="...") # or env SWATGENX_API_KEY
|
|
15
|
+
order = c.order(usgs_station="04124500")
|
|
16
|
+
c.status(order["order_id"])
|
|
17
|
+
c.download("04124500", vpuid="0406", dest="model.zip")
|
|
18
|
+
|
|
19
|
+
Access ladder: guest (public data) -> member (free key: fair-use orders + downloads)
|
|
20
|
+
-> extended access (info@swatgenx.com: HUC8 / SWAT+MODFLOW-6 / HUC14 site models)
|
|
21
|
+
-> calibration (account credit). AI agents can use the same platform via MCP:
|
|
22
|
+
https://www.swatgenx.com/mcp
|
|
23
|
+
"""
|
|
24
|
+
from .client import (
|
|
25
|
+
Client,
|
|
26
|
+
SwatGenXError,
|
|
27
|
+
access_info,
|
|
28
|
+
calibration,
|
|
29
|
+
catalog,
|
|
30
|
+
groundwater_at,
|
|
31
|
+
groundwater_summary,
|
|
32
|
+
pfas_stations,
|
|
33
|
+
pfas_summary,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__version__ = "0.1.0"
|
|
37
|
+
__all__ = [
|
|
38
|
+
"Client", "SwatGenXError", "access_info", "calibration", "catalog",
|
|
39
|
+
"groundwater_at", "groundwater_summary", "pfas_stations", "pfas_summary",
|
|
40
|
+
]
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""HTTP client for the SWATGenX public API (thin, dependency-light).
|
|
2
|
+
|
|
3
|
+
Every call maps 1:1 onto a documented endpoint at https://www.swatgenx.com — the same
|
|
4
|
+
surface the website and the MCP server (https://www.swatgenx.com/mcp) use. Public
|
|
5
|
+
functions need no account; the Client class carries a per-user API key for actions
|
|
6
|
+
that create state (model orders, downloads), where authentication, ownership, and
|
|
7
|
+
fair-use quotas are enforced server-side.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import time
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
import requests
|
|
16
|
+
|
|
17
|
+
BASE = os.environ.get("SWATGENX_BASE_URL", "https://www.swatgenx.com")
|
|
18
|
+
_UA = {"User-Agent": "swatgenx-python/0.1.0"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SwatGenXError(RuntimeError):
|
|
22
|
+
"""API error with the server's message and, when relevant, upgrade guidance."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, message: str, status: int | None = None, detail: Any = None):
|
|
25
|
+
super().__init__(message)
|
|
26
|
+
self.status = status
|
|
27
|
+
self.detail = detail
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
_ACCESS_NOTE = (
|
|
31
|
+
"This action exceeds your current access tier or fair-use allocation. "
|
|
32
|
+
"Free accounts order under daily allocations; extended access (HUC8 whole-basin, "
|
|
33
|
+
"SWAT+MODFLOW-6, HUC14 site models) is granted on request via info@swatgenx.com; "
|
|
34
|
+
"cloud calibration requires account credit."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _request(method: str, path: str, *, key: str | None = None, json: dict | None = None,
|
|
39
|
+
params: dict | None = None, timeout: int = 90) -> Any:
|
|
40
|
+
headers = dict(_UA)
|
|
41
|
+
if key:
|
|
42
|
+
headers["X-SWATGenX-Api-Key"] = key
|
|
43
|
+
r = requests.request(method, f"{BASE}{path}", json=json, params=params,
|
|
44
|
+
headers=headers, timeout=timeout)
|
|
45
|
+
try:
|
|
46
|
+
body = r.json()
|
|
47
|
+
except ValueError:
|
|
48
|
+
body = {"raw": (r.text or "")[:500]}
|
|
49
|
+
if r.status_code == 401:
|
|
50
|
+
raise SwatGenXError(
|
|
51
|
+
"Authentication failed — invalid or revoked API key. Sign in at "
|
|
52
|
+
"https://www.swatgenx.com -> dashboard -> API keys.", 401, body)
|
|
53
|
+
if r.status_code in (402, 403, 413, 429):
|
|
54
|
+
raise SwatGenXError(_ACCESS_NOTE, r.status_code, body)
|
|
55
|
+
if r.status_code >= 400:
|
|
56
|
+
msg = body.get("message") or body.get("error") or f"HTTP {r.status_code}"
|
|
57
|
+
raise SwatGenXError(str(msg), r.status_code, body)
|
|
58
|
+
return body
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# ------------------------------------------------------------------ public (no account)
|
|
62
|
+
def catalog(state: str | None = None, calibrated_only: bool = False,
|
|
63
|
+
min_channels: int | None = None, max_channels: int | None = None) -> list[dict]:
|
|
64
|
+
"""Public example-model catalog: built SWAT+ models across the conterminous US,
|
|
65
|
+
each with structure counts and (when calibrated) cal/val NSE. Filter by two-letter
|
|
66
|
+
state, calibration status, and channel-count bounds."""
|
|
67
|
+
payload = _request("GET", "/api/public/example-swat-models")
|
|
68
|
+
out = []
|
|
69
|
+
for m in payload.get("models") or []:
|
|
70
|
+
if state and str(m.get("state") or "").upper() != state.upper():
|
|
71
|
+
continue
|
|
72
|
+
cal = m.get("calibration")
|
|
73
|
+
if calibrated_only and not cal:
|
|
74
|
+
continue
|
|
75
|
+
ch = m.get("n_channels")
|
|
76
|
+
if min_channels is not None and (ch is None or ch < min_channels):
|
|
77
|
+
continue
|
|
78
|
+
if max_channels is not None and (ch is None or ch > max_channels):
|
|
79
|
+
continue
|
|
80
|
+
out.append(m)
|
|
81
|
+
return out
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def calibration(site_no: str) -> dict | None:
|
|
85
|
+
"""Calibration + held-out validation metrics for one public example model
|
|
86
|
+
(daily/monthly NSE, PBIAS, method, window). None if never calibrated."""
|
|
87
|
+
for m in catalog():
|
|
88
|
+
if str(m.get("site_no")) == str(site_no):
|
|
89
|
+
return m.get("calibration")
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def groundwater_at(lat: float, lon: float, tol_deg: float = 0.05) -> dict:
|
|
94
|
+
"""Nearest well to a point from the national groundwater inventory (28.8M lithology
|
|
95
|
+
intervals, 7.9M wells), with its lithology log when available. tol_deg is the search
|
|
96
|
+
box half-width in degrees (~0.05 = 5 km)."""
|
|
97
|
+
return _request("GET", "/api/gw-wells/at",
|
|
98
|
+
params={"lat": lat, "lon": lon, "tol": tol_deg})
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def groundwater_summary() -> dict:
|
|
102
|
+
"""Live national groundwater-inventory counts (wells, intervals, per-state)."""
|
|
103
|
+
return _request("GET", "/api/gw-wells/summary")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def pfas_stations(bbox: str | None = None, huc12: str | None = None,
|
|
107
|
+
huc8: str | None = None) -> dict:
|
|
108
|
+
"""National PFAS monitoring inventory (GeoJSON stations). Filter by bbox
|
|
109
|
+
('minLon,minLat,maxLon,maxLat'), huc12, or huc8."""
|
|
110
|
+
params = {k: v for k, v in (("bbox", bbox), ("huc12", huc12), ("huc8", huc8)) if v}
|
|
111
|
+
return _request("GET", "/api/pfas/stations", params=params)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def pfas_summary() -> dict:
|
|
115
|
+
"""Live PFAS-inventory summary counts."""
|
|
116
|
+
return _request("GET", "/api/pfas/summary")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def access_info() -> dict:
|
|
120
|
+
"""The SWATGenX access ladder: what guests, members (free key), extended access,
|
|
121
|
+
and calibration credit each unlock — and how to move up."""
|
|
122
|
+
return {
|
|
123
|
+
"tiers": [
|
|
124
|
+
{"tier": "guest", "requires": "nothing",
|
|
125
|
+
"can": "all public data functions in this package + example-model downloads on the website"},
|
|
126
|
+
{"tier": "member", "requires": "free account + API key (swatgenx.com -> dashboard -> API keys)",
|
|
127
|
+
"can": "Client.order under fair-use daily allocations; Client.download for owned models"},
|
|
128
|
+
{"tier": "extended access", "requires": "granted on request: info@swatgenx.com",
|
|
129
|
+
"can": "HUC8 whole-basin, coupled SWAT+MODFLOW-6, HUC14 30 m site models, higher allocations"},
|
|
130
|
+
{"tier": "calibration", "requires": "account credit",
|
|
131
|
+
"can": "cloud calibration campaigns (website dashboard)"},
|
|
132
|
+
],
|
|
133
|
+
"agent_access": "AI agents can use the same platform via MCP: https://www.swatgenx.com/mcp",
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# ------------------------------------------------------------------ authenticated
|
|
138
|
+
class Client:
|
|
139
|
+
"""Authenticated client. Get a key: sign in at swatgenx.com -> dashboard -> API keys,
|
|
140
|
+
or set the SWATGENX_API_KEY environment variable."""
|
|
141
|
+
|
|
142
|
+
def __init__(self, api_key: str | None = None):
|
|
143
|
+
self.api_key = (api_key or os.environ.get("SWATGENX_API_KEY") or "").strip()
|
|
144
|
+
if not self.api_key:
|
|
145
|
+
raise SwatGenXError(
|
|
146
|
+
"API key required: pass Client(api_key=...) or set SWATGENX_API_KEY. "
|
|
147
|
+
"Keys: swatgenx.com -> dashboard -> API keys (free account).")
|
|
148
|
+
|
|
149
|
+
# -- account
|
|
150
|
+
def whoami(self) -> dict:
|
|
151
|
+
"""Subscription status for the key's account."""
|
|
152
|
+
return _request("GET", "/api/user/subscription-status", key=self.api_key)
|
|
153
|
+
|
|
154
|
+
# -- ordering
|
|
155
|
+
def order(self, usgs_station: str | None = None, huc12_outlet: str | None = None,
|
|
156
|
+
force_rebuild: bool = False) -> dict:
|
|
157
|
+
"""Order a real SWAT+ model build (consumes your fair-use allocation).
|
|
158
|
+
Provide either a USGS gauge id or a 12-digit HUC12 outlet. Returns the order
|
|
159
|
+
record incl. order_id; typical build 20 min - 2 h."""
|
|
160
|
+
if huc12_outlet:
|
|
161
|
+
return _request("POST", "/api/model-settings/explorer-watershed", key=self.api_key,
|
|
162
|
+
json={"outlet_huc12": str(huc12_outlet).strip(),
|
|
163
|
+
"force_rebuild": bool(force_rebuild)})
|
|
164
|
+
if usgs_station:
|
|
165
|
+
return _request("POST", "/api/model-settings", key=self.api_key,
|
|
166
|
+
json={"site_no": str(usgs_station).strip(),
|
|
167
|
+
"force_rebuild": bool(force_rebuild)})
|
|
168
|
+
raise SwatGenXError("provide usgs_station or huc12_outlet")
|
|
169
|
+
|
|
170
|
+
def status(self, order_id: str) -> dict:
|
|
171
|
+
"""State/stage/timing of a build order."""
|
|
172
|
+
return _request("GET", f"/api/model-orders/{str(order_id).strip()}", key=self.api_key)
|
|
173
|
+
|
|
174
|
+
def orders(self) -> dict:
|
|
175
|
+
"""All of your build orders, newest first."""
|
|
176
|
+
return _request("GET", "/api/model-orders", key=self.api_key)
|
|
177
|
+
|
|
178
|
+
def wait(self, order_id: str, poll_seconds: int = 60, timeout_hours: float = 4.0) -> dict:
|
|
179
|
+
"""Block until an order reaches a terminal state; returns the final record."""
|
|
180
|
+
deadline = time.time() + timeout_hours * 3600
|
|
181
|
+
while True:
|
|
182
|
+
rec = self.status(order_id)
|
|
183
|
+
state = str(rec.get("state") or rec.get("order_state") or "").upper()
|
|
184
|
+
if state and not any(s in state for s in ("QUEUED", "RUNNING", "PENDING")):
|
|
185
|
+
return rec
|
|
186
|
+
if time.time() > deadline:
|
|
187
|
+
raise SwatGenXError(f"order {order_id} not terminal after {timeout_hours} h "
|
|
188
|
+
f"(last state: {state or 'unknown'})")
|
|
189
|
+
time.sleep(max(10, poll_seconds))
|
|
190
|
+
|
|
191
|
+
# -- delivery (pull-based: the ZIP lands on YOUR disk; no email needed)
|
|
192
|
+
def download_link(self, site_no: str, vpuid: str, level: str = "usgs_station") -> dict:
|
|
193
|
+
"""Mint a fresh 24 h download link for a model you own."""
|
|
194
|
+
return _request("POST", "/api/download_model/link", key=self.api_key,
|
|
195
|
+
json={"site_no": str(site_no).strip(), "vpuid": str(vpuid).strip(),
|
|
196
|
+
"level": level})
|
|
197
|
+
|
|
198
|
+
def download(self, site_no: str, vpuid: str, dest: str,
|
|
199
|
+
level: str = "usgs_station", timeout: int = 1800) -> str:
|
|
200
|
+
"""Download a model you own to a local path (streams the ZIP). Returns dest."""
|
|
201
|
+
link = self.download_link(site_no, vpuid, level)
|
|
202
|
+
url = link.get("download_url")
|
|
203
|
+
if not url:
|
|
204
|
+
raise SwatGenXError("no download_url in response", detail=link)
|
|
205
|
+
with requests.get(url, headers=_UA, stream=True, timeout=timeout) as r:
|
|
206
|
+
if r.status_code >= 400:
|
|
207
|
+
raise SwatGenXError(f"download failed: HTTP {r.status_code}", r.status_code)
|
|
208
|
+
with open(dest, "wb") as fh:
|
|
209
|
+
for chunk in r.iter_content(chunk_size=1 << 20):
|
|
210
|
+
fh.write(chunk)
|
|
211
|
+
return dest
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: swatgenx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for SWATGenX — automated SWAT+/MODFLOW watershed models and open national water datasets for the conterminous US
|
|
5
|
+
Author-email: Vahid Rafiei <info@swatgenx.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://www.swatgenx.com
|
|
8
|
+
Project-URL: Documentation, https://www.swatgenx.com/developer-api
|
|
9
|
+
Project-URL: MCP server (agent access), https://www.swatgenx.com/mcp
|
|
10
|
+
Keywords: SWAT+,MODFLOW,hydrology,watershed,groundwater,PFAS,USGS
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Hydrology
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: requests>=2.28
|
|
19
|
+
|
|
20
|
+
# swatgenx
|
|
21
|
+
|
|
22
|
+
Python client for [SWATGenX](https://www.swatgenx.com) — automated SWAT+ / MODFLOW 6
|
|
23
|
+
watershed models and open national water datasets for the conterminous United States.
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
pip install swatgenx
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Public data — no account needed
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import swatgenx as sg
|
|
33
|
+
|
|
34
|
+
# Example-model catalog: built SWAT+ models with calibration/validation metrics
|
|
35
|
+
models = sg.catalog(state="FL", calibrated_only=True)
|
|
36
|
+
sg.calibration("01451800")
|
|
37
|
+
# {'mode': 'engineer', 'cal_daily_nse': 0.642, 'val_daily_nse': 0.748, ...}
|
|
38
|
+
|
|
39
|
+
# National groundwater inventory: 28.8M lithology intervals, 7.9M wells, 46 states
|
|
40
|
+
sg.groundwater_at(42.73, -84.55) # nearest well + lithology log
|
|
41
|
+
sg.groundwater_summary()
|
|
42
|
+
|
|
43
|
+
# National PFAS monitoring inventory
|
|
44
|
+
sg.pfas_stations(huc8="0405")
|
|
45
|
+
sg.pfas_summary()
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Order and download models — free account + API key
|
|
49
|
+
|
|
50
|
+
Sign in at [swatgenx.com](https://www.swatgenx.com) → dashboard → API keys, then:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
c = sg.Client(api_key="...") # or env SWATGENX_API_KEY
|
|
54
|
+
|
|
55
|
+
order = c.order(usgs_station="04124500") # any of 25,000+ USGS gauges
|
|
56
|
+
c.wait(order["order_id"]) # typical build: 20 min – 2 h
|
|
57
|
+
c.download("04124500", vpuid="0406", dest="model.zip") # ZIP straight to your disk
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Builds run on SWATGenX cloud infrastructure from national data (NHDPlus HR, 3DEP,
|
|
61
|
+
gSSURGO, NLCD, PRISM, USGS NWIS); delivery is pull-based — no email round-trip.
|
|
62
|
+
|
|
63
|
+
## Access ladder
|
|
64
|
+
|
|
65
|
+
| tier | requires | unlocks |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| guest | nothing | all public data functions |
|
|
68
|
+
| member | free account + API key | model orders (fair-use), downloads |
|
|
69
|
+
| extended | request via info@swatgenx.com | HUC8 whole-basin, SWAT+MODFLOW-6, HUC14 30 m site models |
|
|
70
|
+
| calibration | account credit | cloud calibration campaigns |
|
|
71
|
+
|
|
72
|
+
`sg.access_info()` returns this ladder programmatically; quota/tier errors raise
|
|
73
|
+
`SwatGenXError` with upgrade guidance.
|
|
74
|
+
|
|
75
|
+
## AI agents
|
|
76
|
+
|
|
77
|
+
The same platform is agent-native via a public MCP server:
|
|
78
|
+
**https://www.swatgenx.com/mcp** (see the site's `llms.txt`). This package and the MCP
|
|
79
|
+
server expose the same surface, enforced by the same server-side quotas.
|
|
80
|
+
|
|
81
|
+
## Data citations
|
|
82
|
+
|
|
83
|
+
- Groundwater inventory: Zenodo DOI [10.5281/zenodo.21196958](https://doi.org/10.5281/zenodo.21196958)
|
|
84
|
+
- Soil PFAS inventory: Zenodo DOI [10.5281/zenodo.21096358](https://doi.org/10.5281/zenodo.21096358)
|
|
85
|
+
|
|
86
|
+
MIT-licensed client; platform terms at swatgenx.com.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/swatgenx/__init__.py
|
|
4
|
+
src/swatgenx/client.py
|
|
5
|
+
src/swatgenx.egg-info/PKG-INFO
|
|
6
|
+
src/swatgenx.egg-info/SOURCES.txt
|
|
7
|
+
src/swatgenx.egg-info/dependency_links.txt
|
|
8
|
+
src/swatgenx.egg-info/requires.txt
|
|
9
|
+
src/swatgenx.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.28
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
swatgenx
|