Sommar / weather_integration.py
KSAklfszf921
Implementera fullständig väderintegrering för Sommar i P1
52c9875
#!/usr/bin/env python3
"""
🌤️ Väderintegrering för Sommar i P1
Hämtar och integrerar historisk väderdata från SMHI för alla episoder.
"""
import json
import requests
import time
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple
import os
from pathlib import Path
class SMHIWeatherClient:
"""SMHI API-klient för historisk väderdata"""
BASE_URL = "https://opendata-download-metobs.smhi.se/api"
# Relevanta väderparametrar för P1 Sommar
WEATHER_PARAMETERS = {
1: {"name": "Lufttemperatur", "unit": "°C", "type": "instant"},
2: {"name": "Medeltemperatur", "unit": "°C", "type": "daily"},
20: {"name": "Maxtemperatur", "unit": "°C", "type": "daily"},
5: {"name": "Nederbörd dygn", "unit": "mm", "type": "daily"},
7: {"name": "Nederbörd timme", "unit": "mm", "type": "hourly"},
4: {"name": "Vindhastighet", "unit": "m/s", "type": "hourly"},
3: {"name": "Vindriktning", "unit": "°", "type": "hourly"},
16: {"name": "Molnmängd", "unit": "%", "type": "hourly"},
6: {"name": "Luftfuktighet", "unit": "%", "type": "hourly"}
}
# Svenska väderstationer som täcker olika delar av landet
MAJOR_STATIONS = {
"stockholm": {"id": 98230, "name": "Stockholm", "region": "Mellansverige"},
"goteborg": {"id": 71420, "name": "Göteborg", "region": "Västsverige"},
"malmo": {"id": 52350, "name": "Malmö", "region": "Sydsverige"},
"umea": {"id": 140480, "name": "Umeå", "region": "Nordsverige"},
"kiruna": {"id": 166870, "name": "Kiruna", "region": "Nordsverige"},
"visby": {"id": 78280, "name": "Visby", "region": "Gotland"},
"karlstad": {"id": 105260, "name": "Karlstad", "region": "Värmland"}
}
def __init__(self, cache_dir: str = "weather_cache"):
"""Initialisera SMHI weather client"""
self.cache_dir = Path(cache_dir)
self.cache_dir.mkdir(exist_ok=True)
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'P1-Sommar-Weather-Integration/1.0 (research-project)'
})
def get_cache_filename(self, station_id: int, parameter_id: int, date: str) -> Path:
"""Generera cache-filnamn"""
return self.cache_dir / f"weather_{station_id}_{parameter_id}_{date}.json"
def fetch_station_data(self, station_id: int, parameter_id: int,
from_date: str, to_date: str) -> Optional[Dict]:
"""Hämta väderdata från SMHI API"""
# Kontrollera cache först
cache_file = self.get_cache_filename(station_id, parameter_id, from_date)
if cache_file.exists():
try:
with open(cache_file, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"⚠️ Cache-fel: {e}")
# Bygg API URL
url = f"{self.BASE_URL}/version/1.0/parameter/{parameter_id}/station/{station_id}/period/corrected-archive/data.json"
try:
print(f"🌤️ Hämtar väderdata: station {station_id}, parameter {parameter_id}")
response = self.session.get(url, timeout=30)
response.raise_for_status()
data = response.json()
# Spara i cache
with open(cache_file, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# Rate limiting för att inte överbelasta SMHI API
time.sleep(1)
return data
except requests.exceptions.RequestException as e:
print(f"❌ API-fel för station {station_id}: {e}")
return None
except Exception as e:
print(f"❌ Oväntat fel: {e}")
return None
def extract_weather_for_date(self, weather_data: Dict, target_date: str) -> Optional[Dict]:
"""Extrahera väderdata för specifikt datum"""
if not weather_data or 'value' not in weather_data:
return None
target_timestamp = int(datetime.strptime(target_date, "%Y-%m-%d").timestamp() * 1000)
# Hitta närmaste datum inom +/- 1 dag
closest_value = None
min_diff = float('inf')
for entry in weather_data['value']:
if 'date' not in entry or 'value' not in entry:
continue
entry_timestamp = entry['date']
diff = abs(entry_timestamp - target_timestamp)
# Inom 24 timmar (86400000 ms)
if diff < 86400000 and diff < min_diff:
min_diff = diff
closest_value = entry
if closest_value:
return {
'value': closest_value['value'],
'date': closest_value['date'],
'quality': closest_value.get('quality', 'unknown')
}
return None
def get_comprehensive_weather(self, date: str, station_key: str = "stockholm") -> Dict:
"""Hämta omfattande väderdata för ett datum"""
if station_key not in self.MAJOR_STATIONS:
station_key = "stockholm"
station_info = self.MAJOR_STATIONS[station_key]
station_id = station_info["id"]
weather_data = {
"date": date,
"station": station_info,
"parameters": {},
"summary": "",
"condition": "unknown"
}
# Prioriterade parametrar för P1 Sommar
priority_params = [2, 5, 4, 16, 6] # Medeltemp, nederbörd, vind, moln, fukt
for param_id in priority_params:
if param_id in self.WEATHER_PARAMETERS:
raw_data = self.fetch_station_data(station_id, param_id, date, date)
if raw_data:
weather_point = self.extract_weather_for_date(raw_data, date)
if weather_point:
param_info = self.WEATHER_PARAMETERS[param_id]
weather_data["parameters"][param_id] = {
"name": param_info["name"],
"value": weather_point["value"],
"unit": param_info["unit"],
"quality": weather_point["quality"]
}
# Generera vädersammanfattning
weather_data["summary"] = self.generate_weather_summary(weather_data["parameters"])
weather_data["condition"] = self.determine_weather_condition(weather_data["parameters"])
return weather_data
def generate_weather_summary(self, parameters: Dict) -> str:
"""Generera läsbar vädersammanfattning"""
summary_parts = []
# Temperatur
if 2 in parameters: # Medeltemperatur
temp = parameters[2]["value"]
summary_parts.append(f"{temp:.1f}°C")
# Nederbörd
if 5 in parameters: # Dagsnederbörd
precip = parameters[5]["value"]
if precip > 10:
summary_parts.append(f"{precip:.1f}mm regn")
elif precip > 0.1:
summary_parts.append(f"lätt regn ({precip:.1f}mm)")
else:
summary_parts.append("torrt")
# Vind
if 4 in parameters: # Vindhastighet
wind = parameters[4]["value"]
if wind > 10:
summary_parts.append(f"blåsigt ({wind:.1f}m/s)")
elif wind > 5:
summary_parts.append(f"måttlig vind ({wind:.1f}m/s)")
# Molnighet
if 16 in parameters: # Molnmängd
clouds = parameters[16]["value"]
if clouds > 75:
summary_parts.append("mulet")
elif clouds > 50:
summary_parts.append("molnigt")
elif clouds < 25:
summary_parts.append("klart")
return ", ".join(summary_parts) if summary_parts else "Väderdata ej tillgänglig"
def determine_weather_condition(self, parameters: Dict) -> str:
"""Bestäm vädertillstånd för emoji/ikon"""
# Nederbörd först
if 5 in parameters and parameters[5]["value"] > 1:
return "rain"
# Temperatur och molnighet
temp = parameters.get(2, {}).get("value", 15)
clouds = parameters.get(16, {}).get("value", 50)
if temp < 0:
if clouds > 75:
return "snow"
else:
return "cold"
elif temp > 25:
if clouds < 25:
return "sunny_hot"
else:
return "warm"
else:
if clouds < 25:
return "sunny"
elif clouds > 75:
return "cloudy"
else:
return "partly_cloudy"
def get_weather_emoji(self, condition: str) -> str:
"""Returnera emoji för vädertillstånd"""
emoji_map = {
"sunny": "☀️",
"sunny_hot": "🌞",
"partly_cloudy": "⛅",
"cloudy": "☁️",
"rain": "🌧️",
"snow": "❄️",
"cold": "🥶",
"warm": "🌤️",
"unknown": "🌈"
}
return emoji_map.get(condition, "🌈")
def integrate_weather_data():
"""Huvudfunktion för att integrera väderdata med P1 Sommar episoder"""
print("🌤️ Startar väderintegrering för Sommar i P1...")
# Ladda befintliga episoder
try:
with open('data.json', 'r', encoding='utf-8') as f:
episodes = json.load(f)
print(f"✅ Laddade {len(episodes)} episoder")
except Exception as e:
print(f"❌ Kunde inte ladda episoder: {e}")
return
weather_client = SMHIWeatherClient()
weather_enhanced_episodes = []
# Bearbeta episoder med väderdata
for i, episode in enumerate(episodes):
if i % 10 == 0:
print(f"📊 Bearbetar episod {i+1}/{len(episodes)}")
episode_date = episode.get('episode_date', '')
if not episode_date:
print(f"⚠️ Saknar datum för episod: {episode.get('episode_title', 'Okänt')}")
weather_enhanced_episodes.append(episode)
continue
# Hämta väderdata
weather_data = weather_client.get_comprehensive_weather(episode_date)
# Lägg till väderdata till episod
episode_with_weather = episode.copy()
episode_with_weather['weather_data'] = weather_data
weather_enhanced_episodes.append(episode_with_weather)
# Spara mellanresultat var 50:e episod
if (i + 1) % 50 == 0:
backup_file = f'data_with_weather_backup_{i+1}.json'
with open(backup_file, 'w', encoding='utf-8') as f:
json.dump(weather_enhanced_episodes, f, ensure_ascii=False, indent=2)
print(f"💾 Backup sparad: {backup_file}")
# Spara slutresultat
output_file = 'data_with_weather.json'
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(weather_enhanced_episodes, f, ensure_ascii=False, indent=2)
print(f"✅ Väderintegrering klar! Sparad som: {output_file}")
# Generera sammanfattningsrapport
generate_weather_report(weather_enhanced_episodes)
def generate_weather_report(episodes: List[Dict]):
"""Generera rapport över väderdata"""
total_episodes = len(episodes)
weather_episodes = sum(1 for ep in episodes if 'weather_data' in ep)
conditions = {}
temperatures = []
for episode in episodes:
if 'weather_data' in episode:
weather = episode['weather_data']
condition = weather.get('condition', 'unknown')
conditions[condition] = conditions.get(condition, 0) + 1
# Samla temperaturer
temp_data = weather.get('parameters', {}).get(2)
if temp_data:
temperatures.append(temp_data['value'])
avg_temp = sum(temperatures) / len(temperatures) if temperatures else 0
report = f"""
# 🌤️ Väderrapport för Sommar i P1
## Sammanfattning
- **Totalt episoder**: {total_episodes}
- **Med väderdata**: {weather_episodes} ({weather_episodes/total_episodes*100:.1f}%)
- **Genomsnittstemperatur**: {avg_temp:.1f}°C
## Vädertillstånd
"""
for condition, count in sorted(conditions.items(), key=lambda x: x[1], reverse=True):
emoji = SMHIWeatherClient().get_weather_emoji(condition)
percentage = count / weather_episodes * 100 if weather_episodes > 0 else 0
report += f"- {emoji} **{condition}**: {count} episoder ({percentage:.1f}%)\n"
report_file = 'weather_integration_report.md'
with open(report_file, 'w', encoding='utf-8') as f:
f.write(report)
print(f"📊 Väderrapport sparad: {report_file}")
if __name__ == "__main__":
integrate_weather_data()