EPW.tools
Every weather file you'll ever need.
A web application + API for generating high-quality weather files for building energy simulation, solar design, climate research, agriculture, insurance and more.
What it does
EPW.tools fuses measured station observations with global reanalysis and satellite-derived solar data to produce dense, internally consistent weather files in over a dozen formats.
| Layer | Source |
|---|---|
| Backbone | ERA5 (ECMWF) and MERRA-2 (NASA) reanalysis |
| Stations | NOAA ISD via Meteostat (~3 500 curated stations worldwide) |
| Solar | NREL NSRDB / PSM v3 where available, Zhang–Huang fallback |
| Future climate | NASA NEX-GDDP-CMIP6 with Belcher 2005 morphing |
| Design conditions | ASHRAE Handbook of Fundamentals |
Measured station data takes priority; small gaps are interpolated; longer gaps are filled from reanalysis. Every file passes a battery of physical-plausibility checks before release.
See RESOURCES.md for the full source list and references.
Python SDK
The epwtools Python package provides a typed client for the REST API plus a
weatherproof CLI:
pip install epwtools
Quick start
from epwtools import EPWClient
client = EPWClient(api_key="your-key-here") # or set EPWTOOLS_API_KEY
# Single-year AMY
epw = client.generate(lat=40.71, lon=-74.01, year=2023, name="NewYork")
# Typical Meteorological Year
epw = client.tmy(lat=51.50, lon=-0.12, start=2005, end=2024, name="London")
# Future climate (CMIP6, SSP2-4.5, 2050)
epw = client.future(lat=48.85, lon=2.35, scenario="ssp245", horizon=2050)
# Multi-GCM ensemble (one EPW per CMIP6 model — for uncertainty bands)
epws = client.ensemble(lat=48.85, lon=2.35, scenario="ssp245", horizon=2050)
# Climate stress-test for a portfolio (no credits consumed)
table = client.portfolio_stress_test(
[{"lat": 40.71, "lon": -74.01, "label": "NYC"},
{"lat": 51.50, "lon": -0.12, "label": "London"}],
scenario="ssp245", horizon=2050,
)
Real-time progress (SSE)
resp = client._post("/generate/individual", json={...})
for ev in client.stream(resp["job_id"]):
if ev["type"] == "log":
print(" ", ev["line"])
elif ev["type"] == "status" and ev["status"] == "complete":
break
Endpoint namespaces
The client exposes every API endpoint under five thin namespaces — use them when you want to reach a specific endpoint that isn't covered by the high-level generation methods:
client.tools.uhi_adjust("base.epw", ucz="UCZ-2")
client.tools.koppen("file.epw") # → {"koppen": "Cfa", ...}
client.tools.solar_calibrate("file.epw") # PSM3 blend (US)
client.tools.gap_fill("partial.epw")
client.tools.epw_diff("a.epw", "b.epw") # → hourly delta stats
client.epw.wind_rose(job_id, "out.epw") # 16 dirs × 6 speed bins
client.epw.heatmap(job_id, "out.epw", variable="dry_bulb")
client.epw.psychro(job_id, "out.epw")
client.reports.solar("file.epw")
client.reports.viticulture("file.epw") # GDD, Huglin, Winkler
client.reports.datacenter("file.epw") # free-cooling hours
client.reports.insurance("file.epw", heat_threshold=38)
client.data.cities() # ~3,000 curated cities
client.data.stations_isd(40.7, -74.0) # NOAA ISD nearby
client.data.cmip6_models() # available GCMs
client.data.design_conditions(40.7, -74.0) # ASHRAE for nearest station
client.jobs.get(job_id)
client.jobs.cancel(job_id)
client.jobs.csv(job_id, "out.epw") # download CSV alt
Async client (for portfolio workloads)
pip install 'epwtools[aio]'
import asyncio
from epwtools import AsyncEPWClient
async def main():
async with AsyncEPWClient(api_key="...", max_concurrency=8) as client:
paths = await asyncio.gather(*[
client.generate(lat=lat, lon=lon, year=2023, name=label)
for (lat, lon, label) in PORTFOLIO
])
return paths
asyncio.run(main())
CLI
weatherproof generate --lat 39.74 --lon -104.98 --year 2022 --name Denver
weatherproof tmy --lat 40.71 --lon -74.01 --start 2005 --end 2024
weatherproof future --lat 48.85 --lon 2.35 --scenario ssp245 --horizon 2050
weatherproof export myfile.epw --format xlsx
The CLI honours EPWTOOLS_API_KEY and EPWTOOLS_API_URL env vars (or pass
--api-url). Self-hosted backends work too — just point EPWTOOLS_API_URL
at your instance.
AI assistants (MCP server)
EPW.tools ships a Model Context Protocol
server, so AI assistants (Claude Desktop, Cursor, and other MCP-capable agents)
can generate weather files directly — no browser. Tools exposed: generate_epw,
generate_tmy, generate_future_climate, generate_forecast,
get_nearest_stations, get_job_result.
Add this to your MCP client config (one line, nothing to clone — it uses uv to run on demand):
{
"mcpServers": {
"epw-tools": {
"command": "uvx",
"args": ["--from", "epwtools[mcp]", "epw-tools-mcp"],
"env": { "EPWTOOLS_API_KEY": "epw_your_key_here" }
}
}
}
Prefer pip? pip install 'epwtools[mcp]', then set the command to
epw-tools-mcp. The API base defaults to the public backend
(https://epw.tools/api); override with EPWTOOLS_API_BASE for a self-hosted
instance. Get an API key at epw.tools — most generated files
use one credit (a future-climate file uses three), and new accounts start with
free credits.
Features
Generate
- Single-year and multi-year EPW files for any lat/lon
- Composite-year files that blend multiple seasons
- TMY selection via Sandia / Finkelstein–Schafer
- Future-climate morphing through 2100 from CMIP6 scenarios
- Ensemble runs across GCMs
- Batch processing from a CSV of locations
Modify
- Altitude / micro-climate correction (lapse rate, barometric, wind log-law)
- Monthly solar calibration against reference data
- Urban heat-island morphing
- Wildfire smoke / atmospheric attenuation overlay
Inspect
- Visualise any EPW (psychrometric, hourly, monthly)
- Validate against ASHRAE / EnergyPlus range checks
- Köppen–Geiger and ASHRAE / IECC climate-zone classification
- Heating, cooling and billing degree-day reports
- Independent solar sanity check (Zhang–Huang 2002)
Output formats
EPW, DDY, STAT, WEA (Radiance), CLM (ESP-r), PVSyst .met, CSV, JSON,
Excel, Parquet, NetCDF (CF-1.8), and a one-click .zip bundle.
Project structure
epw.tools/
├── backend/ # FastAPI + pandas + xarray
│ ├── app.py # API routes + job management
│ ├── methods.py # Core methods
│ ├── ssl_utils.py
│ ├── resources/ # Static lookup data (design conditions, etc.)
│ ├── outputs/ # Generated files (auto-created)
│ └── requirements.txt
├── frontend/ # React + Vite + Tailwind
│ ├── src/
│ │ ├── App.jsx
│ │ ├── api/client.js
│ │ ├── hooks/
│ │ └── components/
│ └── package.json
├── run_backend.py # Convenience entry point
├── RESOURCES.md # Data sources & methodology
└── README.md
Setup
Quick start (5 minutes)
Generate a single-year EPW for any lat/lon from the command line:
git clone https://github.com/your-org/epwtools.git
cd epwtools
python3 -m venv .venv && source .venv/bin/activate
pip install -r backend/requirements.txt
python run_backend.py & # backend on :8000
sleep 3
# Submit a job: New York City, 2022, with default fusion + QC
curl -X POST http://localhost:8000/api/generate/individual \
-H "Content-Type: application/json" \
-d '{"lat":40.7,"lon":-74.0,"year":2022,"location_name":"NYC"}'
# → returns {"job_id":"abc-..."}
# Poll until status=="completed", then download:
curl -s http://localhost:8000/api/jobs/abc-.../download/NYC_2022.epw -o nyc.epw
The full UI is at http://localhost:5173 after cd frontend && npm install && npm run dev.
Compare two EPWs (e.g. baseline vs UHI-morphed):
python tools/epw_diff.py base.epw morphed.epw --summary
# {"row_count":8760,"max_dT_dry_bulb":4.0,"mean_dT_dry_bulb":1.85,...}
Backend
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r backend/requirements.txt
python run_backend.py # → http://localhost:8000
Frontend
cd frontend
npm install
npm run dev # → http://localhost:5173
The Vite dev server proxies /api requests to the backend automatically.
API overview
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/generate/individual |
Single-year EPW job |
POST |
/api/generate/multi-year |
Multi-year EPW job |
POST |
/api/generate/composite |
Composite-year EPW |
POST |
/api/generate/tmy |
TMY (Sandia / Finkelstein–Schafer) |
POST |
/api/generate/future |
Future-climate (CMIP6 morphing) |
POST |
/api/generate/ensemble |
Multi-GCM ensemble |
POST |
/api/generate/metered |
Raw measured-data download |
POST |
/api/generate/csv-batch |
Bulk job from CSV |
POST |
/api/tools/altitude-correct |
Apply lapse / pressure / wind correction |
POST |
/api/tools/solar-calibrate |
Monthly solar calibration |
POST |
/api/tools/billing-dd |
Heating / cooling / billing DD |
POST |
/api/tools/koppen |
Köppen–Geiger classification |
POST |
/api/tools/solar-check |
Zhang–Huang solar cross-check |
POST |
/api/tools/export-format/{fmt} |
Export EPW to alternative format |
GET |
/api/jobs/{job_id} |
Poll job status / logs / result |
GET |
/api/jobs/{job_id}/stream |
SSE stream for real-time logs |
GET |
/api/jobs/{job_id}/download/{filename} |
Download generated file |
License
Proprietary. All rights reserved.
Release files for epwtools 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| epwtools-0.4.0.tar.gz | 32.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| epwtools-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 67.5 kB
Release files / epwtools-0.4.0.tar.gz
| Download URL | epwtools-0.4.0.tar.gz |
|---|---|
| Size | 32.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
50aeafe748d1023079239b0720471e0f13e5a363d4141a17f8b6b7bdfdc6eb36
|
|
BLAKE2b-256 checksum How to use checksums |
facee92a7ed5b2419876b84c0ef1b23f3f312261a0ccec1d0d4574b413c9b810
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.12
|
Release files / epwtools-0.4.0-py3-none-any.whl
| Download URL | epwtools-0.4.0-py3-none-any.whl |
|---|---|
| Size | 34.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
95a01b142203398032fd03dcfb80129030c43d043d7452b4aee72320df251a95
|
|
BLAKE2b-256 checksum How to use checksums |
45b1cd6e5bd9a311750f932f73865168149f0b76e18a626e716ed134bc0d15f8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.12
|