Weather Data Pipeline¶
This page explains how idfkit's weather module works and the concepts behind weather station data and design days.
Differs in JavaScript
Retrieving weather and design-day files exists in both libraries and does not behave the same way in JavaScript. The ledger records it as Python complete, JavaScript partial, and what differs is stated here rather than left to be discovered.
Both libraries retrieve a station's ZIP archive from climate.onebuilding.org and unpack
the EPW, DDY, and STAT members out of it. What they hand back differs, and it differs in
the first line a reader writes. Python's WeatherDownloader.download returns a
WeatherFiles whose epw, ddy, stat, and zip_path are Path objects, because the files are
on disk by the time it returns and a path is what EnergyPlus is given. TypeScript's
fetchWeatherFiles returns a WeatherFiles whose epw, ddy, and stat are the file TEXT,
alongside a members map holding every archive member as bytes, because a browser has no
disk and the text is what @idfkit/engine takes. Same name, different values, which the
naming register records under the retrieved weather files: code written from one
language's documentation is wrong at runtime against the other rather than merely
awkward.
Three things exist on one side only, and each follows from that one fact rather than from a gap. Python's download(station, only={".epw"}) extracts a chosen subset and returns PartialWeatherFiles; selective extraction is a property of writing into a cache, and the JavaScript side decodes from memory whatever the archive held. TypeScript's FetchWeatherOptions carries fetch, rewriteUrl, and signal, because climate.onebuilding.org sends no Access-Control-Allow-Origin header and a page can reach it only through a proxy the caller supplies; Python runs under no same-origin policy and owns its own urllib requests. TypeScript splits retrieving from writing, so @idfkit/weather/node adds saveWeatherFiles and SavedWeatherFiles, where in Python the two are one operation.
Resolving a canonical EPW filename differs in one argument. Python's index parameter is optional and defaults to the bundled index, because it can always find one on disk. TypeScript's is required, because the caller had to obtain an index already and the function has nowhere to load one from.
typescript = "partial" records the missing selective extraction and nothing else. It is
not a verdict on the on-disk cache: that is
weather-file-cache, which is
permanently absent by decision rather than missing.
The full entry, including the vocabulary this capability owns, is on the capability parity page.
Data Source: climate.onebuilding.org¶
idfkit's weather station index is built from the climate.onebuilding.org TMYx weather file collection. This is the most comprehensive free source of EnergyPlus weather files, containing:
- ~70,000 dataset entries from 10 world regions
- ~17,300 unique physical weather stations
- Coverage of 248 countries and territories
The difference between entries and stations exists because each physical
station may have multiple TMYx year-range variants (e.g., TMYx.2007-2021,
TMYx.2009-2023), each stored as a separate entry with its own download URL.
Station Index Architecture¶
The StationIndex provides two modes of operation:
Bundled Index (No Dependencies)¶
StationIndex.load() loads a pre-compiled index bundled with the package:
from idfkit.weather import StationIndex
index = StationIndex.load() # Instant, no network
print(f"{len(index)} entries, {len(index.countries)} countries")
This works without any extra dependencies or network access.
Live Refresh¶
StationIndex.refresh() downloads the latest regional KML indexes
from climate.onebuilding.org and rebuilds the cached index:
# Check if upstream data has changed
if index.check_for_updates():
index = StationIndex.refresh() # Downloads ~10 regional KML files
Refresh uses the Python standard library only — no third-party packages required.
Station vs Entry¶
Understanding the distinction:
| Concept | Description |
|---|---|
| Station | A physical weather monitoring location (e.g., Chicago O'Hare) |
| Entry | A specific TMYx dataset for a station (e.g., TMYx.2007-2021) |
A single station often has multiple entries with different year ranges.
When searching, results include all matching entries. Use the station's
wmo number to identify the same physical location across entries.
WMO Numbers¶
WMO (World Meteorological Organization) numbers identify weather stations internationally. Important notes:
- WMO numbers are not unique per station — multiple stations can share one
- Use
display_namefor human-readable identification - Use
urlfor the exact dataset you want to download
# Multiple entries can have the same WMO
results = index.search("725300") # Chicago O'Hare WMO
for r in results:
print(f"{r.station.source}: {r.station.url}")
Spatial Search¶
The nearest() method uses the Haversine formula for great-circle
distance calculations:
# Find nearest stations to a coordinate
results = index.nearest(41.88, -87.63, limit=5)
for r in results:
print(f"{r.station.display_name}: {r.distance_km:.1f} km")
Combine with geocode() for address-based lookups:
from idfkit.weather import geocode
lat, lon = geocode("350 Fifth Avenue, New York, NY")
results = index.nearest(lat, lon)
Climate Zone Metadata¶
Each WeatherStation carries the ASHRAE HOF climate zone label
(e.g. "4A - Mixed - Humid") along with 99% heating and 1% cooling
design dry-bulb temperatures, HDD18, and CDD10. These come from the KML
indexes published alongside each WMO region on
climate.onebuilding.org.
Use plain Python list comprehensions to filter by zone:
from idfkit.weather import StationIndex
index = StationIndex.load()
zone_4a = [s for s in index.stations if s.ashrae_climate_zone.startswith("4A")]
When a station inherits design conditions from a neighbouring station,
the source WMO is recorded in design_conditions_source_wmo (otherwise
None).
Design Day Classification¶
DDY files contain SizingPeriod:DesignDay objects using ASHRAE naming
conventions. The DesignDayManager parses these and classifies each
design day by type:
| Type | Pattern | Example |
|---|---|---|
HEATING_99_6 |
Htg 99.6% Condns DB |
Chicago Ann Htg 99.6% Condns DB |
HEATING_99 |
Htg 99% Condns DB |
Chicago Ann Htg 99% Condns DB |
COOLING_DB_0_4 |
Clg .4% Condns DB=>MWB |
Chicago Ann Clg .4% Condns DB=>MWB |
COOLING_DB_1 |
Clg 1% Condns DB=>MWB |
Chicago Ann Clg 1% Condns DB=>MWB |
COOLING_WB_1 |
Clg 1% Condns WB=>MDB |
Chicago Ann Clg 1% Condns WB=>MDB |
Real DDY files typically contain 114+ design days:
- 18 annual design days (heating, cooling, dehumidification, etc.)
- 96 monthly design days (12 months × 4 percentiles × 2 types)
ASHRAE Standards¶
Different ASHRAE standards recommend different design day percentiles:
| Standard | Heating | Cooling |
|---|---|---|
| ASHRAE 90.1 | 99.6% | 1% |
| ASHRAE 62.1 | 99% | 1% |
Use apply_to_model() or apply_ashrae_sizing() with the appropriate
percentiles:
from idfkit.weather import DesignDayManager
ddm = DesignDayManager("chicago.ddy")
# ASHRAE 90.1 (stricter heating condition)
ddm.apply_to_model(model, heating="99.6%", cooling="1%")
# Or use the convenience function with standard presets
from idfkit.weather import apply_ashrae_sizing
apply_ashrae_sizing(model, station, standard="90.1")
Caching¶
Weather data is cached to avoid redundant downloads:
| Data | Cache Location | Lifetime |
|---|---|---|
| Station indexes | ~/.cache/idfkit/weather/indexes/ |
Until refresh |
| Weather files (EPW, DDY) | ~/.cache/idfkit/weather/files/ |
Permanent |
The cache location follows platform conventions:
- Linux:
~/.cache/idfkit/ - macOS:
~/Library/Caches/idfkit/ - Windows:
%LOCALAPPDATA%\idfkit\cache\
See Also¶
- How to search for weather stations — Practical search guide
- How to apply design days — Applying design days to models
- Caching Strategy — General caching architecture