Discussion

Planificateur de calendrier d'entretien automobile

De Wikiprompt, l’encyclopédie libre de prompts

Fatih Kadir Akın

4 nov. 2026

Planificateur de calendrier d'entretien automobile Une compétence complète de style Claude avec une invite structurée, des documents de référence, des modèles, des exemples et un script Python de la bibliothèque standard qui planifie l'entretien automobile en suivant les intervalles de service et l'historique, en regroupant les voyages de travail et en prévoyant les coûts.

Contenu du PromptEnregistrer

🌐
--- name: planificateur-planning-entretien-voiture description: Planifie l'entretien de votre voiture à partir de son calendrier d'entretien et de son historique - estime la distance parcourue à partir des relevés du compteur kilométrique, détermine ce qui est en retard, bientôt dû ou à faire ensuite (selon la distance ou le temps, selon la première échéance), regroupe les opérations en moins de visites à l'atelier et prévoit les coûts avec une épargne mensuelle. Signale les éléments de sécurité et légaux en retard, les enregistrements manquants et les erreurs de frappe du compteur. Inclut un planificateur Python hors ligne de la bibliothèque standard. --- # Planificateur de planning d'entretien de voiture Vous aidez les propriétaires de voitures à suivre l'entretien sans trop dépenser. Les gens perdent le fil parce que chaque opération a son propre calendrier : l'huile tous les 15 000 km ou 12 mois, le liquide de frein tous les 2 ans quelle que soit la distance, le contrôle technique tous les ans, une courroie de distribution à 120 000 km ou 8 ans. Quelque chose est toujours sur le point d'être dû, les reçus sont éparpillés et les articles coûteux arrivent comme une surprise. Vous transformez le calendrier du propriétaire et son historique d'entretien en une liste claire de ce qui est dû, un plan de visite court qui regroupe les opérations en aussi peu de trajets à l'atelier que possible, et une prévision des coûts qu'ils peuvent budgétiser. Vous planifiez à partir des intervalles que le propriétaire vous donne (du manuel du propriétaire, du carnet d'entretien ou du concessionnaire). Vous ne diagnostiquez pas les pannes et ne décidez pas si une pièce est usée ; dites à l'utilisateur de suivre les conclusions de l'atelier et le calendrier du fabricant, et de faire vérifier immédiatement les voyants d'avertissement, les bruits, les problèmes de freins ou de direction. ## Fichiers dans cette compétence - `scripts/car_service_plan.py` - lit un JSON de voiture (véhicule, compteur kilométrique actuel, éléments du calendrier avec intervalles en km et/ou mois, intervalles de service sévère, délai du premier test, coût, indicateurs de sécurité/légal, alias de noms) et un CSV de journal d'entretien (date, compteur, éléments séparés par " ;", coût, atelier) ; estime la distance annuelle à partir des relevés, trouve la dernière fois que chaque élément a été fait, calcule la prochaine échéance par le temps et par la distance projetée, statuts EN RETARD / BIENTÔT DÛ / OK, visites d'atelier regroupées sur l'horizon et coûts par trimestre ; rapporte les constatations CS01-CS09 (bibliothèque standard Python 3.8+ uniquement) - `references/intervals-and-severe-service.md` - comment lire le calendrier du fabricant, le temps par rapport à la distance, les conditions de service sévère, les notes typiques des éléments et ce qu'il ne faut jamais reporter - `references/checks-and-codes.md` - chaque code de constatation, sa gravité et quoi faire, plus les champs d'entrée - `templates/service-plan.md` - le plan d'une page et la liste de contrôle à apporter à l'atelier - `examples/car.json` - une voiture à essence break de 2019 à 83 400 km avec 11 éléments de calendrier (intervalles et prix illustratifs - remplacez par ceux de votre manuel) - `examples/service-log.csv` - sept ans d'historique d'entretien avec des visites combinées, un alias ("Oil change"), un élément non présent dans le calendrier et une erreur de frappe du compteur - `examples/service-plan.md` - la sortie réelle du planificateur sur l'exemple ## Flux de travail ### 1. Collectez le calendrier et l'historique Demandez la marque, le modèle, le moteur et la date de première immatriculation, le relevé du compteur kilométrique d'aujourd'hui, et le calendrier d'entretien du manuel du propriétaire ou du carnet d'entretien (élément, intervalle de distance, intervalle de temps, intervalles de service sévère s'ils conduisent surtout de courts trajets, remorquent, ou conduisent dans la poussière ou une chaleur ou un froid extrêmes). Demandez l'historique d'entretien : factures, tampons dans le carnet d'entretien ou l'imprimé du concessionnaire, avec la date, le compteur et ce qui a été fait. Ajoutez des prix approximatifs à partir des dernières factures de leur atelier. Écrivez-les dans un JSON de voiture comme `examples/car.json` et un journal comme `examples/service-log.csv`. ### 2. Exécutez le planificateur ``` 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 ``` Sortie 0 = aucun élément de sécurité ou légal en retard, 1 = un élément de sécurité/légal est en retard (ou le journal a une constatation élevée), 2 = mauvaise entrée. Sans shell, pour chaque élément, prenez la dernière date et le compteur, ajoutez les deux intervalles, convertissez la distance en une date avec la distance annuelle, et prenez la plus proche. ### 3. Lisez le résultat - Élevé d'abord : CS01 élevé (freins en retard, liquide de frein, pneus, test légal) - conduisez prudemment et réservez maintenant ; CS04 élevé (dates dans le futur, relevés au-dessus du compteur d'aujourd'hui) - corrigez le journal. - Moyen : autres éléments en retard, éléments bientôt dus, éléments sans enregistrement (CS03 - compté à partir du neuf, donc vérifiez le carnet d'entretien avant de payer) et relevés du compteur qui reculent. - Faible/info : articles coûteux dans le plan (CS08), éléments faits bien avant leur intervalle (CS05), entrées de journal non dans le calendrier, intervalles sévères utilisés. - Si la distance annuelle semble fausse, vérifiez les relevés ; utilisez `--odometer` avec le relevé d'aujourd'hui. ### 4. Planifiez les visites Utilisez le tableau des visites : réservez la visite 1 pour tout ce qui est en retard plus ce qui est dû dans la fenêtre de regroupement, et demandez à l'atelier de chiffrer toute la liste en une seule fois. Déplacez un article bon marché quelques semaines plus tôt si cela économise un trajet, mais ne repoussez jamais un élément de sécurité ou légal. Remplissez `templates/service-plan.md` avec les visites, la liste de contrôle pour l'atelier et l'épargne mensuelle. ### 5. Livrez Retournez la liste en retard et bientôt due en mots simples, le plan de visite avec les dates et les coûts estimés, la prévision des coûts sur 12 et 24 mois et le montant mensuel à mettre de côté, plus des questions à vérifier (enregistrements pour les éléments sans historique, l'intervalle exact pour leur moteur, les conditions de garantie qui exigent un entretien chez le concessionnaire). ## Exemple de résultat Pour la Octavia 2019 à 83 400 km (environ 11 000 km par an), quatre éléments sont en retard : le liquide de frein (126 jours, sécurité), le service d'inspection et le filtre à air (par le temps) et la rotation des pneus (3 300 km), et le contrôle technique est dû le 20 nov. 2026 - une visite maintenant couvre les cinq pour environ 410 EUR. Il n'y a aucun enregistrement de la courroie de distribution, donc elle est comptée à partir du neuf et due par âge en novembre 2027 (environ 680 EUR), et de nouveaux pneus sont projetés pour juin 2027 (environ 560 EUR). L'entretien sur les 24 prochains mois s'élève à environ 2 445 EUR, donc mettez de côté environ 102 EUR par mois. Le relevé de 4 790 km en sept. 2023 est une erreur de frappe et est ignoré. ## Règles - Utilisez les intervalles du fabricant pour le moteur exact ; les valeurs de l'exemple sont uniquement illustratives. - Selon la première échéance : un élément est dû à la plus proche de sa limite de distance et de temps, même pour une voiture à peine conduite. - Ne suggérez jamais de reporter les freins, les pneus, la direction, les voyants d'avertissement ou un test légal pour économiser de l'argent ou un trajet. - Un élément sans enregistrement est inconnu, pas fait : dites-le et suggérez de vérifier le carnet d'entretien ou de demander à l'atelier. - Les coûts sont des estimations ; dites à l'utilisateur d'obtenir un devis, et mentionnez les conditions de garantie avant de suggérer un garage indépendant. ## Intervalles et service sévère ## D'où viennent les intervalles - Le manuel du propriétaire ou le carnet d'entretien pour votre modèle, moteur et année de modèle exacts. Le même modèle peut avoir des calendriers différents pour les moteurs à essence, diesel et hybrides, et pour les régimes de service "fixe" vs "flexible/longue durée". - De nombreuses voitures affichent le prochain service d'huile sur le tableau de bord (indicateur de service). S'il est en désaccord avec le carnet, demandez au concessionnaire quel régime la voiture utilise. - Gardez à l'esprit les conditions de garantie : certains fabricants exigent un entretien selon leur calendrier avec des pièces de spécification d'origine ; les garages indépendants sont généralement acceptables s'ils suivent le calendrier et conservent les factures. ## Selon la première échéance Chaque élément avec un intervalle de distance et de temps est dû à la PLUS PROCH E des deux. Les voitures à faible kilométrage atteignent la limite de temps (l'huile vieillit, le liquide de frein absorbe l'eau, les courroies en caoutchouc et les pneus vieillissent) ; les voitures à kilométrage élevé atteignent la limite de distance. Le planificateur projette la limite de distance à une date avec votre distance annuelle. ## Service sévère La plupart des manuels listent des intervalles plus courts lorsque la voiture est utilisée surtout pour : - de courts trajets (moins d'environ 10 km, moteur rarement complètement chaud), surtout par temps froid ; - la circulation urbaine en stop-and-go, l'utilisation de taxi ou de livraison, de longs ralentis ; - le remorquage d'une remorque ou d'une caravane, les charges de toit, la conduite en montagne ; - les routes poussiéreuses ou sablonneuses, les climats très chauds ou très froids. Si l'une de ces conditions décrit la plupart de votre conduite, utilisez `--severe` (ou `"severe": true`) pour que les éléments avec `severe_km` / `severe_months` utilisent l'intervalle plus court. ## Notes sur les éléments (typiques, vérifiez votre manuel) | Élément | Base typique | Notes | |---|---|---| | Huile moteur et filtre | distance et temps | l'élément de routine le plus important ; utilisez la spécification d'huile du manuel | | Service d'inspection | distance et temps | le service plus important ; comprend généralement de nombreux contrôles | | Liquide de frein | temps uniquement (souvent 2 ans) | absorbe l'eau ; ne reportez jamais | | Inspection / plaquettes / disques de frein | distance et temps | remplacez selon l'usure, pas sur un intervalle fixe | | Liquide de refroidissement | temps ou "à vie" | vérifiez le niveau et la protection contre le gel chaque hiver | | Filtre à air, filtre d'habitacle | distance et temps | plus courts dans les zones poussiéreuses ou polluées | | Bougies d'allumage (essence) | distance et temps | longs intervalles sur les moteurs modernes | | Courroie de distribution (si installée) | distance et temps | une défaillance peut détruire le moteur ; souvent changée avec la pompe à eau ; les chaînes n'ont généralement pas d'intervalle | | Pneus | usure et âge | vérifiez la profondeur de la bande de roulement mensuellement ; de nombreux fabricants suggèrent de remplacer à 6-10 ans quel que soit l'état de la bande de roulement | | Rotation des pneus | distance | si votre manuel le recommande (pas pour les pneus de tailles différentes avant/arrière) | | Contrôle technique | temps (légal) | premier test après quelques années (par exemple 3 ou 4), puis tous les 1-2 ans selon le pays | | Batterie 12 V | état | testez avant l'hiver une fois qu'elle a 4-5 ans | ## Tests légaux Les règles diffèrent selon le pays (MOT au Royaume-Uni, TUV/HU en Allemagne, CT en France, TUVTURK en Turquie, inspections d'État aux États-Unis). Entrez le délai du premier test de votre pays comme `first_months` et l'intervalle de répétition comme `months`, et marquez l'élément `"legal": true`. De nombreux systèmes vous permettent de tester un peu plus tôt sans perdre la date ; vérifiez le vôtre avant de le regrouper dans une visite plus tôt. ## Ce qu'il ne faut jamais reporter Les freins et le liquide de frein, les pneus (bande de roulement, dommages, âge), les défauts de direction et de suspension, les voyants d'avertissement (pression d'huile, freins, température du moteur), et le test légal. Le planificateur marque ces éléments avec `safety` / `legal` et les traite comme élevés lorsqu'ils sont en retard. ## Obtention des entrées - Compteur et date de chaque visite : factures, tampons du carnet d'entretien, dossier d'entretien numérique du concessionnaire, certificats de contrôle technique (ils enregistrent le compteur). - Prix : vos dernières factures ou un devis ; ajoutez 10-20% pour les pièces souvent remplacées en même temps (essuie-glaces, ampoules, plaquettes de frein). ## Contrôles et codes de constatation | Code | Gravité | Contrôle | Que faire | |---|---|---|---| | CS01 | élevée : élément de sécurité ou légal en retard ; moyenne : autre élément en retard | date ou distance d'échéance dépassée | réservez-le ; pour les éléments de sécurité, conduisez prudemment jusqu'à ce que ce soit fait | | CS02 | moyenne | dû dans `due_soon.days` ou `due_soon.km` | réservez-le avec la prochaine visite | | CS03 | moyenne | aucun enregistrement de l'élément dans le journal : compté à partir de la première immatriculation | vérifiez le carnet d'entretien / les reçus / le dossier du concessionnaire avant de payer | | CS04 | élevée : date dans le futur, relevé au-dessus du compteur actuel ; moyenne : relevé inférieur à un précédent, compteur illisible | cohérence du journal | corrigez l'erreur de frappe ; le relevé est ignoré pour la distance et les calculs d'échéance | | CS05 | info | dernière fois fait à moins de 60% des deux intervalles | acceptable si prévu (usage sévère, garantie) ; sinon argent dépensé tôt | | CS06 | info | intervalles de service sévère utilisés | vérifiez que les conditions s'appliquent toujours | | CS07 | info | élément du journal non dans votre calendrier (réparations, extras) | conservé pour l'historique ; ajoutez des alias s'il s'agit d'un élément du calendrier sous un autre nom | | CS08 | faible | élément coûtant 400 ou plus tombe dans l'horizon du plan | budgétez-le ; obtenez un devis tôt | | CS09 | moyenne | pas assez de relevés du compteur (un relevé d'au moins 60 jours en arrière) : distance annuelle par défaut utilisée | ajoutez des relevés (certificats de test, application de carburant, photos du tableau de bord) | ## Verdict - **En retard y compris sécurité/légal** : réservez maintenant. - **En retard** : réservez bientôt. - **Bientôt dû** / **à jour**. ## Comment les chiffres sont calculés - Distance annuelle : du dernier relevé du journal d'au moins un an avant aujourd'hui au relevé d'aujourd'hui (le premier relevé si aucun n'est aussi ancien). - Prochaine échéance : dernière date de réalisation + mois, et dernier compteur + km, convertis en une date avec votre distance quotidienne ; la plus proche gagne. Un élément sans enregistrement compte à partir de la première immatriculation à 0 km (`first_months` remplace `months` pour la première fois, par exemple un premier contrôle technique après 36 mois). - Horizon : chaque élément se répète à partir de la date où il est supposé fait (sa date d'échéance, ou aujourd'hui s'il est en retard). - Visites : les éléments dus dans `bundle.days` et `bundle.km` du premier élément d'une visite rejoignent cette visite (avancés, jamais repoussés). - Coûts : le `cost` de chaque occurrence ajouté à son trimestre ; épargne mensuelle = total de l'horizon / mois de l'horizon. ## Clés JSON de la voiture `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[]}]`. ## CSV du journal d'entretien `date, odometer, item, cost, shop, notes` - plusieurs éléments dans une visite séparés par ` ;`. Les noms sont comparés aux éléments du calendrier et aux alias sans tenir compte de la casse. La colonne coût est pour vos dossiers ; la prévision utilise les prix du calendrier. ## Modèle # Plan d'entretien de la voiture : <voiture, immatriculation> **Date :** <AAAA-MM-JJ> **Compteur :** <83 400 km> **Distance annuelle :** <environ 11 000 km> **Calendrier :** <manuel du propriétaire, service normal / sévère> ## À faire maintenant | Élément | Pourquoi | Coût est. | |---|---|---| | <Liquide de frein> | <en retard de 4 mois (intervalle de 2 ans) - sécurité> | <85> | | <Contrôle technique> | <dû le 2026-11-20 - légal> | <60> | ## Prochaines visites | Quand (réserver avant) | À environ | Éléments | Coût est. | |---|---|---|---| | <2027-02-14> | <86 500 km> | <huile et filtre, inspection des freins> | <180> | ## Budget - 12 prochains mois : <1 220> <EUR> - 24 prochains mois : <2 445> <EUR> -> mettez de côté <102> par mois - Gros articles à venir : <kit de courroie de distribution et pompe à eau, nov. 2027, environ 680> ## À vérifier - [ ] <aucun enregistrement de la courroie de distribution - vérifiez le carnet d'entretien / demandez au concessionnaire> - [ ] <intervalle exact du liquide de frein pour ce moteur> - [ ] <garantie : exige-t-elle un entretien chez le concessionnaire?> ## Liste de contrôle pour l'atelier - Voiture : <marque, modèle, moteur, VIN si demandé>, compteur <...> - Veuillez faire : <liste du "À faire maintenant"> - Veuillez vérifier et chiffrer avant de remplacer : <plaquettes/disques de frein, pneus, essuie-glaces, batterie> - Veuillez tamponner le carnet d'entretien / envoyer un dossier numérique et noter le compteur sur la facture. *Intervalles de <source> ; les coûts sont des estimations. Faites vérifier immédiatement tout ce qui est inhabituel (voyants d'avertissement, bruits, tirage, vibrations).* ## Exemple : car.json { "vehicle": {"name": "2019 Skoda Octavia Estate 1.5 TSI (petrol)", "first_registered": "2019-11-14", "unit": "km"}, "odometer": {"reading": 83400, "date": "2026-11-04"}, "driving": {"default_per_year": 12000, "severe": false, "severe_reasons": ["mostly short trips in winter", "occasional caravan towing"]}, "currency": "EUR", "horizon_months": 24, "due_soon": {"km": 1500, "days": 45}, "bundle": {"km": 2500, "days": 60}, "schedule": [ {"item": "Engine oil and filter", "km": 15000, "months": 12, "severe_km": 7500, "severe_months": 6, "cost": 140, "aliases": ["oil service", "oil change"]}, {"item": "Inspection service", "km": 30000, "months": 24, "severe_km": 20000, "cost": 190, "aliases": ["inspection"]}, {"item": "Brake inspection", "km": 20000, "months": 12, "cost": 40, "safety": true}, {"item": "Brake fluid", "months": 24, "cost": 85, "safety": true}, {"item": "Air filter", "km": 60000, "months": 48, "severe_km": 30000, "cost": 45}, {"item": "Cabin filter", "km": 30000, "months": 24, "cost": 40, "aliases": ["pollen filter"]}, {"item": "Spark plugs", "km": 60000, "months": 72, "cost": 160}, {"item": "Tyre rotation", "km": 10000, "cost": 30}, {"item": "Tyres (set of 4)", "km": 45000, "months": 72, "cost": 560, "safety": true}, {"item": "Timing belt kit and water pump", "km": 120000, "months": 96, "cost": 680}, {"item": "Roadworthiness test", "months": 12, "first_months": 36, "cost": 60, "legal": true, "aliases": ["mot", "periodic inspection"]} ] } ## Exemple : service-log.csv date,odometer,item,cost,shop,notes 2020-11-20,11500,Oil service; Brake inspection,165,Main dealer,first service 2021-11-05,22800,Oil service; Brake inspection; Cabin filter,210,Main dealer, 2022-05-28,33900,Oil service; Inspection service; Brake fluid; Air filter,395,Main dealer, 2022-11-08,38000,Roadworthiness test,55,Test centre,passed 2023-06-15,45150,Oil service; Brake inspection; Tyre rotation; Tyres (set of 4),735,QuickFit,new tyres 205/55 R16 2023-09-02,4790,Windscreen chip repair,60,GlassFix, 2023-11-13,49000,Roadworthiness test,55,Test centre,passed 2024-07-01,56900,Oil service; Inspection service; Brake fluid; Spark plugs; Brake inspection,520,Main dealer, 2024-11-12,59800,Roadworthiness test,58,Test centre,advisory: rear wiper 2025-08-20,70100,Oil service; Brake inspection; Cabin filter; Tyre rotation,240,Corner Garage, 2025-11-20,73000,Roadworthiness test,60,Test centre,passed 2026-02-14,75300,Oil change; Brake inspection,150,Corner Garage,dash light came on ## Exemple : service-plan.md # Service plan: 2019 Skoda Octavia Estate 1.5 TSI (petrol) Today 2026-11-04, odometer 83,400 km. You drive about 11,008 km/year (2025-08-20 70,100 -> 2026-11-04 83,400). Normal-service intervals, plan horizon 24 months. Source: examples/service-log.csv. **Verdict: 4 item(s) overdue, including safety/legal items - book a visit now** Next 12 months: about 1,220.00 EUR; next 24 months: 2,445.00 EUR (set aside about 101.88 EUR per month). Findings: 1 high, 6 medium, 2 low, 3 info. ## Items | Item | Interval | Last done | Due by date | Due at km | Next (projected) | Trigger | Status | Est. cost | |---|---|---|---|---|---|---|---|---| | Inspection service | 30,000 km / 24 months | 2024-07-01 at 56,900 | 2026-07-01 | 86,900 | 2026-11-04 | time | **OVERDUE** | 190.00 | | Brake fluid [safety] | 24 months | 2024-07-01 at 56,900 | 2026-07-01 | - | 2026-11-04 | time | **OVERDUE** | 85.00 | | Air filter | 60,000 km / 48 months | 2022-05-28 at 33,900 | 2026-05-28 | 93,900 | 2026-11-04 | time | **OVERDUE** | 45.00 | | Tyre rotation | 10,000 km | 2025-08-20 at 70,100 | - | 80,100 | 2026-11-04 | distance | **OVERDUE** | 30.00 | | Roadworthiness test [legal] | 12 months | 2025-11-20 at 73,000 | 2026-11-20 | - | 2026-11-20 | time | **DUE SOON** | 60.00 | | Engine oil and filter | 15,000 km / 12 months | 2026-02-14 at 75,300 | 2027-02-14 | 90,300 | 2027-02-14 | time | OK | 140.00 | | Brake inspection [safety] | 20,000 km / 12 months | 2026-02-14 at 75,300 | 2027-02-14 | 95,300 | 2027-02-14 | time | OK | 40.00 | | Tyres (set of 4) [safety] | 45,000 km / 72 months | 2023-06-15 at 45,150 | 2029-06-15 | 90,150 | 2027-06-15 | distance | OK | 560.00 | | Cabin filter | 30,000 km / 24 months | 2025-08-20 at 70,100 | 2027-08-20 | 100,100 | 2027-08-20 | time | OK | 40.00 | | Timing belt kit and water pump | 120,000 km / 96 months | no record (counted from new, 2019-11-14) | 2027-11-14 | 120,000 | 2027-11-14 | time | OK | 680.00 | | Spark plugs | 60,000 km / 72 months | 2024-07-01 at 56,900 | 2030-07-01 | 116,900 | 2029-11-18 | distance | OK | 160.00 | ## Workshop visits (items within 60 days / 2,500 km are bundled) | # | Book by | At about | Items | Est. cost | |---|---|---|---|---| | 1 | 2026-11-04 (now) | 83,400 km | Air filter (overdue), Brake fluid (overdue), Inspection service (overdue), Tyre rotation (overdue), Roadworthiness test | 410.00 | | 2 | 2027-02-14 | 86,476 km | Brake inspection, Engine oil and filter | 180.00 | | 3 | 2027-06-15 | 90,125 km | Tyres (set of 4) | 560.00 | | 4 | 2027-08-20 | 92,116 km | Cabin filter, Tyre rotation | 70.00 | | 5 | 2027-11-14 | 94,710 km | Timing belt kit and water pump, Roadworthiness test | 740.00 | | 6 | 2028-02-14 | 97,484 km | Brake inspection, Engine oil and filter | 180.00 | | 7 | 2028-08-27 | 103,365 km | Tyre rotation | 30.00 | | 8 | 2028-11-04 | 105,446 km | Brake fluid, Inspection service | 275.00 | ## Cost forecast by quarter | Quarter | Est. cost | |---|---| | 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 | | **Total 24 months** | **2,445.00** | ## Findings - **HIGH CS01** Brake fluid is overdue by 126 days (safety item) - book it now - **MEDIUM CS01** Air filter is overdue by 160 days - book it now - **MEDIUM CS01** Inspection service is overdue by 126 days - book it now - **MEDIUM CS01** Tyre rotation is overdue by 3,300 km - book it now - **MEDIUM CS02** Roadworthiness test is due soon: 2026-11-20 - **MEDIUM CS03** Timing belt kit and water pump: no record in your log - counted from new; check the service book or receipts - **MEDIUM CS04** row 7: odometer 4,790 on 2023-09-02 is lower than 45,150 on 2023-06-15 - typo or wrong car? reading ignored - **LOW CS08** Timing belt kit and water pump (about 680.00) falls in the plan on 2027-11-14 - budget for it now - **LOW CS08** Tyres (set of 4) (about 560.00) falls in the plan on 2027-06-15 - budget for it now - **INFO CS05** Brake inspection was last done after 5,200 km / 6 months (under 60% of the interval) - fine for severe use, otherwise money spent early - **INFO CS05** Engine oil and filter was last done after 5,200 km / 6 months (under 60% of the interval) - fine for severe use, otherwise money spent early - **INFO CS07** row 7: 'Windscreen chip repair' is not in your schedule - kept in the history only Intervals and prices come from your car file: check them against the owner's manual or service booklet for your exact engine, and follow your workshop if it finds wear earlier. Costs are estimates; tyres and brakes also depend on wear, not only on distance. ## Script Python (car_service_plan.py) ```python #!/usr/bin/env python3 """Planificateur de planning d'entretien de voiture (bibliothèque standard uniquement, Python 3.8+). Lit le calendrier d'entretien de votre voiture (éléments avec intervalles de distance et/ou de temps, du manuel du propriétaire ou du carnet d'entretien) et votre historique d'entretien (CSV), estime la distance parcourue à partir des relevés du compteur dans le journal, et calcule pour chaque élément quand il a été fait pour la dernière fois, quand il est dû ensuite (distance ou temps, selon la première échéance), ce qui est en retard ou bientôt dû, quels éléments peuvent partager une visite à l'atelier, et ce que l'entretien coûtera au cours des prochains mois. Utilisation : car_service_plan.py CAR.json SERVICE_LOG.csv [--odometer N] [--today AAAA-MM-JJ] [--severe] [--horizon MOIS] [--json] car_service_plan.py --self-test Sortie : 0 = rien en retard sur un élément de sécurité/légal, 1 = un élément de sécurité ou légal est en retard (ou le journal a une constatation élevée), 2 = mauvaise entrée. """ import argparse, calendar, csv, datetime, io, json, sys SEV = {"high": 0, "medium": 1, "low": 2, "info": 3} CAR_KEYS = {"vehicle", "odometer", "driving", "currency", "horizon_months", "due_soon", "bundle", "schedule"} class InputError(Exception): pass def pdate(s, what): try: return datetime.date.fromisoformat(str(s).strip()) except ValueError: raise InputError("%s doit être AAAA-MM-JJ, obtenu %r" % (what, 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, what): try: return float(str(x).replace(",", "").replace(" ", "")) except ValueError: raise InputError("%s n'est pas un nombre : %r" % (what, x)) def fk(x): return "{:,.0f}".format(x) def fm(x): return "{:,.2f}".format(x) # ---------------------------------------------------------------- chargement def load_car(cfg, odometer=None, today=None, severe=None, horizon=None): if not isinstance(cfg, dict): raise InputError("le fichier de voiture doit être un objet JSON") bad = set(cfg) - CAR_KEYS if bad: raise InputError("clé(s) inconnue(s) dans le fichier de voiture : %s" % ", ".join(sorted(bad))) v = cfg.get("vehicle") or {} odo = cfg.get("odometer") or {} drv = cfg.get("driving") or {} c = {"name": v.get("name", "Voiture"), "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"), "lecture du compteur") 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("donnez la lecture actuelle du compteur (odometer.reading ou --odometer)") for i, it in enumerate(cfg.get("schedule") or [], 1): name = it.get("item") if not name: raise InputError("l'élément du calendrier %d n'a pas de nom 'item'" % 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("l'élément du calendrier '%s' nécessite km et/ou mois" % 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 + " coût"), "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("le fichier de voiture nécessite un 'schedule' non vide") 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("le journal d'entretien nécessite les colonnes date, odometer, item (a %s)" % ", ".join(rows[0])) entries, readings = [], [] for n, r in enumerate(rows, 2): try: d = pdate(r.get("date"), "date (ligne %d)" % n) except InputError as e: F.append(("high", "CS04", "ligne %d : %s - ligne ignorée" % (n, e))); continue if d > c["today"]: F.append(("high", "CS04", "ligne %d : date %s dans le futur - ligne ignorée" % (n, d))); continue odo = None if (r.get("odometer") or "").strip(): try: odo = num(r["odometer"], "odometer (ligne %d)" % n) except InputError as e: F.append(("medium", "CS04", "%s - distance ignorée pour cette ligne" % 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(("high", "CS04", "ligne %d : odometer %s le %s est supérieur à la lecture actuelle %s - erreur de frappe ? lecture ignorée" % (e["row"], fk(e["odo"]), e["date"], fk(c["odo"])))) e["odo"] = None; continue if last and e["odo"] < last["odo"]: F.append(("medium", "CS04", "ligne %d : odometer %s le %s est inférieur à %s le %s - erreur de frappe ou mauvaise voiture ? lecture ignorée" % (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 par jour à partir du dernier relevé d'au moins un an avant aujourd'hui (sinon le premier).""" 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(("medium", "CS09", "pas assez de relevés du compteur pour estimer votre distance annuelle - utilisation de la valeur par défaut %s %s/an" % (fk(c["default_per_year"]), c["unit"]))) return c["default_per_year"] / 365.0, "défaut" 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)) # ---------------------------------------------------------------- plan 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", "ligne %d : '%s' n'est pas dans votre calendrier - conservé dans l'historique uniquement" % (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 à %s" % (last["date"], fk(last["odo"]) if last["odo"] is not None else "?") else: if not c["first"]: raise InputError("aucun enregistrement de '%s' et pas de vehicle.first_registered pour compter à partir de" % it["item"]) base_d, base_km, base_note = c["first"], 0.0, "aucun enregistrement (compté à partir du neuf, %s)" % c["first"] F.append(("medium", "CS03", "%s : aucun enregistrement dans votre journal - compté à partir du neuf ; vérifiez le carnet d'entretien ou les reçus" % 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, "time"), (km_d, "distance")) if x] if not cands: due_date, trigger = c["today"], "inconnu" 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 = "EN RETARD" if overdue else ("BIENTÔT DÛ" if soon else "OK") if overdue: late = [] if due_d is not None and due_d < c["today"]: late.append("%d jours" % (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 = "high" if (it["safety"] or it["legal"]) else "medium" F.append((sev, "CS01", "%s est en retard de %s%s - réservez-le maintenant" % (it["item"], " et ".join(late), " (élément de sécurité)" if it["safety"] else (" (exigence légale)" if it["legal"] else "")))) elif soon: F.append(("medium", "CS02", "%s est bientôt dû : %s%s" % (it["item"], due_d if due_d and (due_d - c["today"]).days <= c["soon_days"] else "", (" / à %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 a été fait pour la dernière fois après %s %s / %d mois (moins de 60%% de l'intervalle) - acceptable pour un usage sévère, sinon argent dépensé tôt" % (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 mois" % 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": "légal" if it["legal"] else ("sécurité" if it["safety"] else ""), "severe": it["severe_used"]}) # occurrences dans l'horizon (chacune supposée faite à sa date d'échéance) 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(("low", "CS08", "%s (environ %s) tombe dans le plan le %s - budgétez-le maintenant" % (it["item"], fm(it["cost"]), o["date"]))) if c["severe"]: F.append(("info", "CS06", "intervalles de service sévère utilisés%s" % ((" (" + ", ".join(c["severe_reasons"]) + ")") if c["severe_reasons"] else ""))) # visites : regrouper les occurrences dans la fenêtre de regroupement 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]}) # coûts par trimestre quarters = {} for o in occ: q = "%d-T%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} # ---------------------------------------------------------------- rapport 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"] == "EN RETARD"] soon = [r for r in res["rows"] if r["status"] == "BIENTÔT DÛ"] L = ["# Plan d'entretien : %s" % c["name"], ""] L.append("Aujourd'hui %s, compteur %s %s. Vous conduisez environ %s %s/an (%s). Intervalles %s, horizon du plan %d mois. Source : %s." % ( c["today"], fk(c["odo"]), c["unit"], fk(res["rate"] * 365), c["unit"], res["basis"], "de service sévère" if c["severe"] else "de service normal", c["horizon"], title)) L.append("") if any(r["safety"] for r in ov): L.append("**Verdict : %d élément(s) en retard, y compris des éléments de sécurité/légaux - réservez une visite maintenant**" % len(ov)) elif ov: L.append("**Verdict : %d élément(s) en retard - réservez une visite bientôt**" % len(ov)) elif soon: L.append("**Verdict : rien en retard, %d élément(s) bientôt dû(s)**" % len(soon)) else: L.append("**Verdict : à jour**") L.append("") L.append("12 prochains mois : environ %s%s ; %d prochains mois : %s%s (mettez de côté environ %s%s par mois). Constatations : %d élevée(s), %d moyenne(s), %d faible(s), %d info." % ( fm(res["next12"]), cur, c["horizon"], fm(res["total"]), cur, fm(res["total"] / c["horizon"]), cur, cnt["high"], cnt["medium"], cnt["low"], cnt["info"])) L += ["", "## Éléments", "", "| Élément | Intervalle | Dernière fois | Échéance par date | Échéance à %s | Prochaine (projetée) | Déclencheur | Statut | Coût est. |" % c["unit"], "|---|---|---|---|---|---|---|---|---|"] order = {"EN RETARD": 0, "BIENTÔT DÛ": 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"] + (" (sévère)" 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 += ["", "## Visites à l'atelier (éléments dans %d jours / %s %s sont regroupés)" % (c["bundle_days"], fk(c["bundle_km"]), c["unit"]), "", "| # | Réserver avant | À environ | Éléments | Coût est. |", "|---|---|---|---|---|"] for i, v in enumerate(res["visits"], 1): L.append("| %d | %s%s | %s %s | %s | %s |" % (i, v["date"], " (maintenant)" if v["date"] == c["today"] else "", fk(v["km"]), c["unit"], ", ".join(o["item"] + (" (en retard)" if o["overdue"] else "") for o in v["items"]), fm(sum(o["cost"] for o in v["items"])))) L += ["", "## Prévision des coûts par trimestre", "", "| Trimestre | Coût est. |", "|---|---|"] for q in sorted(res["quarters"]): L.append("| %s | %s |" % (q, fm(res["quarters"][q]))) L.append("| **Total %d mois** | **%s** |" % (c["horizon"], fm(res["total"]))) L += ["", "## Constatations", ""] L += ["- **%s %s** %s" % (s.upper(), code, m) for s, code, m in F] or ["- aucune"] L += ["", "Les intervalles et les prix proviennent de votre fichier de voiture : vérifiez-les par rapport au manuel du propriétaire ou au carnet d'entretien pour votre moteur exact, et suivez votre atelier s'il constate une usure plus tôt. Les coûts sont des estimations ; les pneus et les freins dépendent aussi de l'usure, pas seulement de la distance."] 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"]]}) # ---------------------------------------------------------------- auto-test 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": "Huile", "km": 15000, "months": 12, "severe_km": 7500, "severe_months": 6, "cost": 100, "aliases": ["vidange"]}]} 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,Huile,90,X\n" r = run(car(), log) t("taux à partir des relevés : 10000 km/an", abs(r["rate"] * 365 - 10000) < 1) t("échéance par le temps d'abord (12 mois)", row(r, "Huile")["due_date"] == datetime.date(2026, 1, 1) and row(r, "Huile")["trigger"] == "time") t("dû aujourd'hui compte comme bientôt dû", row(r, "Huile")["status"] == "BIENTÔT DÛ") r = run(car(), "2025-06-01,40000,Huile,90,X\n") t("la distance déclenche d'abord quand on conduit beaucoup", row(r, "Huile")["due_km"] == 55000 and row(r, "Huile")["trigger"] == "distance") r = run(car(), "2024-06-01,30000,Huile,90,X\n") t("en retard par le temps et la distance -> CS01", row(r, "Huile")["status"] == "EN RETARD" and ("medium", "CS01") in codes(r)) sched = [{"item": "Liquide de frein", "months": 24, "cost": 80, "safety": True}] r = run(car(schedule=sched), "2023-06-01,30000,Liquide de frein,80,X\n2025-01-01,40000,Autre,0,X\n") t("élément de sécurité en retard -> CS01 élevé", ("high", "CS01") in codes(r)) t("élément non dans le calendrier -> CS07", ("info", "CS07") in codes(r)) r = run(car(), "2025-01-01,40000,vidange,90,X\n") t("les alias correspondent sans tenir compte de la casse", row(r, "Huile")["last"].startswith("2025-01-01")) r = run(car(), "2025-01-01,40000,Essuie-glaces,20,X\n") t("aucun enregistrement -> compté à partir du neuf, CS03", ("medium", "CS03") in codes(r) and "aucun enregistrement" in row(r, "Huile")["last"]) r = run(car(), "2025-01-01,40000,Huile,90,X\n", severe=True) t("intervalles sévères utilisés", row(r, "Huile")["due_date"] == datetime.date(2025, 7, 1) and ("info", "CS06") in codes(r)) r = run(car(), "2024-01-01,30000,Huile,90,X\n2024-06-01,20000,Huile,90,X\n2025-01-01,40000,Huile,90,X\n") t("compteur qui descend -> CS04, lecture ignorée", ("medium", "CS04") in codes(r)) r = run(car(), "2025-01-01,40000,Huile,90,X\n2027-01-01,60000,Huile,90,X\n") t("date future -> CS04 élevé", ("high", "CS04") in codes(r)) r = run(car(), "2025-01-01,90000,Huile,90,X\n") t("lecture au-dessus du compteur actuel -> CS04 élevé", ("high", "CS04") in codes(r)) r = run(car(), "") t("aucun relevé -> taux par défaut + CS09", ("medium", "CS09") in codes(r) and abs(r["rate"] * 365 - 12000) < 1) r = run(car(), "2025-01-01,40000,Huile,90,X\n2025-04-01,42000,Huile,90,X\n") t("fait tôt -> CS05", ("info", "CS05") in codes(r)) sched = [{"item": "Test", "months": 12, "first_months": 36, "cost": 50, "legal": True}] r = run(car(vehicle={"first_registered": "2024-01-01"}, schedule=sched), "2025-01-01,40000,Autre,0,X\n") t("first_months pour le premier test à partir du neuf", row(r, "Test")["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("les éléments dans la fenêtre de regroupement partagent une visite", len(r["visits"][0]["items"]) == 2) t("l'horizon répète les éléments annuels", sum(1 for o in r["occ"] if o["item"] == "A") == 2) t("prévision sur 12 mois et totale", r["next12"] == 150 and r["total"] == 300) sched = [{"item": "Courroie", "km": 120000, "months": 96, "cost": 650}] r = run(car(schedule=sched), "2025-01-01,40000,Autre,0,X\n") t("article coûteux dans l'horizon -> CS08", ("low", "CS08") in codes(r)) t("add_months serre la fin du mois", 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("élément sans intervalle rejeté", ok) try: load_car(dict(car(), colour="rouge")); ok = False except InputError: ok = True t("clé inconnue rejetée", ok) r = run(car(), log) t("le rapport a le verdict, les visites et la prévision", all(s in report(r, "t") for s in ("**Verdict :", "## Visites à l'atelier", "## Prévision des coûts"))) for name, ok in tests: print(("PASS " if ok else "FAIL ") + name) passed = sum(ok for _, ok in tests) print("%d/%d réussis" % (passed, len(tests))) return 0 if passed == len(tests) else 1 def main(argv=None): ap = argparse.ArgumentParser(description="Planifier l'entretien de la voiture à partir d'un calendrier et d'un journal d'entretien.") 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="utiliser les intervalles de service sévère") ap.add_argument("--horizon", type=int, help="mois à planifier (défaut du fichier de voiture, sinon 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("le fichier de voiture n'est pas un JSON valide : %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("erreur : %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] == "high" for f in res["findings"]) else 0 if __name__ == "__main__": sys.exit(main()) ```

Connectez-vous pour voir le prompt complet

Continuer avec:

En vous connectant, vous acceptez nos Conditions et Confidentialité

Utilisation

Ce prompt est conçu pour être utilisé avec productivity. Copiez le contenu ci-dessus et collez-le dans votre outil d’IA préféré.

Pour de meilleurs résultats, personnalisez les espaces réservés (indiqués par des crochets ou des majuscules) selon vos besoins.

Références

Catégories :productivity| prompts.chat| car-maintenance| service-schedule

Discussion