docs: expand pegels API documentation
This commit is contained in:
1 parent
7e9dc86446
commit
bc70066bc2
2 files changed
+238
-140
No files matched your search
@@ -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/)
|
||||
- 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: <http://127.0.0.1:8000/api/pegel>
|
||||
- Swagger: <http://127.0.0.1:8000/docs>
|
||||
- ReDoc: <http://127.0.0.1:8000/redoc>
|
||||
- OpenAPI: <http://127.0.0.1:8000/openapi.json>
|
||||
|
||||
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: <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:
|
||||
|
||||
```
|
||||
/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
|
||||
MIT
|
||||
+88
-3
@@ -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")
|
||||
|
||||
Reference in new issue
Block a user