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.
@@ -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
@@ -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.
@@ -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.