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 <[email protected]>
This commit is contained in:
2026-09-01 21:13:43 +02:00
co-authored by Claude Opus 5
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 <Wertzeile
label="Höchstgeschwindigkeit" label="Höchstgeschwindigkeit"
wert={ 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 <Blattzeile
label={strecke(fahrt)} label={strecke(fahrt)}
labelZusatz={`${datum(fahrt.ts_start)} · ${artText(fahrt.art)}`} labelZusatz={`${datum(fahrt.ts_start)} · ${artText(fahrt.art)}`}
wert={ wert={streckeText(fahrt.distance_km, "—")}
streckeText(fahrt.distance_km, "offen")
}
wertZusatz={ 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 fahrt.verbrauch_l_100km != null
? `${de(fahrt.verbrauch_l_100km, 1)} l/100 km` ? `${de(fahrt.verbrauch_l_100km, 1)} l/100 km`
: fahrt.status : "—"
} }
beiKlick={() => geheZu("trip", fahrt.trip_id)} beiKlick={() => geheZu("trip", fahrt.trip_id)}
letzte={i === monat.eintraege.length - 1} letzte={i === monat.eintraege.length - 1}
+5
View File
@@ -1014,8 +1014,13 @@ button.dm-blatt:active {
color: var(--fg); 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 { .dm-blatt__v {
flex: 0 0 auto; flex: 0 0 auto;
min-width: 104px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 3px; 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> return `<div class="tile teaser"><span class="label">Zuletzt</span>
${t ? `<button class="leaf" style="padding-left:0" data-go="trip:${t.trip_id}"> ${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="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>` : ""} <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}"> ${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> <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}"> tm.forEach((t) => { h += swipeZeile(`trip:${t.trip_id}`, `<button class="leaf" data-go="trip:${t.trip_id}">
<span class="k">${strecke(t)} <span class="k">${strecke(t)}
<small>${dedat(new Date(t.ts_start))} · ${artText(t.art)}</small></span> <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>`); }); <svg class="chev" viewBox="0 0 6 10"><polyline points="1,1 5,5 1,9"/></svg></button>`); });
h += `</div></div>`; 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>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>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>Ø 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> <div class="row"><dt>Verbrauch</dt><dd>${fahrtVerbrauch(t) != null ? de(fahrtVerbrauch(t), 1) + " l/100 km" : "liegt nicht vor"}</dd></div>
</dl> </dl>
<button class="aktion" data-fahrtbearbeiten>Werte bearbeiten</button> <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 { 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 .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); } .leaf .v small { font-size: 12.5px; color: var(--fg3); }
/* .teaser (die "Zuletzt"-Kachel auf der Übersicht, teaser()): genau zwei /* .teaser (die "Zuletzt"-Kachel auf der Übersicht, teaser()): genau zwei
Zeilen mit sehr unterschiedlich breiten Werten ("offen" vs. "51,59 l") - Zeilen mit sehr unterschiedlich breiten Werten ("offen" vs. "51,59 l") -
@@ -57,6 +57,7 @@ from .verlauf import (
verbrauch_aus_literstaenden, verbrauch_aus_literstaenden,
verlauf_lesen, verlauf_lesen,
Verlaufspunkt, Verlaufspunkt,
wert_ab,
wert_bei, wert_bei,
wert_in_naehe, wert_in_naehe,
zahl, zahl,
@@ -256,7 +257,9 @@ async def _fahrten_importieren(k: Koordinator, verlaeufe: dict) -> dict:
"end_lon": wert_bei(lon_verlauf, f_ende), "end_lon": wert_bei(lon_verlauf, f_ende),
"route": route_aus_verlauf(lat_verlauf, lon_verlauf, f_start, f_ende), "route": route_aus_verlauf(lat_verlauf, lon_verlauf, f_start, f_ende),
"verbrauch_l_100km": verbrauch_aus_literstaenden( "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", "status": "vollständig" if distanz is not None else "offen",
}) })
@@ -1,7 +1,7 @@
{ {
"domain": "audi_dashboard", "domain": "audi_dashboard",
"name": "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", "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"],
@@ -41,6 +41,7 @@ from .verlauf import (
route_aus_verlauf, route_aus_verlauf,
verbrauch_aus_literstaenden, verbrauch_aus_literstaenden,
verlauf_lesen, verlauf_lesen,
wert_ab,
wert_bei, wert_bei,
) )
@@ -338,13 +339,12 @@ async def _verbrauch_screenen(
if not verlauf: if not verlauf:
return return
# wert_bei() aus demselben Grund wie beim Kilometerstand (siehe Modulkopf): # Anders als beim Kilometerstand: fuer den Tankstand gilt der erste Wert AB
# der Tankstand zu Fahrtbeginn ist der zuletzt DAVOR gemeldete, nicht der # dem Fahrtbeginn, nicht der letzte davor. Ein Fuellstand ist keine
# zeitlich nächstgelegene - der läge womöglich schon in der Fahrt und hätte # Zaehlung, er driftet im Stand - siehe wert_ab() in verlauf.py fuer die
# einen Teil des Verbrauchs vorweggenommen. historienimport.py liest ihn # Messung, die das gezeigt hat. Das Ende bleibt bei wert_bei().
# seit jeher so.
verbrauch = verbrauch_aus_literstaenden( 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: if verbrauch is not None:
await k.ablage.fahrt_aktualisieren(fahrt["trip_id"], {"verbrauch_l_100km": verbrauch}) await k.ablage.fahrt_aktualisieren(fahrt["trip_id"], {"verbrauch_l_100km": verbrauch})
@@ -244,6 +244,37 @@ def wert_bei(
return davor 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( def route_aus_verlauf(
lat_verlauf: list[Verlaufspunkt], lat_verlauf: list[Verlaufspunkt],
lon_verlauf: list[Verlaufspunkt], lon_verlauf: list[Verlaufspunkt],