Spaces:
Runtime error
Runtime error
| #!/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() |