Fahrten: Verbrauch-Näherung, 0V-Zustandsanzeige gefiltert, Plausibilitätsgrenze für die Live-Vervollständigung

Verbrauch (l/100km) war bislang totes Schema - kein Codepfad füllte
verbrauch_l_100km je. Jetzt aus der Literstand-Differenz (TANK_LITER_SENSOR)
über die Distanz genähert, live und beim Import, in beiden Frontends.

Die "Zustand"-Kachel zeigte die Batteriespannung ungefiltert direkt vom
Sensor, unabhängig von der Plausibilitätsgrenze der Verlaufsaufzeichnung -
ein Sensorausreißer (0V) zeigte sich dort weiterhin, obwohl die Messwertliste
ihn längst verwarf. Dieselbe Grenze gilt jetzt auch für diesen Anzeigepfad.

Reale Fahrtendaten zeigten eine Fahrt mit 22km in 67s (~1180 km/h) - die
Live-Vervollständigung (screening.py) hatte anders als der Import keine
Plausibilitätsprüfung der Durchschnittsgeschwindigkeit. Jetzt gemeinsam in
verlauf.py (UNPLAUSIBLE_KMH/durchschnitt_kmh) für beide Pfade. Dabei einen
zweiten echten Bug gefunden: _fahrt_screenen() zog sein Ergebnis nie ins
In-Memory-Objekt nach (nur in die Ablage) - eine im selben Durchlauf gerade
erst ermittelte Distanz blieb für spätere Schritte (z. B. Verbrauch) bis zum
nächsten Screening unsichtbar. Beide Stellen jetzt behoben.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 21:01:57 +02:00
parent cf57c5f1a5
commit 80d73e8afd
12 changed files with 344 additions and 30 deletions
@@ -44,7 +44,16 @@ from typing import TYPE_CHECKING
from .batterie import SPANNUNG_MIN_V
from .fahrterkennung import leere_fahrt, pausenzeit_sekunden
from .tankerkennung import LITER_SCHWELLE, leerer_tankvorgang, schwelle_prozent
from .verlauf import Verlaufspunkt, route_aus_verlauf, verlauf_lesen, wert_bei, zahl
from .verlauf import (
UNPLAUSIBLE_KMH,
Verlaufspunkt,
durchschnitt_kmh,
route_aus_verlauf,
verbrauch_aus_literstaenden,
verlauf_lesen,
wert_bei,
zahl,
)
if TYPE_CHECKING:
from .koordinator import Koordinator
@@ -61,14 +70,11 @@ TANK_DUBLETTE_MIN = 90
# fluten sie den Bestand aber mit Nulleinträgen. Bewusst konservativ.
MINDESTDAUER_S = 60
# Kein Auto dieser Art erreicht diesen Schnitt. Eine errechnete
# Durchschnittsgeschwindigkeit darüber ist kein Beleg für eine schnelle
# Fahrt, sondern für einen fehlerhaften Kilometerstand-Sprung am Fahrtrand
# (z. B. ein Ausreißer im Verlauf oder ein knapp daneben liegender
# wert_bei()-Treffer über eine Lücke hinweg). Die Strecke wird dann verworfen
# statt eine physikalisch unmögliche Fahrt anzuzeigen - die Fahrt bleibt
# "offen", genau wie bei fehlendem Kilometerstand.
UNPLAUSIBLE_KMH = 300
# UNPLAUSIBLE_KMH selbst lebt in verlauf.py - gemeinsam mit screening.py, das
# dieselbe Prüfung seit 2026.8.27 auch für die Live-Vervollständigung nutzt
# (siehe dortiges _vollstaendig()). Die Strecke wird dann verworfen statt
# eine physikalisch unmögliche Fahrt anzuzeigen - die Fahrt bleibt "offen",
# genau wie bei fehlendem Kilometerstand.
def als_zeit(wert: object) -> datetime.datetime | None:
@@ -150,6 +156,7 @@ async def _fahrten_importieren(k: Koordinator, verlaeufe: dict) -> dict:
km_verlauf = verlaeufe["km"]
lat_verlauf = verlaeufe["lat"]
lon_verlauf = verlaeufe["lon"]
tank_liter_verlauf = verlaeufe["tank_liter"]
angelegt = uebersprungen = zu_kurz = 0
neue: list[dict] = []
@@ -169,19 +176,17 @@ async def _fahrten_importieren(k: Koordinator, verlaeufe: dict) -> dict:
if odo_start is not None and odo_end is not None and odo_end >= odo_start:
distanz = round(odo_end - odo_start, 1)
durchschnitt = None
if distanz is not None and dauer_s > 0:
durchschnitt = round(distanz / (dauer_s / 3600.0), 1)
if durchschnitt > UNPLAUSIBLE_KMH:
_LOGGER.warning(
"Kilometerstand für Fahrt %s bis %s verworfen: unmögliche %s km/h "
"im Schnitt (%s km in %s s) - vermutlich ein Ausreißer im Verlauf. "
"Die Fahrt wird trotzdem angelegt, bleibt aber ohne Strecke.",
f_start.isoformat(), f_ende.isoformat(), durchschnitt, distanz, dauer_s,
)
distanz = None
odo_start = odo_end = None
durchschnitt = None
durchschnitt = durchschnitt_kmh(distanz, dauer_s)
if durchschnitt is not None and durchschnitt > UNPLAUSIBLE_KMH:
_LOGGER.warning(
"Kilometerstand für Fahrt %s bis %s verworfen: unmögliche %s km/h "
"im Schnitt (%s km in %s s) - vermutlich ein Ausreißer im Verlauf. "
"Die Fahrt wird trotzdem angelegt, bleibt aber ohne Strecke.",
f_start.isoformat(), f_ende.isoformat(), durchschnitt, distanz, dauer_s,
)
distanz = None
odo_start = odo_end = None
durchschnitt = None
fahrt = leere_fahrt(f_start, f_ende, "import")
fahrt.update({
@@ -195,6 +200,9 @@ async def _fahrten_importieren(k: Koordinator, verlaeufe: dict) -> dict:
"end_lat": wert_bei(lat_verlauf, f_ende),
"end_lon": wert_bei(lon_verlauf, f_ende),
"route": route_aus_verlauf(lat_verlauf, lon_verlauf, f_start, f_ende),
"verbrauch_l_100km": verbrauch_aus_literstaenden(
wert_bei(tank_liter_verlauf, f_start), wert_bei(tank_liter_verlauf, f_ende), distanz
),
"status": "vollständig" if distanz is not None else "offen",
})
neue.append(fahrt)