Verbrauch: Tankstand ab Fahrtbeginn statt davor (2026.9.1.24)

DER VERBRAUCH WAR SYSTEMATISCH ZU HOCH

Der Startwert kam aus wert_bei(), also dem letzten Literstand VOR der Fahrt -
und das ist im Stand der Wert vom Ende der VORIGEN Fahrt. An der Fahrt
Eichstaett - Adelschlag nachgemessen: 36,4 l um 13:52 (Ende der vorigen Fahrt),
36,2 l um 16:20:49 (erster Datensatz der neuen). In der Standzeit dazwischen
ist niemand gefahren; diese 0,2 l Setzung wurden der neuen Fahrt zugerechnet.

  alt: 36,4 - 35,6 = 0,8 l auf 6,896 km -> 11,6 l/100 km
  neu: 36,2 - 35,6 = 0,6 l auf 6,896 km ->  8,7 l/100 km

Neu wert_ab() in verlauf.py: der erste Wert AB dem Zeitpunkt. Fuer einen
ZAEHLER ist "zuletzt davor" richtig (der Kilometerstand aendert sich im Stand
nicht), fuer einen gemessenen FUELLSTAND nicht - der driftet. Das Fahrtende
bleibt bei wert_bei(). Livepfad und Rueckblick benutzen beide die neue
Funktion.

Bereits gerechnete Fahrten behalten ihren Wert: _verbrauch_screenen() fuellt
nur fehlende. Die Neuberechnung kommt mit dem Korrekturwert aus den
Tankvorgaengen, den der Eigentuemer vorgeschlagen hat.

ANZEIGE

Hoechstgeschwindigkeit ohne Nachkommastelle. Statuswort raus aus der
Fahrtenliste - "vollstaendig" sagt dem Nutzer nichts, und "offen" verspricht
Daten, die bei einer laengst abgeschlossenen Fahrt nie mehr kommen; fehlt ein
Wert, steht dort ein Strich.

Strecke und Verbrauch stehen jetzt in einer Spalte: die Wertspalte wuchs mit
dem Inhalt, gemessen 86px gegen 37px, und die Zahlen standen versetzt. Feste
Mindestbreite in beiden Oberflaechen, nachgemessen alle Zeilen 92px.

Verifiziert: py_compile, Panel als Modul, tsc --noEmit sauber, 165/165 Tests,
wert_ab() gegen vier Randfaelle, im Browser nachgesehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-01 21:13:43 +02:00
parent ce287ccac2
commit 78a8017e95
11 changed files with 64 additions and 18 deletions
+1 -1
View File
@@ -213,7 +213,7 @@ export function FahrtDetail({ id, geheZu }: { id: string | undefined; geheZu: (n
<Wertzeile
label="Höchstgeschwindigkeit"
wert={
fahrt.vmax_kmh != null ? `${de(fahrt.vmax_kmh, 1)} km/h` : "liegt nicht vor"
fahrt.vmax_kmh != null ? `${de(fahrt.vmax_kmh)} km/h` : "liegt nicht vor"
}
/>
</>
+6 -4
View File
@@ -112,13 +112,15 @@ export function Fahrten({ geheZu }: { geheZu: (name: SeitenName, id?: string) =>
<Blattzeile
label={strecke(fahrt)}
labelZusatz={`${datum(fahrt.ts_start)} · ${artText(fahrt.art)}`}
wert={
streckeText(fahrt.distance_km, "offen")
}
wert={streckeText(fahrt.distance_km, "—")}
wertZusatz={
/* Kein Statuswort mehr: "vollstaendig" sagt dem Nutzer nichts,
und "offen" verspricht Daten, die bei einer laengst
abgeschlossenen Fahrt nie mehr kommen. Fehlt der Wert,
steht dort ein Strich. */
fahrt.verbrauch_l_100km != null
? `${de(fahrt.verbrauch_l_100km, 1)} l/100 km`
: fahrt.status
: "—"
}
beiKlick={() => geheZu("trip", fahrt.trip_id)}
letzte={i === monat.eintraege.length - 1}
+5
View File
@@ -1014,8 +1014,13 @@ button.dm-blatt:active {
color: var(--fg);
}
/* Feste Mindestbreite, damit Strecke und Verbrauch ueber alle Zeilen hinweg in
einer Spalte stehen. Ohne sie waechst der Kasten mit dem Inhalt - im Panel
gemessen 86px bei "11,4 l/100 km" gegen 37px bei einer Zeile ohne Werte, und
die Zahlen standen entsprechend versetzt. */
.dm-blatt__v {
flex: 0 0 auto;
min-width: 104px;
display: flex;
flex-direction: column;
gap: 3px;
@@ -1 +1 @@
{"version":"2026.9.1.23","sha256":"c3e1376f4e314b2994bc6341c423b92d251142e0abd2abe4752e1f5f1a578d13","bytes":266223,"gebaut":"2026-09-01T18:35:21Z"}
{"version":"2026.9.1.24","sha256":"1d08e5fe05d30095db34e6eaaf5f9765dad94c8e14fbc1a501cfe3e5daec572a","bytes":266230,"gebaut":"2026-09-01T19:12:38Z"}
@@ -1503,7 +1503,7 @@ function teaser() {
return `<div class="tile teaser"><span class="label">Zuletzt</span>
${t ? `<button class="leaf" style="padding-left:0" data-go="trip:${t.trip_id}">
<span class="k">Letzte Fahrt<small>${esc(t.start_address || "")} ${esc(t.end_address || "")}</small></span>
<span class="v">${streckeText(t.distance_km, "offen")}<small>${t.status}</small></span>
<span class="v">${streckeText(t.distance_km, "")}<small>${fahrtVerbrauch(t) != null ? de(fahrtVerbrauch(t), 1) + " l/100 km" : "—"}</small></span>
<svg class="chev" viewBox="0 0 6 10"><polyline points="1,1 5,5 1,9"/></svg></button>` : ""}
${f ? `<button class="leaf" style="padding-left:0;border-bottom:0" data-go="fill:${f.tank_id}">
<span class="k">Letzter Tankvorgang<small>${esc(f.station_name || "")}</small></span>
@@ -3636,7 +3636,7 @@ function vTrips() {
tm.forEach((t) => { h += swipeZeile(`trip:${t.trip_id}`, `<button class="leaf" data-go="trip:${t.trip_id}">
<span class="k">${strecke(t)}
<small>${dedat(new Date(t.ts_start))} · ${artText(t.art)}</small></span>
<span class="v">${streckeText(t.distance_km, "offen")}<small>${fahrtVerbrauch(t) != null ? de(fahrtVerbrauch(t), 1) + " l/100 km" : t.status}</small></span>
<span class="v">${streckeText(t.distance_km, "")}<small>${fahrtVerbrauch(t) != null ? de(fahrtVerbrauch(t), 1) + " l/100 km" : "—"}</small></span>
<svg class="chev" viewBox="0 0 6 10"><polyline points="1,1 5,5 1,9"/></svg></button>`); });
h += `</div></div>`;
});
@@ -3675,7 +3675,7 @@ function vTrip(id) {
<div class="row"><dt>Endkilometer</dt><dd>${t.odo_end != null ? de(t.odo_end) + " km" : "noch offen"}</dd></div>
<div class="row"><dt>Dauer</dt><dd>${dauerText(t.duration_s)}</dd></div>
<div class="row"><dt>Ø Geschwindigkeit</dt><dd>${t.avg_speed_kmh != null ? de(t.avg_speed_kmh, 1) + " km/h" : "liegt nicht vor"}</dd></div>
<div class="row"><dt>Höchstgeschwindigkeit</dt><dd>${t.vmax_kmh != null ? de(t.vmax_kmh, 1) + " km/h" : "liegt nicht vor"}</dd></div>
<div class="row"><dt>Höchstgeschwindigkeit</dt><dd>${t.vmax_kmh != null ? de(t.vmax_kmh) + " km/h" : "liegt nicht vor"}</dd></div>
<div class="row"><dt>Verbrauch</dt><dd>${fahrtVerbrauch(t) != null ? de(fahrtVerbrauch(t), 1) + " l/100 km" : "liegt nicht vor"}</dd></div>
</dl>
<button class="aktion" data-fahrtbearbeiten>Werte bearbeiten</button>
@@ -461,7 +461,12 @@ button.sicherheitszeile:active { background: var(--tile-2); }
}
.leaf .k { font-size: 14px; color: var(--fg); display: flex; flex-direction: column; gap: 3px; line-height: 1.35; }
.leaf .k small { font-size: 12.5px; color: var(--fg3); }
.leaf .v { font-size: 13px; color: var(--fg2); text-align: right; display: flex; flex-direction: column; gap: 3px; flex: 0 0 auto; }
/* Feste Mindestbreite, damit Strecke und Verbrauch ueber alle Zeilen hinweg
in einer Spalte stehen. Ohne sie waechst der Kasten mit dem Inhalt: gemessen
86px bei "11,4 l/100 km" gegen 37px bei einer Zeile ohne Werte, und die
Zahlen standen entsprechend versetzt. 92px traegt die laengste Form
("11,4 l/100 km") ohne Umbruch. */
.leaf .v { font-size: 13px; color: var(--fg2); text-align: right; display: flex; flex-direction: column; gap: 3px; flex: 0 0 auto; min-width: 92px; }
.leaf .v small { font-size: 12.5px; color: var(--fg3); }
/* .teaser (die "Zuletzt"-Kachel auf der Übersicht, teaser()): genau zwei
Zeilen mit sehr unterschiedlich breiten Werten ("offen" vs. "51,59 l") -
@@ -57,6 +57,7 @@ from .verlauf import (
verbrauch_aus_literstaenden,
verlauf_lesen,
Verlaufspunkt,
wert_ab,
wert_bei,
wert_in_naehe,
zahl,
@@ -256,7 +257,9 @@ async def _fahrten_importieren(k: Koordinator, verlaeufe: dict) -> dict:
"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
# Erster Stand AB Fahrtbeginn, siehe wert_ab() - beide Wege
# muessen dieselbe Fahrt gleich bewerten.
wert_ab(tank_liter_verlauf, f_start), wert_bei(tank_liter_verlauf, f_ende), distanz
),
"status": "vollständig" if distanz is not None else "offen",
})
@@ -1,7 +1,7 @@
{
"domain": "audi_dashboard",
"name": "Audi Dashboard",
"version": "2026.9.1.23",
"version": "2026.9.1.24",
"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"],
@@ -41,6 +41,7 @@ from .verlauf import (
route_aus_verlauf,
verbrauch_aus_literstaenden,
verlauf_lesen,
wert_ab,
wert_bei,
)
@@ -338,13 +339,12 @@ async def _verbrauch_screenen(
if not verlauf:
return
# wert_bei() aus demselben Grund wie beim Kilometerstand (siehe Modulkopf):
# der Tankstand zu Fahrtbeginn ist der zuletzt DAVOR gemeldete, nicht der
# zeitlich nächstgelegene - der läge womöglich schon in der Fahrt und hätte
# einen Teil des Verbrauchs vorweggenommen. historienimport.py liest ihn
# seit jeher so.
# Anders als beim Kilometerstand: fuer den Tankstand gilt der erste Wert AB
# dem Fahrtbeginn, nicht der letzte davor. Ein Fuellstand ist keine
# Zaehlung, er driftet im Stand - siehe wert_ab() in verlauf.py fuer die
# Messung, die das gezeigt hat. Das Ende bleibt bei wert_bei().
verbrauch = verbrauch_aus_literstaenden(
wert_bei(verlauf, start), wert_bei(verlauf, ende), fahrt.get("distance_km")
wert_ab(verlauf, start), wert_bei(verlauf, ende), fahrt.get("distance_km")
)
if verbrauch is not None:
await k.ablage.fahrt_aktualisieren(fahrt["trip_id"], {"verbrauch_l_100km": verbrauch})
@@ -244,6 +244,37 @@ def wert_bei(
return davor
def wert_ab(
verlauf: list[Verlaufspunkt], zeitpunkt: datetime.datetime
) -> float | None:
"""Der erste ab `zeitpunkt` gemeldete Zahlenwert, sonst der letzte davor.
Gegenstueck zu wert_bei(). Fuer einen ZAEHLER ist -zuletzt davor- richtig
(der Kilometerstand aendert sich im Stand nicht), fuer einen gemessenen
FUELLSTAND nicht: der driftet.
Am 01.09.2026 an der Fahrt Eichstaett - Adelschlag nachgemessen. Der
Tankstand stand am Ende der vorigen Fahrt (13:52) auf 36,4 l, beim ersten
Datensatz der neuen Fahrt (16:20:49) auf 36,2 l - in der Standzeit dazwischen
ist niemand gefahren. Diese 0,2 l Setzung wurden der neuen Fahrt zugerechnet:
0,8 l statt 0,6 l auf 6,9 km, also **11,4 statt 8,7 l/100 km**.
Das Fahrtende bleibt bei wert_bei(): dort ist die Fahrt vorbei, und der
letzte waehrend der Fahrt gemeldete Stand ist der richtige - der naechste
kaeme erst Stunden spaeter und traege dieselbe Setzung wieder herein."""
danach = None
for ts, wert in verlauf:
gezahlt = zahl(wert)
if gezahlt is None:
continue
if ts >= zeitpunkt:
danach = gezahlt
break
if danach is not None:
return danach
return wert_bei(verlauf, zeitpunkt)
def route_aus_verlauf(
lat_verlauf: list[Verlaufspunkt],
lon_verlauf: list[Verlaufspunkt],