# GTFS-Verbindungs-PDF: Offline-Precompute statt Live-Routing (2026-08-19) Bewährt für `urlaub.perfekt-ohne-en.de` (Flask + SQLite + GTFS-Zugdaten). Wenn eine Einzel-Verbindungs-PDF eine **komplette Fahrplan-Verbindung inkl. Haltestellenliste** zeigen soll, darf die Flask-Route den Router/RAPTOR/Dijkstra **nicht bei jedem Request** ausführen. ## Symptom `wkhtmltopdf` + RAPTOR auf großen Knoten (Köln, Berlin) lief 40–200 s je Aufruf → **nginx 504 Gateway Timeout** (Gateway-Zeitlimit überschritten). Nutzer meldete „Wenn ich die PDF anklicke … keine Haltestellenliste". ## Fix: einmaliger Offline-Backfill → JSON-Cache → schneller Cache-Read 1. **Precompute-Skript** (`gtfs_pfade_cache.py`): iteriert `SELECT DISTINCT von,nach` aus `urlaub.db`, ruft für jedes Paar `router.path(von,nach)` (liefert Segmentliste der Zwischenhalte), schreibt `pfade_cache.json`: ```json {"|": {"seg": [["Halt1","Halt2",...]], "dauer_h": 1.5}} ``` 2. **Flask-Route** liest NUR `json.load(open('/opt//pfade_cache.json'))` mit Key `f"{start}|{ziel}"` → Antwort < 1 s, kein Timeout. **Nie** den Router zur Laufzeit importieren/rechnen. 3. Skript als **Hintergrund-Job** (background=true, notify_on_complete) laufen lassen, nicht foreground. ## Teilmenge wählen, sonst quadratische Backfill-Zeit Eindeutige `von|nach` über ALLE Ebenen waren **10 510 Paare** → ~2,4 s/Paar = **~7 h**. Auf die **Ebenen 1+2** (`WHERE ebene IN (1,2)`, ~1 979 Paare) beschränkt = **~35 min**. Ebene-3 (Kleinstädte) = reine Verzweigung ohne eigene PDFs → weglassen. - Trefferquote ist KEINE 100 % (Radevormwald/Kleinstädte ohne Bahnhof im Feed). Template ehrlich auf „Keine GTFS-Haltestellenliste verfügbar" zurückfallen, nie erfinden. ## Pfad-Rekonstruktion muss dasselbe Abfahrtsprofil wie die Dauer nutzen Ein Precompute mit **fester Abfahrtszeit (09:00)** rekonstruierte nur ~34 % der Pfade, weil viele Verbindungen erst zu späteren Tageszeiten existieren. Die Dauer-Profile-Suche (06–22 Uhr) liefert mehr Treffer — die Pfad-Rekonstruktion muss **dasselbe Profil** durchlaufen statt fest 09:00, sonst bleibt die Trefferquote konsistent niedrig. ## Verifikation ```bash # Service nach Template-/Code-Änderung neu starten, dann: curl -sk -o /dev/null -w "PDF: %{http_code} (%{time_total}s)\n" \ "https:///pdf//" # Erwartung: 200 in <1 s (nicht 504), Haltestellenliste enthalten sobald Cache da. ``` Flask läuft mit `debug=False` (systemd) → **nach Änderung IMMER `systemctl restart`** (wg. Template-/Code-Reload-Pitfall, siehe Haupt-SKILL).