diff --git a/AGENTS.md b/AGENTS.md index b277392..35f897d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,8 @@ # AGENTS.md — Project state, review findings, open items, and working rules -**Last updated: 2026-09-01** (Setup: gruene Bestaetigung und Erwartungswert je Listenposition, -`2026.9.1.16`, Abschnitt BG. Davor: Rueckblick ohne Nullfahrten, Streckenwahl gemeinsam in `verlauf.py`, +**Last updated: 2026-09-01** (Audit ueber Panel, App und Backend: vier Befunde, zwei behoben, +`2026.9.1.17`, Abschnitt BH. Davor: Setup mit gruener Bestaetigung und Erwartungswert je +Listenposition, Abschnitt BG. Davor: Rueckblick ohne Nullfahrten, Streckenwahl gemeinsam in `verlauf.py`, `2026.9.1.14`, Abschnitt BF. Davor: Fahrtstrecke auf 100 m: CAN als Anker, GNSS als Nachkommastelle, `2026.9.1.10`, Abschnitt BE. Davor: Nachlauf-Abzug nur mit zugeordnetem `TRIP_SENSOR`, `2026.9.1.9`; davor „502" beim Neustart entschärft, OTA-Bündel auf `2026.9.1.8` @@ -6935,6 +6936,64 @@ ganze Zeit richtig. oder Stylesheet - nicht davor. Wer waehrend der Pruefung noch nachbessert, erhoeht danach noch einmal. +## BH. Audit vom 01.09.2026 — vier Befunde, zwei behoben (2026.9.1.17) + +Nach einem Tag mit siebzehn Auslieferungen ein Rundumblick über beide Oberflächen und das Backend. + +**Was sauber war:** 24 Backend-Dateien `py_compile`, Panel als **Modul** geparst (Abschnitt AQ), +`tsc --noEmit` und `vite build` sauber, 165/165 Tests grün, 27 Katalogeinträge gegen 27 +Dataclass-Felder ohne Abweichung in beide Richtungen, keine doppelten Schlüssel, jedes Feld mit +Beispielnamen, jedes Listenfeld mit so vielen Beispielen wie Positionen. In der Konsole nur das +bekannte ServiceWorker-Rauschen. Keine verwaisten Verweise auf das entfernte +`„Auf Standard zurücksetzen"`. + +### Befund 1 (behoben): Livepfad und Rückblick maßen dieselbe Fahrt unterschiedlich + +Die schwerste Sorte Fehler in diesem Projekt — und ich hatte sie am selben Tag selbst eingebaut. + +`screening.py` las den Verlauf über `verlauf_lesen()`, das den Stand **zu Beginn** des Fensters +mitliefert. `historienimport.py` schnitt sein Fenster streng heraus (`f_start <= p[0] <= f_ende`) +und verlor genau diesen Punkt. An der echten Fahrt nachgemessen: **6,896 km gegen 6,816 km** für +denselben Zeitraum, je nachdem welcher Weg sie bewertete. + +Neu `verlauf.zaehlerstrecke(punkte, start, ende)`: der Anker ist der letzte Datensatz **am oder +vor** dem Beginn, dieselbe Überlegung wie bei `wert_bei()`. Beide Wege schneiden jetzt durch +dieselbe Funktion. Nachgewiesen: Rückblick 6,896 — Livepfad 6,896. + +Dritter Fall dieser Art nach `UNPLAUSIBLE_KMH` und `MINDESTDAUER_S`. **Regel bestätigt sich:** +sobald zwei Wege dieselbe Größe berechnen, gehört die Rechnung in `verlauf.py` — nicht als +Aufräumarbeit hinterher, sondern beim ersten Mal. + +### Befund 2 (behoben, kleiner): `STANDARDWERTE` gingen unnötig über die Leitung + +`zuordnung.py` schickte sie mit jeder Katalogantwort ans Frontend. Gelesen hat sie dort seit dem +Entfernen des Zurücksetzen-Knopfes (`2026.9.1.11`) niemand mehr. Die Konstante selbst bleibt — sie +trägt intern die Vorgabewerte und leitet `SCHLUESSEL` ab; nur die Veröffentlichung ist weg. + +### Befund 3 (offen, gehört dem Eigentümer): die Reichweite ist vier Tage alt + +Auf der Übersicht steht **87 km** als größte Zahl des Bildschirms — daneben 61 % Tank. Der Wert +stammt vom 29.08. und aus einem **anderen Fahrzeug**; die beiden zuletzt genutzten Autos liefern +`can.vehicle.remaining.range` nicht über CAN (an flespis Zeitstempeln nachgewiesen, HAs Recorder +kann tote von konstanten Sensoren nicht unterscheiden). + +Kein Codefehler. Solange kein Fahrzeug den Wert liefert, wäre die Rolle `RANGE_SENSOR` besser leer: +dann steht dort „Reichweite unbekannt" statt einer falschen Zahl. Im Endzustand liefert das +Fahrzeug sie wieder — Entscheidung des Eigentümers. + +### Befund 4 (offen, braucht eine Entscheidung): die Nullfahrt-Regel gilt nur im Rückblick + +`ist_gefahren()` wird nur von `historienimport.py` aufgerufen. Der Livepfad kann weiterhin eine +Fahrt anlegen, die sich später zu 0,0 km ausrechnet — genau das, was der Eigentümer aus dem Bestand +haben wollte. Im aktuellen Bestand gibt es keine solche Fahrt, und unter der neuen Gerätekonfiguration +(`Start Speed = 3 km/h`) sollte sie kaum entstehen. Aber es ist wieder eine Regel, die nur ein Weg +kennt. + +**Bewusst nicht einseitig gebaut:** der Livepfad kennt die Strecke erst beim Screening, also +nachträglich. Die Fahrt dann zu verwerfen hieße, einen bereits gespeicherten Datensatz automatisch +zu löschen. Das ist eine Entscheidung über die Daten des Eigentümers, keine über den Code — dieselbe +Linie wie in Abschnitt W. + ## Working conventions (observed — keep them) - German is the project language: identifiers, comments, commits, UI texts. Exceptions: diff --git a/custom_components/audi_dashboard/frontend/app/bundle.json b/custom_components/audi_dashboard/frontend/app/bundle.json index 4f2ba2c..626c31f 100644 --- a/custom_components/audi_dashboard/frontend/app/bundle.json +++ b/custom_components/audi_dashboard/frontend/app/bundle.json @@ -1 +1 @@ -{"version":"2026.9.1.16","sha256":"500a3e3bf6b2c9663bea77d47adc770aa973797f17724297af4a2438096d7221","bytes":265720,"gebaut":"2026-09-01T17:03:56Z"} \ No newline at end of file +{"version":"2026.9.1.17","sha256":"96fa9737a475fe862c2dd309490f687a3e3e4088a8f356acd3f5c718cc919306","bytes":265731,"gebaut":"2026-09-01T17:08:45Z"} \ No newline at end of file diff --git a/custom_components/audi_dashboard/frontend/app/bundle.zip b/custom_components/audi_dashboard/frontend/app/bundle.zip index 421595f..5a39165 100644 Binary files a/custom_components/audi_dashboard/frontend/app/bundle.zip and b/custom_components/audi_dashboard/frontend/app/bundle.zip differ diff --git a/custom_components/audi_dashboard/historienimport.py b/custom_components/audi_dashboard/historienimport.py index 3431851..1faddd3 100644 --- a/custom_components/audi_dashboard/historienimport.py +++ b/custom_components/audi_dashboard/historienimport.py @@ -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): diff --git a/custom_components/audi_dashboard/manifest.json b/custom_components/audi_dashboard/manifest.json index c5fe1d1..49386c4 100644 --- a/custom_components/audi_dashboard/manifest.json +++ b/custom_components/audi_dashboard/manifest.json @@ -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"], diff --git a/custom_components/audi_dashboard/screening.py b/custom_components/audi_dashboard/screening.py index 60417f9..d62508b 100644 --- a/custom_components/audi_dashboard/screening.py +++ b/custom_components/audi_dashboard/screening.py @@ -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: diff --git a/custom_components/audi_dashboard/verlauf.py b/custom_components/audi_dashboard/verlauf.py index 7a50ce5..4e61947 100644 --- a/custom_components/audi_dashboard/verlauf.py +++ b/custom_components/audi_dashboard/verlauf.py @@ -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]: diff --git a/custom_components/audi_dashboard/zuordnung.py b/custom_components/audi_dashboard/zuordnung.py index da989b1..57ea25d 100644 --- a/custom_components/audi_dashboard/zuordnung.py +++ b/custom_components/audi_dashboard/zuordnung.py @@ -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"]