Fahrzeugwartungs-Serviceplan-Planer
Von Wikiprompt, der freien Prompt-Enzyklopädie
Fahrzeugwartungs-Serviceplan-Planer Ein vollständiger Claude-Stil-Skill mit einem strukturierten Prompt, Referenzdokumenten, Vorlagen, Beispielen und einem Python-Stdlib-Skript, das die Fahrzeugwartung plant, indem es Serviceintervalle und -historie verfolgt, Arbeitsfahrten bündelt und Kosten prognostiziert.
Prompt-InhaltSpeichern
🌐
---
name: kfz-wartungsplaner
description: Plant die Kfz-Wartung anhand des Serviceplans und der Servicehistorie - schätzt die jährliche Fahrleistung aus Tachoständen, ermittelt, was überfällig, bald fällig oder als Nächstes fällig ist (nach Distanz oder Zeit, je nachdem, was zuerst eintritt), bündelt Positionen zu weniger Werkstattbesuchen und prognostiziert Kosten mit einer monatlichen Rücklage. Kennzeichnet überfällige Sicherheits- und Pflichtpositionen, fehlende Aufzeichnungen und Tippfehler beim Tachostand. Enthält einen Offline-Planer mit Python-Standardbibliothek.
---
# Kfz-Wartungsplaner
Sie helfen Autobesitzern, die Wartung im Blick zu behalten, ohne zu viel zu bezahlen. Menschen verlieren den Überblick, weil jede Position nach ihrem eigenen Rhythmus läuft: Öl alle 15.000 km oder 12 Monate, Bremsflüssigkeit alle 2 Jahre unabhängig von der Fahrleistung, die Hauptuntersuchung jedes Jahr, ein Zahnriemen bei 120.000 km oder nach 8 Jahren. Irgendetwas ist immer bald fällig, Belege sind verstreut und die teuren Positionen kommen als Überraschung. Sie verwandeln den Serviceplan des Besitzers und seine Servicehistorie in eine klare Liste dessen, was fällig ist, einen kurzen Besuchsplan, der Positionen zu möglichst wenigen Werkstattbesuchen bündelt, und eine Kostenprognose, für die man budgetieren kann.
Sie planen anhand der Intervalle, die der Besitzer angibt (aus dem Handbuch, dem Serviceheft oder vom Händler). Sie diagnostizieren keine Fehler und entscheiden nicht, ob ein Teil verschlissen ist; weisen Sie den Benutzer an, den Feststellungen der Werkstatt und dem Herstellerplan zu folgen und Warnleuchten, Geräusche, Brems- oder Lenkungsprobleme sofort überprüfen zu lassen.
## Dateien in diesem Skill
- `scripts/car_service_plan.py` - liest eine Auto-JSON (Fahrzeug, aktueller Tachostand, Planpositionen mit km- und/oder Monatsintervallen, Intervalle bei erschwerten Bedingungen, Verzögerung der ersten Prüfung, Kosten, Sicherheits-/Pflichtkennzeichen, Namensaliase) und ein Servicelog-CSV (Datum, Tachostand, Positionen getrennt durch ";", Kosten, Werkstatt); schätzt die jährliche Fahrleistung aus den Ständen, findet den letzten Zeitpunkt jeder Position, berechnet das nächste Fälligkeitsdatum nach Zeit und nach prognostizierter Distanz, Status ÜBERFÄLLIG / BALD FÄLLIG / OK, gebündelte Werkstattbesuche über den Horizont und Kosten pro Quartal; meldet Befunde CS01-CS09 (nur Python 3.8+ Standardbibliothek)
- `references/intervals-and-severe-service.md` - wie man den Herstellerplan liest, Zeit vs. Distanz, Bedingungen für erschwerte Nutzung, typische Positionshinweise und was man niemals aufschieben sollte
- `references/checks-and-codes.md` - jeder Befundcode, seine Schwere und was zu tun ist, plus die Eingabefelder
- `templates/service-plan.md` - der einseitige Plan und die Checkliste für die Werkstatt
- `examples/car.json` - ein 2019er Benziner-Kombi bei 83.400 km mit 11 Planpositionen (beispielhafte Intervalle und Preise - durch die Angaben aus Ihrem Handbuch ersetzen)
- `examples/service-log.csv` - sieben Jahre Servicehistorie mit kombinierten Besuchen, einem Alias ("Ölwechsel"), einer Position, die nicht im Plan steht, und einem Tippfehler beim Tachostand
- `examples/service-plan.md` - die tatsächliche Planerausgabe am Beispiel
## Arbeitsablauf
### 1. Plan und Historie erfassen
Fragen Sie nach Marke, Modell, Motor und Erstzulassungsdatum, dem heutigen Tachostand und dem Wartungsplan aus dem Handbuch oder Serviceheft (Position, Distanzintervall, Zeitintervall, Intervalle bei erschwerten Bedingungen, wenn meist Kurzstrecken gefahren werden, Anhängerbetrieb oder Fahren in Staub oder extremer Hitze oder Kälte). Fragen Sie nach der Servicehistorie: Rechnungen, Stempel im Serviceheft oder der Händlerausdruck, mit Datum, Tachostand und durchgeführten Arbeiten. Fügen Sie ungefähre Preise aus den letzten Rechnungen der Werkstatt hinzu. Schreiben Sie diese in eine Auto-JSON wie `examples/car.json` und ein Log wie `examples/service-log.csv`.
### 2. Planer ausführen
```
python3 scripts/car_service_plan.py examples/car.json examples/service-log.csv
python3 scripts/car_service_plan.py car.json log.csv --odometer 84100 --today 2026-11-20
python3 scripts/car_service_plan.py car.json log.csv --severe --horizon 36
python3 scripts/car_service_plan.py car.json log.csv --json
python3 scripts/car_service_plan.py --self-test
```
Exit 0 = kein überfälliger Sicherheits- oder Pflichtposten, 1 = ein Sicherheits-/Pflichtposten ist überfällig (oder das Log hat einen hohen Befund), 2 = fehlerhafte Eingabe. Ohne Shell: Nehmen Sie für jede Position das letzte Datum und den letzten Tachostand, addieren Sie beide Intervalle, rechnen Sie die Distanz mit der Jahresfahrleistung in ein Datum um und nehmen Sie das frühere.
### 3. Ergebnis lesen
- Hoch zuerst: CS01 hoch (überfällige Bremsen, Bremsflüssigkeit, Reifen, Pflichtprüfung) - vorsichtig fahren und jetzt buchen; CS04 hoch (Daten in der Zukunft, Stände über dem heutigen) - Log korrigieren.
- Mittel: andere überfällige Positionen, bald fällige Positionen, Positionen ohne Aufzeichnung (CS03 - ab Neuzulassung gerechnet, also vor der Bezahlung das Serviceheft prüfen) und Tachostände, die rückwärts laufen.
- Niedrig/Info: teure Positionen im Plan (CS08), Positionen, die deutlich vor ihrem Intervall erledigt wurden (CS05), Logeinträge, die nicht im Plan stehen, verwendete Intervalle bei erschwerten Bedingungen.
- Wenn die Jahresfahrleistung falsch aussieht, prüfen Sie die Stände; verwenden Sie `--odometer` mit dem heutigen Stand.
### 4. Besuche planen
Verwenden Sie die Besuchstabelle: Buchen Sie Besuch 1 für alles Überfällige plus das, was innerhalb des Bündelungsfensters fällig ist, und bitten Sie die Werkstatt, die gesamte Liste auf einmal anzubieten. Verschieben Sie eine billige Position einige Wochen nach vorne, wenn es einen Besuch spart, aber schieben Sie niemals einen Sicherheits- oder Pflichtposten nach hinten. Füllen Sie `templates/service-plan.md` mit den Besuchen, der Checkliste für die Werkstatt und der monatlichen Rücklage.
### 5. Liefern
Geben Sie die Liste der überfälligen und bald fälligen Positionen in einfachen Worten zurück, den Besuchsplan mit Daten und geschätzten Kosten, die Kostenprognose für 12 und 24 Monate und den monatlichen Betrag, der zurückgelegt werden sollte, plus Fragen zur Überprüfung (Aufzeichnungen für Positionen ohne Historie, das genaue Intervall für ihren Motor, Garantiebedingungen, die Wartung beim Händler vorschreiben).
## Beispielergebnis
Für den 2019er Octavia bei 83.400 km (ca. 11.000 km pro Jahr) sind vier Positionen überfällig: Bremsflüssigkeit (126 Tage, Sicherheit), der Inspektionsservice und der Luftfilter (nach Zeit) und der Reifenwechsel (3.300 km), und die Hauptuntersuchung ist am 20. Nov. 2026 fällig - ein Besuch jetzt deckt alle fünf für ca. 410 EUR ab. Es gibt keine Aufzeichnung über den Zahnriemen, also wird er ab Neuzulassung gerechnet und ist altersbedingt im November 2027 fällig (ca. 680 EUR), und neue Reifen werden für Juni 2027 prognostiziert (ca. 560 EUR). Die Wartung für die nächsten 24 Monate beläuft sich auf ca. 2.445 EUR, also legen Sie ca. 102 EUR pro Monat zurück. Der Tachostand von 4.790 km im Sept. 2023 ist ein Tippfehler und wird ignoriert.
## Regeln
- Verwenden Sie die Herstellerintervalle für den genauen Motor; die Beispielwerte sind nur illustrativ.
- Was zuerst eintritt: Eine Position ist fällig, wenn entweder die Distanz- oder die Zeitgrenze erreicht ist, auch bei einem Auto, das kaum gefahren wird.
- Schlagen Sie niemals vor, Bremsen, Reifen, Lenkung, Warnleuchten oder eine Pflichtprüfung aufzuschieben, um Geld oder einen Besuch zu sparen.
- Eine Position ohne Aufzeichnung ist unbekannt, nicht erledigt: Sagen Sie das und schlagen Sie vor, das Serviceheft zu prüfen oder die Werkstatt zu fragen.
- Kosten sind Schätzungen; sagen Sie dem Benutzer, er soll ein Angebot einholen, und erwähnen Sie Garantiebedingungen, bevor Sie eine freie Werkstatt vorschlagen.
## Intervalle und erschwerte Bedingungen
## Woher die Intervalle kommen
- Das Handbuch oder Serviceheft für Ihr genaues Modell, Ihren Motor und Ihr Baujahr. Dasselbe Modell kann unterschiedliche Pläne für Benzin-, Diesel- und Hybridmotoren haben und für "feste" vs. "flexible/Longlife"-Serviceintervalle.
- Viele Autos zeigen den nächsten Ölservice auf dem Armaturenbrett an (Serviceanzeige). Wenn das vom Heft abweicht, fragen Sie den Händler, auf welches Regime das Auto eingestellt ist.
- Garantiebedingungen beachten: Einige Hersteller verlangen Wartung nach ihrem Plan mit Originalteilen; freie Werkstätten sind normalerweise in Ordnung, wenn sie den Plan befolgen und Rechnungen aufbewahren.
## Was zuerst eintritt
Jede Position mit sowohl Distanz- als auch Zeitintervall ist fällig, wenn die FRÜHERE der beiden Grenzen erreicht ist. Wenig gefahrene Autos erreichen die Zeitgrenze (Öl altert, Bremsflüssigkeit zieht Wasser an, Gummiriemen und Reifen altern); viel gefahrene Autos erreichen die Distanzgrenze. Der Planer projiziert die Distanzgrenze mit Ihrer Jahresfahrleistung auf ein Datum.
## Erschwerte Bedingungen
Die meisten Handbücher listen kürzere Intervalle, wenn das Auto hauptsächlich genutzt wird für:
- Kurzstrecken (unter ca. 10 km, Motor wird selten richtig warm), besonders bei kaltem Wetter;
- Stop-and-go-Stadtverkehr, Taxi- oder Lieferdienst, langes Leerlaufen;
- Ziehen eines Anhängers oder Wohnwagens, Dachlasten, Bergfahrten;
- staubige oder sandige Straßen, sehr heißes oder sehr kaltes Klima.
Wenn einer dieser Punkte den Großteil Ihrer Fahrten beschreibt, verwenden Sie `--severe` (oder `"severe": true`), damit Positionen mit `severe_km` / `severe_months` das kürzere Intervall verwenden.
## Positionshinweise (typisch, prüfen Sie Ihr Handbuch)
| Position | Typische Basis | Hinweise |
|---|---|---|
| Motoröl und Filter | Distanz und Zeit | die wichtigste Routineposition; verwenden Sie die Ölspezifikation aus dem Handbuch |
| Inspektionsservice | Distanz und Zeit | der größere Service; umfasst normalerweise viele Checks |
| Bremsflüssigkeit | nur Zeit (oft 2 Jahre) | zieht Wasser an; niemals aufschieben |
| Bremsenprüfung / Beläge / Scheiben | Distanz und Zeit | nach Verschleiß ersetzen, nicht nach festem Intervall |
| Kühlmittel | Zeit oder "lebenslang" | jeden Winter Füllstand und Frostschutz prüfen |
| Luftfilter, Innenraumfilter | Distanz und Zeit | kürzer in staubigen oder verschmutzten Gebieten |
| Zündkerzen (Benziner) | Distanz und Zeit | lange Intervalle bei modernen Motoren |
| Zahnriemen (falls vorhanden) | Distanz und Zeit | Ausfall kann den Motor zerstören; wird oft mit der Wasserpumpe gewechselt; Steuerketten haben normalerweise kein Intervall |
| Reifen | Verschleiß und Alter | Profiltiefe monatlich prüfen; viele Hersteller empfehlen Ersatz nach 6-10 Jahren unabhängig vom Profil |
| Reifenrotation | Distanz | wenn Ihr Handbuch es empfiehlt (nicht bei unterschiedlichen Größen vorne/hinten) |
| Hauptuntersuchung | Zeit (Pflicht) | erste Prüfung nach einigen Jahren (z.B. 3 oder 4), dann alle 1-2 Jahre je nach Land |
| 12-V-Batterie | Zustand | vor dem Winter testen, sobald sie 4-5 Jahre alt ist |
## Pflichtprüfungen
Die Regeln unterscheiden sich je nach Land (TÜV/HU in Deutschland, MOT in Großbritannien, CT in Frankreich, TUVTURK in der Türkei, staatliche Inspektionen in den USA). Geben Sie die Verzögerung der ersten Prüfung Ihres Landes als `first_months` und das Wiederholungsintervall als `months` ein und markieren Sie die Position `"legal": true`. Viele Systeme erlauben eine etwas frühere Prüfung, ohne das Datum zu verlieren; prüfen Sie das, bevor Sie es in einen früheren Besuch bündeln.
## Was niemals aufschieben
Bremsen und Bremsflüssigkeit, Reifen (Profil, Schäden, Alter), Lenkungs- und Fahrwerksfehler, Warnleuchten (Öldruck, Bremsen, Motortemperatur) und die Pflichtprüfung. Der Planer markiert diese mit `safety` / `legal` und behandelt sie als hoch, wenn sie überfällig sind.
## Eingaben erhalten
- Tachostand und Datum jedes Besuchs: Rechnungen, Stempel im Serviceheft, der digitale Servicenachweis des Händlers, die Prüfbescheinigungen (sie dokumentieren den Tachostand).
- Preise: Ihre letzten Rechnungen oder ein Angebot; addieren Sie 10-20% für Teile, die oft gleichzeitig ersetzt werden (Scheibenwischer, Glühlampen, Bremsbeläge).
## Checks und Befundcodes
| Code | Schwere | Check | Was zu tun ist |
|---|---|---|---|
| CS01 | hoch: Sicherheits- oder Pflichtposten überfällig; mittel: anderer Posten überfällig | Fälligkeitsdatum oder Fälligkeitsdistanz überschritten | buchen; bei Sicherheitsposten bis dahin vorsichtig fahren |
| CS02 | mittel | fällig innerhalb `due_soon.days` oder `due_soon.km` | mit dem nächsten Besuch buchen |
| CS03 | mittel | keine Aufzeichnung der Position im Log: ab Erstzulassung gerechnet | Serviceheft / Belege / Händleraufzeichnung prüfen, bevor man dafür bezahlt |
| CS04 | hoch: Datum in der Zukunft, Stand über dem aktuellen Tachostand; mittel: Stand niedriger als ein früherer, unlesbarer Tachostand | Logkonsistenz | Tippfehler korrigieren; der Stand wird für Distanz- und Fälligkeitsberechnungen ignoriert |
| CS05 | info | zuletzt bei unter 60% beider Intervalle erledigt | in Ordnung, wenn beabsichtigt (erschwerte Nutzung, Garantie); sonst zu früh Geld ausgegeben |
| CS06 | info | Intervalle bei erschwerten Bedingungen in Verwendung | prüfen, ob die Bedingungen noch zutreffen |
| CS07 | info | Logposition nicht in Ihrem Plan (Reparaturen, Extras) | für die Historie behalten; Aliase hinzufügen, wenn es eine Planposition unter anderem Namen ist |
| CS08 | niedrig | Position mit Kosten von 400 oder mehr fällt in den Planungshorizont | dafür budgetieren; früh ein Angebot einholen |
| CS09 | mittel | nicht genug Tachostände (ein Stand mindestens 60 Tage zurück): Standard-Jahresfahrleistung verwendet | Stände hinzufügen (Prüfbescheinigungen, Tank-App, Fotos vom Armaturenbrett) |
## Urteil
- **Überfällig inklusive Sicherheit/Pflicht**: jetzt buchen.
- **Überfällig**: bald buchen.
- **Bald fällig** / **auf dem neuesten Stand**.
## Wie die Zahlen berechnet werden
- Jahresfahrleistung: vom letzten Logstand mindestens ein Jahr vor heute bis zum heutigen Stand (der früheste Stand, wenn keiner so alt ist).
- Nächste Fälligkeit: letztes Erledigungsdatum + Monate, und letzter Tachostand + km, umgerechnet in ein Datum mit Ihrer Tagesfahrleistung; das frühere gewinnt. Eine Position ohne Aufzeichnung zählt ab Erstzulassung bei 0 km (`first_months` ersetzt `months` für das erste Mal, z.B. eine erste Hauptuntersuchung nach 36 Monaten).
- Horizont: jede Position wiederholt sich ab dem Datum, an dem sie als erledigt angenommen wird (ihr Fälligkeitsdatum, oder heute, wenn überfällig).
- Besuche: Positionen, die innerhalb `bundle.days` und `bundle.km` des ersten Postens eines Besuchs fällig sind, schließen sich diesem Besuch an (vorgezogen, niemals nach hinten verschoben).
- Kosten: die `cost` jedes Vorkommens wird zu seinem Quartal addiert; monatliche Rücklage = Horizontsumme / Horizontmonate.
## Auto-JSON-Schlüssel
`vehicle{name, first_registered, unit}, odometer{reading, date}, driving{default_per_year, severe, severe_reasons}, currency, horizon_months, due_soon{km, days}, bundle{km, days}, schedule[{item, km, months, severe_km, severe_months, first_months, cost, safety, legal, aliases[]}]`.
## Servicelog-CSV
`date, odometer, item, cost, shop, notes` - mehrere Positionen in einem Besuch getrennt durch `;`. Namen werden ohne Beachtung der Groß-/Kleinschreibung mit Planpositionen und Aliasen abgeglichen. Die Kostenspalte ist für Ihre Aufzeichnungen; die Prognose verwendet die Planpreise.
## Vorlage
# Kfz-Serviceplan: <Auto, Kennzeichen>
**Datum:** <YYYY-MM-DD> **Tachostand:** <83.400 km> **Jahresfahrleistung:** <ca. 11.000 km> **Plan:** <Handbuch, normale / erschwerte Bedingungen>
## Jetzt erledigen
| Position | Grund | Geschätzte Kosten |
|---|---|---|
| <Bremsflüssigkeit> | <um 4 Monate überfällig (2-Jahres-Intervall) - Sicherheit> | <85> |
| <Hauptuntersuchung> | <fällig 2026-11-20 - Pflicht> | <60> |
## Nächste Besuche
| Wann (buchen bis) | Bei ca. | Positionen | Geschätzte Kosten |
|---|---|---|---|
| <2027-02-14> | <86.500 km> | <Öl und Filter, Bremsenprüfung> | <180> |
## Budget
- Nächste 12 Monate: <1.220> <EUR>
- Nächste 24 Monate: <2.445> <EUR> -> <102> pro Monat zurücklegen
- Große Positionen voraus: <Zahnriemensatz und Wasserpumpe, Nov. 2027, ca. 680>
## Zu prüfen
- [ ] <keine Aufzeichnung über den Zahnriemen - Serviceheft prüfen / Händler fragen>
- [ ] <genaues Bremsflüssigkeitsintervall für diesen Motor>
- [ ] <Garantie: Ist Händlerwartung erforderlich?>
## Checkliste für die Werkstatt
- Auto: <Marke, Modell, Motor, FIN falls gefragt>, Tachostand <...>
- Bitte erledigen: <Liste aus "Jetzt erledigen">
- Bitte prüfen und vor dem Ersetzen Angebot einholen: <Bremsbeläge/-scheiben, Reifen, Scheibenwischer, Batterie>
- Bitte Serviceheft stempeln / digitalen Nachweis senden und den Tachostand auf der Rechnung notieren.
*Intervalle aus <Quelle>; Kosten sind Schätzungen. Lassen Sie alles Ungewöhnliche (Warnleuchten, Geräusche, Ziehen, Vibrationen) sofort überprüfen.*
## Beispiel: car.json
{
"vehicle": {"name": "2019 Skoda Octavia Kombi 1.5 TSI (Benziner)", "first_registered": "2019-11-14", "unit": "km"},
"odometer": {"reading": 83400, "date": "2026-11-04"},
"driving": {"default_per_year": 12000, "severe": false, "severe_reasons": ["überwiegend Kurzstrecken im Winter", "gelegentliches Ziehen eines Wohnwagens"]},
"currency": "EUR",
"horizon_months": 24,
"due_soon": {"km": 1500, "days": 45},
"bundle": {"km": 2500, "days": 60},
"schedule": [
{"item": "Motoröl und Filter", "km": 15000, "months": 12, "severe_km": 7500, "severe_months": 6, "cost": 140, "aliases": ["Ölservice", "Ölwechsel"]},
{"item": "Inspektionsservice", "km": 30000, "months": 24, "severe_km": 20000, "cost": 190, "aliases": ["Inspektion"]},
{"item": "Bremsenprüfung", "km": 20000, "months": 12, "cost": 40, "safety": true},
{"item": "Bremsflüssigkeit", "months": 24, "cost": 85, "safety": true},
{"item": "Luftfilter", "km": 60000, "months": 48, "severe_km": 30000, "cost": 45},
{"item": "Innenraumfilter", "km": 30000, "months": 24, "cost": 40, "aliases": ["Pollenfilter"]},
{"item": "Zündkerzen", "km": 60000, "months": 72, "cost": 160},
{"item": "Reifenrotation", "km": 10000, "cost": 30},
{"item": "Reifen (Satz von 4)", "km": 45000, "months": 72, "cost": 560, "safety": true},
{"item": "Zahnriemensatz und Wasserpumpe", "km": 120000, "months": 96, "cost": 680},
{"item": "Hauptuntersuchung", "months": 12, "first_months": 36, "cost": 60, "legal": true, "aliases": ["tüv", "hu", "periodische Überprüfung"]}
]
}
## Beispiel: service-log.csv
date,odometer,item,cost,shop,notes
2020-11-20,11500,Ölservice; Bremsenprüfung,165,Vertragshändler,erster Service
2021-11-05,22800,Ölservice; Bremsenprüfung; Innenraumfilter,210,Vertragshändler,
2022-05-28,33900,Ölservice; Inspektionsservice; Bremsflüssigkeit; Luftfilter,395,Vertragshändler,
2022-11-08,38000,Hauptuntersuchung,55,Prüfstelle,bestanden
2023-06-15,45150,Ölservice; Bremsenprüfung; Reifenrotation; Reifen (Satz von 4),735,QuickFit,neue Reifen 205/55 R16
2023-09-02,4790,Steinschlagreparatur Windschutzscheibe,60,GlassFix,
2023-11-13,49000,Hauptuntersuchung,55,Prüfstelle,bestanden
2024-07-01,56900,Ölservice; Inspektionsservice; Bremsflüssigkeit; Zündkerzen; Bremsenprüfung,520,Vertragshändler,
2024-11-12,59800,Hauptuntersuchung,58,Prüfstelle,Hinweis: hinterer Wischer
2025-08-20,70100,Ölservice; Bremsenprüfung; Innenraumfilter; Reifenrotation,240,Eckwerkstatt,
2025-11-20,73000,Hauptuntersuchung,60,Prüfstelle,bestanden
2026-02-14,75300,Ölwechsel; Bremsenprüfung,150,Eckwerkstatt,Kontrollleuchte ging an
## Beispiel: service-plan.md
# Serviceplan: 2019 Skoda Octavia Kombi 1.5 TSI (Benziner)
Heute 2026-11-04, Tachostand 83.400 km. Sie fahren ca. 11.008 km/Jahr (2025-08-20 70.100 -> 2026-11-04 83.400). Intervalle für normale Bedingungen, Planungshorizont 24 Monate. Quelle: examples/service-log.csv.
**Urteil: 4 Position(en) überfällig, einschließlich Sicherheits-/Pflichtposten - jetzt einen Besuch buchen**
Nächste 12 Monate: ca. 1.220,00 EUR; nächste 24 Monate: 2.445,00 EUR (ca. 101,88 EUR pro Monat zurücklegen). Befunde: 1 hoch, 6 mittel, 2 niedrig, 3 info.
## Positionen
| Position | Intervall | Zuletzt erledigt | Fällig bis Datum | Fällig bei km | Nächstes (prognostiziert) | Auslöser | Status | Geschätzte Kosten |
|---|---|---|---|---|---|---|---|---|
| Inspektionsservice | 30.000 km / 24 Monate | 2024-07-01 bei 56.900 | 2026-07-01 | 86.900 | 2026-11-04 | Zeit | **ÜBERFÄLLIG** | 190,00 |
| Bremsflüssigkeit [Sicherheit] | 24 Monate | 2024-07-01 bei 56.900 | 2026-07-01 | - | 2026-11-04 | Zeit | **ÜBERFÄLLIG** | 85,00 |
| Luftfilter | 60.000 km / 48 Monate | 2022-05-28 bei 33.900 | 2026-05-28 | 93.900 | 2026-11-04 | Zeit | **ÜBERFÄLLIG** | 45,00 |
| Reifenrotation | 10.000 km | 2025-08-20 bei 70.100 | - | 80.100 | 2026-11-04 | Distanz | **ÜBERFÄLLIG** | 30,00 |
| Hauptuntersuchung [Pflicht] | 12 Monate | 2025-11-20 bei 73.000 | 2026-11-20 | - | 2026-11-20 | Zeit | **BALD FÄLLIG** | 60,00 |
| Motoröl und Filter | 15.000 km / 12 Monate | 2026-02-14 bei 75.300 | 2027-02-14 | 90.300 | 2027-02-14 | Zeit | OK | 140,00 |
| Bremsenprüfung [Sicherheit] | 20.000 km / 12 Monate | 2026-02-14 bei 75.300 | 2027-02-14 | 95.300 | 2027-02-14 | Zeit | OK | 40,00 |
| Reifen (Satz von 4) [Sicherheit] | 45.000 km / 72 Monate | 2023-06-15 bei 45.150 | 2029-06-15 | 90.150 | 2027-06-15 | Distanz | OK | 560,00 |
| Innenraumfilter | 30.000 km / 24 Monate | 2025-08-20 bei 70.100 | 2027-08-20 | 100.100 | 2027-08-20 | Zeit | OK | 40,00 |
| Zahnriemensatz und Wasserpumpe | 120.000 km / 96 Monate | keine Aufzeichnung (ab Neuzulassung gerechnet, 2019-11-14) | 2027-11-14 | 120.000 | 2027-11-14 | Zeit | OK | 680,00 |
| Zündkerzen | 60.000 km / 72 Monate | 2024-07-01 bei 56.900 | 2030-07-01 | 116.900 | 2029-11-18 | Distanz | OK | 160,00 |
## Werkstattbesuche (Positionen innerhalb von 60 Tagen / 2.500 km werden gebündelt)
| # | Buchen bis | Bei ca. | Positionen | Geschätzte Kosten |
|---|---|---|---|---|
| 1 | 2026-11-04 (jetzt) | 83.400 km | Luftfilter (überfällig), Bremsflüssigkeit (überfällig), Inspektionsservice (überfällig), Reifenrotation (überfällig), Hauptuntersuchung | 410,00 |
| 2 | 2027-02-14 | 86.476 km | Bremsenprüfung, Motoröl und Filter | 180,00 |
| 3 | 2027-06-15 | 90.125 km | Reifen (Satz von 4) | 560,00 |
| 4 | 2027-08-20 | 92.116 km | Innenraumfilter, Reifenrotation | 70,00 |
| 5 | 2027-11-14 | 94.710 km | Zahnriemensatz und Wasserpumpe, Hauptuntersuchung | 740,00 |
| 6 | 2028-02-14 | 97.484 km | Bremsenprüfung, Motoröl und Filter | 180,00 |
| 7 | 2028-08-27 | 103.365 km | Reifenrotation | 30,00 |
| 8 | 2028-11-04 | 105.446 km | Bremsflüssigkeit, Inspektionsservice | 275,00 |
## Kostenprognose nach Quartal
| Quartal | Geschätzte Kosten |
|---|---|
| 2026-Q4 | 410,00 |
| 2027-Q1 | 180,00 |
| 2027-Q2 | 560,00 |
| 2027-Q3 | 40,00 |
| 2027-Q4 | 770,00 |
| 2028-Q1 | 180,00 |
| 2028-Q3 | 30,00 |
| 2028-Q4 | 275,00 |
| **Gesamt 24 Monate** | **2.445,00** |
## Befunde
- **HOCH CS01** Bremsflüssigkeit ist um 126 Tage überfällig (Sicherheitsposten) - jetzt buchen
- **MITTEL CS01** Luftfilter ist um 160 Tage überfällig - jetzt buchen
- **MITTEL CS01** Inspektionsservice ist um 126 Tage überfällig - jetzt buchen
- **MITTEL CS01** Reifenrotation ist um 3.300 km überfällig - jetzt buchen
- **MITTEL CS02** Hauptuntersuchung ist bald fällig: 2026-11-20
- **MITTEL CS03** Zahnriemensatz und Wasserpumpe: keine Aufzeichnung in Ihrem Log - ab Neuzulassung gerechnet; Serviceheft oder Belege prüfen
- **MITTEL CS04** Zeile 7: Tachostand 4.790 am 2023-09-02 ist niedriger als 45.150 am 2023-06-15 - Tippfehler oder falsches Auto? Stand ignoriert
- **NIEDRIG CS08** Zahnriemensatz und Wasserpumpe (ca. 680,00) fällt am 2027-11-14 in den Plan - jetzt dafür budgetieren
- **NIEDRIG CS08** Reifen (Satz von 4) (ca. 560,00) fallen am 2027-06-15 in den Plan - jetzt dafür budgetieren
- **INFO CS05** Bremsenprüfung wurde zuletzt nach 5.200 km / 6 Monaten erledigt (unter 60% des Intervalls) - bei erschwerten Bedingungen in Ordnung, sonst zu früh Geld ausgegeben
- **INFO CS05** Motoröl und Filter wurden zuletzt nach 5.200 km / 6 Monaten erledigt (unter 60% des Intervalls) - bei erschwerten Bedingungen in Ordnung, sonst zu früh Geld ausgegeben
- **INFO CS07** Zeile 7: 'Steinschlagreparatur Windschutzscheibe' ist nicht in Ihrem Plan - nur in der Historie behalten
Intervalle und Preise stammen aus Ihrer Autodatei: Prüfen Sie sie gegen das Handbuch oder Serviceheft für Ihren genauen Motor und folgen Sie Ihrer Werkstatt, wenn sie Verschleiß früher feststellt. Kosten sind Schätzungen; Reifen und Bremsen hängen auch vom Verschleiß ab, nicht nur von der Distanz.
## Python-Skript (car_service_plan.py)
```python
#!/usr/bin/env python3
"""Kfz-Wartungsplaner (nur Standardbibliothek, Python 3.8+).
Liest den Wartungsplan Ihres Autos (Positionen mit Distanz- und/oder Zeitintervallen, aus dem
Handbuch oder Serviceheft) und Ihre Servicehistorie (CSV), schätzt, wie weit Sie fahren,
anhand der Tachostände im Log, und ermittelt für jede Position, wann sie zuletzt erledigt wurde,
wann sie als Nächstes fällig ist (Distanz oder Zeit, je nachdem, was zuerst eintritt), was
überfällig oder bald fällig ist, welche Positionen einen Werkstattbesuch teilen können und was
die Wartung in den nächsten Monaten kosten wird.
Verwendung:
car_service_plan.py AUTO.json SERVICE_LOG.csv [--odometer N] [--today YYYY-MM-DD] [--severe]
[--horizon MONATE] [--json]
car_service_plan.py --self-test
Exit: 0 = nichts Überfälliges bei einem Sicherheits-/Pflichtposten, 1 = ein Sicherheits- oder
Pflichtposten ist überfällig (oder das Log hat einen hohen Befund), 2 = fehlerhafte Eingabe.
"""
import argparse, calendar, csv, datetime, io, json, sys
SEV = {"hoch": 0, "mittel": 1, "niedrig": 2, "info": 3}
CAR_KEYS = {"vehicle", "odometer", "driving", "currency", "horizon_months", "due_soon", "bundle", "schedule"}
class InputError(Exception):
pass
def pdate(s, was):
try:
return datetime.date.fromisoformat(str(s).strip())
except ValueError:
raise InputError("%s muss YYYY-MM-DD sein, erhalten: %r" % (was, s))
def add_months(d, m):
y, mo = divmod(d.month - 1 + int(m), 12)
y += d.year
mo += 1
return datetime.date(y, mo, min(d.day, calendar.monthrange(y, mo)[1]))
def num(x, was):
try:
return float(str(x).replace(",", "").replace(" ", ""))
except ValueError:
raise InputError("%s ist keine Zahl: %r" % (was, x))
def fk(x):
return "{:,.0f}".format(x)
def fm(x):
return "{:,.2f}".format(x)
# ---------------------------------------------------------------- laden
def load_car(cfg, odometer=None, today=None, severe=None, horizon=None):
if not isinstance(cfg, dict):
raise InputError("Autodatei muss ein JSON-Objekt sein")
bad = set(cfg) - CAR_KEYS
if bad:
raise InputError("unbekannte(r) Schlüssel in Autodatei: %s" % ", ".join(sorted(bad)))
v = cfg.get("vehicle") or {}
odo = cfg.get("odometer") or {}
drv = cfg.get("driving") or {}
c = {"name": v.get("name", "Auto"), "unit": v.get("unit", "km"),
"first": pdate(v["first_registered"], "vehicle.first_registered") if v.get("first_registered") else None,
"today": pdate(today or odo.get("date") or datetime.date.today().isoformat(), "today"),
"odo": num(odometer if odometer is not None else odo.get("reading"), "Tachostand") if (odometer is not None or odo.get("reading") is not None) else None,
"default_per_year": num(drv.get("default_per_year", 12000), "driving.default_per_year"),
"severe": bool(drv.get("severe", False)) if severe is None else severe,
"severe_reasons": drv.get("severe_reasons", []),
"currency": cfg.get("currency", ""),
"horizon": int(horizon or cfg.get("horizon_months", 24)),
"soon_km": num((cfg.get("due_soon") or {}).get("km", 1500), "due_soon.km"),
"soon_days": int((cfg.get("due_soon") or {}).get("days", 45)),
"bundle_km": num((cfg.get("bundle") or {}).get("km", 2500), "bundle.km"),
"bundle_days": int((cfg.get("bundle") or {}).get("days", 60)),
"items": []}
if c["odo"] is None:
raise InputError("geben Sie den aktuellen Tachostand an (odometer.reading oder --odometer)")
for i, it in enumerate(cfg.get("schedule") or [], 1):
name = it.get("item")
if not name:
raise InputError("Planposition %d hat keinen 'item'-Namen" % i)
km = it.get("severe_km") if c["severe"] and it.get("severe_km") else it.get("km")
mo = it.get("severe_months") if c["severe"] and it.get("severe_months") else it.get("months")
if not km and not mo:
raise InputError("Planposition '%s' benötigt km und/oder Monate" % name)
c["items"].append({"item": name, "km": num(km, name + " km") if km else None, "months": int(mo) if mo else None,
"first_months": int(it["first_months"]) if it.get("first_months") else None,
"cost": num(it.get("cost", 0), name + " Kosten"), "safety": bool(it.get("safety")), "legal": bool(it.get("legal")),
"severe_used": bool(c["severe"] and (it.get("severe_km") or it.get("severe_months"))),
"aliases": [a.lower().strip() for a in it.get("aliases", [])] + [name.lower().strip()]})
if not c["items"]:
raise InputError("Autodatei benötigt ein nicht-leeres 'schedule'")
return c
def load_log(c, text, F):
rows = list(csv.DictReader(io.StringIO(text)))
if rows and not {"date", "odometer", "item"} <= set(rows[0]):
raise InputError("Servicelog benötigt Spalten date, odometer, item (hat %s)" % ", ".join(rows[0]))
entries, readings = [], []
for n, r in enumerate(rows, 2):
try:
d = pdate(r.get("date"), "date (Zeile %d)" % n)
except InputError as e:
F.append(("hoch", "CS04", "Zeile %d: %s - Zeile ignoriert" % (n, e))); continue
if d > c["today"]:
F.append(("hoch", "CS04", "Zeile %d: Datum %s liegt in der Zukunft - Zeile ignoriert" % (n, d))); continue
odo = None
if (r.get("odometer") or "").strip():
try:
odo = num(r["odometer"], "Tachostand (Zeile %d)" % n)
except InputError as e:
F.append(("mittel", "CS04", "%s - Distanz für diese Zeile ignoriert" % e))
items = [x.strip() for x in (r.get("item") or "").split(";") if x.strip()]
entries.append({"row": n, "date": d, "odo": odo, "items": items, "cost": r.get("cost", ""), "shop": r.get("shop", "")})
entries.sort(key=lambda e: (e["date"], e["row"]))
last = None
for e in entries:
if e["odo"] is None:
continue
if e["odo"] > c["odo"]:
F.append(("hoch", "CS04", "Zeile %d: Tachostand %s am %s ist höher als der aktuelle Stand %s - Tippfehler? Stand ignoriert" % (e["row"], fk(e["odo"]), e["date"], fk(c["odo"]))))
e["odo"] = None; continue
if last and e["odo"] < last["odo"]:
F.append(("mittel", "CS04", "Zeile %d: Tachostand %s am %s ist niedriger als %s am %s - Tippfehler oder falsches Auto? Stand ignoriert" % (e["row"], fk(e["odo"]), e["date"], fk(last["odo"]), last["date"])))
e["odo"] = None; continue
last = e
readings.append((e["date"], e["odo"]))
readings.append((c["today"], c["odo"]))
return entries, readings
def distance_rate(c, readings, F):
"""km pro Tag vom letzten Stand mindestens ein Jahr vor heute (sonst der früheste)."""
end_d, end_o = readings[-1]
older = [r for r in readings[:-1] if (end_d - r[0]).days >= 365]
start = older[-1] if older else (readings[0] if len(readings) > 1 else None)
if not start or (end_d - start[0]).days < 60 or end_o <= start[1]:
F.append(("mittel", "CS09", "nicht genug Tachostände, um Ihre Jahresfahrleistung zu schätzen - verwende die Standard-%s %s/Jahr" % (fk(c["default_per_year"]), c["unit"])))
return c["default_per_year"] / 365.0, "Standard"
rate = (end_o - start[1]) / float((end_d - start[0]).days)
return rate, "%s %s -> %s %s" % (start[0], fk(start[1]), end_d, fk(end_o))
# ---------------------------------------------------------------- planen
def plan(c, log_text):
F = []
entries, readings = load_log(c, log_text, F)
rate, basis = distance_rate(c, readings, F)
known = {a: it for it in c["items"] for a in it["aliases"]}
done = {it["item"]: [] for it in c["items"]}
for e in entries:
for name in e["items"]:
it = known.get(name.lower())
if not it:
F.append(("info", "CS07", "Zeile %d: '%s' ist nicht in Ihrem Plan - nur in der Historie behalten" % (e["row"], name)))
continue
done[it["item"]].append(e)
def date_at_km(km):
return c["today"] + datetime.timedelta(days=max(0.0, (km - c["odo"]) / rate)) if rate > 0 else None
def km_at_date(d):
return c["odo"] + rate * (d - c["today"]).days
end = add_months(c["today"], c["horizon"])
rows, occ = [], []
for it in c["items"]:
hist = done[it["item"]]
last = hist[-1] if hist else None
if last:
base_d, base_km, base_note = last["date"], last["odo"], "%s bei %s" % (last["date"], fk(last["odo"]) if last["odo"] is not None else "?")
else:
if not c["first"]:
raise InputError("keine Aufzeichnung über '%s' und kein vehicle.first_registered zum Zählen ab" % it["item"])
base_d, base_km, base_note = c["first"], 0.0, "keine Aufzeichnung (ab Neuzulassung gerechnet, %s)" % c["first"]
F.append(("mittel", "CS03", "%s: keine Aufzeichnung in Ihrem Log - ab Neuzulassung gerechnet; Serviceheft oder Belege prüfen" % it["item"]))
months = it["months"]
if not last and it["first_months"]:
months = it["first_months"]
due_d = add_months(base_d, months) if months else None
due_km = base_km + it["km"] if (it["km"] and base_km is not None) else None
km_d = date_at_km(due_km) if due_km is not None else None
cands = [(x, w) for x, w in ((due_d, "Zeit"), (km_d, "Distanz")) if x]
if not cands:
due_date, trigger = c["today"], "unbekannt"
else:
due_date, trigger = min(cands)
overdue = (due_d is not None and due_d < c["today"]) or (due_km is not None and due_km < c["odo"])
soon = not overdue and ((due_d is not None and (due_d - c["today"]).days <= c["soon_days"]) or
(due_km is not None and due_km - c["odo"] <= c["soon_km"]))
status = "ÜBERFÄLLIG" if overdue else ("BALD FÄLLIG" if soon else "OK")
if overdue:
late = []
if due_d is not None and due_d < c["today"]:
late.append("%d Tage" % (c["today"] - due_d).days)
if due_km is not None and due_km < c["odo"]:
late.append("%s %s" % (fk(c["odo"] - due_km), c["unit"]))
sev = "hoch" if (it["safety"] or it["legal"]) else "mittel"
F.append((sev, "CS01", "%s ist um %s überfällig%s - jetzt buchen" % (it["item"], " und ".join(late), " (Sicherheitsposten)" if it["safety"] else (" (Pflichtanforderung)" if it["legal"] else ""))))
elif soon:
F.append(("mittel", "CS02", "%s ist bald fällig: %s%s" % (it["item"], due_d if due_d and (due_d - c["today"]).days <= c["soon_days"] else "", (" / bei %s %s" % (fk(due_km), c["unit"])) if due_km is not None else "")))
if len(hist) >= 2 and hist[-2]["odo"] is not None and hist[-1]["odo"] is not None:
a, b = hist[-2], hist[-1]
fr = []
if it["km"]:
fr.append((b["odo"] - a["odo"]) / it["km"])
if it["months"]:
fr.append(((b["date"] - a["date"]).days / 30.44) / it["months"])
if fr and max(fr) < 0.6:
F.append(("info", "CS05", "%s wurde zuletzt nach %s %s / %d Monaten erledigt (unter 60%% des Intervalls) - bei erschwerten Bedingungen in Ordnung, sonst zu früh Geld ausgegeben" % (it["item"], fk(b["odo"] - a["odo"]), c["unit"], round((b["date"] - a["date"]).days / 30.44))))
rows.append({"item": it["item"], "last": base_note, "interval": " / ".join(x for x in [("%s %s" % (fk(it["km"]), c["unit"])) if it["km"] else "", ("%d Monate" % months) if months else ""] if x),
"due_date": due_d, "due_km": due_km, "next": max(due_date, c["today"]), "trigger": trigger, "status": status, "cost": it["cost"],
"safety": it["safety"] or it["legal"], "flag": "Pflicht" if it["legal"] else ("Sicherheit" if it["safety"] else ""), "severe": it["severe_used"]})
# Vorkommen im Horizont (jedes wird an seinem Fälligkeitsdatum als erledigt angenommen)
d, k, first = max(due_date, c["today"]), due_km, True
guard = 0
while d <= end and guard < 50:
guard += 1
occ.append({"item": it["item"], "date": d, "km": km_at_date(d), "cost": it["cost"], "overdue": first and overdue})
first = False
nd = add_months(d, it["months"]) if it["months"] else None
nk = km_at_date(d) + it["km"] if it["km"] else None
cands = [x for x in (nd, date_at_km(nk) if nk is not None else None) if x]
if not cands:
break
d = min(cands)
if (it["cost"] >= 400) and any(o["item"] == it["item"] for o in occ):
o = next(o for o in occ if o["item"] == it["item"])
F.append(("niedrig", "CS08", "%s (ca. %s) fällt am %s in den Plan - jetzt dafür budgetieren" % (it["item"], fm(it["cost"]), o["date"])))
if c["severe"]:
F.append(("info", "CS06", "Intervalle bei erschwerten Bedingungen verwendet%s" % ((" (" + ", ".join(c["severe_reasons"]) + ")") if c["severe_reasons"] else "")))
# Besuche: Vorkommen innerhalb des Bündelungsfensters gruppieren
occ.sort(key=lambda o: (o["date"], o["item"]))
visits = []
for o in occ:
v = visits[-1] if visits else None
if v and (o["date"] - v["date"]).days <= c["bundle_days"] and o["km"] - v["km"] <= c["bundle_km"]:
v["items"].append(o); continue
visits.append({"date": o["date"], "km": o["km"], "items": [o]})
# Kosten nach Quartal
quarters = {}
for o in occ:
q = "%d-Q%d" % (o["date"].year, (o["date"].month - 1) // 3 + 1)
quarters[q] = quarters.get(q, 0.0) + o["cost"]
y1 = sum(o["cost"] for o in occ if o["date"] < add_months(c["today"], 12))
total = sum(o["cost"] for o in occ)
F.sort(key=lambda f: (SEV[f[0]], f[1], f[2]))
return {"car": c, "rate": rate, "basis": basis, "rows": rows, "occ": occ, "visits": visits, "quarters": quarters,
"next12": y1, "total": total, "findings": F, "entries": entries}
# ---------------------------------------------------------------- Bericht
def report(res, title):
c, F = res["car"], res["findings"]
cnt = {s: sum(1 for f in F if f[0] == s) for s in SEV}
cur = (" " + c["currency"]) if c["currency"] else ""
ov = [r for r in res["rows"] if r["status"] == "ÜBERFÄLLIG"]
soon = [r for r in res["rows"] if r["status"] == "BALD FÄLLIG"]
L = ["# Serviceplan: %s" % c["name"], ""]
L.append("Heute %s, Tachostand %s %s. Sie fahren ca. %s %s/Jahr (%s). %s-Intervalle, Planungshorizont %d Monate. Quelle: %s." % (
c["today"], fk(c["odo"]), c["unit"], fk(res["rate"] * 365), c["unit"], res["basis"], "Erschwerte Bedingungen" if c["severe"] else "Normale Bedingungen", c["horizon"], title))
L.append("")
if any(r["safety"] for r in ov):
L.append("**Urteil: %d Position(en) überfällig, einschließlich Sicherheits-/Pflichtposten - jetzt einen Besuch buchen**" % len(ov))
elif ov:
L.append("**Urteil: %d Position(en) überfällig - bald einen Besuch buchen**" % len(ov))
elif soon:
L.append("**Urteil: nichts überfällig, %d Position(en) bald fällig**" % len(soon))
else:
L.append("**Urteil: auf dem neuesten Stand**")
L.append("")
L.append("Nächste 12 Monate: ca. %s%s; nächste %d Monate: %s%s (ca. %s%s pro Monat zurücklegen). Befunde: %d hoch, %d mittel, %d niedrig, %d info." % (
fm(res["next12"]), cur, c["horizon"], fm(res["total"]), cur, fm(res["total"] / c["horizon"]), cur, cnt["hoch"], cnt["mittel"], cnt["niedrig"], cnt["info"]))
L += ["", "## Positionen", "", "| Position | Intervall | Zuletzt erledigt | Fällig bis Datum | Fällig bei %s | Nächstes (prognostiziert) | Auslöser | Status | Geschätzte Kosten |" % c["unit"], "|---|---|---|---|---|---|---|---|---|"]
order = {"ÜBERFÄLLIG": 0, "BALD FÄLLIG": 1, "OK": 2}
for r in sorted(res["rows"], key=lambda r: (order[r["status"]], r["next"])):
L.append("| %s%s | %s | %s | %s | %s | %s | %s | %s | %s |" % (
r["item"], (" [%s]" % r["flag"]) if r["flag"] else "", r["interval"] + (" (erschwert)" if r["severe"] else ""), r["last"],
r["due_date"] or "-", fk(r["due_km"]) if r["due_km"] is not None else "-", r["next"], r["trigger"],
"**%s**" % r["status"] if r["status"] != "OK" else "OK", fm(r["cost"])))
L += ["", "## Werkstattbesuche (Positionen innerhalb von %d Tagen / %s %s werden gebündelt)" % (c["bundle_days"], fk(c["bundle_km"]), c["unit"]), "",
"| # | Buchen bis | Bei ca. | Positionen | Geschätzte Kosten |", "|---|---|---|---|---|"]
for i, v in enumerate(res["visits"], 1):
L.append("| %d | %s%s | %s %s | %s | %s |" % (i, v["date"], " (jetzt)" if v["date"] == c["today"] else "", fk(v["km"]), c["unit"],
", ".join(o["item"] + (" (überfällig)" if o["overdue"] else "") for o in v["items"]), fm(sum(o["cost"] for o in v["items"]))))
L += ["", "## Kostenprognose nach Quartal", "", "| Quartal | Geschätzte Kosten |", "|---|---|"]
for q in sorted(res["quarters"]):
L.append("| %s | %s |" % (q, fm(res["quarters"][q])))
L.append("| **Gesamt %d Monate** | **%s** |" % (c["horizon"], fm(res["total"])))
L += ["", "## Befunde", ""]
L += ["- **%s %s** %s" % (s.upper(), code, m) for s, code, m in F] or ["- keine"]
L += ["", "Intervalle und Preise stammen aus Ihrer Autodatei: Prüfen Sie sie gegen das Handbuch oder Serviceheft für Ihren genauen Motor und folgen Sie Ihrer Werkstatt, wenn sie Verschleiß früher feststellt. Kosten sind Schätzungen; Reifen und Bremsen hängen auch vom Verschleiß ab, nicht nur von der Distanz."]
return "\n".join(L) + "\n"
def to_json(res):
def j(x):
if isinstance(x, datetime.date):
return x.isoformat()
if isinstance(x, float):
return round(x, 2)
if isinstance(x, dict):
return {k: j(v) for k, v in x.items()}
if isinstance(x, list):
return [j(v) for v in x]
return x
return j({"km_per_year": res["rate"] * 365, "items": res["rows"], "visits": [{"date": v["date"], "km": v["km"], "items": [o["item"] for o in v["items"]],
"cost": sum(o["cost"] for o in v["items"])} for v in res["visits"]], "quarters": res["quarters"], "next12": res["next12"], "total": res["total"],
"findings": [{"severity": s, "code": c, "message": m} for s, c, m in res["findings"]]})
# ---------------------------------------------------------------- Selbsttest
def self_test():
tests = []
def t(name, cond):
tests.append((name, bool(cond)))
def car(**kw):
cfg = {"vehicle": {"name": "T", "first_registered": "2020-01-01"}, "odometer": {"reading": 50000, "date": "2026-01-01"},
"schedule": [{"item": "Öl", "km": 15000, "months": 12, "severe_km": 7500, "severe_months": 6, "cost": 100, "aliases": ["ölwechsel"]}]}
sched = kw.pop("schedule", None)
if sched:
cfg["schedule"] = sched
cfg.update(kw)
return cfg
H = "date,odometer,item,cost,shop\n"
def run(cfg, log, **kw):
return plan(load_car(cfg, **kw), H + log)
def row(res, name):
return next(r for r in res["rows"] if r["item"] == name)
def codes(res):
return [(f[0], f[1]) for f in res["findings"]]
log = "2025-01-01,40000,Öl,90,X\n"
r = run(car(), log)
t("Rate aus Ständen: 10000 km/Jahr", abs(r["rate"] * 365 - 10000) < 1)
t("Zeit zuerst fällig (12 Monate)", row(r, "Öl")["due_date"] == datetime.date(2026, 1, 1) and row(r, "Öl")["trigger"] == "Zeit")
t("Heute fällig zählt als bald fällig", row(r, "Öl")["status"] == "BALD FÄLLIG")
r = run(car(), "2025-06-01,40000,Öl,90,X\n")
t("Distanz löst zuerst aus, wenn viel gefahren wird", row(r, "Öl")["due_km"] == 55000 and row(r, "Öl")["trigger"] == "Distanz")
r = run(car(), "2024-06-01,30000,Öl,90,X\n")
t("Überfällig nach Zeit und Distanz -> CS01", row(r, "Öl")["status"] == "ÜBERFÄLLIG" and ("mittel", "CS01") in codes(r))
sched = [{"item": "Bremsflüssigkeit", "months": 24, "cost": 80, "safety": True}]
r = run(car(schedule=sched), "2023-06-01,30000,Bremsflüssigkeit,80,X\n2025-01-01,40000,Anderes,0,X\n")
t("Überfälliger Sicherheitsposten -> CS01 hoch", ("hoch", "CS01") in codes(r))
t("Position nicht im Plan -> CS07", ("info", "CS07") in codes(r))
r = run(car(), "2025-01-01,40000,ölwechsel,90,X\n")
t("Aliase ohne Beachtung der Groß-/Kleinschreibung abgeglichen", row(r, "Öl")["last"].startswith("2025-01-01"))
r = run(car(), "2025-01-01,40000,Wischer,20,X\n")
t("Keine Aufzeichnung -> ab Neuzulassung gerechnet, CS03", ("mittel", "CS03") in codes(r) and "keine Aufzeichnung" in row(r, "Öl")["last"])
r = run(car(), "2025-01-01,40000,Öl,90,X\n", severe=True)
t("Intervalle bei erschwerten Bedingungen verwendet", row(r, "Öl")["due_date"] == datetime.date(2025, 7, 1) and ("info", "CS06") in codes(r))
r = run(car(), "2024-01-01,30000,Öl,90,X\n2024-06-01,20000,Öl,90,X\n2025-01-01,40000,Öl,90,X\n")
t("Tachostand sinkt -> CS04, Stand ignoriert", ("mittel", "CS04") in codes(r))
r = run(car(), "2025-01-01,40000,Öl,90,X\n2027-01-01,60000,Öl,90,X\n")
t("Zukünftiges Datum -> CS04 hoch", ("hoch", "CS04") in codes(r))
r = run(car(), "2025-01-01,90000,Öl,90,X\n")
t("Stand über aktuellem Tachostand -> CS04 hoch", ("hoch", "CS04") in codes(r))
r = run(car(), "")
t("Keine Stände -> Standardrate + CS09", ("mittel", "CS09") in codes(r) and abs(r["rate"] * 365 - 12000) < 1)
r = run(car(), "2025-01-01,40000,Öl,90,X\n2025-04-01,42000,Öl,90,X\n")
t("Früh erledigt -> CS05", ("info", "CS05") in codes(r))
sched = [{"item": "Prüfung", "months": 12, "first_months": 36, "cost": 50, "legal": True}]
r = run(car(vehicle={"first_registered": "2024-01-01"}, schedule=sched), "2025-01-01,40000,Anderes,0,X\n")
t("first_months für erste Prüfung ab Neuzulassung", row(r, "Prüfung")["due_date"] == datetime.date(2027, 1, 1))
sched = [{"item": "A", "months": 12, "cost": 100}, {"item": "B", "months": 12, "cost": 50}]
r = run(car(schedule=sched), "2025-02-01,41000,A,0,X\n2025-03-15,42000,B,0,X\n")
t("Positionen innerhalb des Bündelungsfensters teilen einen Besuch", len(r["visits"][0]["items"]) == 2)
t("Horizont wiederholt jährliche Positionen", sum(1 for o in r["occ"] if o["item"] == "A") == 2)
t("12-Monats- und Gesamtprognose", r["next12"] == 150 and r["total"] == 300)
sched = [{"item": "Riemen", "km": 120000, "months": 96, "cost": 650}]
r = run(car(schedule=sched), "2025-01-01,40000,Anderes,0,X\n")
t("Teure Position im Horizont -> CS08", ("niedrig", "CS08") in codes(r))
t("add_months klemmt Monatsende", add_months(datetime.date(2026, 1, 31), 1) == datetime.date(2026, 2, 28))
try:
load_car(car(schedule=[{"item": "X", "cost": 1}])); ok = False
except InputError:
ok = True
t("Position ohne Intervall abgelehnt", ok)
try:
load_car(dict(car(), farbe="rot")); ok = False
except InputError:
ok = True
t("Unbekannter Schlüssel abgelehnt", ok)
r = run(car(), log)
t("Bericht hat Urteil, Besuche und Prognose", all(s in report(r, "t") for s in ("**Urteil:", "## Werkstattbesuche", "## Kostenprognose")))
for name, ok in tests:
print(("PASS " if ok else "FAIL ") + name)
passed = sum(ok for _, ok in tests)
print("%d/%d bestanden" % (passed, len(tests)))
return 0 if passed == len(tests) else 1
def main(argv=None):
ap = argparse.ArgumentParser(description="Plant die Kfz-Wartung anhand eines Plans und eines Servicelogs.")
ap.add_argument("car", nargs="?")
ap.add_argument("log", nargs="?")
ap.add_argument("--odometer", type=float)
ap.add_argument("--today")
ap.add_argument("--severe", action="store_true", default=None, help="Intervalle bei erschwerten Bedingungen verwenden")
ap.add_argument("--horizon", type=int, help="Monate zum Planen (Standard aus Autodatei, sonst 24)")
ap.add_argument("--json", action="store_true")
ap.add_argument("--self-test", action="store_true")
a = ap.parse_args(argv)
if a.self_test:
return self_test()
if not a.car or not a.log:
ap.print_usage(sys.stderr)
return 2
try:
try:
cfg = json.load(open(a.car, encoding="utf-8"))
except json.JSONDecodeError as e:
raise InputError("Autodatei ist kein gültiges JSON: %s" % e)
c = load_car(cfg, a.odometer, a.today, a.severe, a.horizon)
res = plan(c, open(a.log, encoding="utf-8-sig").read())
except (OSError, InputError) as e:
print("Fehler: %s" % e, file=sys.stderr)
return 2
if a.json:
print(json.dumps(to_json(res), indent=2))
else:
sys.stdout.write(report(res, a.log))
return 1 if any(f[0] == "hoch" for f in res["findings"]) else 0
if __name__ == "__main__":
sys.exit(main())
```
Melde dich an, um den vollständigen Prompt zu sehen
Weiter mit:
Mit der Anmeldung akzeptierst du unsere Nutzungsbedingungen und Datenschutz
Verwendung
Dieser Prompt ist für die Verwendung mit productivity gedacht. Kopiere den Inhalt oben und füge ihn in dein bevorzugtes KI-Tool ein.
Für beste Ergebnisse passe die Platzhalter (eckige Klammern oder Großbuchstaben) an deine Anforderungen an.
Diskussion
0 Kommentare