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:
@@ -1,7 +1,8 @@
|
|||||||
# AGENTS.md — Project state, review findings, open items, and working rules
|
# AGENTS.md — Project state, review findings, open items, and working rules
|
||||||
|
|
||||||
**Last updated: 2026-09-01** (Setup: gruene Bestaetigung und Erwartungswert je Listenposition,
|
**Last updated: 2026-09-01** (Audit ueber Panel, App und Backend: vier Befunde, zwei behoben,
|
||||||
`2026.9.1.16`, Abschnitt BG. Davor: Rueckblick ohne Nullfahrten, Streckenwahl gemeinsam in `verlauf.py`,
|
`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.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`;
|
`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`
|
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
|
oder Stylesheet - nicht davor. Wer waehrend der Pruefung noch nachbessert, erhoeht danach noch
|
||||||
einmal.
|
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)
|
## Working conventions (observed — keep them)
|
||||||
|
|
||||||
- German is the project language: identifiers, comments, commits, UI texts. Exceptions:
|
- German is the project language: identifiers, comments, commits, UI texts. Exceptions:
|
||||||
|
|||||||
@@ -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"}
|
||||||
Binary file not shown.
@@ -49,8 +49,8 @@ from .verlauf import (
|
|||||||
durchschnitt_kmh,
|
durchschnitt_kmh,
|
||||||
fahrtsignal,
|
fahrtsignal,
|
||||||
ist_gefahren,
|
ist_gefahren,
|
||||||
strecke_aus_zaehler,
|
|
||||||
strecke_waehlen,
|
strecke_waehlen,
|
||||||
|
zaehlerstrecke,
|
||||||
hoechstwert_im_fenster,
|
hoechstwert_im_fenster,
|
||||||
route_aus_verlauf,
|
route_aus_verlauf,
|
||||||
UNPLAUSIBLE_KMH,
|
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
|
# 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
|
# von 400 Metern steht im CAN-Wert als Null und wäre sonst verworfen
|
||||||
# worden, obwohl gefahren wurde.
|
# worden, obwohl gefahren wurde.
|
||||||
fein = strecke_aus_zaehler(
|
fein = zaehlerstrecke(gnss_verlauf, f_start, f_ende)
|
||||||
[p for p in gnss_verlauf if f_start <= p[0] <= f_ende]
|
|
||||||
)
|
|
||||||
distanz, gnss_verwendet = strecke_waehlen(distanz, fein)
|
distanz, gnss_verwendet = strecke_waehlen(distanz, fein)
|
||||||
|
|
||||||
if not ist_gefahren(distanz):
|
if not ist_gefahren(distanz):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "audi_dashboard",
|
"domain": "audi_dashboard",
|
||||||
"name": "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",
|
"documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md",
|
||||||
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
|
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
|
||||||
"codeowners": ["@paul"],
|
"codeowners": ["@paul"],
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ from .verlauf import (
|
|||||||
UNPLAUSIBLE_KMH,
|
UNPLAUSIBLE_KMH,
|
||||||
durchschnitt_kmh,
|
durchschnitt_kmh,
|
||||||
hoechstwert_im_fenster,
|
hoechstwert_im_fenster,
|
||||||
strecke_aus_zaehler,
|
|
||||||
strecke_waehlen,
|
strecke_waehlen,
|
||||||
|
zaehlerstrecke,
|
||||||
naechster_wert,
|
naechster_wert,
|
||||||
route_aus_verlauf,
|
route_aus_verlauf,
|
||||||
verbrauch_aus_literstaenden,
|
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:
|
if start is None or ende is None or grob is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
punkte = await verlauf_lesen(k.hass, gnss_sensor, start, ende)
|
# Etwas vor dem Fahrtbeginn mitlesen, damit zaehlerstrecke() ihren Anker
|
||||||
fein = strecke_aus_zaehler(punkte)
|
# 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)
|
gewaehlt, genommen = strecke_waehlen(grob, fein)
|
||||||
if not genommen:
|
if not genommen:
|
||||||
if fein is not None:
|
if fein is not None:
|
||||||
|
|||||||
@@ -283,6 +283,32 @@ def route_aus_verlauf(
|
|||||||
GNSS_TOLERANZ_KM = 1.0
|
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(
|
def strecke_waehlen(
|
||||||
grob: float | None, fein: float | None
|
grob: float | None, fein: float | None
|
||||||
) -> tuple[float | None, bool]:
|
) -> tuple[float | None, bool]:
|
||||||
|
|||||||
@@ -70,7 +70,10 @@ class Zuordnung:
|
|||||||
die Oberfläche bereits angewendete Overrides und eingebaute
|
die Oberfläche bereits angewendete Overrides und eingebaute
|
||||||
Standardwerte einheitlich."""
|
Standardwerte einheitlich."""
|
||||||
werte = {feld["key"]: getattr(self.werte, feld["key"], None) for feld in FELDER}
|
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"]
|
__all__ = ["Zuordnung", "einstellungen"]
|
||||||
|
|||||||
Reference in New Issue
Block a user