Hauptuntersuchung: Ableitung aus Erstzulassung und Wartungsplan, drittes Feld entfernt

Gemeldet: Wartungsplan leer, Erstzulassung 01.03.2025, erwartet 01.03.2028 -
gezeigt wurden drei verschiedene Antworten (Uebersicht 01.08.2026, Mein Audi
"August 2026", Service "kein Eintrag im Wartungsplan").

Ursache war das Profilfeld hauptuntersuchung_faellig ("08/2026"), das die
Ableitung ueberstimmte, in keiner Oberflaeche ein Eingabefeld hatte und im
Backend kein Schema. Drei Bildschirme verarbeiteten denselben Wert
verschieden. Das Feld ist samt Lese- und Schreibweg entfernt; es gibt jetzt
zwei Quellen: Wartungsplan + 24 Monate, sonst Erstzulassung + 36 Monate.

Dabei mitbehoben:

* Die Service-Kachel verschluckte abgeleitete Termine, sobald kein
  Wartungsplan-Eintrag dahinterstand.
* monatePlus() in der App verlor einen Tag ueber die Zeitumstellung (drei von
  fuenf gemessenen Faellen) - betraf Oelwechsel- und Inspektionsprognose
  genauso. Das Panel rechnete dort seit jeher richtig.
* Die App verlangte fuer jeden Wartungsplan-Eintrag eine Kilometerangabe. Eine
  Hauptuntersuchung ist rein datumsbasiert; ohne km waere der erste HU-Eintrag
  ignoriert worden. Ausserdem nahm das Panel den ersten Treffer der Liste, die
  App den juengsten - beide nehmen jetzt den juengsten.
* Monatsgenauigkeit ("03/2025") bleibt auf beiden Seiten erhalten.

Neu: paritaet_hu.test.ts vergleicht die App gegen die aus der Panel-Quelle
herausgeschnittenen Originalfunktionen, 15 Faelle. Gesamt 292 App-Tests, 73
Python-Tests mit 231 Untertests, 0 Tracebacks. Live an der Testinstanz auf
allen drei gemeldeten Bildschirmen geprueft.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-05 01:56:11 +02:00
co-authored by Claude Opus 5
parent 5621b818cc
commit 863284e540
25 changed files with 505 additions and 243 deletions
+135
View File
@@ -11316,3 +11316,138 @@ Noch nicht gebaut. Offen bleiben damit:
* "Jetzt lesen" als echtes Lesen (Cache loeschen, dann holen).
* Der Waechter darf keinen Gleichstand melden, solange unbekannt ist, wann der
Wert zuletzt vom Geraet bestaetigt wurde.
## CR. Ein Feld ohne Eingabefeld hat drei Bildschirme belogen (2026.9.5.3)
Meldung des Eigentuemers, 05.09.2026: Wartungsplan leer, Erstzulassung
`01.03.2025`, erwartet also die Hauptuntersuchung am 01.03.2028. Gezeigt wurde
stattdessen dreierlei:
| Bildschirm | Anzeige |
|---|---|
| Uebersicht | `01.08.2026` |
| Mein Audi | `August 2026` |
| Service | `kein Eintrag im Wartungsplan` |
Drei Antworten auf dieselbe Frage. Die Ursache war eine einzige Zeile in
`/config/audi_dashboard/fahrzeugprofil.json`:
"hauptuntersuchung_faellig": "08/2026"
### Warum das niemand korrigieren konnte
Dieses Feld hatte **in keiner der beiden Oberflaechen ein Eingabefeld** und im
Backend kein Schema - gemessen, nicht vermutet: kein `<input>` im Panel, keine
Zeile in `Einstellungen.tsx`, kein Treffer in den `.py`-Dateien. Es wurde nur
gelesen und beim Speichern unveraendert zurueckgeschrieben. Ein Wert, den man
sieht, aber nicht anfassen kann.
Und er stand in der Ableitung **ganz vorn**: beide Codebasen fragten zuerst
dieses Feld und erst dann Wartungsplan und Erstzulassung. Solange dort
`08/2026` stand, war jede Ableitung wirkungslos.
Die drei verschiedenen Anzeigen erklaeren sich aus drei verschiedenen
Verarbeitungen desselben Wertes:
* **Uebersicht** rief `new Date(fahrzeug.hu)` auf - an der Ableitung vorbei.
`new Date("08/2026")` ergibt in V8 den 01.08.2026.
* **Mein Audi** ging ueber die Ableitung, die `08/2026` als monatsgenau
erkannte und korrekt "August 2026" schrieb.
* **Service** prueft mit `new Date(huFaellig)`; fuer `"08/2026"` liefert das
`Invalid Date`, also der Leertext.
### Die Regel, die jetzt gilt
Vorgabe des Eigentuemers: *"die eingabe reicht fuers erste mal in der
Erstzulassung und danach ueber den Wartungsplan"*. Zwei Quellen, keine dritte:
1. juengster Wartungsplan-Eintrag + **24** Monate
2. sonst Erstzulassung + **36** Monate
Die 36 gegen 24 sind kein Versehen - die erste Hauptuntersuchung eines
Neuwagens ist nach drei Jahren faellig, jede weitere nach zwei.
Das Profilfeld ist samt Lese- und Schreibweg entfernt (Panel, `profilAdapter`,
`Fahrzeug`-Typ, Beispieldaten) und der tote Schluessel aus der Profildatei der
Testinstanz geloescht. **Die reale Instanz traegt ihn noch** - er wird jetzt
nirgends mehr gelesen, aber er liegt dort.
### Drei weitere Fehler, die dabei ans Licht kamen
**Die Service-Kachel verschluckte abgeleitete Termine.** `intervalle(t)` gibt
`null` zurueck, wenn kein Wartungsplan-Eintrag dahintersteht, und der fruehe
Return schrieb dann "kein Eintrag im Wartungsplan" - auch wenn `t.datum` laengst
einen Termin trug. Behoben: der Return greift nur noch, wenn wirklich kein
Datum da ist.
**`monatePlus()` in der App verlor einen Tag ueber die Zeitumstellung.** Sie las
mit `new Date(isoDatum)` (UTC-Mitternacht) und formatierte mit
`toISOString()` zurueck. Fielen Start und Ziel in verschiedene
Zeitzonen-Halbjahre, verschob das um eine Stunde - und die reicht ueber
Mitternacht. Gemessen, drei von fuenf:
2025-01-15 + 6 Monate -> 2025-07-14 statt 2025-07-15
2025-11-20 + 6 Monate -> 2026-05-19 statt 2026-05-20
2025-02-10 + 4 Monate -> 2025-06-09 statt 2025-06-10
Das betraf **Oelwechsel- und Inspektionsprognose genauso**, nicht nur die
Hauptuntersuchung. Das Panel rechnete an derselben Stelle von jeher in Ortszeit
und war richtig - eine stille Paritaetsabweichung. Jetzt rechnen beide in
Ortszeit.
**Der Wartungsplan-Eintrag brauchte in der App eine Kilometerangabe.**
`letzterEintrag()` filtert auf `e.km != null`, weil Oelwechsel und Inspektion
km-Prognosen rechnen. Die Hauptuntersuchung ist rein datumsbasiert; ohne
km-Eintrag haette die App den Eintrag ignoriert und weiter aus der
Erstzulassung gerechnet - drei Jahre statt zwei -, waehrend das Panel ihn
benutzte. Das haette **genau beim ersten HU-Eintrag** zugeschlagen, also bei
dem Weg, den der Eigentuemer beschrieben hat. `letzteHauptuntersuchung()` hat
jetzt einen eigenen Filter ohne km. Dazu nahm das Panel mit `.find()` den
ersten Treffer in Listenreihenfolge, die App den juengsten nach Datum - beide
nehmen jetzt den juengsten.
### Monatsgenauigkeit bleibt erhalten
Das Eingabefeld der Erstzulassung schlaegt `MM/JJJJ` vor. Aus `03/2025` darf
kein Termin mit Tag entstehen - `01.03.2028` waere eine Erfindung. Das Panel
fuehrt die Genauigkeit als `genau` am Termin mit, die App als ISO-Monat im Text
(`"2028-03"`, den `datum()` als "Maerz 2028" ausgibt). Verschiedene Form,
gleiche Aussage.
### Geprueft
`companion-app/src/daten/paritaet_hu.test.ts` schneidet `datumAusText()`,
`monatePlus()`, `dat()` und die Datumsmuster aus der **Panel-Quelle** heraus und
laesst sie gegen die App laufen - 15 Faelle, darunter Schaltjahr-Ueberlauf
(29.02.2024 + 24 Monate -> 01.03.2026 auf beiden Seiten), beide Schreibweisen
derselben Erstzulassung und alle drei Zeitumstellungs-Faelle. Dazu sieben neue
Faelle in `service.test.ts`. Gesamt 292 Tests gruen.
Live an der Testinstanz mit den echten Daten des Eigentuemers geprueft, alle
drei gemeldeten Bildschirme:
| Bildschirm | vorher | jetzt |
|---|---|---|
| Uebersicht | `01.08.2026` (HU als naechster Termin) | `23.07.2027` (Oelwechsel; die HU liegt 2028 dahinter) |
| Service | `kein Eintrag im Wartungsplan` | `01.03.2028` |
| Mein Audi | `August 2026` | `01.03.2028` |
0 Tracebacks nach dem Neustart.
### Zwei Fallen der Umgebung, die eine Auslieferung verschleiert haben
Beim Ausliefern sah es dreimal so aus, als sei die neue Fassung im Container -
sie war es nicht:
* **MSYS-Pfadumwandlung.** `docker exec ... rm -rf /config/www/dmapp` ohne
Quoting macht Git Bash zu einem Windows-Pfad; das `rm` trifft ins Leere,
und das folgende `docker cp` legt seinen Inhalt dann **in** den bestehenden
Ordner (`dmapp/dist/`, `audi_dashboard/audi_dashboard/`). Pfade gehoeren in
`sh -c '...'`, sonst greift die Umwandlung.
* **Binaerpipe in `docker exec -i`.** `tar -cf - | docker exec -i ... tar -xf -`
meldet Erfolg und uebertraegt nichts. Nicht benutzen; `docker cp` auf ein
**nicht vorhandenes** Ziel ist der verlaessliche Weg.
Beides hat dazu gefuehrt, dass ich zwischenzeitlich alte Dateien gelesen und
fuer neue gehalten habe. Gegenprobe, die das aufdeckt: `find <ziel> -name
manifest.json | wc -l` muss `1` ergeben.