Selbst-Update-Sicherung, Service-Prognose ohne Servicebuch und Sicherheit neu gestaltet

- aktualisierung.py: Backup-Ordner verliert sein manifest.json beim Sichern,
  sonst laedt HA ihn nach einem Neustart als zweite audi_dashboard-Integration
  (Ursache des raetselhaften "gleicher Pfad"-Umbenennungs-Fehlers).
- service.ts/audi-dashboard-app.js: Uebersicht faellt jetzt wie Service schon
  auf die Fahrzeugmeldung zurueck, wenn kein Servicebucheintrag existiert;
  eigenes (kuerzeres) Oelwechsel-Intervall wird jetzt auch ohne Servicebuch-
  eintrag naeherungsweise aus der Herstellermeldung hochgerechnet, statt
  weiterhin unveraendert die Herstellervorgabe zu zeigen.
- Sicherheit neu gestaltet: vier Sammelzeilen (Fahrzeug verriegelt / Tueren
  und Klappen geschlossen / Fenster und Dach geschlossen / Kein Licht) statt
  einer flachen Liste, mit neuen Tuerschloss-, Dach- und Standlicht-Sensor-
  rollen, Kreis-Haekchen-Symbolen und einer Detailseite je Tuer/Motorhaube/
  Kofferraum in beiden Frontends.

Details, Verifikation und Sensor-Rollen in AGENTS.md (Abschnitte J/M/N).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-08-24 15:29:37 +02:00
co-authored by Claude Sonnet 5
parent 146f809e9b
commit 5a048488ea
23 changed files with 866 additions and 97 deletions
+94
View File
@@ -5,7 +5,9 @@ import {
kmProTagAusFahrten,
letzterOelwechsel,
meldungsPrognose,
meldungsPrognoseEigenesIntervall,
naechsterService,
oelMeldungsPrognose,
oelwechselPrognose,
} from "./service"
import type { Fahrzeug } from "./profilAdapter"
@@ -252,4 +254,96 @@ describe("meldungsPrognose", () => {
it("liefert nichts ohne jede brauchbare Angabe", () => {
expect(meldungsPrognose(null, null, fahrten, jetzt)).toBeNull()
})
it("gibt die Restkilometer mit zurück", () => {
const p = meldungsPrognose("2027-01-01T00:00:00+00:00", 5000, [], jetzt)
expect(p!.restKm).toBe(5000)
})
})
describe("meldungsPrognoseEigenesIntervall", () => {
const jetzt = new Date("2026-08-11T12:00:00")
// 30 km/Tag, siehe kmProTagAusFahrten-Tests oben.
const fahrten = [
{ ts_start: "2026-07-12T12:00:00", distance_km: 400, status: "abgeschlossen" },
{ ts_start: "2026-08-01T08:00:00", distance_km: 500, status: "abgeschlossen" },
]
it("rechnet Restkilometer und Zeitgrenze auf das eigene, kürzere Intervall um", () => {
// Herstellermeldung: 25.000 km / 24 Monate Rest bis zum 2027-08-11 -
// daraus ergibt sich ein angenommener letzter Service vor 2025-08-11.
// Eigenes Intervall (15.000 km / 12 Monate) ab demselben Punkt: Zeit-
// grenze 2026-08-11 (heute), Restkilometer 25000 - 30000 + 15000 = 10000.
const p = meldungsPrognoseEigenesIntervall(
"2027-08-11T00:00:00+00:00", 25000, 30000, 24, 15000, 12, fahrten, jetzt,
)
expect(p).not.toBeNull()
expect(p!.restKm).toBe(10000)
// 10.000 km bei 30 km/Tag läge weit in der Zukunft - die zurückgerechnete
// eigene Zeitgrenze (heute) greift zuerst.
expect(p!.durchZeitlimit).toBe(true)
expect(p!.datum).toBe("2026-08-11")
})
it("deckelt die Restkilometer bei null, wenn das eigene Intervall rechnerisch schon überschritten ist", () => {
// Erst 10.000 von 30.000 km seit der Herstellerfälligkeit gefahren -
// das kürzere eigene Intervall (15.000 km) ist rechnerisch längst fällig.
const p = meldungsPrognoseEigenesIntervall(
"2027-08-11T00:00:00+00:00", 10000, 30000, 24, 15000, 12, fahrten, jetzt,
)
expect(p).not.toBeNull()
expect(p!.restKm).toBe(0)
expect(p!.durchZeitlimit).toBe(false)
})
it("fällt ohne Fahrtenlog auf die zurückgerechnete eigene Zeitgrenze zurück", () => {
const p = meldungsPrognoseEigenesIntervall(
"2027-08-11T00:00:00+00:00", 25000, 30000, 24, 15000, 12, [], jetzt,
)
expect(p).not.toBeNull()
expect(p!.datum).toBe("2026-08-11")
expect(p!.durchZeitlimit).toBe(true)
})
it("liefert nichts ohne bekannte Herstellerintervall-Länge", () => {
expect(
meldungsPrognoseEigenesIntervall("2027-08-11T00:00:00+00:00", 25000, null, 24, 15000, 12, fahrten, jetzt),
).toBeNull()
})
})
describe("oelMeldungsPrognose", () => {
const jetzt = new Date("2026-08-11T12:00:00")
const fahrten = [
{ ts_start: "2026-07-12T12:00:00", distance_km: 400, status: "abgeschlossen" },
{ ts_start: "2026-08-01T08:00:00", distance_km: 500, status: "abgeschlossen" },
]
it("übernimmt bei Herstellervorgabe unverändert die reine Fahrzeugmeldung", () => {
const f = fahrzeug(10000, "hersteller")
f.oelwechselFaelligTs = "2027-08-11T00:00:00+00:00"
f.oelwechselFaelligKm = 25000
const p = oelMeldungsPrognose(f, fahrten, jetzt)
expect(p!.restKm).toBe(25000)
expect(p!.datum).toBe("2027-08-11")
})
it("rechnet bei eigenem Intervall auf dessen kürzere Vorgabe um", () => {
const f = fahrzeug(10000, "eigen")
f.oelwechselFaelligTs = "2027-08-11T00:00:00+00:00"
f.oelwechselFaelligKm = 25000
const p = oelMeldungsPrognose(f, fahrten, jetzt)
expect(p!.restKm).toBe(10000)
expect(p!.datum).toBe("2026-08-11")
})
it("fällt bei eigenem Intervall ohne Herstellerwerte auf die reine Meldung zurück", () => {
const f = fahrzeug(10000, "eigen")
f.oel.herstellerKm = null
f.oelwechselFaelligTs = "2027-08-11T00:00:00+00:00"
f.oelwechselFaelligKm = 25000
const p = oelMeldungsPrognose(f, fahrten, jetzt)
expect(p!.restKm).toBe(25000)
expect(p!.datum).toBe("2027-08-11")
})
})