"""
weather_cities.py
Fetch monthly mean temperature for 50 cities (year 2023) via Open-Meteo.
Output: assets/toy/weather/weather_cities.csv (600 rows)
"""
from pathlib import Path
import sys
import time
import requests
import pandas as pd
sys.stdout.reconfigure(encoding="utf-8")
YEAR = 2023
CITIES = [
# Asia (12)
{"city": "Tokyo", "country": "Japan", "continent": "Asia", "lat": 35.68, "lon": 139.69},
{"city": "Beijing", "country": "China", "continent": "Asia", "lat": 39.90, "lon": 116.41},
{"city": "Shanghai", "country": "China", "continent": "Asia", "lat": 31.23, "lon": 121.47},
{"city": "Hong Kong", "country": "China", "continent": "Asia", "lat": 22.32, "lon": 114.17},
{"city": "Singapore", "country": "Singapore", "continent": "Asia", "lat": 1.35, "lon": 103.82},
{"city": "Bangkok", "country": "Thailand", "continent": "Asia", "lat": 13.76, "lon": 100.50},
{"city": "Mumbai", "country": "India", "continent": "Asia", "lat": 19.08, "lon": 72.88},
{"city": "Delhi", "country": "India", "continent": "Asia", "lat": 28.61, "lon": 77.21},
{"city": "Seoul", "country": "South Korea", "continent": "Asia", "lat": 37.57, "lon": 126.98},
{"city": "Kuala Lumpur", "country": "Malaysia", "continent": "Asia", "lat": 3.14, "lon": 101.69},
{"city": "Jakarta", "country": "Indonesia", "continent": "Asia", "lat": -6.21, "lon": 106.85},
{"city": "Karachi", "country": "Pakistan", "continent": "Asia", "lat": 24.86, "lon": 67.01},
# Europe (11)
{"city": "London", "country": "UK", "continent": "Europe", "lat": 51.51, "lon": -0.13},
{"city": "Paris", "country": "France", "continent": "Europe", "lat": 48.86, "lon": 2.35},
{"city": "Berlin", "country": "Germany", "continent": "Europe", "lat": 52.52, "lon": 13.41},
{"city": "Moscow", "country": "Russia", "continent": "Europe", "lat": 55.76, "lon": 37.62},
{"city": "Madrid", "country": "Spain", "continent": "Europe", "lat": 40.42, "lon": -3.70},
{"city": "Rome", "country": "Italy", "continent": "Europe", "lat": 41.90, "lon": 12.50},
{"city": "Amsterdam", "country": "Netherlands", "continent": "Europe", "lat": 52.37, "lon": 4.90},
{"city": "Stockholm", "country": "Sweden", "continent": "Europe", "lat": 59.33, "lon": 18.07},
{"city": "Istanbul", "country": "Turkey", "continent": "Europe", "lat": 41.01, "lon": 28.98},
{"city": "Zurich", "country": "Switzerland", "continent": "Europe", "lat": 47.38, "lon": 8.54},
{"city": "Reykjavik", "country": "Iceland", "continent": "Europe", "lat": 64.13, "lon": -21.82},
# North America (8)
{"city": "New York", "country": "USA", "continent": "North America", "lat": 40.71, "lon": -74.01},
{"city": "Los Angeles", "country": "USA", "continent": "North America", "lat": 34.05, "lon": -118.24},
{"city": "Chicago", "country": "USA", "continent": "North America", "lat": 41.88, "lon": -87.63},
{"city": "Miami", "country": "USA", "continent": "North America", "lat": 25.76, "lon": -80.19},
{"city": "Houston", "country": "USA", "continent": "North America", "lat": 29.76, "lon": -95.37},
{"city": "Toronto", "country": "Canada", "continent": "North America", "lat": 43.65, "lon": -79.38},
{"city": "Vancouver", "country": "Canada", "continent": "North America", "lat": 49.28, "lon": -123.12},
{"city": "Mexico City", "country": "Mexico", "continent": "North America", "lat": 19.43, "lon": -99.13},
# South America (5)
{"city": "São Paulo", "country": "Brazil", "continent": "South America", "lat": -23.55, "lon": -46.63},
{"city": "Buenos Aires", "country": "Argentina", "continent": "South America", "lat": -34.60, "lon": -58.38},
{"city": "Lima", "country": "Peru", "continent": "South America", "lat": -12.05, "lon": -77.04},
{"city": "Bogotá", "country": "Colombia", "continent": "South America", "lat": 4.71, "lon": -74.07},
{"city": "Santiago", "country": "Chile", "continent": "South America", "lat": -33.45, "lon": -70.67},
# Africa (6)
{"city": "Cairo", "country": "Egypt", "continent": "Africa", "lat": 30.04, "lon": 31.24},
{"city": "Lagos", "country": "Nigeria", "continent": "Africa", "lat": 6.52, "lon": 3.38},
{"city": "Nairobi", "country": "Kenya", "continent": "Africa", "lat": -1.29, "lon": 36.82},
{"city": "Cape Town", "country": "South Africa", "continent": "Africa", "lat": -33.92, "lon": 18.42},
{"city": "Casablanca", "country": "Morocco", "continent": "Africa", "lat": 33.57, "lon": -7.59},
{"city": "Addis Ababa", "country": "Ethiopia", "continent": "Africa", "lat": 9.03, "lon": 38.74},
# Middle East (4)
{"city": "Dubai", "country": "UAE", "continent": "Middle East", "lat": 25.20, "lon": 55.27},
{"city": "Riyadh", "country": "Saudi Arabia", "continent": "Middle East", "lat": 24.71, "lon": 46.68},
{"city": "Tel Aviv", "country": "Israel", "continent": "Middle East", "lat": 32.09, "lon": 34.78},
{"city": "Tehran", "country": "Iran", "continent": "Middle East", "lat": 35.69, "lon": 51.39},
# Oceania (4)
{"city": "Sydney", "country": "Australia", "continent": "Oceania", "lat": -33.87, "lon": 151.21},
{"city": "Melbourne", "country": "Australia", "continent": "Oceania", "lat": -37.81, "lon": 144.96},
{"city": "Brisbane", "country": "Australia", "continent": "Oceania", "lat": -27.47, "lon": 153.03},
{"city": "Auckland", "country": "New Zealand", "continent": "Oceania", "lat": -36.85, "lon": 174.76},
]
def fetch_monthly(city: dict) -> pd.DataFrame:
resp = requests.get(
"https://archive-api.open-meteo.com/v1/archive",
params={
"latitude": city["lat"],
"longitude": city["lon"],
"start_date": f"{YEAR}-01-01",
"end_date": f"{YEAR}-12-31",
"daily": "temperature_2m_mean",
"timezone": "auto",
},
timeout=30,
)
resp.raise_for_status()
daily = resp.json()["daily"]
df = pd.DataFrame({"date": daily["time"], "temp_c": daily["temperature_2m_mean"]})
df["date"] = pd.to_datetime(df["date"])
df["month"] = df["date"].dt.month
monthly = df.groupby("month")["temp_c"].mean().reset_index()
monthly.rename(columns={"temp_c": "avg_temp_c"}, inplace=True)
monthly["avg_temp_c"] = monthly["avg_temp_c"].round(1)
monthly["city"] = city["city"]
monthly["country"] = city["country"]
monthly["continent"] = city["continent"]
monthly["year"] = YEAR
return monthly[["city", "country", "continent", "year", "month", "avg_temp_c"]]
def main():
records = []
for i, city in enumerate(CITIES, 1):
print(f"[{i:02d}/50] {city['city']}...")
try:
records.append(fetch_monthly(city))
except Exception as e:
print(f" ERROR: {e}")
time.sleep(0.3)
result = pd.concat(records, ignore_index=True)
out = Path(__file__).with_name("weather_cities.csv")
result.to_csv(out, index=False)
print(f"\nDone — {len(result)} rows saved to {out}")
if __name__ == "__main__":
main()