ai_collections/self_created/Tools/weather_forecast.py
Pakobbix 82b8b2d122 Add Tautulli information retrieval, weather forecast, and YouTube transcript tools
- Implemented Tautulli information retrieval in `tautulli_informations.py` to fetch movie, anime, TV show, music amounts, and more.
- Created a weather forecast tool in `weather_forecast.py` that retrieves and formats a 7-day weather forecast in German.
- Developed a YouTube transcript provider in `youtube_summarizer.py` to fetch video transcripts and titles using Langchain Community's YoutubeLoader.
2025-09-26 13:15:10 +02:00

207 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Weather7day forecast German output, no API key required.
The code uses two helper classes:
* Preparations data acquisition & helpers.
* Tools the single “print_forecast” function you asked for.
"""
from __future__ import annotations
import asyncio
from datetime import datetime
from typing import Any, Dict, List, Callable
import requests
class EventEmitter:
def __init__(self, event_emitter: Callable[[dict], Any] = None):
self.event_emitter = event_emitter
async def progress_update(self, description):
await self.emit(description)
async def error_update(self, description):
await self.emit(description, "error", True)
async def success_update(self, description):
await self.emit(description, "success", True)
async def emit(self, description="Unknown State", status="in_progress", done=False):
if self.event_emitter:
await self.event_emitter(
{
"type": "status",
"data": {
"status": status,
"description": description,
"done": done,
},
}
)
# --------------------------------------------------------------------------- #
# Helper mapping (German weekdays)
_WEEKDAY_GERMAN = {
0: "Montag",
1: "Dienstag",
2: "Mittwoch",
3: "Donnerstag",
4: "Freitag",
5: "Samstag",
6: "Sonntag",
}
# --------------------------------------------------------------------------- #
class Preparations:
"""
All functions that *prepare* data (fetching, calculations, formatting).
They are kept separate from the “output” logic.
"""
API_URL = "https://api.open-meteo.com/v1/forecast"
@staticmethod
def fetch_weather(lat: float, lon: float) -> Dict[str, Any]:
"""Retrieve raw forecast JSON from OpenMeteo."""
params = {
"latitude": lat,
"longitude": lon,
"daily": (
"temperature_2m_max,"
"temperature_2m_min,"
"windspeed_10m_max,"
"precipitation_probability_mean"
),
# hourly field used as a fallback for rain probability
"hourly": "precipitation_probability",
"timezone": "auto",
"forecast_days": 7,
}
resp = requests.get(Preparations.API_URL, params=params)
resp.raise_for_status()
return resp.json()
@staticmethod
def _calc_daily_max_precip(hourly: Dict[str, List[float]]) -> List[int | None]:
"""Return the maximum hourly precipitation probability for each day."""
if not hourly or "precipitation_probability" not in hourly:
return []
hourly_vals = hourly["precipitation_probability"]
days = len(hourly_vals) // 24
result: List[int | None] = []
for d in range(days):
slice_ = hourly_vals[d * 24 : (d + 1) * 24]
result.append(int(max(slice_))) if slice_ else result.append(None)
return result
@staticmethod
def _german_date(date_str: str) -> str:
"""Return German date string, e.g. 'Montag, 02.09.2025'."""
dt = datetime.strptime(date_str, "%Y-%m-%d")
weekday_name = _WEEKDAY_GERMAN[dt.weekday()]
return f"{weekday_name}, {dt.strftime('%d.%m.%Y')}"
@staticmethod
def format_day(
date: str,
temp_max: float | None,
temp_min: float | None,
wind_kmh: float | None,
precip_prob: int | None,
) -> str:
"""Return a single formatted line for one day."""
parts = [Preparations._german_date(date)]
if temp_max is not None and temp_min is not None:
parts.append(f"Hoch {temp_max:.1f}°C / Niedrig {temp_min:.1f}°C")
else:
parts.append("Temperaturdaten fehlen")
if wind_kmh is not None:
parts.append(f"Winds Max. {wind_kmh:.0f} km/h")
if precip_prob is not None:
parts.append(f"Regenchance {precip_prob}%")
else:
parts.append("Regenwahrscheinlichkeit fehlt")
return " - ".join(parts)
# --------------------------------------------------------------------------- #
class Tools:
"""
Contains utility functions for working with weather data.
"""
@staticmethod
async def print_forecast(
lat: float, lon: float, __event_emitter__: Callable[[dict], Any] = None
) -> None:
"""
Fetch weather data and print a formatted forecast.
param lat: Latitude of the location
param lon: Longitude of the location
"""
emitter = EventEmitter(__event_emitter__)
try:
await emitter.progress_update("Wetterdaten werden abgerufen...")
raw = Preparations.fetch_weather(lat, lon)
except requests.HTTPError as exc:
await emitter.error_update(f"Fehler beim Abrufen der Wetterdaten: {exc}")
return
daily = raw["daily"]
dates = daily.get("time", [])
t_max_list = daily.get("temperature_2m_max")
t_min_list = daily.get("temperature_2m_min")
wind_list = daily.get("windspeed_10m_max")
# Rain probability: prefer daily, otherwise fallback to hourly max
if "precipitation_probability_mean" in daily:
await emitter.progress_update("Regenwahrscheinlichkeit wird verarbeitet...")
prob_list = [
int(v) if v is not None else None
for v in daily["precipitation_probability_mean"]
]
else:
prob_list = Preparations._calc_daily_max_precip(raw.get("hourly", {}))
await emitter.success_update(
"Wetterdaten erfolgreich abgerufen und verarbeitet."
)
# Build & print the report
header = f"📅 Wettervorhersage für die nächsten {len(dates)} Tage\n"
lines: List[str] = [header]
for i, d in enumerate(dates):
line = Preparations.format_day(
date=d,
temp_max=t_max_list[i] if t_max_list else None,
temp_min=t_min_list[i] if t_min_list else None,
wind_kmh=wind_list[i] if wind_list else None,
precip_prob=prob_list[i] if prob_list and len(prob_list) > i else None,
)
lines.append(line)
final_output = "\n".join(lines)
return final_output
# --------------------------------------------------------------------------- #
if __name__ == "__main__":
# Default to Berlin coordinates replace with your own!
LAT_DEFAULT = 52.556
LON_DEFAULT = 13.335
asyncio.run(Tools.print_forecast(LAT_DEFAULT, LON_DEFAULT))