Skill: zopyx/tuebingen-weather-graphics (version 1.0.0)

--- name: tuebingen-weather-graphics description: Generate and send a 5-day Tübingen weather graphic (PNG) from open-meteo.com. Use when Master wants a nicer visual forecast plus summary text for the next few days. --- # Tübingen Weather Graphics ## Überblick Erstellt eine ansprechende PNG-Grafik (max/min-Temperaturen + Regenwahrscheinlichkeit für 5 Tage) auf Basis von Open-Meteo. Ideal für morgendliche Forecast-Posts via Telegram oder Archivierung. ## Voraussetzungen - Python 3.11+ - `matplotlib`, `numpy`, `pillow` (am einfachsten via virtuelles Environment): ```bash python3 -m venv /tmp/tuebingen-plot source /tmp/tuebingen-plot/bin/activate pip install matplotlib ``` ## Quick Start ```bash /tmp/tuebingen-plot/bin/python \ skills/tuebingen-weather-graphics/scripts/generate_forecast_graph.py \ --days 5 \ --output data/weather/tuebingen_forecast.png ``` Ausgabe: - PNG unter `data/weather/...` - Textzusammenfassung im Terminal (z. B. „Fri 13.02: 5/10°C, Regen 85% | …“) ## Automatischer Versand 1. **Cron-Job 07:30 (Beispiel):** ```bash openclaw cron add <<'JSON' { "name": "tuebingen-forecast-graphic", "schedule": { "kind": "cron", "expr": "30 7 * * *", "tz": "Europe/Berlin" }, "sessionTarget": "isolated", "payload": { "kind": "agentTurn", "model": "default", "message": "Run `/tmp/tuebingen-plot/bin/python skills/tuebingen-weather-graphics/scripts/generate_forecast_graph.py --output data/weather/tuebingen_forecast.png`. Send Master the summary text plus attach the PNG." } } JSON ``` 2. **Telegram Versand:** `message.send` mit `media=data/weather/tuebingen_forecast.png`. ## Troubleshooting - **Matplotlib ImportError:** Stelle sicher, dass das oben genannte venv aktiv ist oder die Pakete systemweit installiert wurden. - **Leere Daten:** Open-Meteo liefert manchmal keine Regenwahrscheinlichkeit → Script zeigt 0%. Kann im Code angepasst werden. - **Mehr Tage:** `--days 7` o. Ä. möglich (die API liefert bis zu 7+ Tage im daily-Block). ## Ressourcen - `scripts/generate_forecast_graph.py` – Lädt Daily-Daten, zeichnet Linien/Flächen-Plot + Regen-Balken, speichert PNG.

path: scripts/generate_forecast_graph.py
content: #!/usr/bin/env python3
"""Generate a 5-day weather graphic for Tübingen using Open-Meteo data."""

from __future__ import annotations

import argparse
import datetime as dt
import json
from pathlib import Path
from typing import List
from urllib import request

import matplotlib.pyplot as plt  # type: ignore

API_URL = (
    "https://api.open-meteo.com/v1/forecast?latitude=48.5216&longitude=9.0576"
    "&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max"
    "&timezone=Europe/Berlin"
)


def fetch_daily() -> dict:
    with request.urlopen(API_URL, timeout=20) as resp:  # noqa: S310
        return json.load(resp)


def build_graph(dates: List[dt.date], t_max: List[float], t_min: List[float], rain: List[int], output: Path) -> None:
    plt.style.use("seaborn-v0_8")
    fig, ax1 = plt.subplots(figsize=(8, 4.5), dpi=150)

    ax1.plot(dates, t_max, marker="o", color="#d95f02", label="max °C")
    ax1.plot(dates, t_min, marker="o", color="#1b9e77", label="min °C")
    ax1.fill_between(dates, t_min, t_max, color="#b3e5fc", alpha=0.3)
    ax1.set_ylabel("Temperatur (°C)")
    ax1.set_ylim(min(t_min) - 3, max(t_max) + 3)
    ax1.grid(axis="y", alpha=0.2)

    ax2 = ax1.twinx()
    ax2.bar(dates, rain, width=0.3, color="#7570b3", alpha=0.4, label="Regen %")
    ax2.set_ylabel("Regenwahrscheinlichkeit (%)")
    ax2.set_ylim(0, 100)

    ax1.set_xticks(dates)
    ax1.set_xticklabels([d.strftime("%a\n%d.%m") for d in dates])

    title = f"Tübingen 5-Tage-Ausblick (Stand {dt.datetime.now().strftime('%d.%m.%Y')})"
    ax1.set_title(title)

    lines, labels = ax1.get_legend_handles_labels()
    l2, lab2 = ax2.get_legend_handles_labels()
    ax1.legend(lines + l2, labels + lab2, loc="upper left")

    fig.tight_layout()
    output.parent.mkdir(parents=True, exist_ok=True)
    fig.savefig(output)
    plt.close(fig)


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Generate Tübingen 5-day weather graphic")
    parser.add_argument("--days", type=int, default=5, help="Number of days to include (default: 5)")
    parser.add_argument(
        "--output",
        type=Path,
        default=Path("data/weather/tuebingen_forecast.png"),
        help="Path to save the PNG graphic",
    )
    return parser.parse_args()


def main() -> None:
    args = parse_args()
    data = fetch_daily()
    daily = data["daily"]

    dates = [dt.date.fromisoformat(d) for d in daily["time"][: args.days]]
    t_max = daily["temperature_2m_max"][: args.days]
    t_min = daily["temperature_2m_min"][: args.days]
    rain = daily["precipitation_probability_max"][: args.days]

    build_graph(dates, t_max, t_min, rain, args.output)
    summary = " | ".join(
        f"{d.strftime('%a %d.%m')}: {mn:.0f}/{mx:.0f}°C, Regen {r}%"
        for d, mn, mx, r in zip(dates, t_min, t_max, rain, strict=False)
    )
    print(f"Grafik gespeichert: {args.output}\n{summary}")


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)