Audit: Livepfad und Rueckblick messen wieder dasselbe (2026.9.1.17)

BEFUND 1 (behoben): dieselbe Fahrt, zwei Strecken

screening.py las den Verlauf ueber verlauf_lesen(), das den Stand zu Beginn
des Fensters mitliefert. historienimport.py schnitt sein Fenster streng heraus
und verlor genau diesen Punkt. An der echten Fahrt nachgemessen: 6,896 km
gegen 6,816 km fuer denselben Zeitraum.

Neu verlauf.zaehlerstrecke(punkte, start, ende) - der Anker ist der letzte
Datensatz am oder vor dem Beginn, dieselbe Ueberlegung wie bei wert_bei().
Beide Wege schneiden jetzt durch dieselbe Funktion. Nachgewiesen: Rueckblick
6,896, Livepfad 6,896.

Dritter Fall dieser Art nach UNPLAUSIBLE_KMH und MINDESTDAUER_S - und ich
hatte ihn am selben Tag selbst eingebaut.

BEFUND 2 (behoben): STANDARDWERTE gingen unnoetig ueber die Leitung.
zuordnung.py schickte sie mit jeder Katalogantwort; gelesen hat sie seit dem
Entfernen des Zuruecksetzen-Knopfes niemand mehr. Die Konstante bleibt, sie
traegt intern die Vorgabewerte und leitet SCHLUESSEL ab.

BEFUND 3 und 4 bleiben offen und gehoeren dem Eigentuemer: die vier Tage alte
Reichweite auf der Uebersicht (RANGE_SENSOR besser leer lassen) und die
Nullfahrt-Regel, die nur der Rueckblick kennt - im Livepfad hiesse sie, einen
bereits gespeicherten Datensatz automatisch zu loeschen.

SAUBER: 24 Backend-Dateien py_compile, Panel als Modul geparst, tsc --noEmit
und vite build sauber, 165/165 Tests, 27 Katalogeintraege gegen 27
Dataclass-Felder ohne Abweichung, jedes Listenfeld mit so vielen Beispielen wie
Positionen, keine verwaisten Verweise, in der Konsole nur das bekannte
ServiceWorker-Rauschen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-01 19:09:59 +02:00
parent 8e73621d85
commit 9d70192da3
8 changed files with 100 additions and 12 deletions
@@ -1 +1 @@
{"version":"2026.9.1.16","sha256":"500a3e3bf6b2c9663bea77d47adc770aa973797f17724297af4a2438096d7221","bytes":265720,"gebaut":"2026-09-01T17:03:56Z"}
{"version":"2026.9.1.17","sha256":"96fa9737a475fe862c2dd309490f687a3e3e4088a8f356acd3f5c718cc919306","bytes":265731,"gebaut":"2026-09-01T17:08:45Z"}
@@ -49,8 +49,8 @@ from .verlauf import (
durchschnitt_kmh,
fahrtsignal,
ist_gefahren,
strecke_aus_zaehler,
strecke_waehlen,
zaehlerstrecke,
hoechstwert_im_fenster,
route_aus_verlauf,
UNPLAUSIBLE_KMH,
@@ -198,9 +198,7 @@ async def _fahrten_importieren(k: Koordinator, verlaeufe: dict) -> dict:
# Sie entscheidet hier zusätzlich über Sein oder Nichtsein: eine Fahrt
# von 400 Metern steht im CAN-Wert als Null und wäre sonst verworfen
# worden, obwohl gefahren wurde.
fein = strecke_aus_zaehler(
[p for p in gnss_verlauf if f_start <= p[0] <= f_ende]
)
fein = zaehlerstrecke(gnss_verlauf, f_start, f_ende)
distanz, gnss_verwendet = strecke_waehlen(distanz, fein)
if not ist_gefahren(distanz):
@@ -1,7 +1,7 @@
{
"domain": "audi_dashboard",
"name": "Audi Dashboard",
"version": "2026.9.1.16",
"version": "2026.9.1.17",
"documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md",
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
"codeowners": ["@paul"],
@@ -35,8 +35,8 @@ from .verlauf import (
UNPLAUSIBLE_KMH,
durchschnitt_kmh,
hoechstwert_im_fenster,
strecke_aus_zaehler,
strecke_waehlen,
zaehlerstrecke,
naechster_wert,
route_aus_verlauf,
verbrauch_aus_literstaenden,
@@ -257,8 +257,10 @@ async def _gnss_verfeinern(k: Koordinator, gnss_sensor: str, fahrt: dict) -> Non
if start is None or ende is None or grob is None:
return
punkte = await verlauf_lesen(k.hass, gnss_sensor, start, ende)
fein = strecke_aus_zaehler(punkte)
# Etwas vor dem Fahrtbeginn mitlesen, damit zaehlerstrecke() ihren Anker
# findet - den letzten Datensatz am oder vor dem Beginn.
punkte = await verlauf_lesen(k.hass, gnss_sensor, start - SUCHFENSTER, ende)
fein = zaehlerstrecke(punkte, start, ende)
gewaehlt, genommen = strecke_waehlen(grob, fein)
if not genommen:
if fein is not None:
@@ -283,6 +283,32 @@ def route_aus_verlauf(
GNSS_TOLERANZ_KM = 1.0
def zaehlerstrecke(
punkte: list[Verlaufspunkt],
start: datetime.datetime,
ende: datetime.datetime,
) -> float | None:
"""Die im Fenster [start, ende] gefahrene Strecke aus einem laufenden
Kilometerzaehler.
Der Anker ist der letzte Datensatz AM ODER VOR `start`, nicht der erste
danach: der Zaehlerstand zu Fahrtbeginn ist der zuletzt gemeldete - dieselbe
Ueberlegung wie bei wert_bei(). Ohne ihn fehlt die Strecke zwischen dem
Beginn und dem ersten Datensatz danach.
WARUM DIESE FUNKTION EXISTIERT: Livepfad und Rueckblick haben dieselbe
Fahrt unterschiedlich gemessen. Das Screening las den Verlauf ueber
verlauf_lesen(), das den Stand ZU BEGINN des Fensters mitliefert; der
Rueckblick schnitt sein Fenster streng heraus und verlor genau diesen
Punkt. Am 01.09.2026 an der echten Fahrt nachgemessen: 6,896 km gegen
6,816 km fuer denselben Zeitraum. Das ist die Doppelbewertung, die dieses
Projekt bei UNPLAUSIBLE_KMH und MINDESTDAUER_S schon zweimal ausgeraeumt
hat - jetzt schneiden beide Wege durch dieselbe Funktion."""
davor = [p for p in punkte if p[0] <= start]
im_fenster = [p for p in punkte if start < p[0] <= ende]
return strecke_aus_zaehler((davor[-1:] if davor else []) + im_fenster)
def strecke_waehlen(
grob: float | None, fein: float | None
) -> tuple[float | None, bool]:
@@ -70,7 +70,10 @@ class Zuordnung:
die Oberfläche bereits angewendete Overrides und eingebaute
Standardwerte einheitlich."""
werte = {feld["key"]: getattr(self.werte, feld["key"], None) for feld in FELDER}
return {"katalog": FELDER, "werte": werte, "standardwerte": STANDARDWERTE}
# "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"]