84 Commits in 6 Wochen: Wie ich eine Auslastungsberechnung für den Rettungsdienst gebaut habe

Im Februar 2026 habe ich angefangen, eine Web-App zu bauen, die die Auslastung von Rettungsdienstfahrzeugen berechnet. Sechs Wochen und 84 Commits später läuft Version 5.4 im Produktivbetrieb. Das klingt nach einem geradlinigen Projekt. War es nicht.

Warum überhaupt?

Im Rettungsdienst gibt es sogenannte Vorhaltungszeiten: festgelegte Zeitfenster, in denen ein Fahrzeug einsatzbereit sein muss. Ob ein Fahrzeug in diesen Zeiten tatsächlich ausgelastet ist, bestimmt langfristig, ob es weiter finanziert wird. Die Berechnung ist nicht trivial: Schichtzeiten, Feiertage, Fahrzeugtypen, Ersatzfahrzeuge, Nachtdienste über Mitternacht. All das fließt ein.

Vorher lief das über Excel-Tabellen. Manuell. Fehleranfällig. Zeitaufwendig. Die Idee war simpel: Eine Web-App, die Einsatzdaten aus der bestehenden MySQL-Datenbank liest und die Auslastung automatisch berechnet.

Der Anfang: v4.0

Die erste Version konnte eines: Einsätze zählen, Vorhaltungszeiten dagegen rechnen, Prozent ausgeben. Flask als Backend, Bootstrap für die Oberfläche, MariaDB als Datenbank. Der Excel-Export kam fast sofort dazu, weil die Ergebnisse weiterverarbeitet werden mussten.

Was ich unterschätzt habe: Die Datenqualität. Einsätze ohne Endzeit. Fahrzeuge mit wechselnden Kennzeichen. Zeitstempel in verschiedenen Formaten. Die ersten Wochen waren vor allem Datenbereinigung.

Die Feiertags-Katastrophe

In Version 5.0 habe ich Feiertage eingebaut. Sonntags-Vorhaltung gilt auch an Feiertagen. Klingt einfach. War es auch, bis ich bei der Analyse der Ergebnisse merkte: Kein einziger Feiertag wurde erkannt. Seit v4.0.

Das Problem war subtil. Python speichert Daten als date oder datetime Objekte. Die sehen gleich aus, aber ein date(2026, 1, 1) ist nicht dasselbe wie ein datetime(2026, 1, 1, 0, 0). Wenn man eins als Dictionary-Key benutzt und mit dem anderen nachschlägt, findet man nichts. Kein Fehler, kein Absturz, einfach keine Treffer.

Die Lösung war eine einzige Zeile: .date() vor dem Dictionary-Lookup. Der Bugfix hat länger gedauert als die ursprüngliche Implementierung.

Kennzeichen-Migration

Im April 2025 wurden alle Fahrzeuge im Kreis Bad Kreuznach von KH-Kennzeichen auf MZ umgestellt. In der Datenbank stehen die alten Einsätze unter KH, die neuen unter MZ. Dasselbe Fahrzeug, zwei Namen.

Die Lösung: 16 Fahrzeug-Mappings hardcodiert, automatische Abfrage über beide Datenbanken, und am Überlappungstag (1. April) wird in beiden gesucht. Nicht elegant, aber es funktioniert. Manchmal ist Pragmatismus wichtiger als Architektur.

KW-Rhythmus: Die komplizierteste Anforderung

Die neueste Herausforderung war der Kalenderwochen-Rhythmus. Manche Fahrzeuge fahren nur in geraden oder ungeraden Kalenderwochen, mit unterschiedlichen Schichtzeiten je nach Woche. Dazu kommen Nachtdienste, die über Mitternacht laufen und damit in die nächste Woche fallen können.

Die Frage “Gilt die Nachtschicht von Sonntag auf Montag noch zur alten Woche oder schon zur neuen?” klingt akademisch. In der Praxis entscheidet sie darüber, ob ein Fahrzeug als ausgelastet gilt oder nicht.

Mehrfachberechnung

Irgendwann kam die Anforderung: “Ich will alle Fahrzeuge einer Wache gleichzeitig sehen.” Also baute ich eine Vergleichsansicht. Alle Fahrzeuge auswählen, berechnen, sortierbare Tabelle mit Farbcodierung. Grün bis 75%, Gelb bis 83%, Rot ab 84%.

Der Excel-Export dazu war die eigentliche Herausforderung: Ein Übersichtsblatt plus einzelne Detail-Sheets pro Fahrzeug, mit DRK-Branding und automatischer Spaltenbreite. openpyxl kann viel, aber es gibt Momente, in denen man sich nach einer einfachen CSV-Datei sehnt.

Was ich gelernt habe

Nach 84 Commits und 6 Wochen Entwicklung ein paar Erkenntnisse:

Datenqualität schlägt Features. Die meiste Zeit ging nicht in neue Funktionen, sondern in das Verständnis und die Bereinigung der vorhandenen Daten. Die schönste Berechnung nützt nichts, wenn die Eingangsdaten inkonsistent sind.

Bugs verstecken sich in Annahmen. Der Feiertags-Bug war kein Programmierfehler, sondern eine falsche Annahme (date und datetime sind austauschbar). Solche Bugs findet man nicht durch Code-Review, sondern durch Ergebnisvergleiche.

Pragmatismus vor Perfektion. Die Kennzeichen-Migration per Hardcoding ist nicht schön. Aber sie funktioniert seit Monaten zuverlässig. Ich kann sie jederzeit durch eine elegantere Lösung ersetzen, wenn es nötig wird.

Nutzer denken anders als Entwickler. “Gleichzeitig vergleichen” klingt nach einer simplen Anforderung. In der Umsetzung bedeutet es: individuelle Parameter pro Fahrzeug, asynchrone Berechnung, aggregierte Darstellung und einen Export, der in einer Besprechung bestehen muss.

Wie es weitergeht

Die App läuft stabil im Produktivbetrieb. Die nächsten Schritte sind kleiner: Bessere Fehlermeldungen, mehr automatische Tests, vielleicht eine API für Drittsysteme. Und irgendwann muss ich die hardcodierten Kennzeichen-Mappings durch eine Admin-Oberfläche ersetzen.

Aber das hat Zeit. Erst mal funktioniert es.