Immer noch nicht richtig gut, also noch **WIP**

This commit is contained in:
rxf
2026-03-23 22:09:51 +01:00
parent c471c0e33a
commit acd509fef6
8 changed files with 969 additions and 86 deletions

514
api/README.md Normal file
View File

@@ -0,0 +1,514 @@
# Wetterstation API
REST API zum Abrufen von Wetterdaten aus der PostgreSQL-Datenbank.
## Übersicht
Die API basiert auf **FastAPI** und bietet Endpunkte für aktuelle Wetterdaten, historische Zeitreihen, Statistiken und aggregierte Daten.
- **Version:** 1.0.0
- **Framework:** FastAPI mit Uvicorn
- **Datenbank:** PostgreSQL
- **Interaktive API-Dokumentation:** `/docs` (Swagger UI) oder `/redoc` (ReDoc)
## Starten der API
### Lokal (Development)
```bash
cd api
python main.py
```
Die API läuft dann auf `http://localhost:8000`
### Docker (Production)
```bash
docker compose up -d
```
## Umgebungsvariablen
Die API benötigt folgende Umgebungsvariablen (definiert in `.env`):
```env
DB_HOST=localhost
DB_PORT=5432
DB_NAME=wetterstation
DB_USER=wetterstation_user
DB_PASSWORD=<passwort>
```
## Endpunkte
### 📋 General
#### `GET /`
**Root-Endpunkt mit API-Informationen**
**Response:**
```json
{
"message": "Wetterstation API",
"version": "1.0.0",
"docs": "/docs"
}
```
---
#### `GET /health`
**Health Check - Prüft API- und Datenbankstatus**
**Response:**
```json
{
"status": "ok",
"database": "connected",
"timestamp": "2026-03-23T14:30:00"
}
```
---
### 🌡️ Weather Data
#### `GET /weather/latest`
**Gibt die neuesten Wetterdaten zurück**
**Response Model:** `WeatherData`
**Beispiel:**
```json
{
"id": 123456,
"datetime": "2026-03-23T14:30:00Z",
"temperature": 15.5,
"humidity": 65,
"pressure": 1013.2,
"wind_speed": 12.5,
"wind_gust": 18.7,
"wind_dir": 225.0,
"rain": 0.0,
"rain_rate": 0.0,
"received_at": "2026-03-23T14:30:05"
}
```
---
#### `GET /weather/current`
**Alias für `/weather/latest` - gibt aktuelle Wetterdaten zurück**
---
#### `GET /weather/history`
**Gibt historische Wetterdaten der letzten X Stunden zurück**
**Query Parameter:**
- `hours` (optional): Anzahl Stunden zurück (1-168, default: 24)
- `limit` (optional): Maximale Anzahl Datensätze (1-10000, default: 1000)
**Beispiel:**
```bash
GET /weather/history?hours=48&limit=500
```
**Response:** Array von `WeatherData`
---
#### `GET /weather/range`
**Gibt Wetterdaten für einen bestimmten Zeitraum zurück**
**Query Parameter:**
- `start` (erforderlich): Startdatum (ISO 8601)
- `end` (erforderlich): Enddatum (ISO 8601)
- `limit` (optional): Maximale Anzahl Datensätze (1-50000, default: 10000)
**Beispiel:**
```bash
GET /weather/range?start=2026-03-01T00:00:00Z&end=2026-03-23T23:59:59Z&limit=5000
```
**Response:** Array von `WeatherData`
---
#### `GET /weather/temperature`
**Gibt nur Temperatur-Zeitreihen zurück (optimiert für Diagramme)**
**Query Parameter:**
- `hours` (optional): Anzahl Stunden zurück (1-168, default: 24)
**Response:**
```json
[
{
"datetime": "2026-03-23T14:00:00Z",
"temperature": 15.3
},
{
"datetime": "2026-03-23T14:05:00Z",
"temperature": 15.5
}
]
```
---
#### `GET /weather/wind`
**Gibt nur Wind-Daten zurück (Geschwindigkeit, Richtung, Böen)**
**Query Parameter:**
- `hours` (optional): Anzahl Stunden zurück (1-168, default: 24)
**Response:**
```json
[
{
"datetime": "2026-03-23T14:00:00Z",
"wind_speed": 12.5,
"wind_gust": 18.7,
"wind_dir": 225.0
}
]
```
---
#### `GET /weather/rain`
**Gibt nur Regen-Daten zurück**
**Query Parameter:**
- `hours` (optional): Anzahl Stunden zurück (1-168, default: 24)
**Response:**
```json
[
{
"datetime": "2026-03-23T14:00:00Z",
"rain": 0.5,
"rain_rate": 2.3
}
]
```
---
### 📊 Statistics
#### `GET /weather/stats`
**Gibt aggregierte Statistiken für den angegebenen Zeitraum zurück**
**Query Parameter:**
- `hours` (optional): Zeitraum in Stunden (1-168, default: 24)
**Response Model:** `WeatherStats`
**Beispiel:**
```json
{
"avg_temperature": 15.2,
"min_temperature": 8.5,
"max_temperature": 22.1,
"avg_humidity": 65.3,
"avg_pressure": 1013.5,
"avg_wind_speed": 10.2,
"max_wind_gust": 28.5,
"total_rain": 3.2,
"data_points": 288
}
```
---
#### `GET /weather/daily`
**Gibt tägliche Statistiken für die letzten X Tage zurück**
**Query Parameter:**
- `days` (optional): Anzahl Tage zurück (1-90, default: 7)
**Response:** Array von `WeatherStats` mit `date` Feld
---
### 📈 Aggregated Data
Die aggregierten Endpunkte sind optimiert für Langzeit-Visualisierungen und reduzieren die Datenmenge durch Mittelwertbildung.
#### `GET /weather/hourly-aggregated`
**Gibt stündlich aggregierte Wetterdaten zurück (Stundenmittel)**
**Query Parameter:**
- `days` (optional): Anzahl Tage zurück (1-60, default: 7)
**Response:** Array von `WeatherData` (stündlich aggregiert)
**Verwendung:** Ideal für 7-Tage- und 30-Tage-Ansichten
---
#### `GET /weather/daily-aggregated`
**Gibt täglich aggregierte Wetterdaten zurück (Tagesmittel)**
**Query Parameter:**
- `days` (optional): Anzahl Tage zurück (1-730, default: 365)
**Response:** Array von `WeatherData` (täglich aggregiert)
**Besonderheit:** Bei `days >= 365` werden automatisch **alle verfügbaren Daten** zurückgegeben (nicht nur die letzten 365 Tage).
**Verwendung:** Ideal für Jahresübersicht (365-Tage-Ansicht)
---
#### `GET /weather/rain-daily`
**Gibt tägliche Regensummen zurück**
**Query Parameter:**
- `days` (optional): Anzahl Tage zurück (1-365, default: 30)
**Response:**
```json
[
{
"date": "2026-03-23T00:00:00Z",
"total_rain": 5.2
},
{
"date": "2026-03-22T00:00:00Z",
"total_rain": 0.0
}
]
```
**Verwendung:** Ideal für 7-Tage- und 30-Tage-Regen-Diagramme
---
#### `GET /weather/rain-weekly`
**Gibt wöchentliche Regensummen zurück (Woche = Mo-So)**
**Query Parameter:**
- `days` (optional): Anzahl Tage zurück (1-730, default: 365)
**Response:**
```json
[
{
"week_start": "2026-03-17T00:00:00Z",
"total_rain": 12.5
}
]
```
**Besonderheit:** Bei `days >= 365` werden automatisch **alle verfügbaren Daten** zurückgegeben.
**Verwendung:** Ideal für Jahresübersicht (365-Tage-Ansicht)
---
## Datenmodelle
### WeatherData
```typescript
{
id: number
datetime: string (ISO 8601)
temperature: number | null // °C
humidity: number | null // %
pressure: number | null // hPa
wind_speed: number | null // km/h (konvertiert von mph)
wind_gust: number | null // km/h (konvertiert von mph)
wind_dir: number | null // Grad (0-360)
rain: number | null // mm
rain_rate: number | null // mm/h
received_at: string (ISO 8601)
}
```
### WeatherStats
```typescript
{
avg_temperature: number | null
min_temperature: number | null
max_temperature: number | null
avg_humidity: number | null
avg_pressure: number | null
avg_wind_speed: number | null
max_wind_gust: number | null
total_rain: number | null
data_points: number
}
```
### HealthResponse
```typescript
{
status: string // "ok" | "error"
database: string // "connected" | "disconnected"
timestamp: string (ISO 8601)
}
```
---
## Einheitenkonvertierung
Die API konvertiert automatisch folgende Einheiten aus der Datenbank:
| Wert | Datenbank | API-Ausgabe |
|------|-----------|-------------|
| Windgeschwindigkeit | mph | km/h (× 1.60934) |
| Windböen | mph | km/h (× 1.60934) |
| Temperatur | °C | °C (unverändert) |
| Luftdruck | hPa | hPa (unverändert) |
| Regen | mm | mm (unverändert) |
---
## CORS
Die API erlaubt CORS-Anfragen von allen Origins (`allow_origins=["*"]`). In Production sollte dies auf spezifische Domains eingeschränkt werden.
---
## Fehlerbehandlung
### HTTP Status Codes
- `200 OK` - Erfolgreiche Anfrage
- `400 Bad Request` - Ungültige Parameter
- `404 Not Found` - Keine Daten gefunden
- `500 Internal Server Error` - Datenbankfehler
### Fehler-Response
```json
{
"detail": "Keine Daten verfügbar"
}
```
---
## Interaktive Dokumentation
FastAPI generiert automatisch eine interaktive API-Dokumentation:
- **Swagger UI:** [http://localhost:8000/docs](http://localhost:8000/docs)
- **ReDoc:** [http://localhost:8000/redoc](http://localhost:8000/redoc)
Dort können alle Endpunkte direkt getestet werden.
---
## Beispiele
### cURL
```bash
# Aktuelle Wetterdaten abrufen
curl http://localhost:8000/weather/current
# Letzte 48 Stunden
curl "http://localhost:8000/weather/history?hours=48"
# Jahresübersicht (alle verfügbaren Daten)
curl "http://localhost:8000/weather/daily-aggregated?days=365"
# Statistiken für letzte 7 Tage
curl "http://localhost:8000/weather/stats?hours=168"
```
### JavaScript (Fetch)
```javascript
// Aktuelle Wetterdaten
const response = await fetch('http://localhost:8000/weather/current')
const data = await response.json()
console.log(`Temperatur: ${data.temperature}°C`)
// Tägliche Aggregation für 365 Tage
const yearData = await fetch('http://localhost:8000/weather/daily-aggregated?days=365')
const year = await yearData.json()
console.log(`${year.length} Tage verfügbar`)
```
### Python (requests)
```python
import requests
# Aktuelle Daten
response = requests.get('http://localhost:8000/weather/current')
data = response.json()
print(f"Temperatur: {data['temperature']}°C")
# Statistiken
stats = requests.get('http://localhost:8000/weather/stats?hours=24')
print(f"Durchschnittstemperatur: {stats.json()['avg_temperature']}°C")
```
---
## Entwicklung
### Abhängigkeiten installieren
```bash
pip install -r requirements.txt
```
### Server starten (Development mit Auto-Reload)
```bash
uvicorn main:app --reload --host 0.0.0.0 --port 8000
```
### Logging
Die API verwendet Python's `logging`-Modul. Log-Level: `INFO`
---
## Deployment
Die API wird als Docker-Container deployed. Siehe `Dockerfile` und `docker-compose.yml` im Hauptverzeichnis.
### Docker Image bauen
```bash
docker build -t wetterstation-api ./api
```
### Container starten
```bash
docker run -d \
-p 8000:8000 \
-e DB_HOST=db \
-e DB_USER=wetterstation_user \
-e DB_PASSWORD=<passwort> \
wetterstation-api
```
---
## Performance-Tipps
1. **Aggregierte Endpunkte verwenden** für Langzeit-Visualisierungen (reduziert Datenmenge)
2. **Limit-Parameter** nutzen, um nur benötigte Datenmenge abzurufen
3. **Spezifische Endpunkte** verwenden (`/weather/temperature` statt `/weather/history` wenn nur Temperatur benötigt wird)
4. **Caching** auf Client-Seite implementieren für historische Daten
---
## Lizenz
Siehe Hauptprojekt-Repository.

View File

@@ -383,24 +383,44 @@ async def get_daily_aggregated_data(
conn = get_db_connection()
try:
with conn.cursor() as cursor:
cursor.execute("""
SELECT
0 as id,
date_trunc('day', datetime) as datetime,
AVG(temperature) as temperature,
ROUND(AVG(humidity)) as humidity,
AVG(pressure) as pressure,
AVG(wind_speed * 1.60934) as wind_speed,
MAX(wind_gust * 1.60934) as wind_gust,
AVG(wind_dir) as wind_dir,
AVG(rain) as rain,
AVG(rain_rate) as rain_rate,
MAX(received_at) as received_at
FROM weather_data
WHERE datetime >= NOW() - make_interval(days => %s)
GROUP BY date_trunc('day', datetime)
ORDER BY datetime ASC
""", (days,))
# Bei 365 Tagen: alle verfügbaren Daten zurückgeben
if days >= 365:
cursor.execute("""
SELECT
0 as id,
date_trunc('day', datetime) as datetime,
AVG(temperature) as temperature,
ROUND(AVG(humidity)) as humidity,
AVG(pressure) as pressure,
AVG(wind_speed * 1.60934) as wind_speed,
MAX(wind_gust * 1.60934) as wind_gust,
AVG(wind_dir) as wind_dir,
AVG(rain) as rain,
AVG(rain_rate) as rain_rate,
MAX(received_at) as received_at
FROM weather_data
GROUP BY date_trunc('day', datetime)
ORDER BY datetime ASC
""")
else:
cursor.execute("""
SELECT
0 as id,
date_trunc('day', datetime) as datetime,
AVG(temperature) as temperature,
ROUND(AVG(humidity)) as humidity,
AVG(pressure) as pressure,
AVG(wind_speed * 1.60934) as wind_speed,
MAX(wind_gust * 1.60934) as wind_gust,
AVG(wind_dir) as wind_dir,
AVG(rain) as rain,
AVG(rain_rate) as rain_rate,
MAX(received_at) as received_at
FROM weather_data
WHERE datetime >= NOW() - make_interval(days => %s)
GROUP BY date_trunc('day', datetime)
ORDER BY datetime ASC
""", (days,))
results = cursor.fetchall()
return [dict(row) for row in results]
@@ -440,15 +460,26 @@ async def get_weekly_rain_data(
conn = get_db_connection()
try:
with conn.cursor() as cursor:
cursor.execute("""
SELECT
date_trunc('week', datetime) as week_start,
SUM(rain) as total_rain
FROM weather_data
WHERE datetime >= NOW() - make_interval(days => %s)
GROUP BY date_trunc('week', datetime)
ORDER BY week_start ASC
""", (days,))
# Bei 365 Tagen: alle verfügbaren Daten zurückgeben
if days >= 365:
cursor.execute("""
SELECT
date_trunc('week', datetime) as week_start,
SUM(rain) as total_rain
FROM weather_data
GROUP BY date_trunc('week', datetime)
ORDER BY week_start ASC
""")
else:
cursor.execute("""
SELECT
date_trunc('week', datetime) as week_start,
SUM(rain) as total_rain
FROM weather_data
WHERE datetime >= NOW() - make_interval(days => %s)
GROUP BY date_trunc('week', datetime)
ORDER BY week_start ASC
""", (days,))
results = cursor.fetchall()
return [dict(row) for row in results]