epwtools 0.3.1__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.
- epwtools-0.3.1/.gitignore +105 -0
- epwtools-0.3.1/PKG-INFO +313 -0
- epwtools-0.3.1/README.md +282 -0
- epwtools-0.3.1/epwtools/__init__.py +15 -0
- epwtools-0.3.1/epwtools/_namespaces.py +458 -0
- epwtools-0.3.1/epwtools/aio.py +332 -0
- epwtools-0.3.1/epwtools/cli.py +481 -0
- epwtools-0.3.1/epwtools/client.py +772 -0
- epwtools-0.3.1/pyproject.toml +99 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.venv/
|
|
5
|
+
backend/.venv/
|
|
6
|
+
*.egg-info/
|
|
7
|
+
.env
|
|
8
|
+
.env.local
|
|
9
|
+
frontend/.env
|
|
10
|
+
frontend/.env.local
|
|
11
|
+
backend/outputs/
|
|
12
|
+
backend/weatherproof.db
|
|
13
|
+
backend/cache/
|
|
14
|
+
|
|
15
|
+
# Secrets / service files (never commit credentials)
|
|
16
|
+
*.service
|
|
17
|
+
*.key
|
|
18
|
+
*.pem
|
|
19
|
+
|
|
20
|
+
# Node
|
|
21
|
+
node_modules/
|
|
22
|
+
frontend/node_modules/
|
|
23
|
+
frontend/dist/
|
|
24
|
+
dist/
|
|
25
|
+
.vite/
|
|
26
|
+
frontend/.vite/
|
|
27
|
+
|
|
28
|
+
# OS
|
|
29
|
+
.DS_Store
|
|
30
|
+
Thumbs.db
|
|
31
|
+
*.swp
|
|
32
|
+
*.swo
|
|
33
|
+
|
|
34
|
+
# Large reference files (not needed in repo)
|
|
35
|
+
*.pdf
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Test fixtures (large, regenerable via scrape)
|
|
39
|
+
backend/resources/test_MOSMIX/
|
|
40
|
+
backend/resources/dc_validation_cache/
|
|
41
|
+
backend/resources/dc_validation_results*.csv
|
|
42
|
+
backend/resources/monthly_stats_cache/
|
|
43
|
+
# ERA5 / ERA5-Land NetCDF tiles — regenerable via Copernicus CDS
|
|
44
|
+
backend/resources/era5_cache/
|
|
45
|
+
backend/backups/
|
|
46
|
+
backend/epwtools.db
|
|
47
|
+
|
|
48
|
+
# RESEARCH/ — keep code & figures, drop large/cacheable artefacts
|
|
49
|
+
RESEARCH/data/raw/_osm_cache/
|
|
50
|
+
RESEARCH/data/raw/*/hourly_*.parquet
|
|
51
|
+
RESEARCH/.env
|
|
52
|
+
# Generated EPW/DDY/STAT weather files (regenerable via the pair generators)
|
|
53
|
+
RESEARCH/METERvsMODELED_Weather_paper/data/epw/
|
|
54
|
+
|
|
55
|
+
# UHI paper -- in-flight LaTeX drafts (paper_A/B/C variants are
|
|
56
|
+
# experimental forks; commit the canonical paper/ tree only). Review #47.
|
|
57
|
+
RESEARCH/UHI_paper/paper_A/
|
|
58
|
+
RESEARCH/UHI_paper/paper_B/
|
|
59
|
+
RESEARCH/UHI_paper/paper_C/
|
|
60
|
+
RESEARCH/UHI_paper/data/processed/uscrn/
|
|
61
|
+
RESEARCH/UHI_paper/results/uscrn_era5_validation_*.csv
|
|
62
|
+
RESEARCH/UHI_paper/scripts/36_uscrn_validation.py
|
|
63
|
+
# LaTeX side-effects in the canonical tree -- regenerated on every build
|
|
64
|
+
RESEARCH/UHI_paper/paper/paper.aux
|
|
65
|
+
RESEARCH/UHI_paper/paper/paper.log
|
|
66
|
+
RESEARCH/UHI_paper/paper/paper.out
|
|
67
|
+
|
|
68
|
+
# Test result artifacts (generated, not source)
|
|
69
|
+
frontend/playwright-report/
|
|
70
|
+
frontend/e2e/test-results/
|
|
71
|
+
frontend/coverage/
|
|
72
|
+
|
|
73
|
+
# Playwright auth state — regenerated by setup project
|
|
74
|
+
frontend/e2e/support/auth-state.json
|
|
75
|
+
|
|
76
|
+
# LaTeX build artefacts (S40)
|
|
77
|
+
*.aux
|
|
78
|
+
*.log
|
|
79
|
+
*.out
|
|
80
|
+
*.toc
|
|
81
|
+
*.lof
|
|
82
|
+
*.lot
|
|
83
|
+
*.bbl
|
|
84
|
+
*.blg
|
|
85
|
+
*.synctex.gz
|
|
86
|
+
*.fdb_latexmk
|
|
87
|
+
*.fls
|
|
88
|
+
*.nav
|
|
89
|
+
*.snm
|
|
90
|
+
*.vrb
|
|
91
|
+
|
|
92
|
+
# Working scratch (S40 — use docs/ or memories/ instead)
|
|
93
|
+
notes.txt
|
|
94
|
+
|
|
95
|
+
# IDE-generated citation scratchpads (review #46) -- never useful in repo
|
|
96
|
+
"frontend/src/# Code Citations.md"
|
|
97
|
+
|
|
98
|
+
# Hostile peer-review item 6: D22 self-test fixture is regenerable.
|
|
99
|
+
# `python scripts/D22_meter_validation.py --build-sample` rebuilds these
|
|
100
|
+
# from the DOE prototype + an existing era5 EPW + synthetic noise; the
|
|
101
|
+
# only file we keep under git is the README that documents the pattern.
|
|
102
|
+
RESEARCH/METERvsMODELED_Weather_paper/data/meters/_SAMPLE_*/idf.idf
|
|
103
|
+
RESEARCH/METERvsMODELED_Weather_paper/data/meters/_SAMPLE_*/year.epw
|
|
104
|
+
RESEARCH/METERvsMODELED_Weather_paper/data/meters/_SAMPLE_*/measured_hourly.csv
|
|
105
|
+
competitors_audit_*.csv
|
epwtools-0.3.1/PKG-INFO
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: epwtools
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Python SDK and CLI for epw.tools — generate EPW/DDY weather files from MERRA-2, ERA5 and CMIP6
|
|
5
|
+
Project-URL: Homepage, https://epw.tools
|
|
6
|
+
Project-URL: Documentation, https://epw.tools/#tab=api
|
|
7
|
+
Project-URL: Repository, https://github.com/carlobianchi89/epwtools
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/carlobianchi89/epwtools/issues
|
|
9
|
+
License: Proprietary
|
|
10
|
+
Keywords: ashrae,building-simulation,climate,cmip6,energyplus,eplus,epw,future-climate,tmy,weather
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: requests>=2.30
|
|
21
|
+
Requires-Dist: rich>=13
|
|
22
|
+
Requires-Dist: typer>=0.12
|
|
23
|
+
Provides-Extra: aio
|
|
24
|
+
Requires-Dist: httpx>=0.27; extra == 'aio'
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
28
|
+
Requires-Dist: responses>=0.25; extra == 'dev'
|
|
29
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# EPW.tools
|
|
33
|
+
|
|
34
|
+
**Every weather file you'll ever need.**
|
|
35
|
+
|
|
36
|
+
A web application + API for generating high-quality weather files for building
|
|
37
|
+
energy simulation, solar design, climate research, agriculture, insurance
|
|
38
|
+
and more.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## What it does
|
|
43
|
+
|
|
44
|
+
EPW.tools fuses measured station observations with global reanalysis and
|
|
45
|
+
satellite-derived solar data to produce dense, internally consistent weather
|
|
46
|
+
files in over a dozen formats.
|
|
47
|
+
|
|
48
|
+
| Layer | Source |
|
|
49
|
+
|---|---|
|
|
50
|
+
| Backbone | ERA5 (ECMWF) and MERRA-2 (NASA) reanalysis |
|
|
51
|
+
| Stations | NOAA ISD via Meteostat (~3 500 curated stations worldwide) |
|
|
52
|
+
| Solar | NREL NSRDB / PSM v3 where available, Zhang–Huang fallback |
|
|
53
|
+
| Future climate | NASA NEX-GDDP-CMIP6 with Belcher 2005 morphing |
|
|
54
|
+
| Design conditions | ASHRAE Handbook of Fundamentals |
|
|
55
|
+
|
|
56
|
+
Measured station data takes priority; small gaps are interpolated; longer
|
|
57
|
+
gaps are filled from reanalysis. Every file passes a battery of
|
|
58
|
+
physical-plausibility checks before release.
|
|
59
|
+
|
|
60
|
+
See [RESOURCES.md](RESOURCES.md) for the full source list and references.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Python SDK
|
|
65
|
+
|
|
66
|
+
The `epwtools` Python package provides a typed client for the REST API plus a
|
|
67
|
+
`weatherproof` CLI:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install epwtools
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Quick start
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from epwtools import EPWClient
|
|
77
|
+
|
|
78
|
+
client = EPWClient(api_key="your-key-here") # or set EPWTOOLS_API_KEY
|
|
79
|
+
|
|
80
|
+
# Single-year AMY
|
|
81
|
+
epw = client.generate(lat=40.71, lon=-74.01, year=2023, name="NewYork")
|
|
82
|
+
|
|
83
|
+
# Typical Meteorological Year
|
|
84
|
+
epw = client.tmy(lat=51.50, lon=-0.12, start=2005, end=2024, name="London")
|
|
85
|
+
|
|
86
|
+
# Future climate (CMIP6, SSP2-4.5, 2050)
|
|
87
|
+
epw = client.future(lat=48.85, lon=2.35, scenario="ssp245", horizon=2050)
|
|
88
|
+
|
|
89
|
+
# Multi-GCM ensemble (one EPW per CMIP6 model — for uncertainty bands)
|
|
90
|
+
epws = client.ensemble(lat=48.85, lon=2.35, scenario="ssp245", horizon=2050)
|
|
91
|
+
|
|
92
|
+
# Climate stress-test for a portfolio (no credits consumed)
|
|
93
|
+
table = client.portfolio_stress_test(
|
|
94
|
+
[{"lat": 40.71, "lon": -74.01, "label": "NYC"},
|
|
95
|
+
{"lat": 51.50, "lon": -0.12, "label": "London"}],
|
|
96
|
+
scenario="ssp245", horizon=2050,
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Real-time progress (SSE)
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
resp = client._post("/generate/individual", json={...})
|
|
104
|
+
for ev in client.stream(resp["job_id"]):
|
|
105
|
+
if ev["type"] == "log":
|
|
106
|
+
print(" ", ev["line"])
|
|
107
|
+
elif ev["type"] == "status" and ev["status"] == "complete":
|
|
108
|
+
break
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Endpoint namespaces
|
|
112
|
+
|
|
113
|
+
The client exposes every API endpoint under five thin namespaces — use them
|
|
114
|
+
when you want to reach a specific endpoint that isn't covered by the
|
|
115
|
+
high-level generation methods:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
client.tools.uhi_adjust("base.epw", ucz="UCZ-2")
|
|
119
|
+
client.tools.koppen("file.epw") # → {"koppen": "Cfa", ...}
|
|
120
|
+
client.tools.solar_calibrate("file.epw") # PSM3 blend (US)
|
|
121
|
+
client.tools.gap_fill("partial.epw")
|
|
122
|
+
client.tools.epw_diff("a.epw", "b.epw") # → hourly delta stats
|
|
123
|
+
|
|
124
|
+
client.epw.wind_rose(job_id, "out.epw") # 16 dirs × 6 speed bins
|
|
125
|
+
client.epw.heatmap(job_id, "out.epw", variable="dry_bulb")
|
|
126
|
+
client.epw.psychro(job_id, "out.epw")
|
|
127
|
+
|
|
128
|
+
client.reports.solar("file.epw")
|
|
129
|
+
client.reports.viticulture("file.epw") # GDD, Huglin, Winkler
|
|
130
|
+
client.reports.datacenter("file.epw") # free-cooling hours
|
|
131
|
+
client.reports.insurance("file.epw", heat_threshold=38)
|
|
132
|
+
|
|
133
|
+
client.data.cities() # ~3,000 curated cities
|
|
134
|
+
client.data.stations_isd(40.7, -74.0) # NOAA ISD nearby
|
|
135
|
+
client.data.cmip6_models() # available GCMs
|
|
136
|
+
client.data.design_conditions(40.7, -74.0) # ASHRAE for nearest station
|
|
137
|
+
|
|
138
|
+
client.jobs.get(job_id)
|
|
139
|
+
client.jobs.cancel(job_id)
|
|
140
|
+
client.jobs.csv(job_id, "out.epw") # download CSV alt
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Async client (for portfolio workloads)
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
pip install 'epwtools[aio]'
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
import asyncio
|
|
151
|
+
from epwtools import AsyncEPWClient
|
|
152
|
+
|
|
153
|
+
async def main():
|
|
154
|
+
async with AsyncEPWClient(api_key="...", max_concurrency=8) as client:
|
|
155
|
+
paths = await asyncio.gather(*[
|
|
156
|
+
client.generate(lat=lat, lon=lon, year=2023, name=label)
|
|
157
|
+
for (lat, lon, label) in PORTFOLIO
|
|
158
|
+
])
|
|
159
|
+
return paths
|
|
160
|
+
|
|
161
|
+
asyncio.run(main())
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### CLI
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
weatherproof generate --lat 39.74 --lon -104.98 --year 2022 --name Denver
|
|
168
|
+
weatherproof tmy --lat 40.71 --lon -74.01 --start 2005 --end 2024
|
|
169
|
+
weatherproof future --lat 48.85 --lon 2.35 --scenario ssp245 --horizon 2050
|
|
170
|
+
weatherproof export myfile.epw --format xlsx
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The CLI honours `EPWTOOLS_API_KEY` and `EPWTOOLS_API_URL` env vars (or pass
|
|
174
|
+
`--api-url`). Self-hosted backends work too — just point `EPWTOOLS_API_URL`
|
|
175
|
+
at your instance.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Features
|
|
180
|
+
|
|
181
|
+
### Generate
|
|
182
|
+
- Single-year and multi-year EPW files for any lat/lon
|
|
183
|
+
- Composite-year files that blend multiple seasons
|
|
184
|
+
- TMY selection via Sandia / Finkelstein–Schafer
|
|
185
|
+
- Future-climate morphing through 2100 from CMIP6 scenarios
|
|
186
|
+
- Ensemble runs across GCMs
|
|
187
|
+
- Batch processing from a CSV of locations
|
|
188
|
+
|
|
189
|
+
### Modify
|
|
190
|
+
- Altitude / micro-climate correction (lapse rate, barometric, wind log-law)
|
|
191
|
+
- Monthly solar calibration against reference data
|
|
192
|
+
- Urban heat-island morphing
|
|
193
|
+
- Wildfire smoke / atmospheric attenuation overlay
|
|
194
|
+
|
|
195
|
+
### Inspect
|
|
196
|
+
- Visualise any EPW (psychrometric, hourly, monthly)
|
|
197
|
+
- Validate against ASHRAE / EnergyPlus range checks
|
|
198
|
+
- Köppen–Geiger and ASHRAE / IECC climate-zone classification
|
|
199
|
+
- Heating, cooling and billing degree-day reports
|
|
200
|
+
- Independent solar sanity check (Zhang–Huang 2002)
|
|
201
|
+
|
|
202
|
+
### Output formats
|
|
203
|
+
EPW, DDY, STAT, WEA (Radiance), CLM (ESP-r), PVSyst .met, CSV, JSON,
|
|
204
|
+
Excel, Parquet, NetCDF (CF-1.8), and a one-click `.zip` bundle.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Project structure
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
epw.tools/
|
|
212
|
+
├── backend/ # FastAPI + pandas + xarray
|
|
213
|
+
│ ├── app.py # API routes + job management
|
|
214
|
+
│ ├── methods.py # Core methods
|
|
215
|
+
│ ├── ssl_utils.py
|
|
216
|
+
│ ├── resources/ # Static lookup data (design conditions, etc.)
|
|
217
|
+
│ ├── outputs/ # Generated files (auto-created)
|
|
218
|
+
│ └── requirements.txt
|
|
219
|
+
├── frontend/ # React + Vite + Tailwind
|
|
220
|
+
│ ├── src/
|
|
221
|
+
│ │ ├── App.jsx
|
|
222
|
+
│ │ ├── api/client.js
|
|
223
|
+
│ │ ├── hooks/
|
|
224
|
+
│ │ └── components/
|
|
225
|
+
│ └── package.json
|
|
226
|
+
├── run_backend.py # Convenience entry point
|
|
227
|
+
├── RESOURCES.md # Data sources & methodology
|
|
228
|
+
└── README.md
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Setup
|
|
234
|
+
|
|
235
|
+
### Quick start (5 minutes)
|
|
236
|
+
|
|
237
|
+
Generate a single-year EPW for any lat/lon from the command line:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
git clone https://github.com/your-org/epwtools.git
|
|
241
|
+
cd epwtools
|
|
242
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
243
|
+
pip install -r backend/requirements.txt
|
|
244
|
+
python run_backend.py & # backend on :8000
|
|
245
|
+
sleep 3
|
|
246
|
+
|
|
247
|
+
# Submit a job: New York City, 2022, with default fusion + QC
|
|
248
|
+
curl -X POST http://localhost:8000/api/generate/individual \
|
|
249
|
+
-H "Content-Type: application/json" \
|
|
250
|
+
-d '{"lat":40.7,"lon":-74.0,"year":2022,"location_name":"NYC"}'
|
|
251
|
+
# → returns {"job_id":"abc-..."}
|
|
252
|
+
|
|
253
|
+
# Poll until status=="completed", then download:
|
|
254
|
+
curl -s http://localhost:8000/api/jobs/abc-.../download/NYC_2022.epw -o nyc.epw
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The full UI is at `http://localhost:5173` after `cd frontend && npm install && npm run dev`.
|
|
258
|
+
|
|
259
|
+
### Compare two EPWs (e.g. baseline vs UHI-morphed):
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
python tools/epw_diff.py base.epw morphed.epw --summary
|
|
263
|
+
# {"row_count":8760,"max_dT_dry_bulb":4.0,"mean_dT_dry_bulb":1.85,...}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Backend
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
python3 -m venv .venv
|
|
270
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
271
|
+
pip install -r backend/requirements.txt
|
|
272
|
+
python run_backend.py # → http://localhost:8000
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Frontend
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
cd frontend
|
|
279
|
+
npm install
|
|
280
|
+
npm run dev # → http://localhost:5173
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
The Vite dev server proxies `/api` requests to the backend automatically.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## API overview
|
|
288
|
+
|
|
289
|
+
| Method | Endpoint | Description |
|
|
290
|
+
|---|---|---|
|
|
291
|
+
| `POST` | `/api/generate/individual` | Single-year EPW job |
|
|
292
|
+
| `POST` | `/api/generate/multi-year` | Multi-year EPW job |
|
|
293
|
+
| `POST` | `/api/generate/composite` | Composite-year EPW |
|
|
294
|
+
| `POST` | `/api/generate/tmy` | TMY (Sandia / Finkelstein–Schafer) |
|
|
295
|
+
| `POST` | `/api/generate/future` | Future-climate (CMIP6 morphing) |
|
|
296
|
+
| `POST` | `/api/generate/ensemble` | Multi-GCM ensemble |
|
|
297
|
+
| `POST` | `/api/generate/metered` | Raw measured-data download |
|
|
298
|
+
| `POST` | `/api/generate/csv-batch` | Bulk job from CSV |
|
|
299
|
+
| `POST` | `/api/tools/altitude-correct` | Apply lapse / pressure / wind correction |
|
|
300
|
+
| `POST` | `/api/tools/solar-calibrate` | Monthly solar calibration |
|
|
301
|
+
| `POST` | `/api/tools/billing-dd` | Heating / cooling / billing DD |
|
|
302
|
+
| `POST` | `/api/tools/koppen` | Köppen–Geiger classification |
|
|
303
|
+
| `POST` | `/api/tools/solar-check` | Zhang–Huang solar cross-check |
|
|
304
|
+
| `POST` | `/api/tools/export-format/{fmt}` | Export EPW to alternative format |
|
|
305
|
+
| `GET` | `/api/jobs/{job_id}` | Poll job status / logs / result |
|
|
306
|
+
| `GET` | `/api/jobs/{job_id}/stream` | SSE stream for real-time logs |
|
|
307
|
+
| `GET` | `/api/jobs/{job_id}/download/{filename}` | Download generated file |
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## License
|
|
312
|
+
|
|
313
|
+
Proprietary. All rights reserved.
|
epwtools-0.3.1/README.md
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# EPW.tools
|
|
2
|
+
|
|
3
|
+
**Every weather file you'll ever need.**
|
|
4
|
+
|
|
5
|
+
A web application + API for generating high-quality weather files for building
|
|
6
|
+
energy simulation, solar design, climate research, agriculture, insurance
|
|
7
|
+
and more.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## What it does
|
|
12
|
+
|
|
13
|
+
EPW.tools fuses measured station observations with global reanalysis and
|
|
14
|
+
satellite-derived solar data to produce dense, internally consistent weather
|
|
15
|
+
files in over a dozen formats.
|
|
16
|
+
|
|
17
|
+
| Layer | Source |
|
|
18
|
+
|---|---|
|
|
19
|
+
| Backbone | ERA5 (ECMWF) and MERRA-2 (NASA) reanalysis |
|
|
20
|
+
| Stations | NOAA ISD via Meteostat (~3 500 curated stations worldwide) |
|
|
21
|
+
| Solar | NREL NSRDB / PSM v3 where available, Zhang–Huang fallback |
|
|
22
|
+
| Future climate | NASA NEX-GDDP-CMIP6 with Belcher 2005 morphing |
|
|
23
|
+
| Design conditions | ASHRAE Handbook of Fundamentals |
|
|
24
|
+
|
|
25
|
+
Measured station data takes priority; small gaps are interpolated; longer
|
|
26
|
+
gaps are filled from reanalysis. Every file passes a battery of
|
|
27
|
+
physical-plausibility checks before release.
|
|
28
|
+
|
|
29
|
+
See [RESOURCES.md](RESOURCES.md) for the full source list and references.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Python SDK
|
|
34
|
+
|
|
35
|
+
The `epwtools` Python package provides a typed client for the REST API plus a
|
|
36
|
+
`weatherproof` CLI:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install epwtools
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Quick start
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from epwtools import EPWClient
|
|
46
|
+
|
|
47
|
+
client = EPWClient(api_key="your-key-here") # or set EPWTOOLS_API_KEY
|
|
48
|
+
|
|
49
|
+
# Single-year AMY
|
|
50
|
+
epw = client.generate(lat=40.71, lon=-74.01, year=2023, name="NewYork")
|
|
51
|
+
|
|
52
|
+
# Typical Meteorological Year
|
|
53
|
+
epw = client.tmy(lat=51.50, lon=-0.12, start=2005, end=2024, name="London")
|
|
54
|
+
|
|
55
|
+
# Future climate (CMIP6, SSP2-4.5, 2050)
|
|
56
|
+
epw = client.future(lat=48.85, lon=2.35, scenario="ssp245", horizon=2050)
|
|
57
|
+
|
|
58
|
+
# Multi-GCM ensemble (one EPW per CMIP6 model — for uncertainty bands)
|
|
59
|
+
epws = client.ensemble(lat=48.85, lon=2.35, scenario="ssp245", horizon=2050)
|
|
60
|
+
|
|
61
|
+
# Climate stress-test for a portfolio (no credits consumed)
|
|
62
|
+
table = client.portfolio_stress_test(
|
|
63
|
+
[{"lat": 40.71, "lon": -74.01, "label": "NYC"},
|
|
64
|
+
{"lat": 51.50, "lon": -0.12, "label": "London"}],
|
|
65
|
+
scenario="ssp245", horizon=2050,
|
|
66
|
+
)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Real-time progress (SSE)
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
resp = client._post("/generate/individual", json={...})
|
|
73
|
+
for ev in client.stream(resp["job_id"]):
|
|
74
|
+
if ev["type"] == "log":
|
|
75
|
+
print(" ", ev["line"])
|
|
76
|
+
elif ev["type"] == "status" and ev["status"] == "complete":
|
|
77
|
+
break
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Endpoint namespaces
|
|
81
|
+
|
|
82
|
+
The client exposes every API endpoint under five thin namespaces — use them
|
|
83
|
+
when you want to reach a specific endpoint that isn't covered by the
|
|
84
|
+
high-level generation methods:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
client.tools.uhi_adjust("base.epw", ucz="UCZ-2")
|
|
88
|
+
client.tools.koppen("file.epw") # → {"koppen": "Cfa", ...}
|
|
89
|
+
client.tools.solar_calibrate("file.epw") # PSM3 blend (US)
|
|
90
|
+
client.tools.gap_fill("partial.epw")
|
|
91
|
+
client.tools.epw_diff("a.epw", "b.epw") # → hourly delta stats
|
|
92
|
+
|
|
93
|
+
client.epw.wind_rose(job_id, "out.epw") # 16 dirs × 6 speed bins
|
|
94
|
+
client.epw.heatmap(job_id, "out.epw", variable="dry_bulb")
|
|
95
|
+
client.epw.psychro(job_id, "out.epw")
|
|
96
|
+
|
|
97
|
+
client.reports.solar("file.epw")
|
|
98
|
+
client.reports.viticulture("file.epw") # GDD, Huglin, Winkler
|
|
99
|
+
client.reports.datacenter("file.epw") # free-cooling hours
|
|
100
|
+
client.reports.insurance("file.epw", heat_threshold=38)
|
|
101
|
+
|
|
102
|
+
client.data.cities() # ~3,000 curated cities
|
|
103
|
+
client.data.stations_isd(40.7, -74.0) # NOAA ISD nearby
|
|
104
|
+
client.data.cmip6_models() # available GCMs
|
|
105
|
+
client.data.design_conditions(40.7, -74.0) # ASHRAE for nearest station
|
|
106
|
+
|
|
107
|
+
client.jobs.get(job_id)
|
|
108
|
+
client.jobs.cancel(job_id)
|
|
109
|
+
client.jobs.csv(job_id, "out.epw") # download CSV alt
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Async client (for portfolio workloads)
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pip install 'epwtools[aio]'
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
import asyncio
|
|
120
|
+
from epwtools import AsyncEPWClient
|
|
121
|
+
|
|
122
|
+
async def main():
|
|
123
|
+
async with AsyncEPWClient(api_key="...", max_concurrency=8) as client:
|
|
124
|
+
paths = await asyncio.gather(*[
|
|
125
|
+
client.generate(lat=lat, lon=lon, year=2023, name=label)
|
|
126
|
+
for (lat, lon, label) in PORTFOLIO
|
|
127
|
+
])
|
|
128
|
+
return paths
|
|
129
|
+
|
|
130
|
+
asyncio.run(main())
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### CLI
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
weatherproof generate --lat 39.74 --lon -104.98 --year 2022 --name Denver
|
|
137
|
+
weatherproof tmy --lat 40.71 --lon -74.01 --start 2005 --end 2024
|
|
138
|
+
weatherproof future --lat 48.85 --lon 2.35 --scenario ssp245 --horizon 2050
|
|
139
|
+
weatherproof export myfile.epw --format xlsx
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The CLI honours `EPWTOOLS_API_KEY` and `EPWTOOLS_API_URL` env vars (or pass
|
|
143
|
+
`--api-url`). Self-hosted backends work too — just point `EPWTOOLS_API_URL`
|
|
144
|
+
at your instance.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Features
|
|
149
|
+
|
|
150
|
+
### Generate
|
|
151
|
+
- Single-year and multi-year EPW files for any lat/lon
|
|
152
|
+
- Composite-year files that blend multiple seasons
|
|
153
|
+
- TMY selection via Sandia / Finkelstein–Schafer
|
|
154
|
+
- Future-climate morphing through 2100 from CMIP6 scenarios
|
|
155
|
+
- Ensemble runs across GCMs
|
|
156
|
+
- Batch processing from a CSV of locations
|
|
157
|
+
|
|
158
|
+
### Modify
|
|
159
|
+
- Altitude / micro-climate correction (lapse rate, barometric, wind log-law)
|
|
160
|
+
- Monthly solar calibration against reference data
|
|
161
|
+
- Urban heat-island morphing
|
|
162
|
+
- Wildfire smoke / atmospheric attenuation overlay
|
|
163
|
+
|
|
164
|
+
### Inspect
|
|
165
|
+
- Visualise any EPW (psychrometric, hourly, monthly)
|
|
166
|
+
- Validate against ASHRAE / EnergyPlus range checks
|
|
167
|
+
- Köppen–Geiger and ASHRAE / IECC climate-zone classification
|
|
168
|
+
- Heating, cooling and billing degree-day reports
|
|
169
|
+
- Independent solar sanity check (Zhang–Huang 2002)
|
|
170
|
+
|
|
171
|
+
### Output formats
|
|
172
|
+
EPW, DDY, STAT, WEA (Radiance), CLM (ESP-r), PVSyst .met, CSV, JSON,
|
|
173
|
+
Excel, Parquet, NetCDF (CF-1.8), and a one-click `.zip` bundle.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Project structure
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
epw.tools/
|
|
181
|
+
├── backend/ # FastAPI + pandas + xarray
|
|
182
|
+
│ ├── app.py # API routes + job management
|
|
183
|
+
│ ├── methods.py # Core methods
|
|
184
|
+
│ ├── ssl_utils.py
|
|
185
|
+
│ ├── resources/ # Static lookup data (design conditions, etc.)
|
|
186
|
+
│ ├── outputs/ # Generated files (auto-created)
|
|
187
|
+
│ └── requirements.txt
|
|
188
|
+
├── frontend/ # React + Vite + Tailwind
|
|
189
|
+
│ ├── src/
|
|
190
|
+
│ │ ├── App.jsx
|
|
191
|
+
│ │ ├── api/client.js
|
|
192
|
+
│ │ ├── hooks/
|
|
193
|
+
│ │ └── components/
|
|
194
|
+
│ └── package.json
|
|
195
|
+
├── run_backend.py # Convenience entry point
|
|
196
|
+
├── RESOURCES.md # Data sources & methodology
|
|
197
|
+
└── README.md
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Setup
|
|
203
|
+
|
|
204
|
+
### Quick start (5 minutes)
|
|
205
|
+
|
|
206
|
+
Generate a single-year EPW for any lat/lon from the command line:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
git clone https://github.com/your-org/epwtools.git
|
|
210
|
+
cd epwtools
|
|
211
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
212
|
+
pip install -r backend/requirements.txt
|
|
213
|
+
python run_backend.py & # backend on :8000
|
|
214
|
+
sleep 3
|
|
215
|
+
|
|
216
|
+
# Submit a job: New York City, 2022, with default fusion + QC
|
|
217
|
+
curl -X POST http://localhost:8000/api/generate/individual \
|
|
218
|
+
-H "Content-Type: application/json" \
|
|
219
|
+
-d '{"lat":40.7,"lon":-74.0,"year":2022,"location_name":"NYC"}'
|
|
220
|
+
# → returns {"job_id":"abc-..."}
|
|
221
|
+
|
|
222
|
+
# Poll until status=="completed", then download:
|
|
223
|
+
curl -s http://localhost:8000/api/jobs/abc-.../download/NYC_2022.epw -o nyc.epw
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The full UI is at `http://localhost:5173` after `cd frontend && npm install && npm run dev`.
|
|
227
|
+
|
|
228
|
+
### Compare two EPWs (e.g. baseline vs UHI-morphed):
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
python tools/epw_diff.py base.epw morphed.epw --summary
|
|
232
|
+
# {"row_count":8760,"max_dT_dry_bulb":4.0,"mean_dT_dry_bulb":1.85,...}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Backend
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
python3 -m venv .venv
|
|
239
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
240
|
+
pip install -r backend/requirements.txt
|
|
241
|
+
python run_backend.py # → http://localhost:8000
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Frontend
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
cd frontend
|
|
248
|
+
npm install
|
|
249
|
+
npm run dev # → http://localhost:5173
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The Vite dev server proxies `/api` requests to the backend automatically.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## API overview
|
|
257
|
+
|
|
258
|
+
| Method | Endpoint | Description |
|
|
259
|
+
|---|---|---|
|
|
260
|
+
| `POST` | `/api/generate/individual` | Single-year EPW job |
|
|
261
|
+
| `POST` | `/api/generate/multi-year` | Multi-year EPW job |
|
|
262
|
+
| `POST` | `/api/generate/composite` | Composite-year EPW |
|
|
263
|
+
| `POST` | `/api/generate/tmy` | TMY (Sandia / Finkelstein–Schafer) |
|
|
264
|
+
| `POST` | `/api/generate/future` | Future-climate (CMIP6 morphing) |
|
|
265
|
+
| `POST` | `/api/generate/ensemble` | Multi-GCM ensemble |
|
|
266
|
+
| `POST` | `/api/generate/metered` | Raw measured-data download |
|
|
267
|
+
| `POST` | `/api/generate/csv-batch` | Bulk job from CSV |
|
|
268
|
+
| `POST` | `/api/tools/altitude-correct` | Apply lapse / pressure / wind correction |
|
|
269
|
+
| `POST` | `/api/tools/solar-calibrate` | Monthly solar calibration |
|
|
270
|
+
| `POST` | `/api/tools/billing-dd` | Heating / cooling / billing DD |
|
|
271
|
+
| `POST` | `/api/tools/koppen` | Köppen–Geiger classification |
|
|
272
|
+
| `POST` | `/api/tools/solar-check` | Zhang–Huang solar cross-check |
|
|
273
|
+
| `POST` | `/api/tools/export-format/{fmt}` | Export EPW to alternative format |
|
|
274
|
+
| `GET` | `/api/jobs/{job_id}` | Poll job status / logs / result |
|
|
275
|
+
| `GET` | `/api/jobs/{job_id}/stream` | SSE stream for real-time logs |
|
|
276
|
+
| `GET` | `/api/jobs/{job_id}/download/{filename}` | Download generated file |
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## License
|
|
281
|
+
|
|
282
|
+
Proprietary. All rights reserved.
|