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 <noreply@anthropic.com>
This commit is contained in:
2026-09-05 01:56:11 +02:00
parent 5621b818cc
commit 863284e540
25 changed files with 505 additions and 243 deletions
@@ -225,7 +225,6 @@ function profilZuCar(p, status) {
finAutomatisch: p.fahrzeug.fin_automatisch !== false,
details: p.fahrzeug.details,
erstzulassung: p.fahrzeug.erstzulassung,
hu: p.fahrzeug.hauptuntersuchung_faellig,
odo: status.km ?? 0,
odoBekannt: status.km != null,
tankPct: status.tankprozent ?? 0,
@@ -287,7 +286,6 @@ function configCarZuProfil(vorherigesProfil) {
p.fahrzeug.zusatz = CONFIG.fahrzeugzusatz;
p.fahrzeug.untertitel = CONFIG.fahrzeuguntertitel;
p.fahrzeug.kennzeichen = CONFIG.kennzeichen;
p.fahrzeug.hauptuntersuchung_faellig = CAR.hu;
p.fahrzeug.fin = CAR.fin;
p.fahrzeug.fin_automatisch = CAR.finAutomatisch;
p.fahrzeug.erstzulassung = CAR.erstzulassung;
@@ -2362,7 +2360,7 @@ function vHome() {
const datum = t.kmDatum || t.datum;
const hauptwert = restKm !== null
? `${de(restKm)}<span class="unit">km</span>`
: (datum ? dedat(datum) : "");
: (datum === t.datum ? datumText(t.datum, t.genau) : (datum ? dedat(datum) : ""));
// Ohne Restkilometer steht das Datum selbst im Zahlenfeld - auf Nutzerwunsch
// jetzt in derselben Groesse wie die km-Zahl (vorher 30px, damit "15.04.2027"
// auf 375px nicht umbricht - am laufenden Panel geprueft, dass es bei 40px
@@ -2924,6 +2922,17 @@ function vService() {
const fmDatum = fm.ts ? dedat(new Date(fm.ts)) : null;
const hatMeldung = fm.km != null || fmDatum;
const iv = intervalle(t);
/* Ohne Wartungsplan-Eintrag und ohne Fahrzeugmeldung bleibt bei der
Hauptuntersuchung trotzdem ein Termin uebrig: der aus der
Erstzulassung abgeleitete (siehe hauptuntersuchungsTermin()). Bis zum
05.09.2026 hat diese Kachel ihn verschluckt und "kein Eintrag im
Wartungsplan" gezeigt, waehrend die Uebersicht denselben Termin
auswies - gemeldet vom Eigentuemer, zwei Bildschirme, zwei
Antworten. */
if (!iv && !hatMeldung && t.datum) return `<div class="termingruppe">
<div class="tg-kopf">${kopf}</div>
<div class="tg-wert">${datumText(t.datum, t.genau)}</div>
</div>`;
if (!iv && !hatMeldung) return `<div class="termingruppe">
<div class="tg-kopf">${kopf}</div>
<div class="tg-wert leer">kein Eintrag im Wartungsplan</div>
@@ -3122,27 +3131,56 @@ function gewaehlterTermin() {
return auswahl[Math.min(svAuswahl, auswahl.length - 1)];
}
/* Die Hauptuntersuchung hat drei moegliche Quellen, in dieser Reihenfolge:
/* Die Hauptuntersuchung hat ZWEI Quellen, in dieser Reihenfolge:
1. die im Profil EINGETRAGENE Faelligkeit (CAR.hu). Sie steht auf dem
Pruefbericht und ist damit die belastbarste Angabe - bis zum
04.09.2026 hat das Panel sie gar nicht benutzt, obwohl sie
gespeichert war (Befund des Eigentuemers).
2. der letzte Servicebuch-Eintrag plus 24 Monate.
3. die Erstzulassung plus 24 Monate.
1. der letzte Servicebuch-Eintrag plus **24** Monate.
2. die Erstzulassung plus **36** Monate.
Die 36 gegen 24 sind kein Versehen: die erste Hauptuntersuchung eines
Neuwagens ist nach drei Jahren faellig, jede weitere nach zwei.
Es gibt KEINE dritte Quelle mehr. Bis zum 05.09.2026 stand hier zuerst
ein Profilfeld `hauptuntersuchung_faellig`, das alles andere ueberstimmt
hat. Dieses Feld hatte in keiner der beiden Oberflaechen ein
Eingabefeld und im Backend kein Schema - es lag nur als toter Schluessel
in fahrzeugprofil.json und war von dort weder zu sehen noch zu aendern.
Gemeldet vom Eigentuemer am 05.09.2026: Wartungsplan leer, Erstzulassung
01.03.2025, angezeigt wurde trotzdem August 2026 - das war jener tote
Schluessel ("08/2026"). Das Feld ist samt Lese- und Schreibweg
entfernt; die Ableitung ist die einzige Quelle. */
const HU_MONATE_FOLGE = 24;
const HU_MONATE_ERSTE = 36;
/* Der JUENGSTE Hauptuntersuchungs-Eintrag des Wartungsplans.
Nicht ueber letzterEintrag(): das nimmt mit .find() den ersten Treffer in
Listenreihenfolge, waehrend die App nach Datum absteigend sortiert. Bei
mehreren Eintraegen haetten beide Seiten verschiedene Termine gezeigt,
ohne dass man es der Anzeige ansieht. */
function letzteHauptuntersuchung() {
let beste = null;
let besteZeit = null;
for (const e of CAR.service.buch) {
if (!e || typeof e.art !== "string" || !e.art.toLowerCase().includes("hauptunter")) continue;
const z = datumAusText(e.datum);
if (!z) continue;
if (besteZeit === null || z.zeit > besteZeit) { beste = e; besteZeit = z.zeit; }
}
return beste;
}
Zu 3.: das ging vorher NUR mit einer Erstzulassung im Format MM/JJJJ.
Ein vollstaendiges Datum (01.03.2025) - eine voellig richtige Eingabe -
liess den Termin still leer. datumAusText() nimmt jetzt beide Formen. */
function hauptuntersuchungsTermin(bauen) {
const gesetzt = datumAusText(CAR.hu);
if (gesetzt) {
return { art: "Hauptuntersuchung", km: null, datum: gesetzt.zeit, kmDatum: null,
zuerst: "zeit", basis: null, ziel: gesetzt.zeit, genau: gesetzt.genau };
const eintrag = letzteHauptuntersuchung();
if (eintrag) {
return bauen("Hauptuntersuchung", eintrag, null, HU_MONATE_FOLGE);
}
const erst = datumAusText(CAR.erstzulassung);
return bauen("Hauptuntersuchung", letzterEintrag("hauptuntersuchung"), null, 24,
const termin = bauen("Hauptuntersuchung", null, null, HU_MONATE_ERSTE,
erst ? erst.zeit : null);
// Eine Erstzulassung als "03/2025" nennt keinen Tag. Dann nennt auch der
// abgeleitete Termin keinen - "01.03.2028" waere eine Erfindung.
if (erst && erst.genau === "monat") termin.genau = "monat";
return termin;
}
function termine() {