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:
@@ -32,7 +32,6 @@ LEBENSZEICHEN = datetime.datetime(2026, 9, 3, 8, 20, 0, tzinfo=datetime.UTC)
|
||||
|
||||
|
||||
class FakeWerte:
|
||||
TRIP_SENSOR = ""
|
||||
ZUENDUNG_SENSOR = "binary_sensor.zuendung"
|
||||
MELDEZEIT_SENSOR = ""
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ T_WECHSEL = datetime.datetime(2026, 9, 2, 21, 22, 12, tzinfo=datetime.UTC)
|
||||
|
||||
|
||||
class FakeWerte:
|
||||
TRIP_SENSOR = "binary_sensor.trip"
|
||||
ZUENDUNG_SENSOR = "binary_sensor.zuendung"
|
||||
|
||||
|
||||
@@ -50,10 +49,9 @@ class FakeKoordinator:
|
||||
self.fahrt_sperre = asyncio.Lock()
|
||||
self.beendet = []
|
||||
self.veroeffentlicht = 0
|
||||
# Dieser Test beschreibt die Welt MIT Trip-Signal: dort bringt das
|
||||
# Geraet seine Pausentoleranz mit, _pausenzeit_s() gibt 0 zurueck und
|
||||
# es wird sofort geschlossen. Gegenstand ist hier die Sperre, nicht
|
||||
# die Wartezeit - die hat test_zuendungspause.py.
|
||||
# Gegenstand ist hier die SPERRE, nicht die Wartezeit - die hat
|
||||
# test_zuendungspause.py. Deshalb ist die Pausenzeit unten auf 0
|
||||
# gesetzt, dann schliesst _signalwechsel() sofort.
|
||||
self.zuordnung = FakeZuordnung()
|
||||
|
||||
# Seit die Pausenregel in Geraetezeit rechnet, liest _signalwechsel() das
|
||||
@@ -72,7 +70,10 @@ class FakeKoordinator:
|
||||
|
||||
class SignalwechselTest(unittest.IsolatedAsyncioTestCase):
|
||||
async def asyncSetUp(self):
|
||||
self._echt = (f._geraetezeit, f._echtes_ende, f.fahrt_beenden)
|
||||
self._echt = (f._geraetezeit, f._echtes_ende, f.fahrt_beenden, f._pausenzeit_s)
|
||||
|
||||
async def pausenzeit(k):
|
||||
return 0
|
||||
|
||||
async def geraetezeit(k, ereigniszeit, standard):
|
||||
# Der eigentliche Punkt: dieser Schritt wartet wirklich (in echt
|
||||
@@ -88,12 +89,12 @@ class SignalwechselTest(unittest.IsolatedAsyncioTestCase):
|
||||
k.beendet.append((start_ts, ende_ts))
|
||||
await k.fahrt_start_setzen(None)
|
||||
|
||||
f._geraetezeit, f._echtes_ende, f.fahrt_beenden = (
|
||||
geraetezeit, echtes_ende, beenden,
|
||||
f._geraetezeit, f._echtes_ende, f.fahrt_beenden, f._pausenzeit_s = (
|
||||
geraetezeit, echtes_ende, beenden, pausenzeit,
|
||||
)
|
||||
|
||||
async def asyncTearDown(self):
|
||||
f._geraetezeit, f._echtes_ende, f.fahrt_beenden = self._echt
|
||||
f._geraetezeit, f._echtes_ende, f.fahrt_beenden, f._pausenzeit_s = self._echt
|
||||
|
||||
async def test_off_und_on_in_derselben_sekunde(self):
|
||||
"""Der genaue Fall vom 02.09.2026: die neue Fahrt darf nicht verloren
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Die Wartezeit nach dem Zuendungs-Aus, und die beiden Nachlaeufe des Geraets.
|
||||
"""Die Wartezeit nach dem Zuendungs-Aus und der Nachlauf des Geraets.
|
||||
|
||||
Warum es das gibt (03.09.2026): das Trip-Signal des FMM003 endet erst nach
|
||||
seinem "Ignition OFF Timeout" von 900 s - und der Schlaf-Timeout des Geraets
|
||||
@@ -46,8 +46,7 @@ WARTEN = 0.15
|
||||
|
||||
|
||||
class FakeWerte:
|
||||
def __init__(self, trip=""):
|
||||
self.TRIP_SENSOR = trip
|
||||
def __init__(self):
|
||||
self.ZUENDUNG_SENSOR = "binary_sensor.zuendung"
|
||||
# Ohne Meldezeit-Sensor keine Geraetezeit und keine Klemme - die Tests
|
||||
# pruefen die Nachlaufrechnung, nicht die Zuordnung.
|
||||
@@ -55,8 +54,8 @@ class FakeWerte:
|
||||
|
||||
|
||||
class FakeZuordnung:
|
||||
def __init__(self, trip=""):
|
||||
self.werte = FakeWerte(trip)
|
||||
def __init__(self):
|
||||
self.werte = FakeWerte()
|
||||
|
||||
|
||||
class FakeAblage:
|
||||
@@ -72,10 +71,10 @@ class FakeAblage:
|
||||
|
||||
|
||||
class FakeKoordinator:
|
||||
def __init__(self, start_ts=T0, trip="", minuten=15, fahrten=None):
|
||||
def __init__(self, start_ts=T0, minuten=15, fahrten=None):
|
||||
self.fahrt_start_ts = start_ts
|
||||
self.fahrt_sperre = asyncio.Lock()
|
||||
self.zuordnung = FakeZuordnung(trip)
|
||||
self.zuordnung = FakeZuordnung()
|
||||
self.ablage = FakeAblage(minuten, fahrten)
|
||||
self.beendet = []
|
||||
self._aufgabe = None
|
||||
@@ -111,14 +110,12 @@ class FakeKoordinator:
|
||||
class Basis(unittest.IsolatedAsyncioTestCase):
|
||||
def _pause_kurz(self):
|
||||
async def pausenzeit(k):
|
||||
return 0 if k.zuordnung.werte.TRIP_SENSOR else PAUSE_S
|
||||
return PAUSE_S
|
||||
|
||||
return pausenzeit
|
||||
|
||||
async def asyncSetUp(self):
|
||||
self._echt = (
|
||||
f._geraetezeit, f.fahrt_beenden, f._pausenzeit_s, f._nachlauf_gegenpruefen,
|
||||
)
|
||||
self._echt = (f._geraetezeit, f.fahrt_beenden, f._pausenzeit_s)
|
||||
|
||||
async def geraetezeit(k, ereigniszeit, standard):
|
||||
return ereigniszeit or standard
|
||||
@@ -127,11 +124,6 @@ class Basis(unittest.IsolatedAsyncioTestCase):
|
||||
k.beendet.append((start_ts, ende_ts))
|
||||
await k.fahrt_start_setzen(None)
|
||||
|
||||
async def gegenpruefen(k, start_ts, signal_ende, gerechnet):
|
||||
# Liest den Verlauf und aendert nichts an der Rechnung (siehe dort);
|
||||
# ohne echtes hass-Objekt ist sie hier nur im Weg.
|
||||
return None
|
||||
|
||||
async def nach_pause(k, start_ts, ende_ts, pause_s):
|
||||
# Wie das Original, nur mit kurzer Wanduhrzeit - die echte
|
||||
# Pausenzeit steckt weiterhin in der Entscheidung, nicht im Warten.
|
||||
@@ -141,14 +133,11 @@ class Basis(unittest.IsolatedAsyncioTestCase):
|
||||
f._geraetezeit = geraetezeit
|
||||
f.fahrt_beenden = beenden
|
||||
f._pausenzeit_s = self._pause_kurz()
|
||||
f._nachlauf_gegenpruefen = gegenpruefen
|
||||
self._echt_nach_pause = f._nach_pause_beenden
|
||||
f._nach_pause_beenden = nach_pause
|
||||
|
||||
async def asyncTearDown(self):
|
||||
(
|
||||
f._geraetezeit, f.fahrt_beenden, f._pausenzeit_s, f._nachlauf_gegenpruefen,
|
||||
) = self._echt
|
||||
(f._geraetezeit, f.fahrt_beenden, f._pausenzeit_s) = self._echt
|
||||
f._nach_pause_beenden = self._echt_nach_pause
|
||||
|
||||
|
||||
@@ -187,11 +176,6 @@ class Wartezeit(Basis):
|
||||
self.assertEqual(k.beendet, [])
|
||||
self.assertEqual(k.fahrt_start_ts, T0)
|
||||
|
||||
async def test_ohne_wartezeit_bleibt_es_wie_bisher(self):
|
||||
"""Mit zugeordnetem Trip-Signal wartet das Geraet - wir nicht."""
|
||||
k = FakeKoordinator(trip="binary_sensor.trip")
|
||||
await f.zuendung_geaendert(k, "off", "on", AUS)
|
||||
self.assertEqual(len(k.beendet), 1, "ohne Pause wird sofort geschlossen")
|
||||
|
||||
|
||||
class Nachlauf(Basis):
|
||||
@@ -202,13 +186,6 @@ class Nachlauf(Basis):
|
||||
_, ende = k.beendet[0]
|
||||
self.assertEqual(ende, AUS - datetime.timedelta(seconds=180))
|
||||
|
||||
async def test_tripweg_zieht_beide_nachlaeufe_ab(self):
|
||||
"""900 s Trip-Nachlauf plus 180 s des Zuendungselements: das Trip-Ende
|
||||
haengt selbst an der bereits verzoegerten Zuendung."""
|
||||
k = FakeKoordinator(trip="binary_sensor.trip")
|
||||
await f.zuendung_geaendert(k, "off", "on", AUS)
|
||||
_, ende = k.beendet[0]
|
||||
self.assertEqual(ende, AUS - datetime.timedelta(seconds=1080))
|
||||
|
||||
async def test_zu_kurz_wird_auf_den_beginn_geklemmt(self):
|
||||
k = FakeKoordinator(start_ts=AUS - datetime.timedelta(seconds=60))
|
||||
@@ -412,9 +389,6 @@ class Einstellung(unittest.IsolatedAsyncioTestCase):
|
||||
k = FakeKoordinator(minuten=25)
|
||||
self.assertEqual(await f._pausenzeit_s(k), 25 * 60)
|
||||
|
||||
async def test_trip_signal_schlaegt_die_einstellung(self):
|
||||
k = FakeKoordinator(minuten=25, trip="binary_sensor.trip")
|
||||
self.assertEqual(await f._pausenzeit_s(k), 0)
|
||||
|
||||
async def test_unsinn_faellt_auf_den_standard_zurueck(self):
|
||||
k = FakeKoordinator()
|
||||
|
||||
Reference in New Issue
Block a user