Technical Documentation

Modellarchitektur, Feature Engineering, Evaluation und API — für Data Scientists, ML Engineers und alle, die es genau wissen wollen. 📘 Interactive API Docs (Swagger)

1. System Overview

Multi-Horizon Occupancy Forecasting für 110 Parkhäuser in 5 Schweizer Städten. Vier unabhängige Modelle (h ∈ {1, 2, 4, 8}) prognostizieren alle 15 Minuten die Belegungsquote occ = (total − free) / total ∈ [0, 1]. Zusätzlich ein Quantil-Modell (α = 0.2) und ein Binärklassifikator (P(free < 5)) pro Horizont.

Datenpunkte
~1.6 Mio.
pls_fetch_current
Parkhäuser
110
5 Städte
Features
40
pro Observation
Trainingsfenster
120 Tage
rollierend

2. Model Architecture

2.1 Regression (Punktprognose)
Algorithmus LightGBM (LGBMRegressor), Fallback: scikit-learn HistGradientBoostingRegressor
Objective regression_l1 (MAE) — robust gegen Ausreisser
Hyperparameter num_leaves=63, n_estimators=600, learning_rate=0.05, subsample=0.8, colsample_bytree=0.8, min_child_samples=50
Target occ ∈ [0, 1] (Belegungsquote), geclippt auf [0, 1] post-prediction
Scope Ein globales Modell pro Horizont über alle Städte und Parkhäuser — mit city und pls_key als kategoriale Features. Einzelne Häuser liefern nur ~96 Samples/Tag; gemeinsames Training ermöglicht Cross-Learning.
2.2 Quantile Regression (pessimistisch)
Objective quantile, α = 0.2
Interpretation «Mit 80 % Wahrscheinlichkeit sind mindestens X Plätze frei.» Konservative Schätzung für risikoaverse Nutzer.
Output predicted_free_q20 in ai_predictions
2.3 Binary Classifier (Voll-Wahrscheinlichkeit)
Algorithmus LightGBM (LGBMClassifier), objective='binary'
Target 1 wenn free < 5, sonst 0
Output predicted_full_prob ∈ [0, 1] — P(Parkhaus praktisch voll)
2.4 Baseline (Referenzmodell)

Saisonaler Mittelwert je (parkhaus, wochentag, stunde, viertelstunde) über 8 Wochen, mit Rückfallkette auf Stundenmittel → Stadtmittel → Hausmittel. Additive Zuschläge für aktive Events (+Bonus) und Regen > 0.5 mm (+2 pp). Dient ausschliesslich als Benchmark — kein Ensemble-Bestandteil.

2.5 Residual-Korrektur (Post-Processing)

Pro (city, pls_id, horizon_h) wird ein leichtes LightGBM-Modell trainiert, das den Fehler des globalen Modells konditioniert korrigiert. Anders als der frühere statische Bias-Offset lernt das Residual-Modell, wann und wie stark das globale Modell bei einem spezifischen Parkhaus daneben liegt.

residual = global_pred_occ − actual_occ       Target des Residual-Modells
Features: hour, weekday, is_weekend, occ_now, delta_occ_1h,
          is_raining, event_active, prior_occ, anomaly, month
free_korrigiert = (1 − (occ_pred − residual_pred)) × total

Hyperparameter bewusst klein gehalten (100 Trees, 15 Leaves, min. 30 Samples), um Overfitting zu vermeiden. Mindestens 200 Trainingszeilen pro Parkhaus nötig, sonst Fallback auf den gewichteten Bias der letzten 14 Tage. Wird nur auf ML-Prognosen angewandt, nicht auf Baseline.

3. Feature Engineering

42 Features pro Observation, gruppiert nach Kategorie:

Temporal (zyklisch kodiert)

hour quarter weekday is_weekend month sin_hour cos_hour sin_weekday cos_weekday sin_month cos_month

Kalender

is_holiday is_bridge_day is_school_holiday

Kantonale Feiertage (BS, BE, LU, SG, ZH) inkl. Osterdatum-basierte bewegliche Feiertage. Brückentage zwischen Feiertag und Wochenende. Schulferien pro Kanton und Jahr. Gespeichert in ai_feiertage und ai_schulferien.

Lag Features & Statistiken

occ_now occ_1h_ago occ_2h_ago occ_3d_ago occ_24h_ago occ_7d_ago delta_occ_1h trend_2h occ_mean_3h occ_mean_24h occ_std_24h prior_occ

prior_occ = saisonaler Erwartungswert aus dem Trainingsteil (kein Leakage ins Holdout). delta_occ_1h = Momentum der letzten Stunde. Fehlende Lags bleiben NaN — LightGBM handhabt das nativ.

Wetter

temperature precipitation is_raining temp_bin temp_sq

Quelle: Open-Meteo API, stündlich pro Stadt. is_raining = binäres Flag bei > 0.1 mm. Zum Zielzeitpunkt interpoliert.

Events

event_active event_soon event_bonus event_category

Echte Veranstaltungsdaten von 18 Venue-Websites (Hallenstadion, KKL, Tonhalle, Opernhaus, Theater Basel, OLMA u.a.). event_bonus = venue-spezifischer Zuschlag aus venues.json (z.B. KKL 0.25, Hallenstadion 0.45, Theater 0.10–0.15); Fallback auf Kategorie-Bonus wenn kein venue_bonus konfiguriert. event_soon = Event beginnt innerhalb der venue-spezifischen Ankunftszeit (soon_hours in venues.json, z.B. KKL 1 h, Messen 2 h, Theater 0.5 h; Default 1 h). event_category = kategorische Variable (Konzert, Sport, Messe, …).

Interaktions-Features

weekend_event rain_hour

weekend_event = is_weekend × event_active — Events am Wochenende haben stärkeren Effekt. rain_hour = is_raining × hour — Regen wirkt je nach Tageszeit unterschiedlich.

Metadaten

log_total city pls_key

log_total = log(Kapazität) — normalisiert den Grösseneffekt. city und pls_key sind kategorial kodiert.

4. Training Pipeline

Schedule Täglich 03:30 Uhr (Europe/Zurich), APScheduler in-process
Trainingsfenster 120 Tage rollierend (konfigurierbar via AI_TRAIN_DAYS)
Holdout Letzte 14 Tage — zeitbasierter Split, kein Random-Split (verhindert Temporal Leakage)
Trainingsgrösse ~780 000 Zeilen pro Horizont
Activation Gate Neues Modell wird nur aktiviert wenn MAE_neu ≤ 1.10 × MAE_alt (gemessen auf demselben Holdout). Schützt vor Qualitätseinbrüchen nach Datenausfällen.
Pro Horizont 3 Modelle: Regression → Quantile (α=0.2) → Classifier (binary)
Artefakte models_store/*.joblib, letzte 7 behalten, aktive geschützt
Speicher ~735 MB bei 60-Tage-Fenster, ~1368 MB bei 120 Tagen

5. Evaluation & Metriken

5.1 Aktuelle Performance (letzte 7 Tage, Prod)
Horizont MAEfree MAEocc (pp) Biasfree Baseline MAE (pp) Skill Score R²

Metriken erklärt:

5.2 Evaluation Pipeline
  1. Alle 15 Min: gereifte Prognosen dem nächsten Messwert zuordnen (±20 Min Toleranz)
  2. Fehler berechnen: error = predicted_free − actual_free
  3. Tagesweise aggregieren in ai_accuracy_daily: MAE, Bias, Anzahl je (day, city, pls_id, model_type, horizon_h)
  4. Stadtweite und globale Aggregate separat (mit pls_id = '')

6. Data Pipeline

Scanner (alle 15 Min)
│  Stadt-APIs → pls_fetch_current (~1.6 Mio Zeilen)
│  Open-Meteo  → weather_forecasts (2× täglich)
│  Venues      → local_events + event_parkhaus (2× täglich, automatisch)
│
FastAPI-ML (Port 80)
├── :10/:25/:40/:55  predict.py   → ai_predictions
│   └── Residual-Korrektur (pro Parkhaus) oder Bias-Fallback
├── :13/:28/:43/:58  evaluate.py  → ai_accuracy_daily
├── 03:15            identity.py  → ai_parkhaus_map
└── 03:30            train.py     → ai_model_runs + models_store/
                     └── Regression + Quantile + Classifier + Residual × 4 Horizonte
Rasterung & Preprocessing

7. Parkhaus Identity Mapping

Die Messwert-IDs (pls_fetch_current.id) und Stammdaten-IDs (parkhaeuser.id) verwenden unterschiedliche Namenskonventionen:

StadtMethodeBeispiel
Basel, ZürichExakter Matchbaselparkhaussteinen
Luzern, St. GallenNamens-ContainmentSP03 → luzernparkhausbahnhof
BernWortmengen-Vergleich«Bahnhof Parking» → bernparkhausbahnhof

Zentral gelöst in core/identity.py, Ergebnis in ai_parkhaus_map. Häuser ohne Treffer werden trotzdem prognostiziert — nur ohne Event-Features.

8. REST API

Vollständige, interaktive Dokumentation: 📘 Swagger UI öffnen

Alle Endpunkte akzeptieren ?env=prod|test.

EndpointMethodeBeschreibung
/api/forecast/current/{stadt}GET Aktuelle Werte + Prognosen aller Häuser einer Stadt. Enthält free_q20 und full_prob.
/api/forecast/parkhaus/{stadt}/{id}GET Ist-Verlauf + Prognosen + Wetter + Events für ein Haus (Chart-Daten)
/api/forecast/best/{stadt}GET Top-5-Empfehlung für einen Zeitpunkt (?at=), inkl. MAE-Konfidenz
/api/accuracy/summaryGET MAE, Bias, Skill-Score je Stadt und Horizont
/api/accuracy/parkhaus/{stadt}/{id}GET Detaillierte Genauigkeit eines Hauses
/api/accuracy/timeseriesGET MAE-Verlauf über die letzten N Tage
/api/chatPOST Chat-Assistent: lokale Intent-Erkennung (Sentence-Transformer) + Gemini-Flash-Fallback für unbekannte Fragen
/api/healthGET Status, aktive Modelle, Scheduler-Zeiten, DB-Info
/api/versionGET Version (Zeitstempel der jüngsten Quelldatei)
/api/citiesGET Verfügbare Städte

9. Datenbank-Schema

MariaDB, zwei parallele Instanzen: ph_fetch_prod und ph_fetch_test. Die KI-App liest bestehende Tabellen nur und schreibt ausschliesslich in ai_-präfixierte Tabellen.

Scanner-Tabellen (read-only)
TabelleInhalt~Zeilen
pls_fetch_currentAlle Belegungsmesswerte (15-Min-Takt)1.6 Mio
weather_forecastsStündliches Wetter je Stadt27 000
local_eventsVeranstaltungen mit Kategorie und Bonus393
event_parkhausEvent → Parkhaus Zuordnung (n:m)720
parkhaeuserStammdaten inkl. parking_group85
citiesStadt-Konfiguration mit Koordinaten5
KI-Tabellen (read-write)
TabelleInhalt
ai_predictions Jede Prognose: predicted_free, predicted_occ, predicted_free_q20, predicted_full_prob, Zielzeit, Horizont, Modelltyp
ai_accuracy_daily Tagesaggregierte Fehler: MAE, Bias, n je (Tag, Stadt, Parkhaus, Modell, Horizont)
ai_model_runs Trainingsprotokoll: Zeitpunkt, Datenmenge, CV-MAE, Artefakt-Pfad, is_active
ai_parkhaus_map Identity-Mapping zwischen Messwert- und Stammdaten-IDs
ai_feiertage Kantonale Feiertage: (datum, kanton, name)
ai_schulferien Schulferien pro Kanton und Jahr: (kanton, jahr, von, bis)
ai_chat_log Chat-Verlauf mit erkannter Absicht

10. Stack & Deployment

Backend Python 3.9+, FastAPI, uvicorn (1 Worker)
ML LightGBM (Fallback: scikit-learn), pandas, numpy
Datenbank MariaDB, pymysql
Scheduler APScheduler (in-process), Zeitzone Europe/Zurich
Frontend HTML, Bootstrap 5.3, Chart.js 4.4 — kein Build-Schritt
Reverse Proxy Nginx 1.24 (Reverse Proxy), FastAPI auf Port 8000 (lokal)
SSL/TLS Let's Encrypt (Certbot), Auto-Renewal
Server 87.106.21.252, Nginx auf Port 80/443, Domain parkhaus-wetter.roil.ch

11. Change History

Änderungsprotokoll aus der Zusammenarbeit mit Claude Code — Anfrage und Umsetzung.

29. September 2026
Fix
MAE-Explosion seit 25.09. im Genauigkeits-Dashboard
Root Cause: Das Parkleitsystem St. Gallen liefert für «Parkhaus Oberer Graben» (pls_id=31) korrupte free-Werte (~1'000'000'031 statt ~31). Die Evaluierung berechnete daraus abs_err_free im Milliardenbereich, was die globalen Tagesaggregate auf mae_free ≈ 60'000–114'000 aufblähte. Fix: evaluate.py und data_access.py filtern jetzt Messwerte mit free < 0 oder free > 2×total als unplausibel heraus. Reparatur-Query setzt die betroffenen Evaluierungen zurück und berechnet die Tagesaggregate neu.
Fix
Residual-Modelle auf Out-of-Fold Predictions umgestellt
Die Residual-Modelle wurden bisher auf In-Sample-Predictions des globalen Modells trainiert — das globale Modell sah seine eigenen Trainingsdaten, deren Fehler künstlich klein waren (Overfitting). Die Residual-Modelle lernten dadurch Muster, die bei echten Prognosen nicht existieren, und verschlechterten die Vorhersagen um ~13%. Fix: Die bestehende Rolling-CV-Schleife sammelt jetzt Out-of-Fold-Predictions, und die Residual-Modelle werden auf diesen realistischen Fehlern trainiert. Zusätzlich: Korrektur auf ±15 Prozentpunkte begrenzt (Clamp), predicted_occ wird jetzt korrigiert gespeichert (vorher nur predicted_free).
4. September 2026
Feature
Residual-Modelle pro Parkhaus
Leichte LightGBM-Modelle pro Parkhaus, die den Fehler des globalen Modells konditioniert korrigieren. 10 Features (Stunde, Wochentag, aktuelle Belegung, Anomalie, Wetter, Events). Ersetzt die statische Bias-Korrektur — statt eines fixen Offsets lernt das Residual-Modell, wann und wie stark das globale Modell bei einem spezifischen Parkhaus daneben liegt. Min. 200 Trainingszeilen pro Haus, Fallback auf statischen Bias wenn zu wenig Daten. Ein Artefakt pro Horizont enthält alle Parkhaus-Modelle.
1. September 2026
Feature
Hybrid-Chatbot mit Gemini Flash
Chat-Assistent nutzt jetzt Claude Haiku als Fallback für Fragen, die der regelbasierte Bot nicht versteht. Bekannte Intents (Parkplätze, Wetter, Events, Prognosen) bleiben regelbasiert und schnell. Unbekannte Fragen gehen an Gemini Flash (kostenloses Kontingent, 1500 Requests/Tag). Nutzungs-Tracking in ai_chat_llm_usage-Tabelle.
31. August 2026
Feature
Feature Importance Logging
ForecastModel speichert nach fit() die Feature-Importances. Top 15 werden in params_json (ai_model_runs) abgelegt, Top 5 ins Trainingslog geschrieben. Transparenz über welche Features das Modell tatsächlich nutzt.
Feature
Tagesform-Features (anomaly, morning_anomaly)
Zwei neue Features in features.py: «anomaly» = aktuelle Belegung minus saisonalem Prior (wie ungewöhnlich ist es jetzt?), «morning_anomaly» = 3h-Mittel minus Prior (genereller Trend des Tages). Erkennt z.B. Feiertage, Streiks, Grossevents die den Tag vom Normalverlauf abweichen lassen.
Verbesserung
Rolling Cross-Validation (4 Folds)
Trainings-Pipeline nutzt neu 4 rolling CV-Fenster à 14 Tage statt einem einzigen Holdout. Gibt robustere MAE/R²-Schätzungen — einzelne «gute» oder «schlechte» Wochen verzerren das Ergebnis nicht mehr. CV-Metriken werden in params_json und Trainingslog gespeichert.
Fix
MAE-occ% Gewichtung nach Garagengrösse
evaluate.py: MAE-occ% wird neu gewichtet nach total_at_pred statt einfachem AVG. Vorher dominierten kleine Garagen (38 Plätze) mit hohen %-Fehlern die Gesamtmetrik — ein Fehler von 5 Plätzen = 13% bei kleinem Haus aber nur 0.3% bei grossem. Jetzt realistischere Gesamtbewertung.
Feature
Modell-Aktivierungen auf Accuracy-Zeitachse sichtbar
Accuracy-Seite: Zeitraum-Dropdown erweitert (60/90 Tage). Timeseries-Charts zeigen vertikale Markierungen (🔄) bei jedem Modell-Training. Tooltip zeigt MAE, R² und CV-Folds. Neuer API-Endpunkt /api/accuracy/activations liefert historische Aktivierungsdaten.
28. August 2026
Verbesserung
Event-Impact pro Venue kalibrieren statt nur Kategorie-Bonus
Analyse historischer Daten zeigte: KKL-Konzerte wurden unterschätzt (Bonus 0.20, tatsächlicher Uplift 0.22–0.41), Luzerner Theater überschätzt (Bonus 0.30, Uplift nur 0.09). Neu: venue_bonus und soon_hours pro Venue in venues.json. Feature-Engineering nutzt venue-spezifisches Ankunftsfenster (z.B. KKL 1h, Messen 2h, Theater 30min) statt fix 3h. Bonus-Berechnung in fetch_events.py priorisiert venue_bonus vor category_bonus.
26. August 2026
Feature
Vereinfachte Ampellogik mit Farbenblind-Icons
Ampel vereinfacht: 👍 grün = bleibt frei bis +8h (garantiertSet), ⚠ gelb = wird knapp, 👎 rot = voll/fast voll. «Effective Class»: aktuell grün aber nicht in garantiertSet → gelb. Toggle «volle ausblenden» entfernt.
Verbesserung
kurzName() – Prefix-Bereinigung in Einfach- und Karten-Ansicht
Neuer Helper kurzName() entfernt Stadt-Prefix und Typ-Prefix (Parkhaus/Parkgarage/Cityparking/Parkplatz) aus Anzeigenamen. Anwendung in Ampel-Kacheln und Karten-Popups.
Feature
Loading-Indicator beim Stadtwechsel
Fullscreen-Overlay mit Spinner und «Daten werden geladen…» während loadForecasts(). CSS-Animation in app.css.
Verbesserung
Datenquelle neben Stadt-Dropdown
Link zur PLS-Datenquelle (z.B. pls-zh.ch) direkt neben der Stadt-Auswahl statt im Footer.
Verbesserung
Detail-Ansicht nur Prognose-Spalten
Detail-Tabelle zeigt nur noch +1h/+2h/+4h/+8h Prognose. Ist-Daten (−2h, −1h, Jetzt) nur in Experten-Ansicht. Unnötige Titel entfernt.
Verbesserung
Karte fullscreen, Routenplaner-Link
Kartenhöhe auf calc(100vh − 180px) erweitert. Popup zeigt neu «📍 Route planen» Link (Google Maps Directions). Titel «Parkhaus-Karte» entfernt.
Fix
Gruppen-Zuordnung komplett (Zürich + St. Gallen)
update_zurich_groups.py: Alle Parkhäuser korrekt gruppiert (Innenstadt/Oerlikon/Zürich-West, St. Gallen: Zentrum West/Klosterviertel/Marktplatz/Zentrum Ost/OLMA). Central → Marktplatz, Raiffeisenzentrum → Klosterviertel. Script läuft auf test + prod DB.
Fix
Koordinaten aller ~96 Parkhäuser korrigiert
fix_coordinates.py: Alle Parkhaus-Koordinaten aus offiziellen Quellen aktualisiert (Basel: data.bs.ch, St. Gallen: daten.stadt.sg.ch, Luzern/Bern/Zürich: Adress-Recherche). Park Hyatt, Puls 5, Central hatten NULL → jetzt korrekt. Beide DBs (test + prod).
Verbesserung
Detail- und Experten-Tabelle getrennt
Eigene tbody-IDs und separate Row-Renderer für Detail- und Experten-Ansicht, damit beide Views unabhängig angepasst werden können.
Feature
Karten-Modus als 4. Persona-Ansicht
Parkhäuser als farbige Ampel-Marker auf OpenStreetMap (Leaflet.js). Horizont-Dropdown (Jetzt / +1h / +2h / +4h / +8h) aktualisiert die Marker-Farben und %-Werte live.
25. August 2026
Verbesserung
Einfach-Modus: Modellwahl ausgeblendet
Im Einfach-Modus wird die Modellauswahl (KI/Basis) komplett ausgeblendet, da sie in dieser Ansicht nicht relevant ist.
Verbesserung
Ampel-Klick öffnet Parkhaus-Detailansicht
Klick auf eine Ampel-Kachel wechselt zur Experten-Ansicht, setzt den Suchfilter auf das gewählte Parkhaus und zeigt Chart, Wetter und Events direkt an.
Verbesserung
Tabelle zeigt % Auslastung statt nur absolute Zahlen
Alle Spalten (Rückblick, Jetzt, Prognose) zeigen primär die prozentuale Auslastung. Absolute Zahlen (frei/total, Veränderung) stehen klein darunter. Spaltenheader «Belegt» statt «Frei jetzt».
Verbesserung
Performance: Connection Pool, Response Cache, DB-Index
1) db.py: Connection Pool (Queue-basiert, 4 Verbindungen pro env) statt connect-per-query — spart ~500ms TCP-Handshakes pro Seitenaufruf. 2) services.py: 60s TTL-Cache für current_forecasts — wiederholte Aufrufe innerhalb einer Minute treffen nicht mehr die DB. 3) SQL-Index auf pls_fetch_current(city, fetch_ts) und (city, id, fetch_ts) für schnellere Rückblick- und Inactive-Queries.
22. August 2026
Erweiterung
Willkommens-Dialog für Erstbesucher
Beim ersten Besuch erscheint ein Modal-Dialog, der nach bevorzugter Ansicht (Einfach/Detail/Experte) und Stadt fragt. Die Auswahl wird in localStorage gespeichert und bei künftigen Besuchen automatisch wiederhergestellt. Wiederkehrende Besucher sehen den Dialog nicht mehr.
19. August 2026
Erweiterung
Persona-basierte Ansichten (Einfach/Detail/Experte) für verschiedene Usergruppen
Drei Modi via Toggle in der Navbar: Einfach = Ampel-Kacheln mit Gruppen, 8h-Trend, Empfehlung pro Gruppe (⭐). Detail = Tabelle volle Breite (ohne rechte Panels). Experte = Tabelle + Chart, Wetter, Events, Konfidenz-Tabelle (Q20, P(voll)), Modellvergleich KI vs. Basis, Methodik. Persona wird via localStorage gemerkt.
Erweiterung
Farbenblind-freundliche Ampelkarten und Parkhaus-Tipps
Icons ✓/⚠/✗ zusätzlich zu Farben auf Ampel-Kacheln. Warnhinweise für problematische Parkhäuser (Flora, Hirzenmatt, am Gütsch, City-Parking). Toggle «Fast volle ausblenden» blendet Parkhäuser mit ≤10% Auslastung aus.
Erweiterung
DB-Felder price_category, url, lat/lon und Datenquellen-Links
parkhaeuser-Tabelle (prod+test) um price_category (VARCHAR 10), url (VARCHAR 512), latitude/longitude (DECIMAL 9,6) erweitert. Alle ~70 Parkhäuser in 5 Städten abgefüllt. Ampel-Kacheln zeigen Preiskategorie ($, $$, $$$) als Badge oben rechts, 🔗 Webseite-Link, 📍 Google-Maps-Route. Detail-/Experten-Tabelle zeigt dieselben Infos. Datenquellen-Link pro Stadt (PLS Luzern, Parkleitsystem Basel, etc.).
Verbesserung
Berner Parkhäuser ergänzt und gemappt
7 neue Berner Parkhäuser in DB angelegt (Expo, Kursaal, Mobiliar, City West, Zentrum, Neufeld, SBB Kurzparking) und in ai_parkhaus_map verknüpft. Alle mit Preis, URL und Koordinaten.
Erweiterung
Chatbot kennt Preise, URLs und Koordinaten
Chatbot-Handler für «current» und «best_parking» zeigen jetzt Preiskategorie, Webseiten-Link und Google-Maps-Routenlink an. _snap_payload liefert price_category, url, lat, lon mit. Mapping-Daten werden in den Handlern nachgeschlagen.
18. August 2026
Erweiterung
12 neue Venue-Scraper für bessere Event-Abdeckung
fetch_events.py: 12 neue Scraper hinzugefügt — Opernhaus Zürich, Schauspielhaus Zürich, Kongresshaus Zürich, THE HALL (ex Samsung Hall), Theater Basel, St. Jakobshalle Basel, Konzert & Theater St. Gallen, BernExpo, KKL Luzern, Messe Luzern, Stadtkeller Luzern, Luzern Top Events (mit Detail-Lookup für exakte Daten von Marathon, Festivals etc.). venues.json entsprechend erweitert. Insgesamt jetzt 18 Scraper (vorher 6), deckt alle 5 Städte ab. luzern.com/veranstaltungen (JS/Vue), myswitzerland.com (Bot-Protection), guidle.com (JS/API) wurden geprüft aber sind nicht direkt scrapbar.
Erweiterung
Nginx Reverse Proxy Setup für parkhaus-wetter.roil.ch
1) Nginx 1.24 als Reverse Proxy installiert, FastAPI auf Port 8000 (lokal, 127.0.0.1) verlagert. 2) HTTP → HTTPS Redirect konfiguriert. 3) SSL/TLS mit Let's Encrypt Certbot eingerichtet, Auto-Renewal aktiviert. 4) DNS A-Record für parkhaus-wetter.roil.ch auf 87.106.21.252 (neue Server-IP) konfiguriert. 5) Umfassende Dokumentation (NGINX-REVERSE-PROXY-SETUP.md) erstellt und ins GitHub gepusht. 6) Nginx-Konfiguration mit Proxy-Headers (X-Real-IP, X-Forwarded-For, X-Forwarded-Proto) für korrekte Client-IP-Erkennung. 7) Production-ready Setup: Domain über HTTPS erreichbar, App liegt hinter Reverse Proxy.
Verbesserung
Alle Dokumentationen (techdoc.html, README.md) mit Nginx und neuer Server-IP aktualisieren
techdoc.html Kapitel 10: Stack aktualisiert mit Nginx, Port, SSL, und Reverse Proxy. NGINX-REVERSE-PROXY-SETUP.md: Komplette Installation, Konfiguration, Fehlerbehandlung, Wartung, Sicherheit.
12. August 2026
Verbesserung
Hardcodierte Werte in JSON-Konfiguration ausgelagert
venues.json (NEU): Venue-URLs, Parkhaus-IDs, VENUE_CITY_MAP, CATEGORY_BONUS aus fetch_events.py externalisiert. cities.json: lat/lon-Koordinaten und Bern parking_map ergänzt. get_event_and_weather_data.py liest Koordinaten aus cities.json. bern.py liest OFFICIAL_PARKINGS aus cities.json. fetch_events.py User-Agent auf constants.USER_AGENT vereinheitlicht. Score B+ (78→82).
Verbesserung
Scanner Code Quality von C+ auf B verbessern (Top-5 Refactoring)
1) constants.py: SWISS_TZ, USER_AGENT zentral (statt 8x dupliziert). 2) Basel/Zürich: _is_stale, normalize_parkendd, collect, _collect_fallback in Basisklasse verschoben (~130 Zeilen eliminiert). 3) scheduler.py: 3 identische run_*-Funktionen durch eine generische _run_script ersetzt. 4) get_event_and_weather_data.py: pymysql durch db_utils.get_connection ersetzt, Weather-Duplikat in _fetch_weather zusammengefasst. 5) Dead Code: store_historical_events (100 Zeilen), unused imports, redundante Bedingungen entfernt.
Fehler
Chatbot erkennt Datumsangaben wie «14.8.» oder «14. August» nicht
entities.py: Datums-Parser um explizite Formate ergänzt (TT.MM., TT.MM.JJJJ, TT. Monatsname).
Fehler
Chatbot antwortet mit Parkhaus-Empfehlung statt Events, wenn nach «events am 14.8.» gefragt
intents.py: Keyword-Overrides eingeführt — enthält der Text «event», «veranstaltung» o.ä., wird immer Intent «events» gewählt, bevor der Semantic Classifier falsch zuordnen kann. Zusätzlich Events-Beispielsätze mit Datumsangaben in semantic.py ergänzt.
Erweiterung
Genauigkeitsseite: Radar-Charts statt Balken, 3 Tabs, 4 Spalten pro Horizont
Balkendiagramm durch 4 Radar-Charts (KI vs. Basis pro Stadt) ersetzt. Seite in 3 Tabs: Übersicht, Parkhaus, Trainingsläufe.
Erweiterung
Events nach Heute/Morgen gruppiert anzeigen mit Datum
forecast.js: Events nach Heute und Morgen getrennt mit Datumsanzeige.
Fehler
fetch_events.py schlägt fehl mit «unrecognized arguments: --days 60»
scheduler.py: --days 60 entfernt, fetch_events.py akzeptiert nur --venue.
Fehler
copy-github.sh klont den falschen Branch
--branch main explizit in git clone eingefügt.
Verbesserung
Fake/Dummy-Events aus der Datenbank löschen
170 Fake-Events + 476 Zuordnungen pro DB gelöscht (prod + test).
Fehler
«Prognose ist veraltet» erscheint nach jedem Server-Neustart
predict.run() wird jetzt sofort beim App-Start ausgeführt (main.py lifespan), damit frische Prognosen vor dem ersten Request vorliegen.
Verbesserung
Parkhäuser ohne Bewegungen in 48 h automatisch ausblenden
services.py: Parkhäuser mit min(free)=max(free) über 48 h werden aus der Prognosetabelle gefiltert (defekter Sensor / inaktiv).
11. August 2026
Erweiterung
Chatbot intelligenter machen mit lokalem ML-Modell (keine externen API-Kosten)
Sentence-Transformer (paraphrase-multilingual-MiniLM-L12-v2, ~120 MB) integriert. 8 Intents, Cosine-Similarity, Regex-Fallback.
Verbesserung
Alle Dokumentationen mit neuem Chatbot und Event-Automatisierung aktualisieren
doku.html, techdoc.html, chat.html, alle README.md: «Regelbasierter Assistent» → «semantische Sprachverarbeitung», Events «manuell» → «automatisch».
Verbesserung
Events sollen automatisch gescrapt werden, nicht manuell erzeugt
scheduler.py: fetch_events.py als Job um 06:30/18:30. store_historical_events() entfernt.
9. August 2026
Fehler
R²-Wert wird in TechDoc nicht angezeigt (zeigt «—»)
Api.env (Property) → Api.env() (Funktion). Fetch-URL war mit Funktionsbody statt «prod» zusammengebaut.
7. August 2026
Erweiterung
R²-Metrik (Bestimmtheitsmass) einführen
R² in train.py berechnet (Holdout), in ai_model_runs gespeichert, über API ausgeliefert, in TechDoc angezeigt.
Erweiterung
Auto-Migration für neue DB-Spalten beim App-Start
main.py: _auto_migrate() prüft information_schema und fügt fehlende Spalten per ALTER TABLE hinzu.
Erweiterung
TechDoc Kapitel 5 (Live-Daten) ist leer
JS repariert: data.per_city → data.entries. Performance-Tabelle gefüllt.
Verbesserung
Features auf 40 erweitern mit Interaktionen
9 Interaktions-Features, exponentielle Bias-Gewichtung, Subsampling für Normalstunden.
Erweiterung
Neue TechDoc-Seite für Data Scientists
techdoc.html mit 5 Kapiteln: Modellarchitektur, Features, Metriken, API, Live-Daten.
6. August 2026
Erweiterung
4 Prognose-Verbesserungen (Bias, Kalender, Quantil, Events)
Bias-Korrektur (14-Tage-Mittel), Kalender aus DB, Quantil-Modell (α=0.2), Voll-Klassifikator, Event-Scraper für 6 Venues.
Fehler
None-Werte bei MAE in _insert_run für Klassifikator
None-Check für mae_free/mae_occ vor INSERT.
5. August 2026
Verbesserung
UI: Prod als Default, Suchfeld, gemerkte Einstellungen
Umschalter nur mit ?admin sichtbar. Suchfeld, localStorage-Persistenz, Footer mit Version.
4. August 2026
Erweiterung
Umzug auf neuen Server (87.106.21.252)
FastAPI-ML auf eigenen Server migriert. Holdout-Vergleich gegen vorheriges Modell.
31. Juli 2026
Fehler
Chat tot über plain HTTP, «jetzt»-Fragen ohne Daten
HTTP-Fallback, Chat-Handler für «jetzt» liefert aktuelle Messwerte.
Verbesserung
Training vom Server auslagern (zu wenig RAM)
Training auf PC, Modelle per SCP übertragen. MODELL-REFRESH.md erstellt.
30. Juli 2026
Erweiterung
KI-Prognose-App (FastAPI-ML) erstellen
Komplette App: LightGBM für 4 Horizonte, Prognoseseite, Genauigkeits-Dashboard, Chat, Dokumentation.
Fehler
Training hängt, Server-Speicher explodiert (641 MB RAM)
Speicherlimits via systemd-run, Training mit --days begrenzt.
Erweiterung
systemd-Units, Autostart, Start-Scripts
4 Service-Dateien, install-systemd.sh, start-all.sh, crontab @reboot.
29. Juli 2026
Erweiterung
Wetter, 24h-Prognose, Prod/Test-Umschaltung im Flask-Dashboard
Wetteranzeige, Event-Sektion, ?env= Parameter, Fallback-APIs für Basel/Zürich.
Verbesserung
Von JSON-Dateien auf DB-Tabellen umstellen
Städte/Parkhäuser aus DB statt JSON. Statische Dateien entfernt.
28. Juli 2026
Erweiterung
Wetter-/Event-Daten sammeln, Projektstruktur aufräumen
Open-Meteo API, Event-Generierung. flask/ und scanner/ Ordnerstruktur.
Verbesserung
Crawler robuster machen
Retry mit Backoff, UPSERT, Snapshot-Fallback, St. Gallen API-Mapping korrigiert.
Erweiterung
Simulationsmodus für den Crawler
--simulation Flag, MockCursor für Tests.
12. Juli 2026
Erweiterung
Initiales Projekt: Parkhaus-Daten-Crawler
5 Stadt-Collectoren, Scheduler alle 15 Min, MariaDB, .env-Konfiguration.