Verschlafene Kilometer am Fahrtanfang zurueckholen
Der Dongle wacht nach dem Motorstart erst nach 28 s bis 19,5 min auf; was in der Zeit gefahren wird, zeichnet er nicht auf. Die Kilometer holt das Screening jetzt aus dem Endkilometerstand der Vorgaengerin zurueck und haelt in luecke_km fest, wie viel davon betroffen ist. Keine Sperre der GNSS-Verfeinerung, sondern eine Zerlegung: verschlafener Anfang in ganzen Kilometern, aufgezeichneter Rest metergenau. Sonst waere die feine Zahl systematisch zu klein und wuerde bei kleinen Luecken die Korrektur stillschweigend wieder kassieren. Zwei Schutzgitter, beide am Bestand gelernt: LUECKE_MAX_KM = 50 gegen Differenzen, die keine Weckverzoegerung mehr sein koennen, und ein Rueckfall, wenn _vollstaendig() die Werte daraufhin verwerfen wuerde. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -47,6 +47,7 @@ from .verlauf import (
|
||||
verbrauch_aus_literstaenden,
|
||||
verlauf_lesen,
|
||||
wert_ab,
|
||||
aufzeichnungsluecke_km,
|
||||
wert_bei,
|
||||
)
|
||||
|
||||
@@ -189,6 +190,11 @@ async def durchfuehren(k: Koordinator) -> None:
|
||||
)
|
||||
fahrt["avg_speed_kmh"] = schnitt
|
||||
|
||||
# Vor der Verfeinerung: eine Fahrt mit Aufzeichnungsluecke bekommt hier
|
||||
# ihren richtigen Startkilometerstand, und die Verfeinerung darf sie
|
||||
# danach nicht mehr anfassen.
|
||||
await _aufzeichnungsluecken_schliessen(k, fahrten)
|
||||
|
||||
# Erst nach den Kilometerstaenden: die Verfeinerung braucht die grobe
|
||||
# Strecke als Anker und laeuft nur auf Fahrten, die sie schon haben.
|
||||
gnss_sensor = k.zuordnung.werte.GNSS_KM_SENSOR
|
||||
@@ -338,6 +344,22 @@ async def _gnss_verfeinern(k: Koordinator, gnss_sensor: str, fahrt: dict) -> Non
|
||||
k.hass, gnss_sensor, start - SUCHFENSTER, ende, k.zuordnung.werte.MELDEZEIT_SENSOR
|
||||
)
|
||||
fein = zaehlerstrecke(punkte, start, ende)
|
||||
|
||||
# Bei einer Aufzeichnungsluecke hat der GNSS-Zaehler nur den Teil gesehen,
|
||||
# in dem das Geraet wach war - seine Zahl waere allein also systematisch zu
|
||||
# klein, und bei einer kleinen Luecke laege sie sogar noch innerhalb von
|
||||
# GNSS_TOLERANZ_KM und kassierte die gerade erst geholte Strecke wieder.
|
||||
#
|
||||
# Deshalb keine Sperre, sondern eine Zerlegung: der verschlafene Anfang in
|
||||
# ganzen Kilometern aus dem Tacho, der aufgezeichnete Rest metergenau aus
|
||||
# dem Zaehler. Die Unschaerfe bleibt damit im verschlafenen Stueck statt in
|
||||
# der ganzen Fahrt (Eigentuemer am 06.09.2026: "das macht die gesamte
|
||||
# Nutzbarkeit des Fahrtenbuchs sehr grob"). Die Summe wird unten wie sonst
|
||||
# auch gegen den Tacho geprueft - passt sie nicht, gilt weiter der Tacho.
|
||||
luecke = fahrt.get("luecke_km")
|
||||
if fein is not None and luecke:
|
||||
fein = round(fein + luecke, 3)
|
||||
|
||||
gewaehlt, genommen = strecke_waehlen(grob, fein)
|
||||
if not genommen:
|
||||
if fein is not None:
|
||||
@@ -352,8 +374,9 @@ async def _gnss_verfeinern(k: Koordinator, gnss_sensor: str, fahrt: dict) -> Non
|
||||
await k.ablage.fahrt_aktualisieren(fahrt["trip_id"], aenderungen)
|
||||
fahrt.update(aenderungen)
|
||||
_LOGGER.info(
|
||||
"Fahrt %s: Strecke auf %s km verfeinert (Kilometerstand sagte %s km)",
|
||||
"Fahrt %s: Strecke auf %s km verfeinert (Kilometerstand sagte %s km)%s",
|
||||
fahrt.get("trip_id"), gewaehlt, grob,
|
||||
f", davon {luecke} km unaufgezeichnet" if luecke else "",
|
||||
)
|
||||
|
||||
|
||||
@@ -509,6 +532,108 @@ async def _vmax_screenen(k: Koordinator, tempo_sensor: str, fahrt: dict) -> None
|
||||
fahrt["vmax_kmh"] = vmax
|
||||
|
||||
|
||||
# Wie viele Kilometer eine Aufzeichnungsluecke hoechstens haben darf, um noch
|
||||
# als solche zu gelten.
|
||||
#
|
||||
# Die Luecke entsteht, waehrend das Geraet nach dem Motorstart noch schlaeft.
|
||||
# Gemessen ueber alle Aufwachvorgaenge vom 18.08. bis 05.09.2026 dauert das
|
||||
# zwischen 28 s und 1170 s; die laengste gemessene Verzoegerung sind also
|
||||
# 19,5 Minuten, bei Autobahntempo rund 42 km. 50 km lassen das durch und
|
||||
# sperren alles, was keine Weckverzoegerung mehr sein kann.
|
||||
#
|
||||
# Ohne diese Grenze griff die Regel am 06.09.2026 sofort daneben: sie fand
|
||||
# 1078 km zwischen zwei Fahrten vom 27.08. (dort fehlen schlicht Fahrten in der
|
||||
# Liste) und 187831 km am 29.08. (dort sprang der Kilometerstand zwischen den
|
||||
# beiden Zahlenbaendern des Geraets, siehe Abschnitt zum Fahrzeugwechsel in
|
||||
# _vollstaendig). Beide Male ist die Differenz echt, aber sie gehoert nicht an
|
||||
# den Anfang der naechsten Fahrt.
|
||||
LUECKE_MAX_KM = 50.0
|
||||
|
||||
|
||||
async def _aufzeichnungsluecken_schliessen(
|
||||
k: Koordinator, fahrten: list[dict]
|
||||
) -> None:
|
||||
"""Holt die Kilometer zurueck, die das Geraet am Anfang einer Fahrt
|
||||
verschlafen hat.
|
||||
|
||||
Das Screening nimmt als Startkilometerstand den zuletzt VOR dem
|
||||
Fahrtbeginn gemeldeten Wert (wert_bei). Beginnt die Fahrt aber erst in dem
|
||||
Moment, in dem sich das Geraet zurueckmeldet, ist dieser Wert schon der
|
||||
Stand NACH den verschlafenen Kilometern - am 05.09.2026 stand dort 21353
|
||||
statt 21347, und die Fahrt haette 1 km statt 7 km bekommen.
|
||||
|
||||
Der ehrliche Anker ist der Endkilometerstand der Vorgaengerin: dazwischen
|
||||
stand das Fahrzeug. Die Strecke stimmt danach auf ganze Kilometer genau -
|
||||
mehr gibt der CAN-Tacho nicht her, und die Route der verschlafenen Minuten
|
||||
ist unwiederbringlich weg. luecke_km haelt fest, wie viel davon betroffen
|
||||
ist, damit die Oberflaeche es zeigen kann und die GNSS-Verfeinerung die
|
||||
Finger davon laesst."""
|
||||
sortiert = sorted(fahrten, key=lambda f: f.get("ts_start") or "")
|
||||
for vorherige, fahrt in zip(sortiert, sortiert[1:]):
|
||||
fehlt = aufzeichnungsluecke_km(
|
||||
vorherige.get("odo_end"), fahrt.get("odo_start")
|
||||
)
|
||||
if fehlt is None:
|
||||
continue
|
||||
if fehlt > LUECKE_MAX_KM:
|
||||
_LOGGER.info(
|
||||
"Fahrt %s: %s km Unterschied zur Vorgaengerin - zu viel fuer "
|
||||
"eine Weckverzoegerung (Grenze %s km), bleibt unangetastet",
|
||||
fahrt.get("trip_id"), fehlt, LUECKE_MAX_KM,
|
||||
)
|
||||
continue
|
||||
# Der Anker ist nur so gut wie das ENDE der Vorgaengerin. Wurde die von
|
||||
# Hand beendet, steht ihr Endkilometerstand auf einem vorlaeufigen Ende
|
||||
# - dann koennen die Kilometer auch ihr gehoeren und nur falsch
|
||||
# zugeschlagen sein. Die Gesamtleistung stimmt in beiden Faellen, die
|
||||
# Aufteilung zwischen zwei Fahrten nicht (Einwand des Eigentuemers,
|
||||
# 06.09.2026). Deshalb wird es vermerkt statt still verrechnet.
|
||||
alt_start = fahrt.get("odo_start")
|
||||
alt_quelle = fahrt.get("km_quelle")
|
||||
fahrt.update({
|
||||
"luecke_km": fehlt,
|
||||
"luecke_unsicher": bool(vorherige.get("ende_vorlaeufig")),
|
||||
"odo_start": vorherige["odo_end"],
|
||||
"km_quelle": "odometer",
|
||||
})
|
||||
aenderungen = _vollstaendig(
|
||||
{
|
||||
"luecke_km": fehlt,
|
||||
"luecke_unsicher": fahrt["luecke_unsicher"],
|
||||
"odo_start": fahrt["odo_start"],
|
||||
"odo_end": fahrt.get("odo_end"),
|
||||
},
|
||||
fahrt,
|
||||
)
|
||||
# Letzte Sicherung: _vollstaendig() verwirft beide Kilometerstaende,
|
||||
# wenn die Strecke daraus unplausibel wird (Rueckwaertssprung oder
|
||||
# unmoegliche Durchschnittsgeschwindigkeit). Passiert das durch UNSERE
|
||||
# Korrektur, war die Annahme falsch, dass die beiden Fahrten wirklich
|
||||
# aufeinanderfolgen - dann darf sie den Bestand nicht schlechter
|
||||
# zuruecklassen, als sie ihn vorgefunden hat. Am 06.09.2026 genau so
|
||||
# eingetreten, an einer Fahrt aus der Testphase vom 27.08.
|
||||
if aenderungen.get("odo_start") is None:
|
||||
fahrt["odo_start"] = alt_start
|
||||
fahrt["km_quelle"] = alt_quelle
|
||||
fahrt["luecke_km"] = None
|
||||
fahrt["luecke_unsicher"] = False
|
||||
_LOGGER.info(
|
||||
"Fahrt %s: %s km Unterschied zur Vorgaengerin ergaeben eine "
|
||||
"unplausible Strecke - Korrektur verworfen, Bestand bleibt",
|
||||
fahrt.get("trip_id"), fehlt,
|
||||
)
|
||||
continue
|
||||
|
||||
await k.ablage.fahrt_aktualisieren(fahrt["trip_id"], aenderungen)
|
||||
fahrt.update(aenderungen)
|
||||
_LOGGER.info(
|
||||
"Fahrt %s: %s km am Anfang nicht aufgezeichnet - Startkilometer "
|
||||
"von der Vorgaengerin uebernommen (%s statt %s)",
|
||||
fahrt.get("trip_id"), fehlt, vorherige["odo_end"],
|
||||
round(vorherige["odo_end"] + fehlt, 1),
|
||||
)
|
||||
|
||||
|
||||
async def _lueckenlos_verketten(k: Koordinator) -> None:
|
||||
"""Fahrt-Ende(N) == Fahrt-Start(N+1), solange dazwischen nicht gefahren
|
||||
wurde.
|
||||
|
||||
Reference in New Issue
Block a user