PyWeatherEnriched
Real geocoding + real historical weather, wired into a Rust core with pandas/numpy climate feature engineering on top.
Given a location name and a timestamp, PyWeatherEnriched geocodes the location (OpenStreetMap Nominatim) and looks up the real, genuinely observed historical weather for that place and hour (Open-Meteo's free Archive API) — no formula-generated or fabricated numbers. The lookup/geocoding core is Rust (via PyO3) for speed and a small memory footprint; a Python layer on top adds pandas/numpy feature engineering (rolling aggregates, heating/cooling degree-days, cyclical time encoding, anomaly z-scores) useful for feeding weather into an ML pipeline.
Install
pip install pyweatherenriched
Requires Python 3.10+. Ships as prebuilt wheels for Linux (x86_64/ARM64), macOS (Intel/Apple Silicon), and Windows (x86_64) — no Rust toolchain needed to install.
Quick start
import pyweatherenriched as pwe
enricher = pwe.WeatherEnricher()
result = enricher.enrich_row("New York", "2024-06-15T12:00:00")
print(result)
# {'location': 'New York', 'latitude': 40.7127281, 'longitude': -74.0060152,
# 'temperature': 19.4, 'humidity': 74.0, 'condition': 'Clear',
# 'timestamp': '2024-06-15T12:00:00'}
enrich_row makes two real network calls the first time it sees a
location/timestamp pair (one to Nominatim to geocode location, one to
Open-Meteo for the historical weather at those coordinates); the in-process
cache means repeating the same lookup doesn't hit the network again:
fresh = pwe.WeatherEnricher()
fresh.enrich_row("New York", "2024-06-15T12:00:00") # network
fresh.enrich_row("New York", "2024-06-15T12:00:00") # cache hit
print(fresh.cache_stats()) # {'hits': 1, 'misses': 1, 'size': 1}
Feature engineering on a DataFrame
enrich_dataframe bridges the Rust core to pandas — fetch real weather for
every row of a DataFrame in one call — and the features functions build
standard climate ML features on top of the result:
import pandas as pd
import pyweatherenriched as pwe
orders = pd.DataFrame({
"order_id": ["A1", "A2", "A3"],
"location": ["Chicago", "Miami", "Denver"],
"timestamp": ["2024-01-15T12:00:00", "2024-06-15T12:00:00", "2024-03-15T12:00:00"],
})
enricher = pwe.WeatherEnricher()
enriched = pwe.enrich_dataframe(enricher, orders) # + latitude/longitude/temperature/humidity/condition
features = pwe.build_features(enriched) # + hdd/cdd, cyclical time, rolling stats, anomaly z-score
print(features[["order_id", "temperature", "hdd", "cdd", "temperature_zscore"]])
Rows whose lookup fails (unresolvable location, upstream error) get NaN
in the new columns instead of raising or fabricating a value, so one bad
row doesn't lose the batch.
build_features is a convenience pipeline; each step is also a standalone
function you can call individually with more control:
| Function | What it adds |
|---|---|
add_degree_days(df, temp_col, base_temp=18.0) |
hdd, cdd — heating/cooling degree-days (standard energy-demand/agriculture signal: max(0, base - t) / max(0, t - base)) |
add_cyclical_time_features(df, timestamp_col) |
hour_sin/cos, day_of_week_sin/cos, day_of_year_sin/cos — sin/cos encoding so e.g. 23:59 and 00:00 stay numerically adjacent |
add_rolling_features(df, value_col, window, group_col=None, stats=(...)) |
trailing rolling aggregates (mean/std/min/max/...), optionally computed independently per group (e.g. per location) |
add_anomaly_features(df, value_col, group_col=None, baseline="expanding"|"rolling"|"global") |
a z-score measuring how unusual each reading is relative to a chosen baseline |
Caching
WeatherEnricher has a small built-in LRU cache. For larger workloads,
EnhancedCache adds a second, SQLite-backed persistent tier with
geospatial-proximity matching, TTL expiration, batch deduplication, and
date-range queries:
from pyweatherenriched import EnhancedCache
cache = EnhancedCache(cache_size=5000, db_path="weather_cache.db")
cache.set_proximity_radius(10.0) # treat lookups within 10km as cache hits
cache.set_ttl(72) # hours before an entry expires
cache.put("New York", 40.7128, -74.0060, 15.2, 65.0, "Partly Cloudy", "2024-01-15T12:00:00Z")
result = cache.get("New York", 40.7128, -74.0060, "2024-01-15T12:00:00Z")
# Deduplicate a batch before making any API calls.
batch = [
("New York", 40.7128, -74.0060, "2024-01-15T12:00:00Z"),
("New York", 40.7128, -74.0060, "2024-01-15T12:00:00Z"), # duplicate
]
missing_indices, cache_hits = cache.deduplicate_batch(batch)
# Every observation in a date range near a point (requires db_path).
rows = cache.get_range(40.7128, -74.0060, "2024-01-01T00:00:00Z", "2024-01-31T23:59:59Z")
stats = cache.stats()
print(f"hit ratio: {stats['hit_ratio']:.1%}")
get() checks the memory tier, then the persistent tier (if db_path was
given), then falls back to a proximity search; get_range only queries the
persistent tier, so it always returns [] without db_path.
What's real vs. not (yet)
- Real: forward geocoding (Nominatim), historical weather (Open-Meteo
Archive API), in-memory LRU cache, SQLite-backed
EnhancedCache(proximity matching, TTL, dedup, date-range queries), pandas/numpy feature engineering. - Not yet exposed to Python: the crate has additional Rust-side,
independently unit-tested building blocks — elevation/lapse-rate
adjustment (real SRTM GeoTIFF parsing), urban-heat-island modeling (real
OSM building-density analysis), and OSM-based reverse geocoding — that
aren't wired into the Python API yet. They live under
src/geospatial/if you want to build on them. - Deliberately unimplemented: vegetation/NDVI, soil, and flood-risk
layers, plus Google Maps/USPS reverse-geocoding sources, are framework
stubs (
src/geospatial/optional.rs) that return a clear "not yet implemented" error rather than fake data.
Development
git clone https://github.com/Mullassery/PyWeatherEnriched.git
cd PyWeatherEnriched
maturin develop --release # build the Rust extension + install editable
cargo test # Rust unit tests (32 tests)
pip install -e ".[dev]"
pytest tests/ -v # Python tests (33 tests, incl. live-network ones)
cargo run --bin pyweatherenriched-validate is a small smoke-test CLI that
exercises real geocoding + weather fetch end to end — useful to confirm a
build/environment can actually reach Nominatim and Open-Meteo.
See BUILD.md for wheel-building/release details.
Requirements
- Python 3.10+
- Rust 1.75+ (only if building from source — not needed to
pip install) - Network access to
nominatim.openstreetmap.organdarchive-api.open-meteo.comat call time (no API key needed for either)
License
Proprietary — see LICENSE. Source is public on GitHub for review; this isn't an open-source license, so redistribution/reuse isn't granted by default.
Support
Release files for pyweatherenriched 0.6.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 | |
|---|---|---|---|
| pyweatherenriched-0.6.0.tar.gz | 65.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pyweatherenriched-0.6.0-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details |
Total release size: 2.1 MB
Release files / pyweatherenriched-0.6.0.tar.gz
| Download URL | pyweatherenriched-0.6.0.tar.gz |
|---|---|
| Size | 65.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5de9dd603b752e3ec5e0a4bb9872b6245dfc31904242e673c2b09fe5c28d7fdd
|
|
BLAKE2b-256 checksum How to use checksums |
64f15487b399e4fdd562795dbc36cbbbca21c0932ec8bab5ffad17343f4cae94
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.15
|
Release files / pyweatherenriched-0.6.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | pyweatherenriched-0.6.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 2.0 MB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
c1fde715fe97a356bcdc998873bcaf3a26912d70dd0d1c948bcdc64b1efba0cc
|
|
BLAKE2b-256 checksum How to use checksums |
733009371f3f30ab105e073686e6f9a63ab052eec77df96463d8a79356d18b9d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.15
|