Skip to main content

Python SDK and CLI for epw.tools — generate EPW/DDY weather files from MERRA-2, ERA5 and CMIP6

Project description

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.


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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

epwtools-0.3.0-py3-none-any.whl (28.0 kB view details)

Uploaded Python 3

File details

Details for the file epwtools-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: epwtools-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 28.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for epwtools-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d176bb9320accc165fadd14e483999b0659b3940850c670a9d56352599e5d992
MD5 33f94ad399eea2c415daa6bcd4ab911a
BLAKE2b-256 a0b8c4f2738feaac9ec98b674f10990ef9f8d933d1276dcd073582e401c9b05e

See more details on using hashes here.

Provenance

The following attestation bundles were made for epwtools-0.3.0-py3-none-any.whl:

Publisher: publish-pypi.yml on carlobianchi89/epwtools

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page