From bc70066bc2cc03070cfd92fac46998d26a464bb4 Mon Sep 17 00:00:00 2001 From: Matthias Jacob Date: Wed, 14 Oct 2026 05:33:29 +0200 Subject: [PATCH] docs: expand pegels API documentation --- README.md | 287 ++++++++++++++++++++++++++++------------------------- api/app.py | 91 ++++++++++++++++- 2 files changed, 238 insertions(+), 140 deletions(-) diff --git a/README.md b/README.md index 83e698e..1b36527 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,40 @@ -# Sachsen Pegel Proxy (HWIMS → JSON) +# Sachsen Pegel Proxy -Small FastAPI service that converts the Sachsen HWIMS RSS feed into clean, machine-readable JSON. +Kleiner FastAPI-Dienst, der Pegelstationen und Messwerte aus dem sächsischen Hochwasser- und Wasserstands-Informationssystem (HWIMS) als JSON bereitstellt. Die Daten stammen aus der offiziellen Stationsübersicht und den RSS-Feeds der Stationen. -Perfect for use with Home Assistant or other automation tools. +## Live-Dienst und API-Dokumentation -## 🌐 Live Service +- API-Basis-URL: +- Interaktive Swagger-Dokumentation: +- Alternatives ReDoc: +- OpenAPI-Schema: -- Running instance: [https://sachsen-pegel-proxy.vercel.app/](https://sachsen-pegel-proxy.vercel.app/) -- API docs: [https://sachsen-pegel-proxy.vercel.app/docs](https://sachsen-pegel-proxy.vercel.app/docs) +Alle API-Antworten sind JSON. Die Endpunkte benötigen keine Authentifizierung. ---- +## Endpunkte -## ✨ Features +| Methode | Pfad | Beschreibung | +| --- | --- | --- | +| `GET` | `/api/pegel` | Verfügbare Pegelstationen auflisten | +| `GET` | `/api/pegel/{pegel_id}` | Messwerte und Feed-Historie einer Station abrufen | -* Converts RSS → structured JSON -* Supports **any Pegel ID** -* Extracts: +### Stationsliste - * water level (cm) - * flow (m³/s) - * flood alert level - * timestamp (ISO format) -* Provides: +`GET /api/pegel` - * latest value - * full history from feed +Optional kann die Anzahl der zurückgegebenen Stationen mit `limit` begrenzt werden: ---- - -## 🔗 Example - -Request: - -``` -GET /api/pegel/560021 -``` - -Response: - -```json -{ - "station_id": "560021", - "station_title": "Golzern 1 / Vereinigte Mulde", - "latest": { - "timestamp": "2026-01-01T12:00:00+00:00", - "water_level_cm": 98, - "flow_m3s": 20.8, - "flood_alert_level": "Kein Hochwasser" - } -} -``` - -Index request (all discovered stations): - -``` -GET /api/pegel -``` - -Index request with limit: - -``` +```text GET /api/pegel?limit=5 ``` -Index response shape: +`limit` muss eine positive ganze Zahl sein. `count` ist die Anzahl der tatsächlich zurückgegebenen Elemente; `total_available` gibt die Gesamtzahl der gefundenen Stationen an und wird durch `limit` nicht verändert. + +Beispielantwort: ```json { - "count": 5, + "count": 1, "total_available": 312, "items": [ { @@ -77,113 +44,159 @@ Index response shape: } ], "source": "https://www.umwelt.sachsen.de/umwelt/infosysteme/hwims/portal/web/wasserstand-uebersicht", - "fetched_at": "2026-05-05T01:00:00+00:00", - "warnings": [ - "optional, only present for partial failures" - ] + "fetched_at": "2026-05-05T00:15:00+00:00" } ``` ---- +| Feld | Typ | Bedeutung | +| --- | --- | --- | +| `count` | integer | Anzahl der Elemente in `items` | +| `total_available` | integer | Gesamtzahl der gefundenen Stationen vor Anwendung von `limit` | +| `items` | array | Stationen mit `station_id`, `station_title` und RSS-Feed-URL `source` | +| `station_title` | string oder `null` | Stationsname, sofern er aus der Quelle ermittelt werden konnte | +| `source` | string | Verwendete Discovery-Quelle; beim Cache-Fallback der Wert `"cache"` | +| `fetched_at` | string | Zeitpunkt der Antworterstellung als ISO-8601-Zeitstempel | +| `warnings` | array, optional | Hinweise auf Fehler der Discovery-Quelle, wenn ein Cache-Fallback verwendet wurde | +| `cache_fetched_at` | string, optional | Zeitpunkt, an dem der verwendete Discovery-Cache geladen wurde; nur beim Cache-Fallback | -## 🚀 Local Development +Die Stationsübersicht wird intern 15 Minuten zwischengespeichert. Ist die Quelle vorübergehend nicht erreichbar, liefert der Dienst vorhandene Cache-Daten mit `warnings` und `cache_fetched_at`. Gibt es keinen verwendbaren Cache, antwortet der Endpunkt mit HTTP `502`. + +### Messwerte einer Station + +`GET /api/pegel/{pegel_id}` + +Die Pegel-ID muss aus mindestens sechs Ziffern bestehen. Der Endpunkt liefert alle Einträge, die im RSS-Feed der Station verfügbar sind. `latest` ist der neueste Messwert und entspricht dem ersten Eintrag in `entries`. + +```text +GET /api/pegel/560021 +``` + +Beispielantwort: + +```json +{ + "station_id": "560021", + "station_title": "Golzern 1 / Vereinigte Mulde", + "count": 2, + "latest": { + "timestamp": "2026-05-05T02:15:00+02:00", + "title": "Wasserstand und Durchfluss für 05.05.2026 02:15 Uhr (MESZ)", + "water_level_cm": 98.0, + "flow_m3s": 20.8, + "flood_alert_level": "Kein Hochwasser" + }, + "entries": [ + { + "timestamp": "2026-05-05T02:15:00+02:00", + "title": "Wasserstand und Durchfluss für 05.05.2026 02:15 Uhr (MESZ)", + "water_level_cm": 98.0, + "flow_m3s": 20.8, + "flood_alert_level": "Kein Hochwasser" + }, + { + "timestamp": "2026-05-05T02:00:00+02:00", + "title": "Wasserstand und Durchfluss für 05.05.2026 02:00 Uhr (MESZ)", + "water_level_cm": 98.0, + "flow_m3s": 20.8, + "flood_alert_level": "Kein Hochwasser" + } + ], + "source": "https://www.umwelt.sachsen.de/umwelt/infosysteme/hwims/portal/web/feed/wasserstand-pegel-560021", + "fetched_at": "2026-05-05T00:16:00+00:00" +} +``` + +| Feld | Typ | Bedeutung | +| --- | --- | --- | +| `station_id` | string | Angefragte Pegel-ID | +| `station_title` | string oder `null` | Stationsname aus dem RSS-Feed | +| `count` | integer | Anzahl der vom Feed gelieferten Einträge | +| `latest` | Objekt | Neuester Eintrag; identisch mit `entries[0]` | +| `entries` | array | Feed-Einträge in der Reihenfolge der Quelle, normalerweise neueste zuerst | +| `timestamp` | string oder `null` | Messzeitpunkt als ISO-8601 mit Zeitzonenoffset | +| `title` | string oder `null` | Originaltitel des RSS-Eintrags | +| `water_level_cm` | number oder `null` | Wasserstand in Zentimetern | +| `flow_m3s` | number oder `null` | Durchfluss in Kubikmetern pro Sekunde | +| `flood_alert_level` | string oder `null` | Meldestufe als Text | +| `source` | string | URL des verwendeten RSS-Feeds | +| `fetched_at` | string | Zeitpunkt des Abrufs durch die API als UTC-Zeitstempel | + +Ein Messwert ist `null`, wenn er im jeweiligen RSS-Eintrag fehlt oder nicht erkannt werden kann. Zeitstempel werden, wenn möglich, aus der Uhrzeit im RSS-Titel gebildet; andernfalls verwendet die API das Datumsfeld des Feeds. Sommer- und Winterzeit werden als Offset berücksichtigt. + +## Fehlerantworten + +Fehler der API enthalten ein JSON-Objekt mit dem Feld `detail`. + +| HTTP-Status | Ursache | Beispiel für `detail` | +| --- | --- | --- | +| `400` | `limit` ist kleiner oder gleich null | `Ungültiger limit-Parameter` | +| `400` | Pegel-ID hat nicht mindestens sechs Ziffern | `Ungültige Pegel-ID` | +| `404` | Feed enthält keine Einträge für die Pegel-ID | `Keine Daten gefunden` | +| `422` | Query-Parameter kann nicht als Zahl interpretiert werden | FastAPI-Validierungsfehler | +| `502` | Stationsübersicht nicht erreichbar und kein Cache vorhanden | `Upstream-Discovery nicht verfügbar` | + +## Beispiele + +Mit `curl`: + +```bash +curl "https://sachsen-pegel-proxy.vercel.app/api/pegel?limit=5" +curl "https://sachsen-pegel-proxy.vercel.app/api/pegel/560021" +``` + +### Home Assistant + +```yaml +rest: + - resource: "https://sachsen-pegel-proxy.vercel.app/api/pegel/560021" + scan_interval: 300 + sensor: + - name: "Pegel Golzern Wasserstand" + value_template: "{{ value_json.latest.water_level_cm }}" + unit_of_measurement: "cm" + json_attributes_path: "$.latest" + json_attributes: + - timestamp + - flow_m3s + - flood_alert_level +``` + +## Lokale Entwicklung ```bash python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate - pip install -r requirements.txt - uvicorn api.app:app --reload ``` -Open: +Lokal erreichbare Endpunkte: -``` -http://127.0.0.1:8000/api/pegel -http://127.0.0.1:8000/api/pegel/560021 -``` +- API: +- Swagger: +- ReDoc: +- OpenAPI: -API docs: +## Bereitstellung auf Vercel -``` -http://127.0.0.1:8000/docs -``` - ---- - -## ☁️ Deploy (Vercel) - -This repository does not include platform-specific deployment configuration. -Deploy by connecting the repo in Vercel (or using the CLI), then verify the API endpoints after deployment. +Das Repository enthält keine plattformspezifische Deployment-Konfiguration. Das Projekt kann mit Vercel verbunden und anschließend beispielsweise mit der CLI bereitgestellt werden: ```bash npx vercel npx vercel --prod ``` ---- +Nach der Bereitstellung beide API-Endpunkte und `/docs` prüfen. -## 🏠 Home Assistant Example +## Datenquelle und Hinweise -```yaml -rest: - - resource: "https://your-app.vercel.app/api/pegel/560021" - scan_interval: 300 - json_attributes_path: "$.latest" - sensor: - - name: "Pegel Wasserstand" - value_template: "{{ value_json.latest.water_level_cm }}" - unit_of_measurement: "cm" - json_attributes: - - flow_m3s - - flood_alert_level -``` +Die Messwerte werden von der offiziellen Website des Freistaats Sachsen bereitgestellt: ---- +- Übersicht: +- Beispiel-Feed: -## ⚙️ Configuration +Verfügbarkeit und Richtigkeit der Quelldaten werden nicht garantiert. Änderungen am Aufbau der HWIMS-Seiten oder RSS-Feeds können die Verarbeitung beeinträchtigen. Dieses Projekt ist nicht mit dem Freistaat Sachsen verbunden. -No config required. +## Lizenz -Pegel ID is passed via URL: - -``` -/api/pegel/ -``` - -Index endpoint: - -``` -/api/pegel -``` - -Query params for index: - - * `limit` (positive integer, optional; invalid values return HTTP 400) - ---- - -## 📡 Data Source - -Data provided by the official Sachsen environmental portal: - -https://www.umwelt.sachsen.de/ - -Example feed: - -https://www.umwelt.sachsen.de/umwelt/infosysteme/hwims/portal/web/feed/wasserstand-pegel-560021 - ---- - -## ⚠️ Disclaimer - -* No guarantees on data accuracy or availability -* Feed format may change -* Not affiliated with the State of Saxony - ---- - -## 📝 License - -MIT +MIT \ No newline at end of file diff --git a/api/app.py b/api/app.py index c55f879..52fa84e 100644 --- a/api/app.py +++ b/api/app.py @@ -1,5 +1,6 @@ from fastapi import FastAPI, HTTPException from fastapi.responses import JSONResponse +from pydantic import BaseModel import feedparser import re import html @@ -9,7 +10,56 @@ from urllib.error import URLError, HTTPError from email.utils import parsedate_to_datetime from typing import TypedDict, Any -app = FastAPI() +app = FastAPI( + title="Sachsen Pegel Proxy API", + description=( + "Liefert Stationslisten und Wasserstandsmessungen des sächsischen " + "Hochwasser- und Wasserstands-Informationssystems (HWIMS) als JSON.\n\n" + "Die API liest die offiziellen HWIMS-Übersichts- und RSS-Quellen aus. " + "Messwerte und Stationsnamen können `null` sein, wenn sie in der " + "Quelldatei fehlen oder nicht erkannt werden. Zeitstempel werden als " + "ISO-8601-Werte mit Zeitzoneninformation ausgegeben.\n\n" + "Die interaktive Dokumentation steht unter `/docs`, das OpenAPI-Schema " + "unter `/openapi.json` bereit." + ), + version="1.0.0", + openapi_tags=[ + { + "name": "Pegel", + "description": "Entdecken von Pegelstationen und Abrufen ihrer Messwerte.", + } + ], +) + +class PegelStation(BaseModel): + station_id: str + station_title: str | None + source: str + +class PegelIndexResponse(BaseModel): + count: int + total_available: int + items: list[PegelStation] + source: str + fetched_at: str + warnings: list[str] | None = None + cache_fetched_at: str | None = None + +class PegelEntry(BaseModel): + timestamp: str | None + title: str | None + water_level_cm: float | None + flow_m3s: float | None + flood_alert_level: str | None + +class PegelResponse(BaseModel): + station_id: str + station_title: str | None + count: int + latest: PegelEntry + entries: list[PegelEntry] + source: str + fetched_at: str BASE = "https://www.umwelt.sachsen.de/umwelt/infosysteme/hwims/portal/web/feed/wasserstand-pegel-{}" DISCOVERY_URL = "https://www.umwelt.sachsen.de/umwelt/infosysteme/hwims/portal/web/wasserstand-uebersicht" @@ -173,7 +223,25 @@ def _get_latest_for_station(station_id: str): } return latest -@app.get("/api/pegel") +@app.get( + "/api/pegel", + response_model=PegelIndexResponse, + tags=["Pegel"], + summary="Pegelstationen auflisten", + description=( + "Liefert die auf der HWIMS-Übersichtsseite gefundenen Pegelstationen. " + "Mit `limit` lässt sich die zurückgegebene Liste begrenzen; " + "`total_available` enthält weiterhin die Gesamtzahl. Der Index wird " + "15 Minuten zwischengespeichert. Ist die Quelle nicht erreichbar, " + "wird ein vorhandener Cache verwendet und die Antwort enthält `warnings` " + "sowie `cache_fetched_at`. Ohne verfügbaren Cache wird HTTP 502 geliefert." + ), + response_description="Stationsliste mit Quelle und Abrufzeitpunkt.", + responses={ + 400: {"description": "`limit` ist keine positive Zahl."}, + 502: {"description": "Die Discovery-Quelle ist nicht verfügbar und es gibt keinen Cache."}, + }, +) def list_pegel(limit: int | None = None): if limit is not None and limit <= 0: raise HTTPException(400, "Ungültiger limit-Parameter") @@ -219,7 +287,24 @@ def list_pegel(limit: int | None = None): return JSONResponse(response) -@app.get("/api/pegel/{pegel_id}") +@app.get( + "/api/pegel/{pegel_id}", + response_model=PegelResponse, + tags=["Pegel"], + summary="Messwerte eines Pegels abrufen", + description=( + "Liefert alle im RSS-Feed verfügbaren Einträge einer Station. " + "`latest` ist der neueste Eintrag und entspricht `entries[0]`; " + "die Reihenfolge von `entries` folgt dem Feed (neueste zuerst). " + "Pegel-IDs bestehen aus mindestens sechs Ziffern. Nicht vorhandene " + "Messwerte werden als `null` ausgegeben." + ), + response_description="Stationsmetadaten, neuester Messwert und Feed-Historie.", + responses={ + 400: {"description": "Die Pegel-ID besteht nicht aus mindestens sechs Ziffern."}, + 404: {"description": "Für diese Pegel-ID enthält der RSS-Feed keine Einträge."}, + }, +) def get_pegel(pegel_id: str): if not re.fullmatch(r"\d{6,}", pegel_id): raise HTTPException(400, "Ungültige Pegel-ID")