Files
audi-app/custom_components/audi_dashboard/zuordnung.py
T
tobias 863284e540 Hauptuntersuchung: Ableitung aus Erstzulassung und Wartungsplan, drittes Feld entfernt
Gemeldet: Wartungsplan leer, Erstzulassung 01.03.2025, erwartet 01.03.2028 -
gezeigt wurden drei verschiedene Antworten (Uebersicht 01.08.2026, Mein Audi
"August 2026", Service "kein Eintrag im Wartungsplan").

Ursache war das Profilfeld hauptuntersuchung_faellig ("08/2026"), das die
Ableitung ueberstimmte, in keiner Oberflaeche ein Eingabefeld hatte und im
Backend kein Schema. Drei Bildschirme verarbeiteten denselben Wert
verschieden. Das Feld ist samt Lese- und Schreibweg entfernt; es gibt jetzt
zwei Quellen: Wartungsplan + 24 Monate, sonst Erstzulassung + 36 Monate.

Dabei mitbehoben:

* Die Service-Kachel verschluckte abgeleitete Termine, sobald kein
  Wartungsplan-Eintrag dahinterstand.
* monatePlus() in der App verlor einen Tag ueber die Zeitumstellung (drei von
  fuenf gemessenen Faellen) - betraf Oelwechsel- und Inspektionsprognose
  genauso. Das Panel rechnete dort seit jeher richtig.
* Die App verlangte fuer jeden Wartungsplan-Eintrag eine Kilometerangabe. Eine
  Hauptuntersuchung ist rein datumsbasiert; ohne km waere der erste HU-Eintrag
  ignoriert worden. Ausserdem nahm das Panel den ersten Treffer der Liste, die
  App den juengsten - beide nehmen jetzt den juengsten.
* Monatsgenauigkeit ("03/2025") bleibt auf beiden Seiten erhalten.

Neu: paritaet_hu.test.ts vergleicht die App gegen die aus der Panel-Quelle
herausgeschnittenen Originalfunktionen, 15 Faelle. Gesamt 292 App-Tests, 73
Python-Tests mit 231 Untertests, 0 Tracebacks. Live an der Testinstanz auf
allen drei gemeldeten Bildschirmen geprueft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 01:56:11 +02:00

101 lines
4.4 KiB
Python

"""Sensor-zu-Entität-Zuordnung fürs Setup-Menü (Einstellungen -> Fahrzeug
einrichten -> Setup).
audi_dashboard/entitaeten.json enthält nur die vom Nutzer über das Setup-Menü
geänderten Zuordnungen, keyed nach den Feldnamen aus einstellungen.py.
anwenden() legt sie über die eingebauten Standardwerte.
Wichtig ist, dass JEDES bekannte Feld bei jedem Durchlauf gesetzt wird, nicht
nur die belegten. Eine frühere Fassung übersprang leere Werte - weil die
Zuordnung aber ein dauerhaftes Objekt ist, blieb ein einmal gesetzter Wert
danach für immer stehen. "Zurücksetzen" im Setup-Menü hatte bei allen Feldern
mit leerem Standardwert (15 von 17) schlicht keine Wirkung, und die Oberfläche
zeigte beim nächsten Zeichnen wieder den alten Wert, als sei das Speichern
fehlgeschlagen. Umgekehrt wurde eine Liste aus leeren Einträgen gesetzt statt
übersprungen, was den Sicherheitscheck mit zwölf "unbekannt"-Zeilen füllte und
"Sicher abgestellt" dauerhaft grau werden ließ.
Jedes Feld bei jedem Durchlauf zu schreiben ist damit nicht nur korrekt,
sondern auch idempotent: der Zustand hängt allein an entitaeten.json, nicht an
der Reihenfolge vorheriger Aufrufe.
"""
from __future__ import annotations
import logging
from . import einstellungen
from .ablage import Ablage
from .einstellungen import FELDER, SCHLUESSEL, STANDARDWERTE, Sensorzuordnung
_LOGGER = logging.getLogger(__name__)
def _ist_leer(wert: object) -> bool:
"""Leer heißt: keine Zuordnung. Auch eine Liste, die nur leere Einträge
enthält - das Setup-Menü schickt für unbelegte Positionen ["","",...], und
das ist inhaltlich dasselbe wie gar keine Liste."""
if wert in (None, ""):
return True
if isinstance(wert, list):
return not any(eintrag for eintrag in wert)
return False
class Zuordnung:
"""Hält die wirksame Sensorzuordnung und pflegt sie aus entitaeten.json."""
def __init__(self, ablage: Ablage) -> None:
self._ablage = ablage
self.werte = Sensorzuordnung()
async def anwenden(self) -> None:
"""Setzt für jedes bekannte Feld den wirksamen Wert - den Override,
wenn einer hinterlegt ist, sonst den eingebauten Standardwert.
Räumt dabei einmalig auf: Schlüssel, die der Katalog nicht mehr kennt,
werden aus entitaeten.json entfernt. `speichern()` filtert sie schon
beim Schreiben heraus, aber eine bestehende Datei behielt sie bis zum
nächsten Speichern - und ein Feld, das es nicht mehr gibt, soll auch
nicht mehr in den Daten stehen. Angefallen ist das mit dem Entfernen
von TRIP_SENSOR am 05.09.2026."""
overrides = await self._ablage.zuordnung_lesen()
verwaist = [k for k in overrides if k not in SCHLUESSEL]
if verwaist:
_LOGGER.info(
"Zuordnung: %s kennt der Katalog nicht mehr - entfernt",
", ".join(sorted(verwaist)),
)
await self._ablage.zuordnung_schreiben(
{k: v for k, v in overrides.items() if k in SCHLUESSEL}
)
for key in SCHLUESSEL:
wert = overrides.get(key)
standard = STANDARDWERTE[key]
gesetzt = standard if _ist_leer(wert) else wert
setattr(self.werte, key, list(gesetzt) if isinstance(gesetzt, list) else gesetzt)
async def speichern(self, mapping: dict) -> None:
"""Schreibt nur bekannte Schlüssel; unbekannte werden stillschweigend
verworfen, damit ein späterer Katalog-Umbau keine Datenleichen in
entitaeten.json hinterlässt."""
bereinigt = {k: v for k, v in mapping.items() if k in SCHLUESSEL}
await self._ablage.zuordnung_schreiben(bereinigt)
await self.anwenden()
def aktueller_stand(self) -> dict:
"""Katalog + aktuell wirksamer Wert je Feld, Grundlage für die Entität
sensor.audi_dashboard_entitaeten. Die Werte kommen aus dem
Zuordnungsobjekt selbst, nicht direkt aus der JSON-Datei - damit zeigt
die Oberfläche bereits angewendete Overrides und eingebaute
Standardwerte einheitlich."""
werte = {feld["key"]: getattr(self.werte, feld["key"], None) for feld in FELDER}
# "standardwerte" ist mit dem Zuruecksetzen-Knopf (2026.9.1.11)
# entfallen - niemand liest sie mehr, also gehen sie auch nicht mehr
# ueber die Leitung.
return {"katalog": FELDER, "werte": werte}
__all__ = ["Zuordnung", "einstellungen"]