docs: expand pegels API documentation

This commit is contained in:
Matthias Jacob committed 2026-10-14 05:33:29 +02:00
1 parent 7e9dc86446
commit bc70066bc2
2 files changed
+238 -140

No files matched your search

+150 -137
View File
@@ -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: <https://sachsen-pegel-proxy.vercel.app>
- Interaktive Swagger-Dokumentation: <https://sachsen-pegel-proxy.vercel.app/docs>
- Alternatives ReDoc: <https://sachsen-pegel-proxy.vercel.app/redoc>
- OpenAPI-Schema: <https://sachsen-pegel-proxy.vercel.app/openapi.json>
- Running instance: [https://sachsen-pegel-proxy.vercel.app/](https://sachsen-pegel-proxy.vercel.app/) Alle API-Antworten sind JSON. Die Endpunkte benötigen keine Authentifizierung.
- API docs: [https://sachsen-pegel-proxy.vercel.app/docs](https://sachsen-pegel-proxy.vercel.app/docs)
--- ## 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 ### Stationsliste
* Supports **any Pegel ID**
* Extracts:
* water level (cm) `GET /api/pegel`
* flow (m³/s)
* flood alert level
* timestamp (ISO format)
* Provides:
* latest value Optional kann die Anzahl der zurückgegebenen Stationen mit `limit` begrenzt werden:
* full history from feed
--- ```text
## 🔗 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:
```
GET /api/pegel?limit=5 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 ```json
{ {
"count": 5, "count": 1,
"total_available": 312, "total_available": 312,
"items": [ "items": [
{ {
@@ -77,113 +44,159 @@ Index response shape:
} }
], ],
"source": "https://www.umwelt.sachsen.de/umwelt/infosysteme/hwims/portal/web/wasserstand-uebersicht", "source": "https://www.umwelt.sachsen.de/umwelt/infosysteme/hwims/portal/web/wasserstand-uebersicht",
"fetched_at": "2026-05-05T01:00:00+00:00", "fetched_at": "2026-05-05T00:15:00+00:00"
"warnings": [
"optional, only present for partial failures"
]
} }
``` ```
--- | 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 ```bash
python -m venv .venv python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt pip install -r requirements.txt
uvicorn api.app:app --reload uvicorn api.app:app --reload
``` ```
Open: Lokal erreichbare Endpunkte:
``` - API: <http://127.0.0.1:8000/api/pegel>
http://127.0.0.1:8000/api/pegel - Swagger: <http://127.0.0.1:8000/docs>
http://127.0.0.1:8000/api/pegel/560021 - ReDoc: <http://127.0.0.1:8000/redoc>
``` - OpenAPI: <http://127.0.0.1:8000/openapi.json>
API docs: ## Bereitstellung auf Vercel
``` Das Repository enthält keine plattformspezifische Deployment-Konfiguration. Das Projekt kann mit Vercel verbunden und anschließend beispielsweise mit der CLI bereitgestellt werden:
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.
```bash ```bash
npx vercel npx vercel
npx vercel --prod npx vercel --prod
``` ```
--- Nach der Bereitstellung beide API-Endpunkte und `/docs` prüfen.
## 🏠 Home Assistant Example ## Datenquelle und Hinweise
```yaml Die Messwerte werden von der offiziellen Website des Freistaats Sachsen bereitgestellt:
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
```
--- - Übersicht: <https://www.umwelt.sachsen.de/umwelt/infosysteme/hwims/portal/web/wasserstand-uebersicht>
- Beispiel-Feed: <https://www.umwelt.sachsen.de/umwelt/infosysteme/hwims/portal/web/feed/wasserstand-pegel-560021>
## ⚙️ 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: MIT
```
/api/pegel/<id>
```
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
+88 -3
View File
@@ -1,5 +1,6 @@
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from pydantic import BaseModel
import feedparser import feedparser
import re import re
import html import html
@@ -9,7 +10,56 @@ from urllib.error import URLError, HTTPError
from email.utils import parsedate_to_datetime from email.utils import parsedate_to_datetime
from typing import TypedDict, Any 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-{}" 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" 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 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): def list_pegel(limit: int | None = None):
if limit is not None and limit <= 0: if limit is not None and limit <= 0:
raise HTTPException(400, "Ungültiger limit-Parameter") raise HTTPException(400, "Ungültiger limit-Parameter")
@@ -219,7 +287,24 @@ def list_pegel(limit: int | None = None):
return JSONResponse(response) 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): def get_pegel(pegel_id: str):
if not re.fullmatch(r"\d{6,}", pegel_id): if not re.fullmatch(r"\d{6,}", pegel_id):
raise HTTPException(400, "Ungültige Pegel-ID") raise HTTPException(400, "Ungültige Pegel-ID")