Modellarchitektur, Feature Engineering, Evaluation und API — für Data Scientists, ML Engineers und alle, die es genau wissen wollen. 📘 Interactive API Docs (Swagger)
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.
| 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. |
| 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 |
| Algorithmus | LightGBM (LGBMClassifier), objective='binary' |
| Target | 1 wenn free < 5, sonst 0 |
| Output | predicted_full_prob ∈ [0, 1] — P(Parkhaus praktisch voll) |
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.
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.
42 Features pro Observation, gruppiert nach Kategorie:
hour quarter weekday is_weekend month sin_hour cos_hour sin_weekday cos_weekday sin_month cos_month
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.
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.
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.
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, …).
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.
log_total = log(Kapazität) — normalisiert den Grösseneffekt.
city und pls_key sind kategorial kodiert.
| 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 |
| Horizont | MAEfree | MAEocc (pp) | Biasfree | Baseline MAE (pp) | Skill Score | R² |
|---|---|---|---|---|---|---|
Metriken erklärt:
1 − MAE(ML) / MAE(Baseline).
0.72 = 72 % weniger Fehler als der saisonale Durchschnitt.
Negativ = Modell schlechter als Baseline.1 − SSres / SStot.
Anteil der erklärten Varianz. 0.85 = das Modell erklärt 85 % der
Belegungsschwankungen. Berechnet auf dem Holdout (letzte 14 Tage)
und beim Training gespeichert.free → 0 explodiert der
relative Fehler.error = predicted_free − actual_freeai_accuracy_daily:
MAE, Bias, Anzahl je (day, city, pls_id, model_type, horizon_h)pls_id = '')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
NaN
(fallen aus dem Training)occ = (total − free) / total — ermöglicht ein
globales Modell über Häuser mit 50 bis 1100 Plätzentotal
Die Messwert-IDs (pls_fetch_current.id) und Stammdaten-IDs
(parkhaeuser.id) verwenden unterschiedliche Namenskonventionen:
| Stadt | Methode | Beispiel |
|---|---|---|
| Basel, Zürich | Exakter Match | baselparkhaussteinen |
| Luzern, St. Gallen | Namens-Containment | SP03 → luzernparkhausbahnhof |
| Bern | Wortmengen-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.
Vollständige, interaktive Dokumentation: 📘 Swagger UI öffnen
Alle Endpunkte akzeptieren ?env=prod|test.
| Endpoint | Methode | Beschreibung |
|---|---|---|
/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/summary | GET | MAE, Bias, Skill-Score je Stadt und Horizont |
/api/accuracy/parkhaus/{stadt}/{id} | GET | Detaillierte Genauigkeit eines Hauses |
/api/accuracy/timeseries | GET | MAE-Verlauf über die letzten N Tage |
/api/chat | POST | Chat-Assistent: lokale Intent-Erkennung (Sentence-Transformer) + Gemini-Flash-Fallback für unbekannte Fragen |
/api/health | GET | Status, aktive Modelle, Scheduler-Zeiten, DB-Info |
/api/version | GET | Version (Zeitstempel der jüngsten Quelldatei) |
/api/cities | GET | Verfügbare Städte |
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.
| Tabelle | Inhalt | ~Zeilen |
|---|---|---|
pls_fetch_current | Alle Belegungsmesswerte (15-Min-Takt) | 1.6 Mio |
weather_forecasts | Stündliches Wetter je Stadt | 27 000 |
local_events | Veranstaltungen mit Kategorie und Bonus | 393 |
event_parkhaus | Event → Parkhaus Zuordnung (n:m) | 720 |
parkhaeuser | Stammdaten inkl. parking_group | 85 |
cities | Stadt-Konfiguration mit Koordinaten | 5 |
| Tabelle | Inhalt |
|---|---|
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 |
| 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 |
Änderungsprotokoll aus der Zusammenarbeit mit Claude Code — Anfrage und Umsetzung.
| 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). |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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). |
| 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. |
| Fehler | R²-Wert wird in TechDoc nicht angezeigt (zeigt «—»)
Api.env (Property) → Api.env() (Funktion). Fetch-URL war mit Funktionsbody statt «prod» zusammengebaut. |
| 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. |
| 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. |
| Verbesserung | UI: Prod als Default, Suchfeld, gemerkte Einstellungen
Umschalter nur mit ?admin sichtbar. Suchfeld, localStorage-Persistenz, Footer mit Version. |
| Erweiterung | Umzug auf neuen Server (87.106.21.252)
FastAPI-ML auf eigenen Server migriert. Holdout-Vergleich gegen vorheriges Modell. |
| 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. |
| 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. |
| 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. |
| 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. |
| Erweiterung | Initiales Projekt: Parkhaus-Daten-Crawler
5 Stadt-Collectoren, Scheduler alle 15 Min, MariaDB, .env-Konfiguration. |