skyalpha / data /weather.py
puravky
Make apify_client import lazy (not installed on HF Spaces)
2cdb18a
Raw
History Blame Contribute Delete
5.54 kB
import os
import requests
from loguru import logger
from dotenv import load_dotenv
load_dotenv()
# ── City config ──────────────────────────────────────────────────────────────
# These cities have active temperature markets on Polymarket
TARGET_CITIES = {
"New York": {"lat": 40.7128, "lon": -74.0060, "apify": "New York, New York"},
"London": {"lat": 51.5074, "lon": -0.1278, "apify": "London, England"},
"Miami": {"lat": 25.7617, "lon": -80.1918, "apify": "Miami, Florida"},
"Tokyo": {"lat": 35.6762, "lon": 139.6503, "apify": "Tokyo, Japan"},
"Los Angeles": {"lat": 34.0522, "lon": -118.2437, "apify": "Los Angeles, California"},
}
# ── Apify fetcher ────────────────────────────────────────────────────────────
def fetch_weather_apify(cities: dict = TARGET_CITIES) -> dict:
"""
Fetch 10-day weather forecast via Apify's weather-scraper actor.
Returns a dict keyed by city name.
"""
token = os.getenv("APIFY_API_TOKEN")
if not token:
raise ValueError("APIFY_API_TOKEN not set in .env")
from apify_client import ApifyClient
client = ApifyClient(token)
locations = [info["apify"] for info in cities.values()]
logger.info(f"Fetching Apify weather for: {locations}")
run_input = {
"locations": locations,
"timeFrame": "ten_day",
"units": "imperial",
"maxItems": len(locations) * 10,
"proxyConfiguration": {"useApifyProxy": True},
}
run = client.actor("epctex/weather-scraper").call(run_input=run_input)
items = list(client.dataset(run["defaultDatasetId"]).iterate_items())
# Map back by city name
results = {}
for city_name in cities:
city_data = [
item for item in items
if cities[city_name]["apify"].split(",")[0].lower()
in str(item.get("city", "")).lower()
]
if city_data:
results[city_name] = city_data
logger.success(f" βœ“ {city_name}: {len(city_data)} forecast records")
else:
logger.warning(f" βœ— {city_name}: no data from Apify, falling back")
results[city_name] = _fetch_openmeteo_single(city_name, cities[city_name])
return results
# ── Open-Meteo fallback ──────────────────────────────────────────────────────
def _fetch_openmeteo_single(city_name: str, city_info: dict) -> list:
"""
Free fallback: Open-Meteo gives hourly/daily temp forecasts.
Returns list of daily dicts matching Apify output shape.
"""
url = "https://api.open-meteo.com/v1/forecast"
params = {
"latitude": city_info["lat"],
"longitude": city_info["lon"],
"daily": "temperature_2m_max,temperature_2m_min,precipitation_sum,weathercode",
"temperature_unit": "fahrenheit",
"forecast_days": 10,
"timezone": "auto",
}
resp = requests.get(url, params=params, timeout=10)
resp.raise_for_status()
data = resp.json()["daily"]
records = []
for i, date in enumerate(data["time"]):
records.append({
"city": city_name,
"date": date,
"temperature": f"{data['temperature_2m_max'][i]}/{data['temperature_2m_min'][i]}",
"high_f": data["temperature_2m_max"][i],
"low_f": data["temperature_2m_min"][i],
"precipitation": data["precipitation_sum"][i],
"source": "open-meteo",
})
logger.info(f" Open-Meteo fallback: {city_name} β†’ {len(records)} days")
return records
def fetch_weather_openmeteo(cities: dict = TARGET_CITIES) -> dict:
"""Fetch all cities directly from Open-Meteo (no Apify needed)."""
results = {}
for city_name, info in cities.items():
results[city_name] = _fetch_openmeteo_single(city_name, info)
return results
def get_today_high(city_name: str, weather_data: dict) -> float | None:
"""Extract today's high temperature for a city from fetched weather data."""
records = weather_data.get(city_name, [])
if not records:
return None
today = records[0] # First record = today
# Handle Apify format: "temperature": "24/16"
if isinstance(today.get("temperature"), str) and "/" in today["temperature"]:
try:
return float(today["temperature"].split("/")[0])
except ValueError:
pass
# Handle Open-Meteo format
return today.get("high_f")
def summarize_weather(weather_data: dict) -> str:
"""
Returns a human-readable weather summary string for the LLM agent prompt.
"""
lines = ["=== Current Weather Forecasts ==="]
for city, records in weather_data.items():
if not records:
lines.append(f" {city}: No data available")
continue
today = records[0]
temp = today.get("temperature", "N/A")
if today.get("high_f"):
temp = f"{today['high_f']}Β°F high / {today.get('low_f', '?')}Β°F low"
forecast = today.get("forecast", today.get("weathercode", "N/A"))
lines.append(f" {city}: {temp} | Conditions: {forecast}")
return "\n".join(lines)