{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "22e1b7f0",
   "metadata": {},
   "source": [
    "# Proof of Concept – WegWahl\n",
    "\n",
    "Im Rahmen dieses Proof of Concept wurde ein Jupyter Notebook entwickelt, das exemplarisch zeigt, wie verschiedene Mobilitätsdaten verarbeitet und verglichen werden können. Ziel war es, einen ersten funktionalen Prototypen zu erstellen, der verschiedene Verkehrsmittel analysiert und basierend auf persönlichen Präferenzen die beste Transportoption berechnet.\n",
    "\n",
    "Das Notebook kombiniert mehrere externe APIs zur Verarbeitung von ÖV-, Auto-, Velo- und Wetterdaten. Zusätzlich werden Benutzerpräferenzen, gespeicherte Fahrten sowie eine ÖV-Abo-Empfehlung berücksichtigt.\n",
    "\n",
    "Der Aufbau erfolgt modular. Die einzelnen Abschnitte demonstrieren verschiedene Funktionen und Teilprozesse der Anwendung WegWahl."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c051a184",
   "metadata": {},
   "source": [
    "## Verwendete Libraries\n",
    " \n",
    "In diesem Abschnitt werden die benötigten Python-Module und Libraries importiert, welche für die Funktionalitäten des Proof of Concept verwendet werden.\n",
    " \n",
    "- `requests` → Kommunikation mit externen APIs (Transport API, OpenRouteService, Open-Meteo)\n",
    "- `datetime` → Verarbeitung von Datums- und Zeitangaben\n",
    "- `json` → Verarbeitung von JSON-Daten aus API-Antworten\n",
    "- `math` → Mathematische Berechnungen und Auswertungen"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "27bb9c1d",
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests\n",
    "import json\n",
    "import math\n",
    "from datetime import datetime"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "540c9156",
   "metadata": {},
   "source": [
    "## Einfache Registrierung und Login\n",
    "\n",
    "Dieses Notebook zeigt eine minimale Benutzerverwaltung mit Registrierung und Login:\n",
    "\n",
    "- Die Funktion `registrieren(name, passwort, passwort_bestaetigen)` prueft, ob der Benutzername schon existiert, ob das Passwort mindestens 4 Zeichen hat und ob die Passwort-Bestaetigung uebereinstimmt.\n",
    "- Ist die Registrierung erfolgreich, wird der Benutzer im `benutzer`-Dictionary gespeichert.\n",
    "- Die Funktion `login(name, passwort)` vergleicht Benutzernamen und Passwort mit den gespeicherten Daten und gibt entweder `Login erfolgreich` oder `Login fehlgeschlagen` zurueck.\n",
    "- Nach der Registrierung wird ein Login-Versuch mit Benutzereingaben ausgefuehrt."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a506c51b",
   "metadata": {},
   "outputs": [],
   "source": [
    "benutzer = {}\n",
    "\n",
    "def registrieren(name, passwort, passwort_bestaetigen):\n",
    "    if name in benutzer:\n",
    "        return \"Benutzer existiert bereits\"\n",
    "\n",
    "    if len(passwort) < 4:\n",
    "        return \"Passwort muss mindestens 4 Zeichen haben\"\n",
    "\n",
    "    if passwort != passwort_bestaetigen:\n",
    "        return \"Passwörter stimmen nicht überein\"\n",
    "\n",
    "    benutzer[name] = passwort\n",
    "    return \"Registrierung erfolgreich\"\n",
    "\n",
    "def login(name, passwort):\n",
    "    if benutzer.get(name) == passwort:\n",
    "        return \"Login erfolgreich\"\n",
    "\n",
    "    return \"Login fehlgeschlagen\"\n",
    "\n",
    "\n",
    "# Registrierung mit Benutzereingabe\n",
    "name = input(\"Benutzername: \")\n",
    "passwort = input(\"Passwort: \")\n",
    "passwort_bestaetigen = input(\"Passwort bestätigen: \")\n",
    "print(registrieren(name, passwort, passwort_bestaetigen))\n",
    "\n",
    "# Login mit Benutzereingabe\n",
    "name = input(\"Benutzername: \")\n",
    "passwort = input(\"Passwort: \")\n",
    "print(login(name, passwort))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6560de9e",
   "metadata": {},
   "source": [
    "## Dein Profil\n",
    "\n",
    "Dieser Abschnitt erfasst deine Verkehrsmittel-Präferenzen und Einschränkungen:\n",
    "\n",
    "- **Auto**: Ja/Nein-Frage, ob du Zugang zu einem Auto hast\n",
    "- **SBB Tarif**: Auswahl zwischen Generalabonnement (GA), Halbtax-Abo oder Normaltarif\n",
    "- **Fussweg**: Maximale Gehzeit pro Fahrt in Minuten (leer = unbegrenzt)\n",
    "- **Velo**: Ja/Nein-Frage, ob du ein eigenes Fahrrad hast\n",
    "- **Fahrtdauer Velo**: Maximale Fahrtdauer pro Fahrt in Minuten, falls vorhanden (leer = unbegrenzt)\n",
    "\n",
    "Am Ende wird dein komplettes Profil angezeigt."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7b600f40",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Profilerfassung\n",
    "profil = {}\n",
    "\n",
    "print(\"\\n=== Dein Profil ===\\n\")\n",
    "\n",
    "# Auto\n",
    "auto_frage = input(\"Hast du Zugang zu einem Auto? (Ja/Nein): \").strip().lower()\n",
    "profil[\"Auto\"] = auto_frage in [\"ja\", \"j\", \"yes\", \"y\"]\n",
    "\n",
    "# SBB Tarif\n",
    "print(\"\\nWelches SBB-Abo hast du?\")\n",
    "print(\"1. Generalabonnement (GA)\")\n",
    "print(\"2. Halbtax-Abo\")\n",
    "print(\"3. Normaltarif (Standard)\")\n",
    "sbb_wahl = input(\"Wähle 1, 2 oder 3: \").strip()\n",
    "sbb_optionen = {\"1\": \"Generalabonnement\", \"2\": \"Halbtax-Abo\", \"3\": \"Normaltarif\"}\n",
    "profil[\"SBB_Tarif\"] = sbb_optionen.get(sbb_wahl, \"Normaltarif\")\n",
    "\n",
    "# Fussweg\n",
    "fussweg_dauer = input(\"\\nMaximale Fussweg-Dauer pro Fahrt (Minuten) [leer = unbegrenzt]: \").strip()\n",
    "profil[\"Fussweg_Minuten\"] = int(fussweg_dauer) if fussweg_dauer.isdigit() else \"unbegrenzt\"\n",
    "\n",
    "# Velo\n",
    "velo_frage = input(\"\\nHast du ein eigenes Fahrrad? (Ja/Nein): \").strip().lower()\n",
    "profil[\"Velo\"] = velo_frage in [\"ja\", \"j\", \"yes\", \"y\"]\n",
    "\n",
    "# Fahrtdauer Velo\n",
    "if profil[\"Velo\"]:\n",
    "    fahrt_dauer = input(\"Maximale Fahrtdauer pro Fahrt (Minuten) [leer = unbegrenzt]: \").strip()\n",
    "    profil[\"Velo_Fahrtdauer_Minuten\"] = int(fahrt_dauer) if fahrt_dauer.isdigit() else \"unbegrenzt\"\n",
    "\n",
    "print(\"\\n=== Dein Profil Summary ===\")\n",
    "for key, value in profil.items():\n",
    "    print(f\"{key}: {value}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "84e85dcb",
   "metadata": {},
   "source": [
    "## Deine Präferenzen\n",
    "\n",
    "Dieser Abschnitt erfasst deine Prioritäten für die Verkehrsmittelwahl:\n",
    "\n",
    "- **Umwelt**: Wie wichtig ist dir, CO₂-Emissionen zu minimieren?\n",
    "- **Kosten**: Wie wichtig ist dir, günstig zu reisen?\n",
    "- **Zeit**: Wie wichtig ist dir, schnell anzukommen?\n",
    "\n",
    "Du gibst direkt Prozentwerte ein, die zusammen genau 100% ergeben müssen. Der Code validiert die Eingabe und speichert die Präferenzen im Profil."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "07e20798",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "print(\"\\n=== Deine Präferenzen ===\\n\")\n",
    "\n",
    "# Präferenzen via Input eingeben (direkt in Prozent)\n",
    "while True:\n",
    "    try:\n",
    "        umwelt_pct = int(input(\"Umwelt (%): \"))\n",
    "        kosten_pct = int(input(\"Kosten (%): \"))\n",
    "        zeit_pct = int(input(\"Zeit (%): \"))\n",
    "        \n",
    "        total = umwelt_pct + kosten_pct + zeit_pct\n",
    "        \n",
    "        if total != 100:\n",
    "            print(f\"❌ Die Summe muss genau 100% ergeben. Du hast {total}% eingegeben.\")\n",
    "            print(\"Bitte versuche es erneut.\\n\")\n",
    "            continue\n",
    "        \n",
    "        print(f\"\\n✅ Deine Präferenzen:\")\n",
    "        print(f\"  Umwelt:  {umwelt_pct}%\")\n",
    "        print(f\"  Kosten:  {kosten_pct}%\")\n",
    "        print(f\"  Zeit:    {zeit_pct}%\")\n",
    "        \n",
    "        # Speichern\n",
    "        preferenzen = {\n",
    "            \"Umwelt\": umwelt_pct,\n",
    "            \"Kosten\": kosten_pct,\n",
    "            \"Zeit\": zeit_pct\n",
    "        }\n",
    "        profil[\"Praeferenzen\"] = preferenzen\n",
    "        print(f\"\\n✅ Präferenzen gespeichert: {preferenzen}\")\n",
    "        break\n",
    "        \n",
    "    except ValueError:\n",
    "        print(\"❌ Bitte gebe ganze Zahlen ein (0-100).\\n\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b1d6b90e",
   "metadata": {},
   "source": [
    "## Reiseangaben\n",
    "\n",
    "In diesem Abschnitt werden die Reisedaten der Nutzerin oder des Nutzers erfasst. Dazu gehören Startort, Zielort, Datum sowie die gewünschte Reisezeit. Falls keine Angaben für Datum oder Uhrzeit gemacht werden, werden automatisch die aktuellen Werte übernommen. Die eingegebenen Informationen dienen als Grundlage für sämtliche API-Abfragen und Berechnungen der verschiedenen Verkehrsmittel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c36fcc3d",
   "metadata": {},
   "outputs": [],
   "source": [
    "from datetime import datetime\n",
    "\n",
    "print(\"\\n=== Reiseangaben ===\\n\")\n",
    "\n",
    "# Orte eingeben\n",
    "von = input(\"Abfahrtsort: \").strip()\n",
    "nach = input(\"Zielort: \").strip()\n",
    "\n",
    "# Aktuelle Zeit\n",
    "now = datetime.now()\n",
    "\n",
    "# Datum/Zeit eingeben\n",
    "datum = input(\n",
    "    f\"Datum [Enter = heute {now.strftime('%d.%m.%Y')}]: \"\n",
    ").strip()\n",
    "\n",
    "zeit = input(\n",
    "    f\"Uhrzeit [Enter = jetzt {now.strftime('%H:%M')}]: \"\n",
    ").strip()\n",
    "\n",
    "# Standardwerte setzen\n",
    "if not datum:\n",
    "    datum = now.strftime(\"%d.%m.%Y\")\n",
    "\n",
    "if not zeit:\n",
    "    zeit = now.strftime(\"%H:%M\")\n",
    "\n",
    "print(\"\\n✅ Reiseangaben gespeichert\")\n",
    "print(f\"Von: {von}\")\n",
    "print(f\"Nach: {nach}\")\n",
    "print(f\"Datum: {datum}\")\n",
    "print(f\"Zeit: {zeit}\")\n",
    "\n",
    "datum_api = datetime.strptime(datum, \"%d.%m.%Y\").strftime(\"%Y-%m-%d\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0c346ed",
   "metadata": {},
   "source": [
    "## Wetterdaten und Wetterwarnungen\n",
    "\n",
    "In diesem Abschnitt werden aktuelle Wetterdaten für den Start- und Zielort der Reise über die Open-Meteo API abgerufen. Dazu werden die Ortsnamen zunächst in geografische Koordinaten umgewandelt. Anschliessend werden Wetterinformationen wie Regen, Schneefall oder Gewitter überprüft.\n",
    "\n",
    "Die Wetterdaten dienen ausschliesslich zur Anzeige von Warnhinweisen und beeinflussen die Berechnung der besten Verkehrsoption nicht direkt. Die Abfrage erfolgt separat für Start- und Zielort, um regionale Wetterunterschiede zu berücksichtigen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "acdb85e0",
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests\n",
    "\n",
    "# ══════════════════════════════════════════════\n",
    "#  API: open-meteo.com  (kein Key nötig)\n",
    "# ══════════════════════════════════════════════\n",
    "\n",
    "def ort_zu_koordinaten(ort):\n",
    "    url = \"https://geocoding-api.open-meteo.com/v1/search\"\n",
    "    params = {\n",
    "        \"name\": ort,\n",
    "        \"count\": 1,\n",
    "        \"language\": \"de\",\n",
    "        \"format\": \"json\"\n",
    "    }\n",
    "\n",
    "    response = requests.get(url, params=params)\n",
    "    data = response.json()\n",
    "\n",
    "    if \"results\" not in data:\n",
    "        return None\n",
    "\n",
    "    return data[\"results\"][0][\"latitude\"], data[\"results\"][0][\"longitude\"]\n",
    "\n",
    "\n",
    "def wetter_abrufen(ort):\n",
    "    koordinaten = ort_zu_koordinaten(ort)\n",
    "\n",
    "    if koordinaten is None:\n",
    "        return None\n",
    "\n",
    "    lat, lon = koordinaten\n",
    "\n",
    "    url = \"https://api.open-meteo.com/v1/forecast\"\n",
    "    params = {\n",
    "        \"latitude\": lat,\n",
    "        \"longitude\": lon,\n",
    "        \"current\": \"temperature_2m,precipitation,rain,showers,snowfall,weather_code\",\n",
    "        \"hourly\": \"precipitation_probability\",\n",
    "        \"forecast_days\": 1,\n",
    "        \"timezone\": \"Europe/Zurich\"\n",
    "    }\n",
    "\n",
    "    response = requests.get(url, params=params)\n",
    "    data = response.json()\n",
    "\n",
    "    return data\n",
    "\n",
    "\n",
    "wetter_start = wetter_abrufen(von)\n",
    "wetter_ziel = wetter_abrufen(nach)\n",
    "\n",
    "\n",
    "def pruefe_wetterwarnung(wetter, ort_name, ort_typ):\n",
    "    warnungen = []\n",
    "\n",
    "    if not wetter or \"current\" not in wetter:\n",
    "        return [f\"⚠️ Wetterdaten für {ort_typ} ({ort_name}) konnten nicht geladen werden.\"]\n",
    "\n",
    "    aktuell = wetter[\"current\"]\n",
    "    wetter_code = aktuell.get(\"weather_code\", 0)\n",
    "    regen = aktuell.get(\"rain\", 0)\n",
    "    schnee = aktuell.get(\"snowfall\", 0)\n",
    "\n",
    "    regenwahrscheinlichkeit = 0\n",
    "    if \"hourly\" in wetter and \"precipitation_probability\" in wetter[\"hourly\"]:\n",
    "        werte = wetter[\"hourly\"][\"precipitation_probability\"][:3]\n",
    "        if werte:\n",
    "            regenwahrscheinlichkeit = max(werte)\n",
    "\n",
    "    if schnee > 0 or wetter_code in [71, 73, 75, 77, 85, 86]:\n",
    "        warnungen.append(f\"❄️ Schneefall am {ort_typ} ({ort_name}) erwartet! Vorsicht bei Velo und Auto. ÖV empfohlen.\")\n",
    "\n",
    "    if wetter_code in [95, 96, 99]:\n",
    "        warnungen.append(f\"⛈️ Gewitter am {ort_typ} ({ort_name}) möglich! Bitte ÖV oder Auto nutzen und Velo vermeiden.\")\n",
    "\n",
    "    if regen > 0 or wetter_code in [51, 53, 55, 61, 63, 65, 80, 81, 82]:\n",
    "        warnungen.append(f\"🌧️ Regen am {ort_typ} ({ort_name}) erwartet. Denk an Regenschutz oder wähle ÖV/Auto.\")\n",
    "\n",
    "    if regenwahrscheinlichkeit >= 70:\n",
    "        warnungen.append(f\"🌧️ {regenwahrscheinlichkeit}% Regenwahrscheinlichkeit am {ort_typ} ({ort_name}) in den nächsten Stunden. Regenschutz empfohlen.\")\n",
    "\n",
    "    elif regenwahrscheinlichkeit >= 50:\n",
    "        warnungen.append(f\"🌦️ {regenwahrscheinlichkeit}% Regenwahrscheinlichkeit am {ort_typ} ({ort_name}) möglich. Sicherheitshalber Regenschutz mitnehmen.\")\n",
    "\n",
    "    return warnungen\n",
    "\n",
    "\n",
    "warnungen = []\n",
    "\n",
    "warnungen += pruefe_wetterwarnung(wetter_start, von, \"Startort\")\n",
    "warnungen += pruefe_wetterwarnung(wetter_ziel, nach, \"Zielort\")\n",
    "\n",
    "print(\"\\n=== Wetterwarnungen ===\")\n",
    "\n",
    "if warnungen:\n",
    "    for warnung in warnungen:\n",
    "        print(warnung)\n",
    "else:\n",
    "    print(\"✅ Keine Wetterwarnungen für Start- oder Zielort.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "89f88cd7",
   "metadata": {},
   "source": [
    "## ÖV API Abfrage\n",
    "\n",
    "In diesem Abschnitt werden Verbindungen des öffentlichen Verkehrs über die Transport API Schweiz abgerufen. Basierend auf den zuvor erfassten Reiseangaben werden mögliche ÖV-Verbindungen zwischen Start- und Zielort gesucht.\n",
    "\n",
    "Für die Bewertung werden unter anderem Reisedauer, Preis sowie der geschätzte CO₂-Ausstoss berücksichtigt. Die erhaltenen Daten werden anschliessend für die spätere Berechnung der besten Verkehrsoption verwendet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1594fe8c",
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests\n",
    "from datetime import datetime\n",
    "\n",
    "# ══════════════════════════════════════════════\n",
    "#  API: transport.opendata.ch  (kein Key nötig)\n",
    "# ══════════════════════════════════════════════\n",
    "\n",
    "\n",
    "# ── API-Abfrage ────────────────────────────────\n",
    "print(f\"\\n🔍  Suche Verbindungen von '{von}' nach '{nach}' ...\")\n",
    "\n",
    "response = requests.get(\n",
    "    \"https://transport.opendata.ch/v1/connections\",\n",
    "    params={\n",
    "        \"from\":  von,\n",
    "        \"to\":    nach,\n",
    "        \"date\":  datum_api,\n",
    "        \"time\":  zeit,\n",
    "        \"limit\": 3          # 3 Verbindungen anzeigen\n",
    "    }\n",
    ")\n",
    "response.raise_for_status()\n",
    "data = response.json()\n",
    "connections = data.get(\"connections\", [])\n",
    "\n",
    "if not connections:\n",
    "    print(\"❌  Keine Verbindungen gefunden.\")\n",
    "else:\n",
    "    print(f\"\\n{'═'*52}\")\n",
    "    print(f\"  🚆  Verbindungen  {von}  →  {nach}\")\n",
    "    print(f\"  📅  {datum} um {zeit} Uhr\")\n",
    "    print(f\"{'═'*52}\")\n",
    "\n",
    "    for i, conn in enumerate(connections, 1):\n",
    "        # Zeiten\n",
    "        abfahrt = conn[\"from\"][\"departure\"][11:16]\n",
    "        ankunft = conn[\"to\"][\"arrival\"][11:16]\n",
    "        gleis   = conn[\"from\"].get(\"platform\") or \"–\"\n",
    "\n",
    "        # Dauer aus \"00d00:34:00\"\n",
    "        dur = conn.get(\"duration\", \"00d00:00:00\")\n",
    "        dur_parts = dur.split('d')\n",
    "        if len(dur_parts) == 2:\n",
    "            hms = dur_parts[1].split(':')\n",
    "            dauer_min = int(hms[0]) * 60 + int(hms[1])\n",
    "        else:\n",
    "            dauer_min = 0\n",
    "\n",
    "        # Entfernungsschätzung basierend auf Zugdauer\n",
    "        distance_km = max(1.0, round((dauer_min / 60) * 50, 1))\n",
    "\n",
    "        # Kosten/CO2 basierend auf SBB-Tarif\n",
    "        profil = globals().get('profil', {})\n",
    "        if not profil:\n",
    "            profil = {\"SBB_Tarif\": globals().get('SBB_TARIF', 'Normaltarif')}\n",
    "\n",
    "        sbb_tarif = profil.get(\"SBB_Tarif\", \"Normaltarif\")\n",
    "        if sbb_tarif == \"Generalabonnement\":\n",
    "            pt_cost = 0.0\n",
    "        elif sbb_tarif == \"Halbtax-Abo\":\n",
    "            pt_cost = max(3.6, distance_km * 0.125)\n",
    "        else:\n",
    "            pt_cost = max(3.6, distance_km * 0.25)\n",
    "        pt_co2 = round(distance_km * 0.027, 2)\n",
    "\n",
    "        # Umstiege & Zuglinien\n",
    "        sections = conn.get(\"sections\", [])\n",
    "        zuege    = [f\"{s['journey']['category']} {s['journey']['number']}\"\n",
    "                    for s in sections if s.get(\"journey\")]\n",
    "        umstiege = max(0, len(zuege) - 1)\n",
    "\n",
    "        print(f\"\\n  Verbindung {i}\")\n",
    "        print(f\"  {'─'*46}\")\n",
    "        print(f\"  🕐  {abfahrt} → {ankunft}   ({dauer_min} min)\")\n",
    "        print(f\"  📏  Distanz (geschätzt): {distance_km} km\")\n",
    "        print(f\"  💰  Tarif: {sbb_tarif}   |   Kosten: CHF {pt_cost:.2f}\")\n",
    "        print(f\"  🌍  CO₂: {pt_co2:.2f} kg   (27 g/km)\")\n",
    "        print(f\"  🚉  Gleis {gleis}   |   Umstiege: {umstiege}\")\n",
    "        if zuege:\n",
    "            print(f\"  🚆  {' → '.join(zuege)}\")\n",
    "\n",
    "        # ÖV-Ergebnis für späteren Vergleich speichern\n",
    "        oev = {\n",
    "            \"typ\": \"ÖV\",\n",
    "            \"dauer_min\": dauer_min,\n",
    "            \"distanz_km\": distance_km,\n",
    "            \"kosten\": round(pt_cost, 2),\n",
    "            \"co2\": pt_co2\n",
    "        }    \n",
    "        print(\"✅ ÖV-Option gespeichert:\", oev)\n",
    "\n",
    "    print(f\"\\n{'═'*52}\\n\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "49d42a7b",
   "metadata": {},
   "source": [
    "## Auto API Abfrage\n",
    "\n",
    "In diesem Abschnitt werden Fahrtdaten für das Verkehrsmittel Auto berechnet. Mithilfe der OpenRouteService API wird die Fahrstrecke zwischen Start- und Zielort ermittelt.\n",
    "\n",
    "Basierend auf Distanz und Fahrzeit werden anschliessend die geschätzten Kosten sowie der CO₂-Ausstoss berechnet. Die Resultate dienen später zum Vergleich mit den anderen Verkehrsoptionen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c1222e3e",
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests\n",
    "from datetime import datetime\n",
    "\n",
    "# ══════════════════════════════════════════════\n",
    "#  API: openrouteservice.org\n",
    "#  API: open-meteo.com  (kein Key nötig)\n",
    "# ══════════════════════════════════════════════\n",
    "\n",
    "OPENROUTESERVICE_API_KEY = \"eyJvcmciOiI1YjNjZTM1OTc4NTExMTAwMDFjZjYyNDgiLCJpZCI6ImI3YTU2ZDA3YzIxZDQ4YWM5MmExNzA0NDg0NzQwODQzIiwiaCI6Im11cm11cjY0In0=\"\n",
    "\n",
    "def ort_zu_koordinaten(ort):\n",
    "    url = \"https://geocoding-api.open-meteo.com/v1/search\"\n",
    "    \n",
    "    params = {\n",
    "        \"name\": ort,\n",
    "        \"count\": 1,\n",
    "        \"language\": \"de\",\n",
    "        \"format\": \"json\"\n",
    "    }\n",
    "    \n",
    "    response = requests.get(url, params=params)\n",
    "    data = response.json()\n",
    "    \n",
    "    if \"results\" not in data:\n",
    "        return None\n",
    "    \n",
    "    lat = data[\"results\"][0][\"latitude\"]\n",
    "    lon = data[\"results\"][0][\"longitude\"]\n",
    "    \n",
    "    return [lon, lat]\n",
    "\n",
    "\n",
    "def auto_route_berechnen(von, nach, datum=None, zeit=None):\n",
    "    start = ort_zu_koordinaten(von)\n",
    "    ziel = ort_zu_koordinaten(nach)\n",
    "    \n",
    "    if start is None or ziel is None:\n",
    "        return None\n",
    "    \n",
    "    url = \"https://api.openrouteservice.org/v2/directions/driving-car\"\n",
    "    \n",
    "    headers = {\n",
    "        \"Authorization\": OPENROUTESERVICE_API_KEY,\n",
    "        \"Content-Type\": \"application/json\"\n",
    "    }\n",
    "    \n",
    "    body = {\n",
    "        \"coordinates\": [start, ziel]\n",
    "    }\n",
    "    \n",
    "    response = requests.post(url, json=body, headers=headers)\n",
    "    data = response.json()\n",
    "    \n",
    "    if \"routes\" not in data:\n",
    "        print(\"Fehler bei der Auto API:\")\n",
    "        print(data)\n",
    "        return None\n",
    "    \n",
    "    route = data[\"routes\"][0]\n",
    "    \n",
    "    dauer_min = round(route[\"summary\"][\"duration\"] / 60)\n",
    "    distanz_km = round(route[\"summary\"][\"distance\"] / 1000, 1)\n",
    "    kosten = round(distanz_km * 0.70, 2)\n",
    "    co2 = round(distanz_km * 0.150, 2)\n",
    "    \n",
    "    return {\n",
    "        \"typ\": \"Auto\",\n",
    "        \"von\": von,\n",
    "        \"nach\": nach,\n",
    "        \"datum\": datum,\n",
    "        \"zeit\": zeit,\n",
    "        \"dauer_min\": dauer_min,\n",
    "        \"distanz_km\": distanz_km,\n",
    "        \"kosten\": kosten,\n",
    "        \"co2\": co2\n",
    "    }\n",
    "\n",
    "\n",
    "auto = auto_route_berechnen(von, nach, datum, zeit)\n",
    "\n",
    "if auto:\n",
    "    print(\"\\n🚗 Auto-Verbindung\")\n",
    "    print(f\"Route: {auto['von']} → {auto['nach']}\")\n",
    "    print(f\"Datum/Zeit: {auto['datum']} um {auto['zeit']}\")\n",
    "    print(f\"Dauer: {auto['dauer_min']} Minuten\")\n",
    "    print(f\"Distanz: {auto['distanz_km']} km\")\n",
    "    print(f\"Kosten: CHF {auto['kosten']}\")\n",
    "    print(f\"CO₂: {auto['co2']} kg\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e3b86530",
   "metadata": {},
   "source": [
    "## Velo API Abfrage\n",
    "\n",
    "In diesem Abschnitt werden Fahrradrouten zwischen Start- und Zielort mithilfe der OpenRouteService API berechnet. Dabei werden Distanz sowie geschätzte Fahrdauer ermittelt.\n",
    "\n",
    "Da das Fahrrad keine direkten Treibstoffkosten verursacht und emissionsfrei ist, werden Kosten und CO₂-Ausstoss mit null bewertet. Die berechneten Daten werden anschliessend für den Vergleich der Verkehrsoptionen verwendet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "84617717",
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests\n",
    "from datetime import datetime\n",
    "\n",
    "# ══════════════════════════════════════════════\n",
    "#  API: openrouteservice.org\n",
    "#  API: open-meteo.com  (kein Key nötig)\n",
    "# ══════════════════════════════════════════════\n",
    "\n",
    "OPENROUTESERVICE_API_KEY = \"eyJvcmciOiI1YjNjZTM1OTc4NTExMTAwMDFjZjYyNDgiLCJpZCI6ImI3YTU2ZDA3YzIxZDQ4YWM5MmExNzA0NDg0NzQwODQzIiwiaCI6Im11cm11cjY0In0=\"\n",
    "\n",
    "def ort_zu_koordinaten(ort):\n",
    "    url = \"https://geocoding-api.open-meteo.com/v1/search\"\n",
    "    \n",
    "    params = {\n",
    "        \"name\": ort,\n",
    "        \"count\": 1,\n",
    "        \"language\": \"de\",\n",
    "        \"format\": \"json\"\n",
    "    }\n",
    "    \n",
    "    response = requests.get(url, params=params)\n",
    "    data = response.json()\n",
    "    \n",
    "    if \"results\" not in data:\n",
    "        return None\n",
    "    \n",
    "    lat = data[\"results\"][0][\"latitude\"]\n",
    "    lon = data[\"results\"][0][\"longitude\"]\n",
    "    \n",
    "    return [lon, lat]\n",
    "\n",
    "\n",
    "def velo_route_berechnen(von, nach, datum=None, zeit=None):\n",
    "    start = ort_zu_koordinaten(von)\n",
    "    ziel = ort_zu_koordinaten(nach)\n",
    "    \n",
    "    if start is None or ziel is None:\n",
    "        return None\n",
    "    \n",
    "    url = \"https://api.openrouteservice.org/v2/directions/cycling-regular\"\n",
    "    \n",
    "    headers = {\n",
    "        \"Authorization\": OPENROUTESERVICE_API_KEY,\n",
    "        \"Content-Type\": \"application/json\"\n",
    "    }\n",
    "    \n",
    "    body = {\n",
    "        \"coordinates\": [start, ziel]\n",
    "    }\n",
    "    \n",
    "    response = requests.post(url, json=body, headers=headers)\n",
    "    data = response.json()\n",
    "    \n",
    "    if \"routes\" not in data:\n",
    "        print(\"Fehler bei der Velo API:\")\n",
    "        print(data)\n",
    "        return None\n",
    "    \n",
    "    route = data[\"routes\"][0]\n",
    "    \n",
    "    dauer_min = round(route[\"summary\"][\"duration\"] / 60)\n",
    "    distanz_km = round(route[\"summary\"][\"distance\"] / 1000, 1)\n",
    "    \n",
    "    kosten = 0.00\n",
    "    co2 = 0.00\n",
    "    \n",
    "    return {\n",
    "        \"typ\": \"Velo\",\n",
    "        \"von\": von,\n",
    "        \"nach\": nach,\n",
    "        \"datum\": datum,\n",
    "        \"zeit\": zeit,\n",
    "        \"dauer_min\": dauer_min,\n",
    "        \"distanz_km\": distanz_km,\n",
    "        \"kosten\": kosten,\n",
    "        \"co2\": co2\n",
    "    }\n",
    "\n",
    "\n",
    "velo = velo_route_berechnen(von, nach, datum, zeit)\n",
    "\n",
    "if velo:\n",
    "    print(\"\\n🚲 Velo-Verbindung\")\n",
    "    print(f\"Route: {velo['von']} → {velo['nach']}\")\n",
    "    print(f\"Datum/Zeit: {velo['datum']} um {velo['zeit']}\")\n",
    "    print(f\"Dauer: {velo['dauer_min']} Minuten\")\n",
    "    print(f\"Distanz: {velo['distanz_km']} km\")\n",
    "    print(f\"Kosten: CHF {velo['kosten']}\")\n",
    "    print(f\"CO₂: {velo['co2']} kg\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7a54043f",
   "metadata": {},
   "source": [
    "## Berechnung der besten Verkehrsoption\n",
    "\n",
    "In diesem Abschnitt werden die zuvor berechneten Verkehrsoptionen miteinander verglichen. Für jede Option wird ein individueller Score berechnet, welcher auf den Präferenzen der Nutzerin oder des Nutzers basiert.\n",
    "\n",
    "Berücksichtigt werden dabei:\n",
    "- Umweltfreundlichkeit (CO₂-Ausstoss)\n",
    "- Kosten\n",
    "- Reisezeit\n",
    "\n",
    "Die Gewichtung erfolgt anhand der zuvor definierten Präferenzen in Prozent. Je tiefer der berechnete Score einer Verkehrsoption ist, desto besser passt diese zur gewünschten Priorisierung der Nutzerin oder des Nutzers.\n",
    "\n",
    "Zusätzlich werden Einschränkungen aus dem Benutzerprofil berücksichtigt. Beispielsweise kann das Verkehrsmittel Velo ausgeschlossen werden, wenn die maximale gewünschte Fahrdauer überschritten wird.\n",
    "\n",
    "Die Wetterdaten beeinflussen die Berechnung der besten Option nicht direkt. Sie dienen ausschliesslich zur Anzeige zusätzlicher Warnhinweise."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2a228c6f",
   "metadata": {},
   "outputs": [],
   "source": [
    "praeferenzen = profil[\"Praeferenzen\"]\n",
    "optionen = []\n",
    "\n",
    "# ÖV immer möglich\n",
    "if oev:\n",
    "    optionen.append(oev)\n",
    "\n",
    "# Auto nur wenn vorhanden\n",
    "if profil[\"Auto\"] and auto:\n",
    "    optionen.append(auto)\n",
    "\n",
    "# Velo nur wenn vorhanden\n",
    "if profil[\"Velo\"] and velo:\n",
    "\n",
    "    max_velo = profil.get(\"Velo_Fahrtdauer_Minuten\", \"unbegrenzt\")\n",
    "\n",
    "    if max_velo == \"unbegrenzt\":\n",
    "        optionen.append(velo)\n",
    "\n",
    "    elif velo[\"dauer_min\"] <= max_velo:\n",
    "        optionen.append(velo)\n",
    "\n",
    "def berechne_score(option, praeferenzen):\n",
    "    umwelt = praeferenzen[\"Umwelt\"] / 100\n",
    "    kosten = praeferenzen[\"Kosten\"] / 100\n",
    "    zeit = praeferenzen[\"Zeit\"] / 100\n",
    "\n",
    "    score = (\n",
    "        option[\"co2\"] * umwelt +\n",
    "        option[\"kosten\"] * kosten +\n",
    "        option[\"dauer_min\"] * zeit\n",
    "    )\n",
    "\n",
    "    return score\n",
    "\n",
    "for option in optionen:\n",
    "    option[\"score\"] = berechne_score(option, praeferenzen)\n",
    "\n",
    "beste_option = min(optionen, key=lambda x: x[\"score\"])\n",
    "\n",
    "print(f\"🏆 Beste Wahl heute: {beste_option['typ']}\\n\")\n",
    "\n",
    "for option in optionen:\n",
    "    badge = \" ✅ BESTE\" if option[\"typ\"] == beste_option[\"typ\"] else \"\"\n",
    "\n",
    "    print(f\"{option['typ']}{badge}\")\n",
    "    print(f\"Dauer: {option['dauer_min']} min\")\n",
    "    print(f\"Kosten: CHF {option['kosten']}\")\n",
    "    print(f\"CO₂: {option['co2']} kg\")\n",
    "    print(f\"Score: {round(option['score'], 2)}\")\n",
    "    print(\"--------------------\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4da6632b",
   "metadata": {},
   "source": [
    "## Gebuchte Fahrten speichern\n",
    "\n",
    "In diesem Abschnitt werden bereits gebuchte sowie simulierte Fahrten gespeichert. Die Fahrten dienen als Grundlage für spätere Analysen und die Berechnung einer möglichen ÖV-Abo-Empfehlung.\n",
    "\n",
    "Da für unsere Projektarbeit aktuell kein Backend beziehungsweise keine Datenbank vorhanden ist und lediglich ein Mockup entwickelt wurde, wurden zusätzliche Testfahrten integriert.\n",
    "\n",
    "Zusätzlich zur aktuell berechneten besten Verkehrsoption können dadurch auch manuell definierte Testfahrten gespeichert werden. Dies ermöglicht es, mehrere vergangene Reisen realistisch zu simulieren und die Funktionen technisch zu überprüfen.\n",
    "\n",
    "Für jede Fahrt werden folgende Informationen gespeichert:\n",
    "\n",
    "* Verkehrsmittel\n",
    "* Start- und Zielort\n",
    "* Datum und Uhrzeit\n",
    "* Fahrdauer\n",
    "* Kosten\n",
    "* CO₂-Ausstoss\n",
    "\n",
    "Die gespeicherten Daten werden anschliessend für die Anzeige der Fahrthistorie sowie für die Analyse des Mobilitätsverhaltens verwendet. Testfahrten können je nach Bedarf hinzugefügt oder entfernt werden.\n",
    "\n",
    "Beispiel einer Testfahrt:\n",
    "\n",
    "```python\n",
    "{\n",
    "    \"typ\": \"ÖV\",\n",
    "    \"von\": \"Bern\",\n",
    "    \"nach\": \"Zürich\",\n",
    "    \"distanz_km\": 487\n",
    "    \"datum\": \"03.05.2026\",\n",
    "    \"zeit\": \"08:00\",\n",
    "    \"dauer_min\": 56,\n",
    "    \"kosten\": 34.50,\n",
    "    \"co2\": 0.8\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a81e66ba",
   "metadata": {},
   "outputs": [],
   "source": [
    "gebuchte_fahrten = []\n",
    "\n",
    "# Testfahrten manuell hinzufügen\n",
    "gebuchte_fahrten.append({\n",
    "    \"typ\": \"ÖV\",\n",
    "    \"von\": \"Bern\",\n",
    "    \"nach\": \"Zürich\",\n",
    "    \"distanz_km\": 100.5,\n",
    "    \"datum\": \"03.05.2026\",\n",
    "    \"zeit\": \"08:00\",\n",
    "    \"dauer_min\": 56,\n",
    "    \"kosten\": 34.50,\n",
    "    \"co2\": 0.8\n",
    "})\n",
    "\n",
    "gebuchte_fahrten.append({\n",
    "    \"typ\": \"ÖV\",\n",
    "    \"von\": \"Bern\",\n",
    "    \"nach\": \"Lausanne\",\n",
    "    \"distanz_km\": 120.0,\n",
    "    \"datum\": \"12.05.2026\",\n",
    "    \"zeit\": \"14:15\",\n",
    "    \"dauer_min\": 67,\n",
    "    \"kosten\": 42.00,\n",
    "    \"co2\": 0.9\n",
    "})\n",
    "\n",
    "gebuchte_fahrten.append({\n",
    "    \"typ\": \"Velo\",\n",
    "    \"von\": \"Bern\",\n",
    "    \"nach\": \"Köniz\",\n",
    "    \"distanz_km\": 5.2,\n",
    "    \"datum\": \"20.05.2026\",\n",
    "    \"zeit\": \"17:00\",\n",
    "    \"dauer_min\": 28,\n",
    "    \"kosten\": 0,\n",
    "    \"co2\": 0\n",
    "})\n",
    "\n",
    "gebuchte_fahrten.append({\n",
    "    \"typ\": \"ÖV\",\n",
    "    \"von\": \"Bern\",\n",
    "    \"nach\": \"Genf\",\n",
    "    \"distanz_km\": 150.0,\n",
    "    \"datum\": \"18.05.2026\",\n",
    "    \"zeit\": \"09:45\",\n",
    "    \"dauer_min\": 115,\n",
    "    \"kosten\": 58.00,\n",
    "    \"co2\": 1.2\n",
    "})\n",
    "\n",
    "gebuchte_fahrten.append({\n",
    "    \"typ\": \"ÖV\",\n",
    "    \"von\": \"Bern\",\n",
    "    \"nach\": \"Genf\",\n",
    "    \"distanz_km\": 150.0,\n",
    "    \"datum\": \"18.05.2026\",\n",
    "    \"zeit\": \"09:45\",\n",
    "    \"dauer_min\": 115,\n",
    "    \"kosten\": 58.00,\n",
    "    \"co2\": 1.2\n",
    "})\n",
    "\n",
    "gebuchte_fahrten.append({\n",
    "    \"typ\": \"ÖV\",\n",
    "    \"von\": \"Bern\",\n",
    "    \"nach\": \"Basel\",\n",
    "    \"distanz_km\": 120.0,\n",
    "    \"datum\": \"07.05.2026\",\n",
    "    \"zeit\": \"10:30\",\n",
    "    \"dauer_min\": 62,\n",
    "    \"kosten\": 29.00,\n",
    "    \"co2\": 0.7\n",
    "})\n",
    "\n",
    "# Aktuelle beste Fahrt speichern\n",
    "gebuchte_fahrten.append({\n",
    "    \"typ\": beste_option[\"typ\"],\n",
    "    \"von\": von,\n",
    "    \"nach\": nach,\n",
    "    \"distanz_km\": beste_option.get(\"distanz_km\", 0),\n",
    "    \"datum\": datum,\n",
    "    \"zeit\": zeit,\n",
    "    \"dauer_min\": beste_option[\"dauer_min\"],\n",
    "    \"kosten\": beste_option[\"kosten\"],\n",
    "    \"co2\": beste_option[\"co2\"]\n",
    "})\n",
    "\n",
    "print(\"✅ Fahrten wurden gespeichert.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "96458d00",
   "metadata": {},
   "source": [
    "## Gebuchte Fahrten anzeigen\n",
    "\n",
    "Hier werden alle gespeicherten Fahrten übersichtlich dargestellt. Angezeigt werden unter anderem Verkehrsmittel, Route, Datum, Kosten, Reisezeit und CO₂-Ausstoss."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1ccda353",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"\\n=== Gebuchte Fahrten ===\")\n",
    "\n",
    "for i, fahrt in enumerate(gebuchte_fahrten, 1):\n",
    "    print(f\"\\nFahrt {i}\")\n",
    "    print(f\"Typ: {fahrt['typ']}\")\n",
    "    print(f\"Route: {fahrt['von']} → {fahrt['nach']}\")\n",
    "    print(f\"Distanz: {fahrt['distanz_km']} km\")\n",
    "    print(f\"Datum/Zeit: {fahrt['datum']} um {fahrt['zeit']}\")\n",
    "    print(f\"Dauer: {fahrt['dauer_min']} min\")\n",
    "    print(f\"Kosten: CHF {fahrt['kosten']:.2f}\")\n",
    "    print(f\"CO₂: {fahrt['co2']} kg\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fef65556",
   "metadata": {},
   "source": [
    "## Abo-Empfehlung\n",
    "\n",
    "Dieser Abschnitt analysiert die bisher gespeicherten ÖV-Fahrten und berechnet eine passende ÖV-Abo-Empfehlung. Abhängig von Anzahl Fahrten und bisherigen Kosten wird beispielsweise ein Normaltarif, ein Halbtax-Abo oder ein Generalabonnement empfohlen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "472a6810",
   "metadata": {},
   "outputs": [],
   "source": [
    "oev_fahrten = [fahrt for fahrt in gebuchte_fahrten if fahrt[\"typ\"] == \"ÖV\"]\n",
    "\n",
    "anzahl_oev = len(oev_fahrten)\n",
    "gesamt_distanz = sum(fahrt[\"distanz_km\"] for fahrt in oev_fahrten)\n",
    "\n",
    "print(\"\\n=== Abo-Empfehlung ===\")\n",
    "print(f\"Anzahl ÖV-Fahrten: {anzahl_oev}\")\n",
    "print(f\"Gesamtdistanz: {gesamt_distanz:.1f} km\")\n",
    "\n",
    "if anzahl_oev == 0:\n",
    "    print(\"Empfehlung: Keine Abo-Empfehlung möglich.\")\n",
    "else:\n",
    "    distanz_pro_fahrt = gesamt_distanz / anzahl_oev\n",
    "\n",
    "    normaltarif_ticket = max(3.60, distanz_pro_fahrt * 0.25)\n",
    "    normaltarif_monat = normaltarif_ticket * anzahl_oev\n",
    "\n",
    "    halbtax_ticket = normaltarif_monat * 0.5\n",
    "    halbtax_monat = halbtax_ticket + 18.50\n",
    "\n",
    "    ga_monat = 333.00\n",
    "\n",
    "    kostenvergleich = {\n",
    "        \"Normaltarif\": normaltarif_monat,\n",
    "        \"Halbtax-Abo\": halbtax_monat,\n",
    "        \"Generalabonnement (GA)\": ga_monat\n",
    "    }\n",
    "\n",
    "    empfehlung = min(kostenvergleich, key=kostenvergleich.get)\n",
    "\n",
    "    print(f\"Distanz pro Fahrt: {distanz_pro_fahrt:.1f} km\")\n",
    "    print(f\"Normaltarif: CHF {normaltarif_monat:.2f}\")\n",
    "    print(f\"Halbtax-Abo: CHF {halbtax_monat:.2f}\")\n",
    "    print(f\"GA: CHF {ga_monat:.2f}\")\n",
    "    print(f\"Empfehlung: {empfehlung}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.14.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
