/** * Eigene Service-Prognosen (Ölwechsel, Inspektion). Portiert aus * `oelwechselPrognose()` im alten Panel; die Inspektion kam am 2026-08-17 * hinzu (§ AGENTS.md, "Inspektion: voraussichtliches Datum ergänzen"). * * Die Rechnung beginnt bewusst neu ab dem letzten tatsächlich durchgeführten * Eintrag dieser Art — der allgemeine Langzeitdurchschnitt seit dem * allerersten Servicebucheintrag wäre dafür zu träge. Aus km und Tagen seit * dem letzten Eintrag ergibt sich die aktuelle Fahrleistung pro Tag; * hochgerechnet auf die verbleibenden km bis zum Intervall ergibt das ein * Datum, gedeckelt auf spätestens das Zeitlimit des Intervalls. */ import type { Fahrzeug } from "./profilAdapter" const TAG_MS = 86_400_000 export interface Oelprognose { /** ISO-Tagesdatum. */ datum: string restKm: number kmProTag: number /** Die Zeitgrenze hat gegriffen, nicht die Kilometergrenze. */ durchZeitlimit: boolean } interface Buchhaltung { datum?: string km?: number art?: string } function monatePlus(isoDatum: string, monate: number): string { const d = new Date(isoDatum) if (Number.isNaN(d.getTime())) return isoDatum d.setMonth(d.getMonth() + monate) return d.toISOString().slice(0, 10) } /** Der jüngste Servicebucheintrag, dessen Art auf das gesuchte Stichwort passt (z. B. "öl"/"oel" oder "inspektion"). */ function letzterEintrag(buch: readonly Buchhaltung[], stichwort: RegExp): Buchhaltung | null { const treffer = buch .filter((e) => typeof e.art === "string" && stichwort.test(e.art) && e.datum && e.km != null) .sort((a, b) => String(b.datum).localeCompare(String(a.datum))) return treffer[0] ?? null } /** Der jüngste Servicebucheintrag, dessen Art auf einen Ölwechsel hindeutet. */ export function letzterOelwechsel(buch: readonly Buchhaltung[]): Buchhaltung | null { return letzterEintrag(buch, /öl|oel/i) } /** Gemeinsamer Kern für Ölwechsel- und Inspektionsprognose - beide rechnen identisch, nur mit anderem Intervall und anderer Suche im Servicebuch. */ function servicePrognose( fahrzeug: Fahrzeug, letzter: Buchhaltung | null, intervallKm: number, intervallMonate: number, jetzt: Date, ): Oelprognose | null { if (!fahrzeug.odoBekannt) return null if (!letzter?.datum || letzter.km == null) return null if (!Number.isFinite(intervallKm) || intervallKm <= 0) return null const zielKm = letzter.km + intervallKm const restKm = Math.max(0, zielKm - fahrzeug.odo) const kappDatum = monatePlus(letzter.datum, Number.isFinite(intervallMonate) ? intervallMonate : 24) if (restKm <= 0) { return { datum: jetzt.toISOString().slice(0, 10), restKm: 0, kmProTag: 0, durchZeitlimit: false } } const tageSeit = (jetzt.getTime() - new Date(letzter.datum).getTime()) / TAG_MS const kmSeit = fahrzeug.odo - letzter.km // Ohne belastbare Fahrleistung (direkt nach dem Eintrag) gibt es keine // seriöse Kilometerprognose — dann gilt das Zeitlimit. if (tageSeit <= 0 || kmSeit <= 0) { return { datum: kappDatum, restKm, kmProTag: 0, durchZeitlimit: true } } const kmProTag = kmSeit / tageSeit const tageBisZiel = restKm / kmProTag const ausKm = new Date(jetzt.getTime() + tageBisZiel * TAG_MS).toISOString().slice(0, 10) const durchZeitlimit = kappDatum < ausKm return { datum: durchZeitlimit ? kappDatum : ausKm, restKm, kmProTag, durchZeitlimit, } } export function oelwechselPrognose( fahrzeug: Fahrzeug, buch: readonly Buchhaltung[], jetzt = new Date(), ): Oelprognose | null { const oel = fahrzeug.oel const intervallKm = oel.modus === "hersteller" ? (oel.herstellerKm ?? 0) : Math.min(oel.km ?? Infinity, oel.herstellerKm ?? Infinity) const intervallMonate = oel.modus === "hersteller" ? (oel.herstellerMonate ?? 0) : Math.min(oel.monate ?? Infinity, oel.herstellerMonate ?? Infinity) return servicePrognose(fahrzeug, letzterOelwechsel(buch), intervallKm, intervallMonate, jetzt) } /** Inspektion: feste 30.000 km / 24 Monate, wie auch das Fahrzeug selbst und das alte Panel rechnen - kein einstellbares Intervall wie beim Ölwechsel. */ export function inspektionPrognose( fahrzeug: Fahrzeug, buch: readonly Buchhaltung[], jetzt = new Date(), ): Oelprognose | null { return servicePrognose(fahrzeug, letzterEintrag(buch, /inspektion/i), 30000, 24, jetzt) }