Skip to main content

PyWeatherEnriched

Real geocoding + real historical weather, wired into a Rust core with pandas/numpy climate feature engineering on top.

PyPI Python 3.10+ Tests License: Proprietary

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.org and archive-api.open-meteo.com at 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)

Source distribution for pyweatherenriched 0.6.0
File Size Uploaded
pyweatherenriched-0.6.0.tar.gz 65.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyweatherenriched 0.6.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

0.7.0

2 release files

This release

0.6.0 This release

2 release files

0.5.0

1 release file

0.4.0

1 release file

0.3.0

1 release file

0.2.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page