Files
audi-app/homeassistant
tobias 39da68cd87 Fix broken map rendering, split GPS cleanup, receipt parser and design backlog
Leaflet's stylesheet was appended to document.head, so it never reached the
panel's shadow root: .leaflet-tile{position:absolute} never applied, tiles laid
out as in-flow images (~1040px inside a 190px box) and the marker pane ended up
far below the visible area. That is the real cause of the long-standing
"fragmented Leaflet rendering" finding and of the invisible vehicle pin - every
tile request had actually succeeded. The stylesheet now goes into the shadow
root and is awaited before the map is built.

Also in this round:
- Map tiles are always light (Google-Maps-style), no dark variant at night.
- Vehicle marker uses the real CI poi-car icons (poi-car-l >=34px, poi-car-s
  below), with a white halo so the outline stays readable on tiles.
- Removed the obsolete combined STANDORT_TRACKER field; only the split
  lat/lon sensors remain.
- Receipt upload: widened the try block so base64/save failures surface, and
  the frontend call site now reports a rejected service call.
- Generic receipt parser: total detection is line-based (letter-spaced
  headings, no more matching the SUMME-EUR column header, tax lines excluded),
  address heuristic handles 4-digit postcodes and single-line address blocks,
  and "Preis/L" matches without a spelled-out "Liter".
- Design backlog: red hairline frame on list rows (delete button bled through
  at fractional row heights) and select fields now use the grey background box.

Shell 10-receipt regression suite still passes; all changes verified live in
the audi_ha_test container.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 12:46:48 +02:00
..

Audi-Dashboard — Backend und Frontend, Stand nach dritter Bauphase

Dieser Ordner enthält Backend und Frontend aus dem Lastenheft (bauauftrag.md): das pyscript-Backend für Fahrterkennung, Fahrtabschluss, Reifenzähler und Belegverarbeitung, plus der zum panel_custom-Custom- Element umgebaute Prototyp.

Installation: siehe INSTALL.md — Schritt für Schritt, inklusive der einzigen Stelle, die inhaltlich angepasst werden muss (pyscript/modules/einstellungen.py, Entity-IDs).

Getestet, nicht nur geprüft

Beides lief in einer Wegwerf-Testinstanz (Home Assistant in Docker), nicht nur gegen Dokumentation abgeglichen:

  • Backend: alle Module laden fehlerfrei, alle Trigger und Services registrieren sich korrekt. Zwei echte Service-Aufrufe über die HA-API haben tatsächlich Daten geschrieben — audi_dashboard_reifen_wechseln hat fahrzeugprofil.json von „sommer" auf „winter" umgestellt, audi_dashboard_fahrt_manuell_anlegen hat einen vollständigen Datensatz an fahrten.jsonl angehängt.
  • Frontend: das Custom Element wurde mit genau diesen echten Backend-Daten gefüttert (per REST abgerufen) und hat daraus korrekt die Übersicht und die Fahrtenliste gerendert — inklusive der zuvor umgeschalteten Winterbereifung, sichtbar an der Fahrbahn-Szene, und der manuell angelegten Testfahrt in der Fahrten-Ansicht.

Dabei wurden drei echte pyscript-Eigenheiten gefunden und behoben, die in keiner Dokumentation standen:

  1. task.executor() akzeptiert nur echte externe Python-Funktionen (z. B. io.open), keine im pyscript-Ordner selbst definierten.
  2. Variablen aus einem with ... as f:-Block waren danach außerhalb nicht mehr auffindbar (NameError).
  3. Das eingebaute open() existiert in pyscript nicht.

Eine Lücke bleibt: die Testinstanz ließ keine WebSocket-Verbindung zu (Einschränkung der Testumgebung, nicht des Codes). Der letzte Schritt — dass panel_custom das Element beim normalen Draufklicken in der Sidebar selbst erzeugt und mit hass versorgt — läuft über genau diese Verbindung und ließ sich deshalb nicht End-to-End nachstellen. panel_custom ist aber Home Assistants eigener, langjährig stabiler Mechanismus, nicht eigener Code — das Risiko dort ist gering, verglichen mit dem, was schon getestet werden konnte.

Persönliche Daten

Alles Fahrzeug- und Personenspezifische (VIN, Kennzeichen, Versicherung, Werkstattkontakt, Servicehistorie, Fahrten, Tankvorgänge) liegt ausschließlich in data/ und wird nie im App-Code (pyscript/, www/) fest hinterlegt — data/fahrzeugprofil.json, data/fahrten.jsonl, data/tankvorgaenge.jsonl sowie die echten Tankbeleg-PDFs unter data/tests/belege/ sind deshalb per .gitignore von der Versionierung ausgeschlossen. data/fahrzeugprofil. example.json ist die getrackte Vorlage ohne echte Werte für eine Neuinstallation (siehe INSTALL.md Schritt 2); die Kernangaben lassen sich danach direkt in der App unter Einstellungen → Fahrzeug einrichten eintragen, ohne die JSON-Datei von Hand zu bearbeiten. Das Backup (Einstellungen → Backup) exportiert ausschließlich diese drei Datendateien — nie App-Code oder -Konfiguration.

Danach noch offen

  • Reifen-Kilometerstände (reifen.saetze.sommer/winter.km) laufen seit der Frontend-Anpassung automatisch mit: jeder gefahrene Kilometer zählt auf den Satz, der laut reifen.aktiv gerade montiert ist (reifenzaehler.py, reagiert auf KM_SENSOR). Beide starten bei 0 — Laufleistung von vor der Installation muss einmalig direkt im Profil nachgetragen werden.
  • Kfz-Steuer-Fälligkeit: steuer.faellig steht auf null, aus §13 noch offen.
  • shell_beleg_parser.py fehlte im Projektordner beim Bau und wurde nachträglich neu geschrieben — liegt jetzt unter data/shell_beleg_parser.py und landet mit dem data-Ordner automatisch unter /config/audi_dashboard/shell_beleg_parser.py (siehe INSTALL.md Schritt 2 und Schritt 8). Braucht dort einmalig pip install pypdf im System-python3 des Containers (eigener Prozess, nicht die pyscript-Sandbox). Geprüft gegen zehn echte Mobile-Payment-Belege aus vier verschiedenen Stationen (Ingolstadt/ Zrenner, Rain am Lech/Bauch, Ansbach/Sengül, Königsbronn/Mogler) — die Erkennung ist bewusst nicht an Markenzeilen-Wortlaut ("SHELL STATION" vs. "Shell-Station") oder Rabatt-Label ("V-Power Smart Deal" vs. "ClubSmartRabatt") gebunden, siehe Regressionstest unter data/tests/test_shell_beleg_parser.py (Aufruf: python3 data/tests/test_shell_beleg_parser.py). Andere Zahlungswege als Mobile Payment oder Belege mit mehreren Positionen (z. B. zusätzlicher Autowäsche-Posten mit abweichendem MwSt.-Satz) sind nicht durch reale Belege abgedeckt und müssten bei Bedarf nachgezogen werden.
  • Tailscale: „VPN On Demand" mit Regel „Only On", gebunden an Audi_MMI_2804_5GHz, muss in der Tailscale-App selbst eingerichtet werden (§9, §10 Punkt 8) — das lässt sich nicht von hier aus mitinstallieren.
  • GPS-Fallback (§7.2) ist nicht umgesetzt. Fahrten ohne passenden Kilometerstand bleiben aktuell dauerhaft offen, statt auf die GPS-Strecke auszuweichen. Erfordert eine device_tracker-Anbindung und Adressauflösung.
  • Fahrterkennung übersteht keinen HA-Neustart mitten in einer Fahrt oder Wartezeit — der Zwischenstand lebt nur im Arbeitsspeicher von fahrterkennung.py.
  • Bilder fehlen (§7a) — Ordner www/bilder/ ist noch leer. Layout springt nicht (Maße sind reserviert), aber die Flächen bleiben leer.
  • Leaflet lädt per CDN, wie im Prototyp — braucht zusätzlich zu Tailscale echten Internetzugang auf dem Gerät.

Was schon funktioniert (getestet)

  • Fahrterkennung über den Zündungssensor mit Pausenregel (task.unique + task.sleep); bis 2026-08-12 lief sie über den WLAN-Sensor des iPhones
  • Zweistufiger Fahrtabschluss per Recorder-Historie-Screening, inklusive Verkettung direkt anschließender Fahrten
  • Reifenzähler per direkter Subtraktion, kein utility_meter
  • Belegverarbeitung mit Duplikatserkennung über receipt_key (die zunächst geplante 97-%-Volltankungsregel wurde per Änderungswunsch entfernt, siehe Kopfkommentar in pyscript/belegverarbeitung.py)
  • Manuelle Fallback-Wege für Fahrten und Tankvorgänge ohne Beleg
  • Frontend: Übersicht, Fahrtenliste, Navigation zwischen allen Ansichten, Datenadapter Profil→Oberfläche, Schreibaktionen über hass.callService
  • Statistik-Seite mit echter Auswertung aus Fahrten und Tankvorgängen (Zeiträume, Verbrauch, Tag/Nacht, privat/Arbeitsweg)
  • Setup-Menü für die Sensor-Zuordnung, inklusive Sicherung in data/entitaeten.json

Dateiübersicht

homeassistant/
├── INSTALL.md                          Schritt-für-Schritt-Installation
├── README.md                           diese Datei
├── configuration_snippet.yaml          Ergänzung zur configuration.yaml
├── data/
│   ├── fahrzeugprofil.json             Fahrzeugprofil (§6.1), mit echten Daten befüllt — gitignored
│   ├── fahrzeugprofil.example.json     Vorlage ohne echte Daten, bleibt getrackt (siehe INSTALL.md Schritt 2)
│   ├── fahrten.jsonl                   Fahrten-Archiv (§6.1) — gitignored
│   └── tankvorgaenge.jsonl             Tankvorgänge-Archiv (§6.1) — gitignored
├── pyscript/
│   ├── modules/
│   │   ├── einstellungen.py            zentrale Entity-ID-Konfiguration (Schritt 4)
│   │   ├── profil.py                   Datenzugriff (§6)
│   │   ├── entitaeten.py               Sensor-Zuordnung aus dem Setup-Menü
│   │   ├── fahrtabschluss_logik.py     Screening-Logik (§7.2)
│   │   └── frontend_veroeffentlichung.py  Zustände fürs Frontend (§10 Punkt 7 Ersatz)
│   ├── fahrterkennung.py               §7.1 (Zündungssensor, früher WLAN)
│   ├── fahrtabschluss.py               §7.2 (Trigger-Registrierung)
│   ├── tankerkennung.py                §7.4 (automatische Erkennung am Füllstandsanstieg)
│   ├── reifenzaehler.py                §7.5
│   ├── belegverarbeitung.py            §7.4, §7.7
│   ├── batterieverlauf.py              12-V-Spannung, Tagesminimum/-maximum
│   ├── bilderverwaltung.py             Upload/Löschen der Fahrzeugfotos
│   ├── backup.py                       tägliche Sicherung und Wiederherstellung
│   ├── updateverwaltung.py             Update suchen und installieren
│   └── frontend_api.py                 Lese-/Schreib-Anbindung fürs Frontend
├── www/
│   ├── audi-dashboard-panel.js         Lade-Stub (zeigt configuration.yaml hierher), ändert sich kaum
│   ├── audi-dashboard-app.js           das eigentliche Custom Element, hier passiert die Arbeit
│   ├── audi-dashboard-version.json     Cache-Buster, wird von update.ps1 automatisch neu geschrieben
│   ├── audi-dashboard.css              aus dem Prototyp extrahiert, für Shadow DOM angepasst
│   └── bilder/                         noch leer, siehe „Danach noch offen"
└── update.ps1                          Updates einspielen ohne Neustart (Schritt 10 in INSTALL.md)

Updates einspielen

pyscript/ lädt sich bei Änderungen selbst neu, ganz ohne HA-Neustart — an der Testinstanz gemessen (Datei überschrieben, Service erschien innerhalb von Millisekunden). Fürs Frontend gibt es dafür keinen eingebauten Mechanismus, dazu kommt ein echtes Cache-Problem: /local/ wird 31 Tage lang gecacht (Cache-Control: max-age=2678400, ebenfalls gemessen). Gelöst über einen kleinen Lade-Stub (audi-dashboard-panel.js), der bei jedem Seitenaufruf ungecacht eine Versionsnummer nachlädt und darüber den eigentlichen Code cache-sicher nachzieht — siehe Schritt 10 in INSTALL.md und update.ps1.