Ortsnamen aus dem Backend, Verbrauchsfaktor aus dem Tankbeleg, Radzaehler ueber Fahrten
Ortsauflösung serverseitig (geokodierung.py): der Ort steht jetzt in der Fahrt statt im Zwischenspeicher jedes Geräts. Beide Oberflächen fragen nichts mehr ab. Verbrauchskorrektur (verbrauchskorrektur.py): Faktor = Beleg-Liter / Summe der Einzelfahrten, voll zu voll. Statt einer Schranke am Faktor wird der Nenner geprüft - decken die erkannten Fahrten 90-110 % der Tacho-Spanne ab? Ausgelöst beim Schreiben eines Tankvorgangs, nicht erst bei der nächsten Fahrt. Radzähler: summiert die Fahrten des montierten Satzes statt Tacho-Deltas. Der alte Weg hatte Fahrzeugwechsel als gefahrene Strecke verbucht (375.962 km bei einem Tacho von 61.823). Null heißt unbekannt: ein Kilometerstand von 0 oder ein leeres Feld werden zu None normalisiert, statt an sechs Lesestellen als "Tachostand null" zu gelten. Momentanwerte der Sensoren gelten nur für einen Tankvorgang von jetzt. Oberfläche, beide Codebasen: Wertespalte der Fahrtenliste ausgerichtet, Ort -> Ort in "Zuletzt", "Räder" statt "Reifen", "Termin vereinbart" entfernt, Markenlogo 20 % größer, Übersicht-Symbol auf volle Rasterbreite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
# AGENTS.md — Project state, review findings, open items, and working rules
|
||||
|
||||
**Last updated: 2026-09-01** (Einzelfahrt: Orte aus den Koordinaten, Kleingedrucktes weg, „Min.",
|
||||
**Last updated: 2026-09-02** (Null heisst unbekannt, Momentanwerte nur fuer
|
||||
"jetzt", Uebersicht-Symbol vergroessert, `2026.9.2.3`, Abschnitt BN.
|
||||
Davor: Verbrauchsfaktor aus dem Tankbeleg, `2026.9.1.32`,
|
||||
Abschnitt BM. Davor: Reifenzaehler ohne Fahrzeugwechsel, „Raeder“
|
||||
statt „Reifen“, `2026.9.1.29`, Abschnitt BL. Davor: Ortsnamen aus dem Backend, Verbrauchseichung an
|
||||
den Betankungen, Wertespalte ausgerichtet, `2026.9.1.27`, Abschnitt BK.
|
||||
Davor: Einzelfahrt: Orte aus den Koordinaten, Kleingedrucktes weg, „Min.",
|
||||
`2026.9.1.23`, Abschnitt BJ - dazu Orte in der Fahrtenliste ohne Netzabruf.
|
||||
Davor: Strecken gestaffelt, Abschnitt BI. Davor: Audit ueber Panel, App und Backend, Abschnitt BH. Davor: Setup mit gruener Bestaetigung und Erwartungswert je
|
||||
Listenposition, Abschnitt BG. Davor: Rueckblick ohne Nullfahrten, Streckenwahl gemeinsam in `verlauf.py`,
|
||||
@@ -7097,6 +7103,433 @@ Geprüft: 7 Fälle gegen den Schnitt (mit/ohne Straße, benannter Platz, ohne PL
|
||||
`null`). Im Browser nachgesehen: `„Eichstätt → Adelschlag"` in der Liste, volle Anschrift auf der
|
||||
Einzelfahrt.
|
||||
|
||||
## BK. Der Ort kommt mit der Fahrt, und der Verbrauch wird an den Betankungen geeicht (2026.9.1.25-.27)
|
||||
|
||||
### Die Wertespalte der Fahrtenliste stand nicht in einer Linie
|
||||
|
||||
Gemeldet: km und l/100 km fluchten nicht, und bei langen Ortsnamen wird der
|
||||
Abstand zu eng. Gemessen: die rechte Kante der Wertespalte lag je nach Zeile
|
||||
bei 549px oder 520px, obwohl beide Zeilen gleich breit sind und an derselben
|
||||
Stelle beginnen.
|
||||
|
||||
Ursache war nicht die Breite, sondern die **Anzahl der Kinder**: eine `.leaf`
|
||||
hat drei (Label, Werte, Pfeil), und `justify-content: space-between` verteilt
|
||||
freien Raum ZWISCHEN allen dreien. Die Wertespalte stand damit in der Mitte,
|
||||
ihre Lage abhaengig von der Laenge des Labels. Mit `flex: 1` am Label bleibt
|
||||
kein freier Raum uebrig, Werte und Pfeil sitzen buendig rechts.
|
||||
|
||||
`min-width: 0` gehoert dazu, sonst darf ein Flex-Kind nicht unter seine
|
||||
Inhaltsbreite schrumpfen und `text-overflow` greift nie.
|
||||
|
||||
Nachgewiesen: alle sechs Zeilen der Liste teilen sich jetzt 629/721 statt
|
||||
zweier verschiedener Kanten.
|
||||
|
||||
**Merkposten:** eine Zeile, deren Werte "mal so, mal so" stehen, ist fast nie
|
||||
ein Breitenproblem - erst die Kinder zaehlen, dann messen.
|
||||
|
||||
### Ortsnamen loest jetzt das Backend auf
|
||||
|
||||
Bis hierher tat es jede Oberflaeche selbst und legte das Ergebnis in ihren
|
||||
eigenen Zwischenspeicher. Drei Folgen, alle unnoetig: jedes Geraet fragte
|
||||
dieselben Koordinaten erneut ab, ein frisch eingerichtetes Telefon zeigte fuer
|
||||
alte Fahrten wieder "unbekannt", und ein geleerter Browserspeicher warf alles
|
||||
weg.
|
||||
|
||||
Neu `geokodierung.py` plus ein Screening-Schritt `_orte_screenen()`. Der Ort
|
||||
steht jetzt in der Fahrt: `start_address`/`end_address` (volle Anschrift) und
|
||||
`start_stadt`/`end_stadt` (nur der Ortsname, fuer die Liste).
|
||||
|
||||
Nominatims Nutzungsbedingungen bestimmen fast jede Entscheidung darin: eine
|
||||
Anfrage je Sekunde (`ABSTAND_S = 1.1`), hoechstens acht je Durchlauf
|
||||
(`PRO_DURCHLAUF`, sonst waere ein Jahresrueckblick genau der Vorratsabruf, um
|
||||
dessen Unterlassung gebeten wird), ein identifizierbarer User-Agent, und jedes
|
||||
Ergebnis wird in `orte.json` gemerkt statt erneut abgefragt.
|
||||
|
||||
**Zwei Sperren gegen das Ueberschreiben von Hand Eingetragenem**, absichtlich
|
||||
beide: der Schritt fuellt nur ein LEERES Feld, und `fahrt_aktualisieren()`
|
||||
laesst ohnehin jedes Feld in `edited_fields` unberuehrt. Der Ortsname wird
|
||||
dagegen auch neben einer handischen Anschrift gesetzt - er beschreibt die
|
||||
Koordinate, nicht die Eingabe. In der Liste sticht die handische Anschrift
|
||||
trotzdem: beide Oberflaechen pruefen dafuer `edited_fields`.
|
||||
|
||||
Ein Netz- oder Serverfehler wird NICHT gemerkt - sonst stuende eine
|
||||
voruebergehend gedrosselte Antwort fuer immer als "kein Ort hier". Ein echtes
|
||||
leeres Ergebnis dagegen schon.
|
||||
|
||||
Beide Oberflaechen fragen jetzt gar nichts mehr ab; `ortAusCache()`,
|
||||
`stadtAusCache()`, `stadtAusAnschrift()` und `fahrtOrteNachtragen()` sind
|
||||
ersatzlos entfallen.
|
||||
|
||||
Nachgewiesen: nach zwei Screening-Laeufen tragen 12 von 14 Fahrten einen Ort,
|
||||
die beiden anderen haben gar keine Koordinaten. Die Liste zeigt
|
||||
"Eichstaett -> Adelschlag"; damit ist auch der gemeldete Unterschied zwischen
|
||||
Panel ("Adelschlag") und App ("Adelschlag -> Eichstaett") weg - er kam daher,
|
||||
dass die beiden verschiedene Zwischenspeicher hatten.
|
||||
|
||||
### Verbrauch: Rohwert, Faktor, Anzeigewert
|
||||
|
||||
Der Anzeigewert einer bereits gespeicherten Fahrt wurde nie neu gerechnet -
|
||||
die `wert_ab()`-Korrektur vom selben Tag erreichte die Altfahrten also nicht.
|
||||
Das Auswahlkriterium haengt jetzt am ROHwert, nicht am Anzeigewert: die Fahrt
|
||||
vom 01.09. ist damit von 11,4 auf **8,7 l/100 km** gewandert, genau auf den
|
||||
Wert, den die Messung vorhergesagt hatte.
|
||||
|
||||
Drei Felder statt einem: `verbrauch_roh_l_100km` (die reine Messung, damit die
|
||||
Rechnung wiederholbar bleibt), `verbrauch_faktor`, und `verbrauch_l_100km` als
|
||||
Produkt - der Wert, den die Oberflaechen anzeigen. Die brauchten deshalb keine
|
||||
Aenderung.
|
||||
|
||||
### Die Eichung an zwei Betankungen (verbrauchskorrektur.py)
|
||||
|
||||
Vorschlag des Eigentuemers, und er ist besser als meiner:
|
||||
|
||||
Faktor = (Tankstand nach A - Tankstand vor B) / Summe der Fahrtverbraeuche
|
||||
|
||||
**Dafuer braucht es keinen Tankbeleg.** Zaehler und Nenner sind mit demselben
|
||||
Geber gemessen; ein Massstabsfehler stuende in beiden und kuerzt sich heraus.
|
||||
Damit taugt auch eine Teilbetankung - die Voll-zu-voll-Bedingung, die mein
|
||||
erster Ansatz ("Verbrauch zwischen zwei Betankungen") gebraucht haette,
|
||||
entfaellt.
|
||||
|
||||
Ebenso entfaellt mein Nichtlinearitaets-Vorbehalt: `can_fuel_volume` kommt
|
||||
schon als Liter, die Kennlinie des Schwimmers hat das Steuergeraet angewandt
|
||||
(Hinweis des Eigentuemers). Was bleibt, ist die **Daempfung** - der gemeldete
|
||||
Stand hinkt nach, auf sieben Kilometern anteilig weit mehr als auf
|
||||
dreihundert. Genau die faengt der Faktor mit ab, weil sie Kraftstoff nur
|
||||
zwischen Fahrten verschiebt und die Intervallsumme stimmt.
|
||||
|
||||
Der Faktor enthaelt auch Verbrauch, der zu KEINER Fahrt gehoert (Standheizung,
|
||||
nicht erkannte Fahrt, Rangieren unter 0,1 km). Entscheidung des Eigentuemers:
|
||||
auf die Fahrten verteilen, damit deren Summe dem entspricht, was im Tank
|
||||
fehlt - eine einzelne Fahrt traegt dafuer einen Anteil, den sie nicht selbst
|
||||
verbraucht hat.
|
||||
|
||||
**Schranke 0,7-1,5**, ausserhalb wird verworfen und protokolliert. Sie hat auf
|
||||
echten Daten sofort gegriffen: das Intervall 15.08. bis 27.08. ergab **5,616**
|
||||
- 45 l im Tank verbraucht, aber nur 8 l aus zwei Fahrten gerechnet. Das ist
|
||||
kein Rechenfehler, sondern der Befund, dass in diesen zwoelf Tagen fast keine
|
||||
Fahrt erkannt wurde (die Umsteckphase des Dongles). Ohne die Schranke haetten
|
||||
beide Fahrten das Fuenffache angezeigt.
|
||||
|
||||
Gerechnet werden nur Intervalle, in denen noch eine Fahrt ohne Faktor steht -
|
||||
das Screening laeuft bei jeder Aenderung des Kilometerstands, und jedes
|
||||
Intervall kostet eine eigene Abfrage des Tankverlaufs.
|
||||
|
||||
Eine Fahrt, die ueber eine Betankung hinweggeht, bekommt keinen Faktor: sie
|
||||
laesst sich keinem der beiden Intervalle zuordnen.
|
||||
|
||||
**Noch offen:** in den vorhandenen Daten liefert kein Intervall einen
|
||||
brauchbaren Faktor. Der Mechanismus ist nachgewiesen, seine Eichung nicht -
|
||||
dafuer braucht es ein Intervall mit vollstaendig erkannten Fahrten.
|
||||
|
||||
## BL. Der Radzaehler summiert Fahrten statt Tacho-Deltas; Raeder statt Reifen (2026.9.1.29)
|
||||
|
||||
### 375.962 km auf Raedern eines Autos mit 61.823 km
|
||||
|
||||
Gemeldet als "der Zaehler rechnet die Gesamtkilometer des Fahrzeugs auf die
|
||||
Raeder". Nachgesehen: Sommerraeder 375.962 km, Tacho 61.823.
|
||||
|
||||
`fortschreiben()` zaehlte jedes POSITIVE Delta zum zuletzt verarbeiteten
|
||||
Kilometerstand. Ein Sprung nach unten fiel schon immer heraus - beim Umstecken
|
||||
des Dongles zwischen zwei Fahrzeugen gibt es aber beide Richtungen, und
|
||||
61.823 -> 209.177 ist ein positives Delta von 147.354 km.
|
||||
|
||||
**Erster Ansatz, vom Eigentuemer verworfen:** ein Sprungschutz am Delta, der
|
||||
nicht die Groesse pruefte, sondern die daraus folgende Geschwindigkeit
|
||||
(`UNPLAUSIBLE_KMH`, wie bei den Fahrten) - noetig, weil der Modulkopf
|
||||
verspricht, dass Kilometer aus einem HA-Ausfall noch mitzaehlen. Dafuer haette
|
||||
das Profil den Zeitpunkt des Referenzstands mitfuehren muessen. Gebaut, an
|
||||
sechs Faellen belegt, dann auf die Rueckfrage "warum so kompliziert? einfach
|
||||
die Trip distance addieren?" wieder entfernt.
|
||||
|
||||
### Der Radzaehler summiert jetzt Fahrten
|
||||
|
||||
Die Rueckfrage war berechtigt, und die Messung stuetzt sie. Summe der
|
||||
Fahrtstrecken gegen die Tacho-Spanne im selben Zeitraum, am echten Bestand:
|
||||
|
||||
| Tacho-Spanne | Summe der Fahrten | erfasst |
|
||||
|---|---|---|
|
||||
| 61.816 -> 61.823 (7 km) | 6,9 km | 99 % |
|
||||
| 209.156 -> 209.281 (125 km) | 125,0 km | 100 % |
|
||||
| 21.188 -> 21.325 (137 km) | 141,0 km | 103 % |
|
||||
| 20.500 -> 20.580 (80 km) | 205,0 km | 256 % |
|
||||
|
||||
Der Ausreisser stammt aus dem Bestand von vor den Plausibilitaetspruefungen,
|
||||
nicht aus dem Verfahren. In den sauberen Bereichen trifft die Fahrtsumme den
|
||||
Tacho auf ein bis drei Prozent - und sie ist genauer, weil die Fahrten seit
|
||||
der GNSS-Verfeinerung auf 100 m messen statt auf ganze Kilometer (6,9 gegen 7).
|
||||
|
||||
Damit entfallen `referenz_odo_km`, die Delta-Buchhaltung und der eben erst
|
||||
gebaute Sprungschutz: die Fahrten pruefen Rueckwaertssprunge, unmoegliches
|
||||
Tempo und Mindestdauer laengst selbst.
|
||||
|
||||
**Die Form hat der Eigentuemer festgelegt**, nachdem ich zuerst einen Sockel
|
||||
mit Rueckrechnung vorgeschlagen hatte ("nein. Ich moechte: Manuell jederzeit
|
||||
einen neuen Wert eintragen koennen und jeder gefahrene Kilometer wird einfach
|
||||
dazuaddiert"). Also: `saetze.<satz>.km` IST der Stand, von Hand direkt
|
||||
gesetzt, und jede Fahrt legt ihre Strecke oben drauf.
|
||||
|
||||
Zwei neue Felder an der Fahrt tragen das:
|
||||
|
||||
* `reifensatz` - welcher Satz montiert war. Der Satz steht an der FAHRT, nicht
|
||||
am Zeitpunkt; ein Satzwechsel braucht deshalb keine Sonderbehandlung.
|
||||
* `reifen_gezaehlt_km` - wieviel davon schon gutgeschrieben ist. Verfeinert das
|
||||
Screening die Strecke spaeter (7,0 -> 6,896 km ist real vorgekommen), wird
|
||||
nur die Differenz nachgetragen. Ohne diesen Merker waere entweder jede
|
||||
Korrektur verloren oder jede Fahrt doppelt gezaehlt.
|
||||
|
||||
**Der Rueckblick stempelt bewusst KEINEN Radsatz.** Er rekonstruiert
|
||||
Vergangenes, und das steckt im gespeicherten Radstand laengst drin - ein
|
||||
erneuter Import schriebe dieselben Kilometer sonst ein zweites Mal auf die
|
||||
Raeder. Gleiches gilt fuer den Altbestand: Fahrten ohne `reifensatz` zaehlen
|
||||
nie mit, der gespeicherte Stand bleibt bei der Umstellung also unveraendert.
|
||||
|
||||
**Was das kostet, vom Eigentuemer ausdruecklich akzeptiert:** was die
|
||||
Fahrterkennung nicht sieht, landet nie auf den Raedern. Der Tacho ist die
|
||||
Wahrheit des Fahrzeugs und holt sich alles zurueck, eine Fahrtsumme nicht.
|
||||
|
||||
Live nachgewiesen in `audi_ha_test`: von Hand auf 1000 gesetzt -> 1000,0;
|
||||
Fahrt ueber 12,3 km angelegt -> 1012,3; Screening erneut ausgeloest -> 1012,3
|
||||
(keine zweite Gutschrift). Protokollzeilen: `Laufleistung sommer von Hand auf
|
||||
1000.0 km gesetzt` und `Fahrt t-bb1ca655...: 12.3 km auf sommer
|
||||
gutgeschrieben, neuer Stand 1012.3 km`.
|
||||
|
||||
**Der falsche Bestand bleibt stehen.** Die 375.962 km korrigiert der
|
||||
Eigentuemer auf der realen Instanz selbst ueber den Stift, unabhaengig von der
|
||||
Entwicklung.
|
||||
|
||||
### Drei Oberflaechen-Aenderungen, beide Codebasen
|
||||
|
||||
* **"Termin vereinbart" samt Schalter entfernt** (Wunsch des Eigentuemers).
|
||||
Damit entfaellt auch der Zusatz "vereinbart" unter dem Datum auf "Mein
|
||||
Audi". Das Feld `reifen.wechsel.vereinbart` selbst bleibt im Profil stehen -
|
||||
es zu loeschen waere eine Datenaenderung, keine Anzeigeaenderung.
|
||||
* **"Reifen" heisst "Raeder"** in allem Sichtbaren: Seitentitel, Kachel auf
|
||||
"Mein Audi", Kalendereintrag ("Radwechsel auf ..."), Loeschabfrage im Archiv,
|
||||
Backup-Beschreibung. NICHT umbenannt: Route, Profilschluessel, Dienstnamen,
|
||||
Funktionsnamen - dieselbe Trennung wie bei "Servicebuch"/"Wartungsplan"
|
||||
(Abschnitt AB).
|
||||
* **Markenlogo 20 % groesser**, 16px -> 19,2px, an allen vier Stellen
|
||||
(`.marke-logo img` und die eine feste Angabe im Panel, `.dm-marke img` und
|
||||
`.dm-markenlogo` in der App). Gemessen: 19,19px im Browser, drei Logos auf
|
||||
der Tanken-Seite.
|
||||
|
||||
## BM. Der Verbrauchsfaktor kommt aus dem Tankbeleg, nicht aus dem Geber (2026.9.1.30)
|
||||
|
||||
### Die Formel ist die des Eigentuemers
|
||||
|
||||
Faktor = getankte Liter laut Beleg / Summe der Liter aus den Einzelfahrten
|
||||
|
||||
Genau die Rechnung, die er ohnehin von Hand macht. Die Annahme dahinter ist
|
||||
seine und traegt: die Ungenauigkeit des Gebers trifft jede Fahrt
|
||||
gleichermassen, ist also ein Faktor und kein Summand.
|
||||
|
||||
**Meine erste Fassung rechnete anders** - Tankstand nach A minus Tankstand vor
|
||||
B, geteilt durch die Fahrtsumme. Die braucht keinen Beleg (der Maszstab steht
|
||||
in Zaehler und Nenner und kuerzt sich heraus) und funktioniert auch bei
|
||||
Teilbetankung. Sie korrigiert dafuer aber NUR die Verteilung, nicht den
|
||||
absoluten Maszstab des Gebers. Die Belegvariante kann beides und ist deshalb
|
||||
die richtige - der Preis ist die Voll-zu-Voll-Bedingung.
|
||||
|
||||
### Drei Bedingungen, jede aus einem konkreten Fehler heraus
|
||||
|
||||
1. **Volltankung an BEIDEN Enden.** Nur dann gilt "eingefuellte Liter = seit
|
||||
dem letzten Mal verbrauchte Liter". Erkannt am Tankstand eine Minute nach
|
||||
der Betankung: mindestens `VOLL_ANTEIL` (95 %) des `tankvolumen_liter` aus
|
||||
dem Profil. Die eine Minute ist die Vorgabe des Eigentuemers; zu kurz zu
|
||||
greifen ist die harmlose Richtung (der Stand faellt unter die Grenze, es
|
||||
entsteht kein Faktor), zu lang die gefaehrliche - nach der Abfahrt sinkt der
|
||||
Stand wieder und eine echte Volltankung saehe nachtraeglich unvoll aus.
|
||||
2. **Die Liter muessen aus einem Beleg stammen.** Ein automatisch erkannter
|
||||
Tankvorgang traegt in `liters` den vom GEBER gemessenen Anstieg. Den gegen
|
||||
die ebenfalls aus dem Geber gerechneten Fahrten zu stellen, ist ein
|
||||
Zirkelschluss und ergaebe immer ungefaehr 1,0 - der Faktor koennte gar
|
||||
nichts korrigieren. `beleg_liter()` laesst deshalb nur durch, was einen
|
||||
`receipt_key` hat, von Hand bearbeitet wurde oder `source == "manual"`
|
||||
traegt. An fuenf Faellen geprueft.
|
||||
3. **Die Fahrten muessen die gefahrene Strecke abdecken** (`DECKUNG_MIN`/
|
||||
`DECKUNG_MAX`, 90-110 %). Nicht der Faktor wird begrenzt, sondern der
|
||||
Nenner geprueft - siehe unten.
|
||||
|
||||
### Warum nicht der Faktor begrenzt wird
|
||||
|
||||
Erst stand hier eine feste Schranke 0,7-1,5. Der Eigentuemer hat sie
|
||||
hinterfragt ("unsere Rechnung ist gut genug"), und die Pruefung der Frage gab
|
||||
ihm halb recht:
|
||||
|
||||
* **Am Faktor** war sie tatsaechlich am falschen Ort. Die beiden Tore oben
|
||||
sichern den Zaehler; ein echter Geberfehler liegt bei ein paar Prozent und
|
||||
waere an 0,7-1,5 nie angeschlagen. Eine groessere, aber echte Abweichung
|
||||
haette sie dagegen abgeklemmt.
|
||||
* **Der Nenner** bleibt aber verwundbar: fehlt im Intervall eine Fahrt, ist
|
||||
die Summe zu klein und der Faktor zu gross - und er multipliziert jede
|
||||
Fahrt des Zeitraums mit. Genau das war der 5,616-Fall (45 l im Tank, zwei
|
||||
erkannte Fahrten ueber zwoelf Tage); Beleg und Volltankung haetten dort
|
||||
beide gestimmt.
|
||||
|
||||
Geprueft wird deshalb die Vollstaendigkeit der Fahrtmenge, an einem Mass, das
|
||||
beim Radzaehler-Umbau desselben Tages schon belegt wurde:
|
||||
|
||||
Deckung = Summe der Fahrtstrecken / Tacho-Spanne des Intervalls
|
||||
|
||||
Liegt sie bei 90-110 %, gilt der Faktor - ohne obere oder untere Klemme.
|
||||
Sonst wird das Intervall uebersprungen und protokolliert. Die Grenze gilt in
|
||||
BEIDE Richtungen: 256 % (Altbestand mit doppelten Fahrten) ist genauso ein
|
||||
Zeichen fuer einen unbrauchbaren Nenner wie 15 %.
|
||||
|
||||
An sechs Faellen mit den echten Zahlen geprueft, `_deckung()` direkt
|
||||
aufgerufen: 100 % / 99 % / 103 % angewandt, 256 % und 15 % verworfen, ein
|
||||
Rueckwaertssprung (Fahrzeugwechsel) als "nicht bestimmbar" behandelt statt
|
||||
als Deckung 0.
|
||||
|
||||
### Standlauf verzerrt den Faktor nicht
|
||||
|
||||
Frage des Eigentuemers, und die Antwort ist beruhigend: laeuft der Motor im
|
||||
Stand bei eingeschalteter Zuendung, bleibt die Fahrt offen. Der Verbrauch
|
||||
steht damit in der Summe der Einzelfahrten UND in der getankten Menge - er
|
||||
kuerzt sich heraus. Die betroffene Fahrt zeigt zu Recht einen hohen Verbrauch.
|
||||
|
||||
Verzerrend ist nur Verbrauch OHNE offene Fahrt (Standheizung bei Zuendung
|
||||
aus): der fehlt im Nenner und verteilt sich ueber den Faktor auf alle Fahrten
|
||||
des Zeitraums - genau das, was der Eigentuemer bewusst so wollte.
|
||||
|
||||
### Zwei Ausloeser, jeder mit eigenem Grund
|
||||
|
||||
Der Eigentuemer hat den wunden Punkt benannt: der Faktor steht erst
|
||||
rueckwirkend fest, eine Korrektur "nach einer Fahrt" kann nicht funktionieren.
|
||||
Stimmt - und die Rechnung hielt sich schon daran (die Schleife laeuft ueber
|
||||
PAARE aufeinanderfolgender Tankvorgaenge, das laufende Intervall hat kein
|
||||
Gegenstueck). Die Frage legte aber eine echte Luecke frei: **ein Tankvorgang
|
||||
loeste gar nichts aus.** Der einzige Aufruf hing am Screening, gerechnet wurde
|
||||
also erst bei der naechsten Fahrt.
|
||||
|
||||
| Ausloeser | wofuer |
|
||||
|---|---|
|
||||
| `koordinator.tankvorgang_nachbereiten()` | das Intervall schliesst sich - hier entsteht der Faktor |
|
||||
| `screening.durchfuehren()` | eine Fahrt im Intervall bekommt ihren Rohwert erst spaeter |
|
||||
|
||||
`tankvorgang_nachbereiten()` ersetzt `tankvorgaenge_veroeffentlichen()` an
|
||||
allen sechs Schreibstellen (`belege.py` 4x, `tankerkennung.py`,
|
||||
`historienimport.py`). Der 60-Sekunden-Takt ruft bewusst weiter nur
|
||||
`tankvorgaenge_veroeffentlichen()`: dort aendert sich nichts, was einen neuen
|
||||
Faktor ergeben koennte.
|
||||
Gerechnet werden nur Intervalle, in denen noch eine Fahrt ohne Faktor steht -
|
||||
sonst liefen dieselben Verlaufsabfragen bei jedem Fahrtende erneut. Die teure
|
||||
Voll-Pruefung kommt zuletzt, nach den billigen Ausschluessen.
|
||||
|
||||
**Noch nicht belegt:** in den Testdaten gibt es keinen Tankvorgang mit
|
||||
Beleg-Litern, also auch kein gerechnetes Intervall. Der Mechanismus ist
|
||||
geprueft, seine Anwendung auf echte Zahlen nicht.
|
||||
|
||||
### Uebersicht/Zuletzt zeigt Ort -> Ort
|
||||
|
||||
Die Zeile "Letzte Fahrt" zeigte die vollen Anschriften. Sie benutzt jetzt
|
||||
dieselbe `strecke()` wie die Fahrtenliste - eine Quelle, damit die beiden
|
||||
nicht auseinanderlaufen koennen; in der App dafuer aus `Fahrten.tsx`
|
||||
exportiert statt ein zweites Mal gebaut. Dabei fielen in der App zwei
|
||||
Abweichungen zum Panel auf und sind mitgezogen: die Strecke lief noch ueber
|
||||
`de()` statt `streckeText()` (also ohne die gestaffelte Aufloesung), und im
|
||||
Wertzusatz stand das Statuswort, das der Eigentuemer aus der Liste hatte
|
||||
entfernen lassen - jetzt der Verbrauch wie im Panel.
|
||||
|
||||
## BN. Null heisst unbekannt, und der Momentanwert gehoert nur zu "jetzt" (2026.9.2.1-.3)
|
||||
|
||||
### Der Kilometerstand darf fehlen duerfen
|
||||
|
||||
Wunsch des Eigentuemers: ein Weg, einen Tankvorgang ohne Kilometerstand
|
||||
einzutragen - "eine 0 waere hilfreich, bzw. ein leeres Feld wenn alle Zahlen
|
||||
geloescht werden".
|
||||
|
||||
Beides geht jetzt, und beides landet als **None** im Datensatz, nicht als 0.
|
||||
Das ist der eigentliche Punkt: **sechs Stellen** lesen `odometer_km` und
|
||||
behandeln ausschliesslich None als unbekannt. Eine gespeicherte 0 waere fuer
|
||||
sie ein Fahrzeug mit Tachostand null - `distanz_seit_letzter_tankung()` haette
|
||||
den gesamten Tachostand als "Strecke seit der letzten Tankung" geliefert, und
|
||||
der Langzeitverbrauch rechnete gegen eine Spanne, die es nie gab.
|
||||
|
||||
`verlauf.als_kilometerstand()` faengt das an der Eingabe ab (0, leer, negativ,
|
||||
unlesbar -> None). Eine Sonderbehandlung an sechs Lesestellen waere sechsmal
|
||||
die Gelegenheit gewesen, sie zu vergessen. An acht Faellen geprueft.
|
||||
|
||||
Beide Formulare senden bei geleertem Feld ohnehin schon `null` - geprueft, nicht
|
||||
angenommen (`kmFeld === "" ? null : ...` im Panel, `Number.isFinite(kmZahl) &&
|
||||
km ? ... : null` in der App).
|
||||
|
||||
### Ein Momentanwert gehoert zu dem Moment, in dem er gemessen wurde
|
||||
|
||||
Beim Pruefen fiel ein aelterer Fehler auf, den die 0 sonst wirkungslos gemacht
|
||||
haette: `tankvorgang_manuell()` ersetzte einen fehlenden Kilometerstand durch
|
||||
den **aktuellen** Tacho - unabhaengig vom eingetragenen Zeitpunkt. Fuer eine
|
||||
nachgetragene Tankung von vorgestern war das doppelt falsch: die gefahrene
|
||||
Distanz wurde gegen einen zu hohen Stand gerechnet, und die neue
|
||||
Datumspruefung haette das Intervall verworfen.
|
||||
|
||||
`_km_aus_sensor_wenn_jetzt()` setzt den Sensorwert nur noch ein, wenn der
|
||||
eingetragene Zeitpunkt hoechstens `SENSOR_ERSATZ_FENSTER` (2 h) von jetzt
|
||||
entfernt liegt. Dasselbe gilt fuer `distance_km`: `distanz_seit_tankung()`
|
||||
nimmt `TANK_DISTANZ_SENSOR` nur noch mit `jetzt=True` und faellt sonst auf die
|
||||
Kilometerstand-Subtraktion zurueck, die zeitunabhaengig ist.
|
||||
|
||||
Live nachgewiesen ueber den echten Dienst, beide Faelle in einem Durchgang:
|
||||
|
||||
| Zeitpunkt | `km` eingegeben | gespeicherter Kilometerstand |
|
||||
|---|---|---|
|
||||
| jetzt | 0 (unbekannt) | **61.823** - vom Tacho, Normalfall ohne Tippen |
|
||||
| vorgestern | 0 (unbekannt) | **leer** - der Tacho von heute passt nicht dazu |
|
||||
|
||||
### Datum und Kilometerstand muessen zusammenpassen
|
||||
|
||||
Die Fahrten werden ueber die ZEIT zugeordnet, nicht ueber den Tacho. Ein
|
||||
falsch datierter Tankvorgang laesst die Liter deshalb auf den falschen Fahrten
|
||||
landen - und die Deckungspruefung merkt es nicht, weil sie den Tacho zu
|
||||
denselben verschobenen Zeitpunkten liest.
|
||||
|
||||
`_datum_passt()` vergleicht deshalb den eigenen `odometer_km` des
|
||||
Tankvorgangs mit dem Tacho zu seinem Zeitstempel, Toleranz
|
||||
`ODOMETER_TOLERANZ_KM = 5.0` (Vorgabe des Eigentuemers). Ein automatisch
|
||||
erkannter Tankvorgang besteht sie zwangslaeufig - beide Werte stammen aus
|
||||
demselben Datensatz. Ohne Kilometerstand (die 0 von oben) gibt es nichts zu
|
||||
vergleichen, dann gilt der Zeitpunkt wie eingetragen; der Waechter faellt fuer
|
||||
diesen Tankvorgang aus, was unvermeidlich und immer noch besser ist, als eine
|
||||
erfundene Zahl zu verlangen. An sechs Faellen geprueft.
|
||||
|
||||
### Getippte Liter zaehlen als Beleg
|
||||
|
||||
`beleg_liter()` liess nur `receipt_key`, `source == "manual"` oder einen
|
||||
Vermerk in `edited_fields` durch. Ein automatisch erkannter Tankvorgang, dessen
|
||||
Liter der Nutzer nachtippt, hatte nichts davon - die Zahl galt weiter als
|
||||
Geberwert und wurde verworfen. `belege.tankvorgang_aktualisieren()` traegt
|
||||
jetzt `"liters"` in `edited_fields` ein, wenn Liter hereinkommen.
|
||||
|
||||
Das ist zuverlaessig, weil die automatische Erkennung Tankvorgaenge nur
|
||||
ANLEGT und nie aktualisiert: was durch diese Funktion an Litern kommt, stammt
|
||||
konstruktionsbedingt von einem Menschen (Formular oder bearbeitetes CSV).
|
||||
|
||||
Nebenwirkung, bewusst: `edited_fields` schuetzt das Feld vor der Automatik.
|
||||
Wird spaeter doch ein PDF zu demselben Tankvorgang hochgeladen, bleibt die
|
||||
getippte Zahl stehen.
|
||||
|
||||
Live nachgewiesen: getippte Liter akzeptiert, Korrektur lief in derselben
|
||||
Millisekunde wie "Tankvorgang aktualisiert" (nicht erst bei der naechsten
|
||||
Fahrt), und die Deckungspruefung verwarf das Intervall korrekt mit "0 % der
|
||||
gefahrenen Strecke" - dort liegt ein Fahrzeugwechsel.
|
||||
|
||||
### Das Uebersicht-Symbol war das kleinste der fuenf
|
||||
|
||||
Gemeldet, live per `getBBox()` nachgemessen: 18,0 x 10,3 von 24 - **43 %** der
|
||||
Hoehe, waehrend die vier Nachbarn zwischen 65 % (Tanken) und 89 % (Fahrten)
|
||||
liegen.
|
||||
|
||||
Auf volle Rasterbreite gezogen (Faktor 1,33, `stroke-width` 1,125 als
|
||||
Ausgleich, damit die Strichstaerke gerendert wieder 1,5 betraegt): jetzt
|
||||
23,9 x 13,7, also **57 %** - die Breite entspricht exakt "Mein Audi".
|
||||
|
||||
**Bewusst nicht weiter:** das Symbol ist ein Halbkreis und damit bauartbedingt
|
||||
halb so hoch wie breit. Fuer 72 % Hoehe braeuchte es Faktor 1,68 und waere mit
|
||||
30 Einheiten breiter als das Raster. Wirklich gleich hoch wird es nur durch
|
||||
Neuzeichnen des Bogens mit groesserem Winkel - dem Eigentuemer angeboten,
|
||||
nicht unaufgefordert gemacht.
|
||||
|
||||
## Working conventions (observed — keep them)
|
||||
|
||||
- German is the project language: identifiers, comments, commits, UI texts. Exceptions:
|
||||
|
||||
Reference in New Issue
Block a user