{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "717828f5",
   "metadata": {},
   "source": [
    "# EcoScore — Proof of Concept\n",
    "**Gurtenfestival 2026 · Bern · 15.–18. Juli 2026**\n",
    "\n",
    "Dieser PoC demonstriert den technischen Kernalgorithmus der EcoScore App gemäss Zielarchitektur (Kapitel 4).\n",
    "\n",
    "Die Punktevergabe erfolgt über zwei Pfade:\n",
    "\n",
    "| Pfad | Typ | Beschreibung |\n",
    "|---|---|---|\n",
    "| **Pfad A** | Automatisch | Punktevergabe via Cashless-Karte (Webhook) |\n",
    "| **Pfad B** | Manuell | Anreise-Erfassung via Transport-Screen |\n",
    "\n",
    "Der finale EcoScore (0–100) wird aus den Rohpunkten beider Pfade über eine gewichtete Formel berechnet (vgl. Kap. 3.2):\n",
    "\n",
    "> **EcoScore = (M × 0.50) + (F × 0.20) + (R × 0.30)**\n",
    "\n",
    "> Die Transaktionsdaten sind simuliert und repräsentieren reale Webhook-Daten des Cashless-Systems. In der Zielarchitektur werden diese automatisch via API übermittelt (vgl. Kap. 4.3)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc9a4f29",
   "metadata": {},
   "source": [
    "## Setup\n",
    "Import der benötigten Bibliotheken."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "4202aa19",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Pakete bereit\n"
     ]
    }
   ],
   "source": [
    "import subprocess\n",
    "subprocess.run(['pip3', 'install', 'matplotlib', 'ipywidgets', '--break-system-packages'], capture_output=True)\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import matplotlib.patches as mpatches\n",
    "import ipywidgets as widgets\n",
    "from IPython.display import display, clear_output\n",
    "\n",
    "print('Pakete bereit')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2c4a5d40",
   "metadata": {},
   "source": [
    "## 1. Konfiguration\n",
    "\n",
    "Definiert die drei zentralen Datentabellen des EcoScore-Systems gemäss Datenmodell (Kap. 4.4).\n",
    "\n",
    "**scoring_categories** — Eco-Aktionen mit Rohpunkten und CO₂-Faktor. Die `eco_category`-Felder entsprechen den Cashless-Webhook-Payloads (Pfad A).\n",
    "\n",
    "**transport_options** — 4 Anreiseoptionen mit Rohpunkten und CO₂-Einsparung. Wahl erfolgt manuell im Transport-Screen (Pfad B).\n",
    "\n",
    "**reward_tiers** — 5 Reward-Stufen basierend auf dem finalen EcoScore (0–100). `rank_name` entspricht dem Feld `rank_name` in der REWARD-Entität der Zielarchitektur (Kap. 4.4)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "78a6dcc1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Scoring-Kategorien : 9\n",
      "Transportoptionen  : 4\n",
      "Reward-Stufen      : 5\n"
     ]
    }
   ],
   "source": [
    "# Scoring-Kategorien\n",
    "scoring_categories = {\n",
    "    'food_vegetarian': {'points': 40, 'co2_kg': 0.9, 'label': 'Vegetarische Mahlzeit', 'group': 'Ernährung'},\n",
    "    'food_vegan':      {'points': 40, 'co2_kg': 2.1, 'label': 'Vegane Mahlzeit',        'group': 'Ernährung'},\n",
    "    'food_chicken':    {'points': 10, 'co2_kg': 0.5, 'label': 'Chicken / Fisch',        'group': 'Ernährung'},\n",
    "    'food_beef':       {'points':  0, 'co2_kg': 0.0, 'label': 'Rindfleischgericht',     'group': 'Ernährung'},\n",
    "    'waste_cup_return':{'points': 30, 'co2_kg': 0.2, 'label': 'Becher zurückgeben',     'group': 'Recycling'},\n",
    "    'waste_recycle':   {'points': 15, 'co2_kg': 0.4, 'label': 'Recyclingstation nutzen','group': 'Recycling'},\n",
    "    'engage_challenge':{'points':  5, 'co2_kg': 0.0, 'label': 'Eco Challenge',          'group': 'Engagement'},\n",
    "    'engage_quiz':     {'points':  3, 'co2_kg': 0.0, 'label': 'Eco Quiz',               'group': 'Engagement'},\n",
    "    'engage_invite':   {'points':  2, 'co2_kg': 0.0, 'label': 'Freunde einladen',       'group': 'Engagement'},\n",
    "}\n",
    "\n",
    "# Transportoptionen\n",
    "transport_options = {\n",
    "    'oev':  {'points': 100, 'co2_kg': 14.8, 'label': 'Öffentlicher Verkehr (ÖV)', 'eco': True},\n",
    "    'bike': {'points':  80, 'co2_kg': 14.8, 'label': 'Velo',                      'eco': True},\n",
    "    'walk': {'points':  80, 'co2_kg': 14.8, 'label': 'Fussweg',                   'eco': True},\n",
    "    'car':  {'points':   0, 'co2_kg':  0.0, 'label': 'Auto / Fahrgemeinschaft',   'eco': False},\n",
    "}\n",
    "\n",
    "# Reward-Stufen\n",
    "reward_tiers = [\n",
    "    {'min': 90, 'max': 100, 'rank_name': 'Eco Champion',         'reward': 'Top Reward (Ticket-Gewinnchance)'},\n",
    "    {'min': 75, 'max':  89, 'rank_name': 'Eco Hero',             'reward': 'Merch-Rabatt'},\n",
    "    {'min': 60, 'max':  74, 'rank_name': 'Nachhaltig unterwegs', 'reward': 'Gratis Getränk'},\n",
    "    {'min': 40, 'max':  59, 'rank_name': 'Solider Start',        'reward': 'Badge + Verlosung'},\n",
    "    {'min':  0, 'max':  39, 'rank_name': 'Kein Rang',            'reward': 'Kein Reward'},\n",
    "]\n",
    "\n",
    "print(f'Scoring-Kategorien : {len(scoring_categories)}')\n",
    "print(f'Transportoptionen  : {len(transport_options)}')\n",
    "print(f'Reward-Stufen      : {len(reward_tiers)}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6d7517ee",
   "metadata": {},
   "source": [
    "## 2. Hilfsfunktionen\n",
    "\n",
    "Wiederverwendbare Funktionen für die Berechnung des EcoScores und die Bestimmung des Rangs. Werden von allen nachfolgenden Schritten verwendet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "e88904d2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hilfsfunktionen bereit\n"
     ]
    }
   ],
   "source": [
    "def berechne_ecoscore(t_key, selected_keys):\n",
    "    \"\"\"\n",
    "    Berechnet EcoScore, Rohpunkte, CO₂ und Rang.\n",
    "    t_key:         Schlüssel der gewählten Transportoption\n",
    "    selected_keys: Liste der gewählten Cashless-Aktionen\n",
    "    \"\"\"\n",
    "    t         = transport_options[t_key]\n",
    "    selected  = {k: scoring_categories[k] for k in selected_keys}\n",
    "\n",
    "    # Rohpunkte\n",
    "    t_raw     = t['points']\n",
    "    food_raw  = sum(v['points'] for v in selected.values() if v['group'] == 'Ernährung')\n",
    "    waste_raw = sum(v['points'] for v in selected.values() if v['group'] == 'Recycling')\n",
    "\n",
    "    # CO₂\n",
    "    t_co2     = t['co2_kg']\n",
    "    food_co2  = sum(v['co2_kg'] for v in selected.values() if v['group'] == 'Ernährung')\n",
    "    waste_co2 = sum(v['co2_kg'] for v in selected.values() if v['group'] == 'Recycling')\n",
    "    total_co2 = t_co2 + food_co2 + waste_co2\n",
    "\n",
    "    # Normalisierung & Gewichtungsformel (Kap. 3.2)\n",
    "    M         = min(t_raw     / 100 * 100, 100)\n",
    "    F         = min(food_raw  /  80 * 100, 100)\n",
    "    R         = min(waste_raw /  60 * 100, 100)\n",
    "    eco_score = int(M * 0.50 + F * 0.20 + R * 0.30)\n",
    "\n",
    "    # Rang\n",
    "    rank = next((tier for tier in reward_tiers if tier['min'] <= eco_score <= tier['max']), reward_tiers[-1])\n",
    "\n",
    "    return {\n",
    "        'eco_score': eco_score,\n",
    "        't_raw': t_raw, 'food_raw': food_raw, 'waste_raw': waste_raw,\n",
    "        't_co2': t_co2, 'food_co2': food_co2, 'waste_co2': waste_co2,\n",
    "        'total_co2': total_co2,\n",
    "        'M': M, 'F': F, 'R': R,\n",
    "        'rank': rank,\n",
    "        'transport': t,\n",
    "        'selected': selected,\n",
    "    }\n",
    "\n",
    "def get_selected_keys():\n",
    "    \"\"\"Gibt Liste der aktuell gewählten Cashless-Aktionen zurück.\"\"\"\n",
    "    return [k for k, cb in activity_cbs.items() if cb.value]\n",
    "\n",
    "print('Hilfsfunktionen bereit')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dbf90e22",
   "metadata": {},
   "source": [
    "## 3. Widgets & Outputs\n",
    "\n",
    "Alle Widgets und Output-Objekte werden hier einmalig definiert. Die Widgets werden in den nachfolgenden Schritten angezeigt und die Outputs dort befüllt."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "f6ae0656",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Widgets & Outputs bereit\n"
     ]
    }
   ],
   "source": [
    "# Input-Widgets\n",
    "name_input = widgets.Text(\n",
    "    value='',\n",
    "    placeholder='Dein Name',\n",
    "    description='Name:',\n",
    "    style={'description_width': '60px'},\n",
    "    layout=widgets.Layout(width='300px')\n",
    ")\n",
    "\n",
    "transport_dd = widgets.Dropdown(\n",
    "    options=[(opt['label'], key) for key, opt in transport_options.items()],\n",
    "    value='oev',\n",
    "    description='Anreise:',\n",
    "    style={'description_width': '80px'},\n",
    "    layout=widgets.Layout(width='370px')\n",
    ")\n",
    "\n",
    "activity_cbs = {\n",
    "    key: widgets.Checkbox(\n",
    "        value=False,\n",
    "        description=f\"{cat['label']} (+{cat['points']} Pts)\",\n",
    "        style={'description_width': 'initial'},\n",
    "        layout=widgets.Layout(width='370px')\n",
    "    )\n",
    "    for key, cat in scoring_categories.items()\n",
    "    if cat['group'] != 'Engagement'\n",
    "}\n",
    "\n",
    "export_button = widgets.Button(\n",
    "    description='Als PNG exportieren',\n",
    "    button_style='success',\n",
    "    icon='download',\n",
    "    layout=widgets.Layout(width='220px', height='40px')\n",
    ")\n",
    "\n",
    "# Output-Widgets (je einer pro Schritt)\n",
    "out_name      = widgets.Output()\n",
    "out_transport = widgets.Output()\n",
    "out_cashless  = widgets.Output()\n",
    "out_ecoscore  = widgets.Output()\n",
    "out_reward    = widgets.Output()\n",
    "out_report    = widgets.Output()\n",
    "out_viz       = widgets.Output()\n",
    "out_export    = widgets.Output()\n",
    "\n",
    "print('Widgets & Outputs bereit')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "85568277",
   "metadata": {},
   "source": [
    "## 4. Update-Funktion\n",
    "\n",
    "Zentrale Funktion die bei jeder Änderung automatisch aufgerufen wird und alle Outputs der nachfolgenden Schritte gleichzeitig aktualisiert."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "76e1a625",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Update-Funktion & Observers bereit\n"
     ]
    }
   ],
   "source": [
    "def update(change=None):\n",
    "    name      = name_input.value.strip() or 'Anonym'\n",
    "    t_key     = transport_dd.value\n",
    "    keys      = get_selected_keys()\n",
    "    r         = berechne_ecoscore(t_key, keys)\n",
    "\n",
    "    # Schritt 1: Transport\n",
    "    with out_transport:\n",
    "        clear_output(wait=True)\n",
    "        eco = 'Ja' if r['transport']['eco'] else 'Nein'\n",
    "        print(f'{\"─\"*45}')\n",
    "        print(f'  Gewählte Option : {r[\"transport\"][\"label\"]}')\n",
    "        print(f'  Rohpunkte       : +{r[\"t_raw\"]} Pts')\n",
    "        print(f'  CO₂ gespart     : {r[\"t_co2\"]:.1f} kg')\n",
    "        print(f'  Öko-Option      : {eco}')\n",
    "        print(f'{\"─\"*45}')\n",
    "\n",
    "    # Schritt 2: Cashless\n",
    "    with out_cashless:\n",
    "        clear_output(wait=True)\n",
    "        if not r['selected']:\n",
    "            print('  Keine Aktionen gewählt.')\n",
    "        else:\n",
    "            print(f'  {\"Aktion\":<30} {\"Punkte\":>6} {\"CO₂\":>8}')\n",
    "            print(f'  {\"─\"*46}')\n",
    "            for k, cat in r['selected'].items():\n",
    "                print(f'  {cat[\"label\"]:<30} {cat[\"points\"]:>5} Pts {cat[\"co2_kg\"]:>5.1f} kg')\n",
    "            print(f'  {\"─\"*46}')\n",
    "            print(f'  {\"Cashless Rohpunkte:\":<30} {r[\"food_raw\"] + r[\"waste_raw\"]:>5} Pts')\n",
    "\n",
    "    # Schritt 3: EcoScore\n",
    "    with out_ecoscore:\n",
    "        clear_output(wait=True)\n",
    "        print(f'  {\"─\"*53}')\n",
    "        print(f'  Transport  : {r[\"t_raw\"]:>4} Rohpunkte  →  M-Score : {r[\"M\"]:.0f} / 100')\n",
    "        print(f'  Ernährung  : {r[\"food_raw\"]:>4} Rohpunkte  →  F-Score : {r[\"F\"]:.0f} / 100')\n",
    "        print(f'  Recycling  : {r[\"waste_raw\"]:>4} Rohpunkte  →  R-Score : {r[\"R\"]:.0f} / 100')\n",
    "        print(f'  {\"─\"*53}')\n",
    "        print(f'  Formel : ({r[\"M\"]:.0f} × 0.50) + ({r[\"F\"]:.0f} × 0.20) + ({r[\"R\"]:.0f} × 0.30)')\n",
    "        print(f'         = {r[\"M\"]*0.50:.1f} + {r[\"F\"]*0.20:.1f} + {r[\"R\"]*0.30:.1f}')\n",
    "        print(f'  {\"─\"*53}')\n",
    "        print(f'  EcoScore : {r[\"eco_score\"]} / 100 Punkte')\n",
    "\n",
    "    # Schritt 4: Rang & Reward\n",
    "    with out_reward:\n",
    "        clear_output(wait=True)\n",
    "        print(f'  {\"─\"*68}')\n",
    "        print(f'  {\"Punkte\":<12} {\"Rang\":<25} {\"Reward\":<25} {\"Status\"}')\n",
    "        print(f'  {\"─\"*68}')\n",
    "        for tier in reversed(reward_tiers):\n",
    "            marker   = '  ◀ aktuell' if tier['rank_name'] == r['rank']['rank_name'] else ''\n",
    "            unlocked = '[x]' if r['eco_score'] >= tier['min'] else '[ ]'\n",
    "            print(f'  {tier[\"min\"]:>3}–{tier[\"max\"]:<6} {tier[\"rank_name\"]:<25} {tier[\"reward\"]:<25} {unlocked}{marker}')\n",
    "        print(f'  {\"─\"*68}')\n",
    "        print(f'  Rang: {r[\"rank\"][\"rank_name\"]}  |  Reward: {r[\"rank\"][\"reward\"]}')\n",
    "\n",
    "    # Schritt 5: Impact Report\n",
    "    with out_report:\n",
    "        clear_output(wait=True)\n",
    "        print('  ' + '=' * 48)\n",
    "        print(f'        {name} — IMPACT REPORT')\n",
    "        print('         Gurtenfestival 2026 · Bern')\n",
    "        print('  ' + '=' * 48)\n",
    "        print(f'  EcoScore    : {r[\"eco_score\"]} / 100 Punkte')\n",
    "        print(f'  Rang        : {r[\"rank\"][\"rank_name\"]}')\n",
    "        print(f'  Reward      : {r[\"rank\"][\"reward\"]}')\n",
    "        print(f'  CO₂ gespart : {r[\"total_co2\"]:.1f} kg')\n",
    "        print(f'  {\"─\"*46}')\n",
    "        print(f'  Transport   : {r[\"t_co2\"]:.1f} kg')\n",
    "        print(f'  Ernährung   : {r[\"food_co2\"]:.1f} kg')\n",
    "        print(f'  Recycling   : {r[\"waste_co2\"]:.1f} kg')\n",
    "        print('  ' + '=' * 48)\n",
    "\n",
    "    # Schritt 6: Visualisierung\n",
    "    with out_viz:\n",
    "        clear_output(wait=True)\n",
    "        p_data   = {'Transport': r['t_raw'],  'Ernährung': r['food_raw'],  'Recycling': r['waste_raw']}\n",
    "        c_data   = {'Transport': r['t_co2'],  'Ernährung': r['food_co2'],  'Recycling': r['waste_co2']}\n",
    "        p_labels = [k for k, v in p_data.items() if v > 0]\n",
    "        p_values = [v for v in p_data.values() if v > 0]\n",
    "        c_labels = [k for k, v in c_data.items() if v > 0]\n",
    "        c_values = [v for v in c_data.values() if v > 0]\n",
    "        colors_bar = ['#E8304A', '#FF9E9E', '#FFD6D8'][:len(p_labels)]\n",
    "        colors_pie = ['#2D6A0F', '#E8304A', '#FF9E9E'][:len(c_labels)]\n",
    "\n",
    "        fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5))\n",
    "        fig.patch.set_facecolor('#FFF2F3')\n",
    "\n",
    "        if p_values:\n",
    "            bars = ax1.barh(p_labels, p_values, color=colors_bar, height=0.55,\n",
    "                            edgecolor='white', linewidth=1.2)\n",
    "            ax1.set_facecolor('#FFF2F3')\n",
    "            ax1.set_xlabel('Rohpunkte', fontsize=11, color='#2C2C2A', labelpad=8)\n",
    "            ax1.set_title('Rohpunkte nach Kategorie', fontsize=12, fontweight='bold',\n",
    "                          color='#2C2C2A', pad=12)\n",
    "            ax1.tick_params(colors='#2C2C2A', labelsize=11)\n",
    "            ax1.set_xlim(0, max(p_values) + 20)\n",
    "            for bar, val in zip(bars, p_values):\n",
    "                ax1.text(bar.get_width() + 1, bar.get_y() + bar.get_height() / 2,\n",
    "                         f'+{val} Pts', va='center', fontsize=10, color='#2C2C2A')\n",
    "            ax1.spines['top'].set_visible(False)\n",
    "            ax1.spines['right'].set_visible(False)\n",
    "            ax1.spines['left'].set_color('#E0D8D9')\n",
    "            ax1.spines['bottom'].set_color('#E0D8D9')\n",
    "            ax1.tick_params(axis='both', which='both', length=0)\n",
    "            ax1.xaxis.grid(True, color='#F0E8E9', linewidth=0.8, zorder=0)\n",
    "            ax1.set_axisbelow(True)\n",
    "            ax1.text(0.98, 0.98, f'EcoScore: {r[\"eco_score\"]} Pts\\n{r[\"rank\"][\"rank_name\"]}',\n",
    "                     transform=ax1.transAxes, ha='right', va='top',\n",
    "                     fontsize=10, fontweight='bold', color='#E8304A',\n",
    "                     bbox=dict(boxstyle='round,pad=0.4', facecolor='white',\n",
    "                               edgecolor='#FFD6D8', linewidth=1.5))\n",
    "        else:\n",
    "            ax1.text(0.5, 0.5, 'Keine Aktionen gewählt', ha='center', va='center',\n",
    "                     fontsize=12, color='#b0848a')\n",
    "            ax1.axis('off')\n",
    "            ax1.set_facecolor('#FFF2F3')\n",
    "\n",
    "        if c_values:\n",
    "            ax2.pie(\n",
    "                c_values, labels=None, autopct=None, colors=colors_pie,\n",
    "                startangle=90,\n",
    "                wedgeprops={'edgecolor': '#FFF2F3', 'linewidth': 2.5}\n",
    "            )\n",
    "            total = sum(c_values)\n",
    "            legend_patches = [mpatches.Patch(color=colors_pie[i],\n",
    "                              label=f'{c_labels[i]}  {c_values[i]:.1f} kg  ({c_values[i]/total*100:.1f}%)')\n",
    "                              for i in range(len(c_labels))]\n",
    "            ax2.legend(handles=legend_patches, loc='lower center',\n",
    "                       bbox_to_anchor=(0.5, -0.12), ncol=1, fontsize=9,\n",
    "                       frameon=False, labelcolor='#2C2C2A')\n",
    "        else:\n",
    "            ax2.text(0.5, 0.5, 'Keine CO₂-Einsparung', ha='center', va='center',\n",
    "                     fontsize=12, color='#b0848a')\n",
    "            ax2.axis('off')\n",
    "\n",
    "        ax2.set_facecolor('#FFF2F3')\n",
    "        ax2.set_title(f'CO₂-Einsparung total: {r[\"total_co2\"]:.1f} kg',\n",
    "                      fontsize=12, fontweight='bold', color='#2C2C2A', pad=12)\n",
    "        fig.suptitle(f'EcoScore — {name}  |  Gurtenfestival 2026',\n",
    "                     fontsize=14, fontweight='bold', color='#E8304A', y=1.02)\n",
    "        plt.tight_layout(pad=2.0)\n",
    "        plt.show()\n",
    "\n",
    "# Observers\n",
    "name_input.observe(update, names='value')\n",
    "transport_dd.observe(update, names='value')\n",
    "for cb in activity_cbs.values():\n",
    "    cb.observe(update, names='value')\n",
    "\n",
    "print('Update-Funktion & Observers bereit')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ae14ddfb",
   "metadata": {},
   "source": [
    "## 5. Export-Funktion\n",
    "\n",
    "Speichert den aktuellen Impact Report als PNG-Datei."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "def79829",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Export-Funktion bereit\n"
     ]
    }
   ],
   "source": [
    "def export_report(b=None):\n",
    "    with out_export:\n",
    "        clear_output(wait=True)\n",
    "        name = name_input.value.strip() or 'Anonym'\n",
    "        t_key = transport_dd.value\n",
    "        keys  = get_selected_keys()\n",
    "        r     = berechne_ecoscore(t_key, keys)\n",
    "\n",
    "        p_data   = {'Transport': r['t_raw'],  'Ernährung': r['food_raw'],  'Recycling': r['waste_raw']}\n",
    "        c_data   = {'Transport': r['t_co2'],  'Ernährung': r['food_co2'],  'Recycling': r['waste_co2']}\n",
    "        p_labels = [k for k, v in p_data.items() if v > 0]\n",
    "        p_values = [v for v in p_data.values() if v > 0]\n",
    "        c_labels = [k for k, v in c_data.items() if v > 0]\n",
    "        c_values = [v for v in c_data.values() if v > 0]\n",
    "        colors_bar = ['#E8304A', '#FF9E9E', '#FFD6D8'][:len(p_labels)]\n",
    "        colors_pie = ['#2D6A0F', '#E8304A', '#FF9E9E'][:len(c_labels)]\n",
    "\n",
    "        fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))\n",
    "        fig.patch.set_facecolor('#FFF2F3')\n",
    "\n",
    "        if p_values:\n",
    "            bars = ax1.barh(p_labels, p_values, color=colors_bar, height=0.55,\n",
    "                            edgecolor='white', linewidth=1.2)\n",
    "            ax1.set_facecolor('#FFF2F3')\n",
    "            ax1.set_xlabel('Rohpunkte', fontsize=11, color='#2C2C2A', labelpad=8)\n",
    "            ax1.set_title('Rohpunkte nach Kategorie', fontsize=13, fontweight='bold',\n",
    "                          color='#2C2C2A', pad=14)\n",
    "            ax1.tick_params(colors='#2C2C2A', labelsize=11)\n",
    "            ax1.set_xlim(0, max(p_values) + 20)\n",
    "            for bar, val in zip(bars, p_values):\n",
    "                ax1.text(bar.get_width() + 1, bar.get_y() + bar.get_height() / 2,\n",
    "                         f'+{val} Pts', va='center', fontsize=11, color='#2C2C2A')\n",
    "            ax1.spines['top'].set_visible(False)\n",
    "            ax1.spines['right'].set_visible(False)\n",
    "            ax1.spines['left'].set_color('#E0D8D9')\n",
    "            ax1.spines['bottom'].set_color('#E0D8D9')\n",
    "            ax1.tick_params(axis='both', which='both', length=0)\n",
    "            ax1.xaxis.grid(True, color='#F0E8E9', linewidth=0.8, zorder=0)\n",
    "            ax1.set_axisbelow(True)\n",
    "            ax1.text(0.98, 0.98, f'EcoScore: {r[\"eco_score\"]} Pts\\n{r[\"rank\"][\"rank_name\"]}',\n",
    "                     transform=ax1.transAxes, ha='right', va='top',\n",
    "                     fontsize=11, fontweight='bold', color='#E8304A',\n",
    "                     bbox=dict(boxstyle='round,pad=0.4', facecolor='white',\n",
    "                               edgecolor='#FFD6D8', linewidth=1.5))\n",
    "        else:\n",
    "            ax1.text(0.5, 0.5, 'Keine Aktionen gewählt', ha='center', va='center',\n",
    "                     fontsize=13, color='#b0848a')\n",
    "            ax1.axis('off')\n",
    "            ax1.set_facecolor('#FFF2F3')\n",
    "\n",
    "        if c_values:\n",
    "            ax2.pie(\n",
    "                c_values, labels=None, autopct=None, colors=colors_pie,\n",
    "                startangle=90,\n",
    "                wedgeprops={'edgecolor': '#FFF2F3', 'linewidth': 2.5}\n",
    "            )\n",
    "            total = sum(c_values)\n",
    "            legend_patches = [mpatches.Patch(color=colors_pie[i],\n",
    "                              label=f'{c_labels[i]}  {c_values[i]:.1f} kg  ({c_values[i]/total*100:.1f}%)')\n",
    "                              for i in range(len(c_labels))]\n",
    "            ax2.legend(handles=legend_patches, loc='lower center',\n",
    "                       bbox_to_anchor=(0.5, -0.12), ncol=1, fontsize=9.5,\n",
    "                       frameon=False, labelcolor='#2C2C2A')\n",
    "        else:\n",
    "            ax2.text(0.5, 0.5, 'Keine CO₂-Einsparung', ha='center', va='center',\n",
    "                     fontsize=13, color='#b0848a')\n",
    "            ax2.axis('off')\n",
    "\n",
    "        ax2.set_facecolor('#FFF2F3')\n",
    "        ax2.set_title(f'CO₂-Einsparung total: {r[\"total_co2\"]:.1f} kg',\n",
    "                      fontsize=13, fontweight='bold', color='#2C2C2A', pad=14)\n",
    "        fig.suptitle(f'EcoScore — {name}  |  Gurtenfestival 2026',\n",
    "                     fontsize=15, fontweight='bold', color='#E8304A', y=1.02)\n",
    "        plt.tight_layout(pad=2.5)\n",
    "        filename = f'ecoscore_impact_{name.replace(\" \", \"_\").lower()}.png'\n",
    "        plt.savefig(filename, dpi=180, bbox_inches='tight', facecolor='#FFF2F3')\n",
    "        plt.close()\n",
    "        print(f'Gespeichert als {filename}')\n",
    "\n",
    "export_button.on_click(export_report)\n",
    "print('Export-Funktion bereit')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86284952",
   "metadata": {},
   "source": [
    "## Schritt 1 — Name eingeben\n",
    "\n",
    "Gib deinen Namen ein. Er erscheint auf dem persönlichen Impact Report und dem exportierten PNG."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "08bf58ab",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "a690469732d042efb6d5c5a0278c38d4",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "VBox(children=(HTML(value='<b style=\"font-size:13px\">Name:</b>'), Text(value='', description='Name:', layout=L…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(widgets.VBox([\n",
    "    widgets.HTML('<b style=\"font-size:13px\">Name:</b>'),\n",
    "    name_input,\n",
    "    out_name\n",
    "]))\n",
    "update()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "95fd73cd",
   "metadata": {},
   "source": [
    "## Schritt 2 — Pfad B: Transportmittel wählen\n",
    "\n",
    "Wähle das Transportmittel für die Anreise. Entspricht dem `POST /user/transport` Endpunkt in der Zielarchitektur (vgl. Kap. 4.3)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "id": "15cd7631",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "9f6e596be8fe4eb987fe1c8387a3feb1",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "VBox(children=(HTML(value='<b style=\"font-size:13px\">Transportmittel:</b>'), Dropdown(description='Anreise:', …"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(widgets.VBox([\n",
    "    widgets.HTML('<b style=\"font-size:13px\">Transportmittel:</b>'),\n",
    "    transport_dd,\n",
    "    out_transport\n",
    "]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec408a0a",
   "metadata": {},
   "source": [
    "## Schritt 3 — Pfad A: Cashless-Aktionen wählen\n",
    "\n",
    "Wähle die nachhaltigen Aktionen die via Cashless-Karte erfasst wurden. Jede Aktion entspricht einem Webhook-Payload mit `eco_category`-Feld (vgl. Kap. 4.3, Pfad A)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "45fa3872",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "940c62cb60b3417bbe225d0581fc3414",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "VBox(children=(HTML(value='<b style=\"font-size:13px\">Ernährung:</b>'), Checkbox(value=False, description='Vege…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "food_cbs  = {k: cb for k, cb in activity_cbs.items()\n",
    "             if scoring_categories[k]['group'] == 'Ernährung'}\n",
    "waste_cbs = {k: cb for k, cb in activity_cbs.items()\n",
    "             if scoring_categories[k]['group'] == 'Recycling'}\n",
    "\n",
    "display(widgets.VBox([\n",
    "    widgets.HTML('<b style=\"font-size:13px\">Ernährung:</b>'),\n",
    "    *food_cbs.values(),\n",
    "    widgets.HTML('<b style=\"font-size:13px\">Recycling:</b>'),\n",
    "    *waste_cbs.values(),\n",
    "    out_cashless\n",
    "]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6c497b4b",
   "metadata": {},
   "source": [
    "## Schritt 4 — EcoScore-Berechnung\n",
    "\n",
    "Normalisierung der Rohpunkte und Anwendung der Gewichtungsformel (vgl. Kap. 3.2):\n",
    "\n",
    "**EcoScore = (M × 0.50) + (F × 0.20) + (R × 0.30)**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "31de49f3",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "6595e009e7f64e38bc5f09d577a06fb6",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(out_ecoscore)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e539ced1",
   "metadata": {},
   "source": [
    "## Schritt 5 — Rang & Reward\n",
    "\n",
    "Zuweisung der Reward-Stufe basierend auf dem EcoScore. Entspricht der REWARD-Entität im Datenmodell (Kap. 4.4)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "id": "26668fbb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "0c4ff8e20a8c4083bba9a54da603f65a",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(out_reward)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25b4a7d9",
   "metadata": {},
   "source": [
    "## Schritt 6 — Impact Report\n",
    "\n",
    "Persönliche Zusammenfassung aller Ergebnisse. Entspricht dem `/user/impact-report` Endpunkt in der Zielarchitektur (vgl. Kap. 4.3)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "id": "df73d3ab",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "0dfc9b9688974ef3a6ae51ef90f8d8a8",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(out_report)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a238c37",
   "metadata": {},
   "source": [
    "## Schritt 7 — Visualisierung\n",
    "\n",
    "Grafische Darstellung der Rohpunkte nach Kategorie und der CO₂-Einsparung."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "id": "e28705ea",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "856e8c915b334c3599a3c9814436802f",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(out_viz)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cdc572de",
   "metadata": {},
   "source": [
    "## Schritt 8 — Impact Report exportieren\n",
    "\n",
    "Klicke auf den Button um den aktuellen Impact Report als PNG-Datei zu speichern."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "id": "eefab168",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "87ef54be01394141b1a274ff42cd35b1",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "VBox(children=(Button(button_style='success', description='Als PNG exportieren', icon='download', layout=Layou…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(widgets.VBox([export_button, out_export]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "424b3ab0",
   "metadata": {},
   "source": [
    "## Fazit\n",
    "\n",
    "Der interaktive PoC demonstriert erfolgreich den Kernalgorithmus der EcoScore App:\n",
    "\n",
    "| Schritt | Was wird gezeigt |\n",
    "|---|---|\n",
    "| Schritt 1 | Name eingeben → erscheint auf Impact Report und PNG |\n",
    "| Schritt 2 | Transportwahl (Pfad B) → Rohpunkte + CO₂ |\n",
    "| Schritt 3 | Cashless-Aktionen (Pfad A) → Rohpunkte + CO₂ |\n",
    "| Schritt 4 | Gewichtungsformel → EcoScore 0–100 |\n",
    "| Schritt 5 | Rang & Reward basierend auf EcoScore |\n",
    "| Schritt 6 | Persönlicher Impact Report |\n",
    "| Schritt 7 | Visualisierung |\n",
    "| Schritt 8 | Export als PNG |\n",
    "\n",
    "### Abgrenzung Prototyp vs. Zielarchitektur\n",
    "\n",
    "| Funktion | Prototyp (PoC) | Zielarchitektur |\n",
    "|---|---|---|\n",
    "| Datenhaltung | Simulierte Daten (Python) | PostgreSQL (AWS RDS) |\n",
    "| Cashless-Integration | Simuliert | Echte Webhook-API |\n",
    "| Authentifizierung | Nicht implementiert | Auth0 SSO |\n",
    "| Push-Notifications | Nicht implementiert | Vorgesehen |\n",
    "| Veranstalter-Dashboard | Nicht implementiert | Web-Dashboard + CSV-Export |\n",
    "\n",
    "Der vollständige Code ist öffentlich verfügbar unter: https://github.com/endrh1/EcoScore"
   ]
  }
 ],
 "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
}
