Files
mvg-departures/SPEC.md
T

4.5 KiB

SPEC.md — MVG Departures Monitor

1. Überblick

Kompakter Abfahrtsmonitor für Münchner U-Bahn- und S-Bahn-Abfahrten. Primär für mobile Nutzung optimiert. Baut auf der öffentlichen MVG-API auf.

2. Architektur

mvg-departures/
├── app/
│   ├── __init__.py
│   ├── main.py           # FastAPI-App, Endpunkte, Caching
│   ├── config.py          # Config-Loader (YAML → Dataclasses)
│   └── templates/
│       └── index.html     # Jinja2-Template (mobile HTML-Ansicht)
├── config.yaml            # Stationskonfiguration
├── requirements.txt       # Python-Dependencies
├── Dockerfile             # Single-Stage Build (python:3.14-slim)
├── docker-compose.yml     # Deployment-Beispiel
└── .github/workflows/
    └── build.yml          # CI/CD: Docker-Build + GHCR-Push

3. Technologie-Stack

Komponente Technologie
Runtime Python 3.14
Web-Framework FastAPI
Templating Jinja2
API-Client mvg (PyPI)
Config YAML (pyyaml)
Server Uvicorn
Container Docker (python:3.14-slim)
CI/CD GitHub Actions → GHCR

4. Datenfluss

config.yaml
    ↓
load_config() → AppConfig (Dataclass)
    ↓
Profile auswählen (?profile= param oder erstes Profil)
    ↓
Für jede Station im aktiven Profil:
    resolve_station_id(name) → station_id (mit Memory-Cache)
    ↓
MvgApi(station_id).departures(limit, transport_types)
    ↓
Filter: exclude_destinations (exakter Stringvergleich)
    ↓
Berechnung: delay_min, format time_str
    ↓
In-memory Cache (cache_seconds)
    ↓
Response: JSON oder HTML-Template

5. Endpunkte

Route Methode Beschreibung
/ GET Mobile HTML-Ansicht, Auto-Refresh alle 60s
/?profile=<name> GET HTML-Ansicht für ein bestimmtes Profil
/api/departures GET JSON-Liste aller gefilterten Abfahrten (erstes Profil als Fallback)
/api/departures?profile=<name> GET JSON-Liste für ein bestimmtes Profil
/healthz GET Healthcheck ({"status": "ok"})

6. Konfiguration (config.yaml)

refresh_seconds: 60          # Meta-Refresh-Intervall (HTML)
cache_seconds: 20            # API-Cache pro Station
departures_limit: 10         # Max. Abfahrten pro Station

profiles:
  - name: "Hinfahrt"         # Profilname (einzigartig)
    stations:
      - name: "Station Name, München"
        type: "UBAHN"        # UBAHN | SBAHN | TRAM | BUS
        exclude_destinations: # Exakter Stringvergleich
          - "Zielstation"
  - name: "Rückfahrt"
    stations:
      - name: "Andere Station"
        type: "SBAHN"
        exclude_destinations: []

Filter-Logik

  • exclude_destinations filtert per exaktem destination-Stringvergleich
  • Alles, was NICHT in der Liste steht, wird angezeigt
  • Neue/unbekannte Ziele erscheinen automatisch → keine lautlos verschwundenen Linienänderungen

7. Caching

  • Station-ID-Cache: Globales Dict _station_id_cache, lifetime=Session
  • Departures-Cache: Pro Station+Typ, TTL=cache_seconds (Standard: 20s)
  • Kein Persistence-Layer → Cache geht bei Neustart verloren

8. UI

  • Dunkles Theme (#111417 Background)
  • Profil-Kacheln (nur bei >1 Profil): 50% Breite, Basis #1e242b, aktiv #005ca9 (MVG-Blau)
  • Farbliche Unterscheidung: U-Bahn (blau #005ca9), S-Bahn (grün #00933b), Tram (rot #e2001a), Bus (grau #55545a)
  • Anzeige: Linie, Ziel, Abfahrtszeit, Verspätung (gelb/rot), Entfall, Störungsmeldungen
  • Responsive, für Smartphone-First optimiert

9. Deployment

  • Lokal: uvicorn app.main:app --reload
  • Docker: docker run -p 8000:8000 -v $(pwd)/config.yaml:/app/config.yaml mvg-departures
  • Production: Docker-Image von GHCR, Config via Volume-Mount, TZ=Europe/Berlin
  • Healthcheck: Uvicorn-Endpoint /healthz (30s Interval)

10. CI/CD

  • Trigger: Push auf main
  • Build: Docker-Image → ghcr.io/<owner>/<repo>:latest + Short-SHA-Tag
  • Cleanup: Behält nur letzte 4 Image-Versionen
  • Voraussetzung: Repo-Actions Permissions auf "Read and write"

11. MVG-API

  • Basis: https://www.mvg.de/api/bgw-pt/v3/
  • Station-Lookup: /locations?query=<name>
  • Abfahrten: /departures?globalId=<id>&limit=<n>&transportTypes=<type>
  • Auth: Keine (öffentlich)

12. Einschränkungen

  • Kein WebSocket/Live-Updates → nur periodischer Refresh
  • Keine persistenten Daten → Cache geht bei Restart verloren
  • Kein Test-Suite vorhanden
  • Kein Rate-Limiting auf Client-Seite
  • Ein config.yaml pro Deployment (kein Multi-Tenancy)