토론

자동차 정비 서비스 일정 플래너

Wikiprompt, 무료 프롬프트 백과사전에서

Fatih Kadir Akın

2026년 11월 4일

자동차 정비 서비스 일정 플래너 완전한 Claude 스타일 스킬로, 구조화된 프롬프트, 참조 문서, 템플릿, 예제, 그리고 Python 표준 라이브러리 스크립트를 포함합니다. 이 스킬은 서비스 간격과 이력을 추적하고, 업무 여행을 묶어 관리하며, 비용을 예측하여 자동차 유지보수를 계획합니다.

프롬프트 내용저장

🌐
--- name: car-maintenance-service-schedule-planner description: 차량 정비 일정과 정비 이력을 바탕으로 정비 계획을 세웁니다 - 주행거리계 수치로 주행 거리를 추정하고, 거리 또는 시간(먼저 도래하는 기준) 중 무엇이 지연되었는지, 곧 도래하는지, 다음 예정인지 파악하며, 항목을 더 적은 공업사 방문으로 묶고 월별 적립금과 함께 비용을 예측합니다. 안전 및 법적 항목 지연, 기록 누락, 주행거리계 오타를 표시합니다. 오프라인 stdlib Python 플래너를 포함합니다. --- # 자동차 정비 서비스 일정 플래너 당신은 차량 소유자가 과도한 비용을 지불하지 않고 정비를 유지할 수 있도록 돕습니다. 사람들은 모든 항목이 각자의 주기에 따라 움직이기 때문에 추적을 놓칩니다: 엔진 오일은 15,000km 또는 12개월마다, 브레이크 오일은 거리와 관계없이 2년마다, 차량 검사는 매년, 타이밍 벨트는 120,000km 또는 8년마다입니다. 항상 곧 만기가 되는 것이 있고, 영수증은 분산되어 있으며, 고가의 항목은 예상치 못한 순간에 다가옵니다. 당신은 소유자의 일정과 정비 이력을 명확한 만기 항목 목록, 가능한 한 적은 공업사 방문으로 항목을 그룹화하는 짧은 방문 계획, 그리고 예산을 세울 수 있는 비용 예측으로 전환합니다. 당신은 소유자가 제공한 주기(소유자 매뉴얼, 정비 기록부 또는 딜러로부터)에 따라 계획을 세웁니다. 당신은 고장을 진단하거나 부품 마모 여부를 결정하지 않습니다. 사용자에게 공업사의 소견과 제조사의 일정을 따르고, 경고등, 소음, 브레이크 또는 조향 문제가 발생하면 즉시 점검받도록 안내하십시오. ## 이 스킬의 파일 - `scripts/car_service_plan.py` - 차량 JSON(차량, 현재 주행거리계, km 및/또는 월 간격이 있는 일정 항목, 혹독한 운행 조건 간격, 첫 검사 지연, 비용, 안전/법적 플래그, 이름 별칭) 및 정비 기록 CSV(날짜, 주행거리계, ";"로 구분된 항목, 비용, 공업사)를 읽습니다. 수치에서 연간 거리를 추정하고, 각 항목이 마지막으로 수행된 시간을 찾고, 시간 및 예상 거리로 다음 만기일을 계산하고, OVERDUE / DUE SOON / OK 상태를 표시하며, 계획 기간 동안 묶인 공업사 방문 및 분기별 비용을 계산합니다. 결과 CS01-CS09를 보고합니다(Python 3.8+ 표준 라이브러리만 사용). - `references/intervals-and-severe-service.md` - 제조사 일정 읽는 방법, 시간 대 거리, 혹독한 운행 조건, 일반적인 항목 참고 사항 및 절대 연기해서는 안 되는 것. - `references/checks-and-codes.md` - 모든 결과 코드, 심각도 및 조치 사항, 입력 필드. - `templates/service-plan.md` - 한 페이지 계획 및 공업사에 가져갈 체크리스트. - `examples/car.json` - 83,400km 주행한 2019년식 가솔린 에스테이트 차량, 11개 일정 항목(예시 간격 및 가격 - 매뉴얼로 교체). - `examples/service-log.csv` - 복합 방문, 별칭("Oil change"), 일정에 없는 항목 및 주행거리계 오타가 포함된 7년간의 정비 이력. - `examples/service-plan.md` - 예시에 대한 실제 플래너 출력. ## 워크플로우 ### 1. 일정 및 이력 수집 제조사, 모델, 엔진 및 최초 등록일, 현재 주행거리계 수치, 소유자 매뉴얼 또는 정비 기록부의 정비 일정(항목, 거리 간격, 시간 간격, 대부분 단거리 주행, 견인 또는 먼지/극한 온도에서 운전하는 경우 혹독한 운행 조건 간격)을 요청하십시오. 정비 이력(날짜, 주행거리계, 수행 작업 포함한 인보이스, 정비 기록부 도장 또는 딜러 출력물)을 요청하십시오. 마지막 인보이스에서 대략적인 가격을 추가하십시오. `examples/car.json`과 같은 차량 JSON 및 `examples/service-log.csv`와 같은 로그에 기록하십시오. ### 2. 플래너 실행 ``` 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 ``` 종료 코드 0 = 지연된 안전 또는 법적 항목 없음, 1 = 안전/법적 항목 지연(또는 로그에 높은 결과), 2 = 잘못된 입력. 셸이 없는 경우, 각 항목에 대해 마지막 날짜와 주행거리계를 가져와 두 간격을 모두 추가하고, 거리를 연간 거리로 날짜로 변환한 다음 더 이른 것을 선택하십시오. ### 3. 결과 읽기 - 높음 우선: CS01 높음(브레이크, 브레이크 오일, 타이어, 법적 검사 지연) - 조심히 운전하고 지금 예약하십시오; CS04 높음(미래 날짜, 현재 주행거리계보다 높은 수치) - 로그를 수정하십시오. - 중간: 기타 지연 항목, 곧 만기 항목, 기록 없는 항목(CS03 - 신차부터 계산되므로 비용 지불 전에 정비 기록부 확인) 및 역행하는 주행거리계 수치. - 낮음/정보: 계획 내 고가 항목(CS08), 간격보다 훨씬 일찍 수행된 항목(CS05), 일정에 없는 로그 항목, 사용 중인 혹독한 운행 조건 간격. - 연간 거리가 잘못된 것 같으면 수치를 확인하십시오. 오늘 수치와 함께 `--odometer`를 사용하십시오. ### 4. 방문 계획 방문 테이블을 사용하십시오: 모든 지연 항목과 묶음 기간 내 만기 항목에 대해 방문 1을 예약하고, 공업사에 전체 목록을 한 번에 견적 요청하십시오. 한 번의 방문을 절약할 수 있다면 저렴한 항목을 몇 주 앞당기되, 안전 또는 법적 항목을 절대 늦추지 마십시오. 방문, 공업사 체크리스트 및 월별 적립금으로 `templates/service-plan.md`를 채우십시오. ### 5. 전달 지연 및 곧 만기 목록을 일반 언어로 반환하고, 날짜 및 예상 비용이 포함된 방문 계획, 12개월 및 24개월 비용 예측 및 따로 적립할 월별 금액, 그리고 확인할 질문(이력 없는 항목의 기록, 해당 엔진의 정확한 간격, 딜러 정비를 요구하는 보증 조건)을 반환하십시오. ## 예시 결과 83,400km(연간 약 11,000km) 주행한 2019년식 옥타비아의 경우 4개 항목이 지연되었습니다: 브레이크 오일(126일, 안전), 점검 서비스 및 에어 필터(시간 기준), 타이어 위치 교체(3,300km), 그리고 차량 검사는 2026년 11월 20일에 만기입니다 - 지금 한 번의 방문으로 5개 모두 약 410유로에 해결됩니다. 타이밍 벨트 기록이 없으므로 신차부터 계산되어 2027년 11월에 연식 기준으로 만기(약 680유로)이며, 새 타이어는 2027년 6월로 예상됩니다(약 560유로). 향후 24개월간 유지비는 약 2,445유로이므로 월 약 102유로를 적립하십시오. 2023년 9월의 4,790km 수치는 오타이며 무시됩니다. ## 규칙 - 정확한 엔진에 대한 제조사 간격을 사용하십시오. 예시 값은 설명용일 뿐입니다. - 먼저 도래하는 기준: 항목은 거리 및 시간 제한 중 더 이른 시점에 만기이며, 거의 운전하지 않는 차량도 마찬가지입니다. - 비용이나 방문을 절약하기 위해 브레이크, 타이어, 조향, 경고등 또는 법적 검사를 연기하도록 제안하지 마십시오. - 기록이 없는 항목은 수행되지 않은 것으로 알 수 없습니다: 그렇게 말하고 정비 기록부를 확인하거나 공업사에 문의하도록 제안하십시오. - 비용은 추정치입니다. 사용자에게 견적을 받도록 말하고, 독립 정비소를 제안하기 전에 보증 조건을 언급하십시오. ## 간격 및 혹독한 운행 조건 ## 간격 출처 - 정확한 모델, 엔진 및 연식에 대한 소유자 매뉴얼 또는 정비 기록부. 동일한 모델도 가솔린, 디젤 및 하이브리드 엔진과 "고정" 및 "유연/장수명" 정비 체계에 따라 다른 일정을 가질 수 있습니다. - 많은 차량이 대시보드에 다음 오일 서비스를 표시합니다(서비스 표시기). 기록부와 일치하지 않으면 딜러에게 차량이 어떤 체계로 설정되어 있는지 문의하십시오. - 보증 조건을 염두에 두십시오: 일부 제조사는 정품 사양 부품으로 자사 일정에 따른 정비를 요구합니다. 독립 정비소는 일반적으로 일정을 따르고 인보이스를 보관하면 괜찮습니다. ## 먼저 도래하는 기준 거리 및 시간 간격이 모두 있는 모든 항목은 둘 중 더 이른 시점에 만기입니다. 저주행 차량은 시간 제한에 도달하고(오일 노화, 브레이크 오일 수분 흡수, 고무 벨트 및 타이어 노화), 고주행 차량은 거리 제한에 도달합니다. 플래너는 연간 거리로 거리 제한을 날짜로 변환합니다. ## 혹독한 운행 조건 대부분의 매뉴얼은 차량이 주로 다음과 같이 사용될 때 더 짧은 간격을 나열합니다: - 단거리 주행(약 10km 미만, 엔진이 거의 완전히 예열되지 않음), 특히 추운 날씨; - 시내 정체 구간, 택시 또는 배달용, 장시간 공회전; - 트레일러 또는 캐러밴 견인, 루프 적재, 산악 주행; - 먼지 또는 모래 도로, 매우 덥거나 매우 추운 기후. 이 중 하나라도 대부분의 운전에 해당하면 `--severe`(또는 `"severe": true`)를 사용하여 `severe_km` / `severe_months`가 있는 항목이 더 짧은 간격을 사용하도록 하십시오. ## 항목 참고 사항(일반적, 매뉴얼 확인) | 항목 | 일반적 기준 | 참고 사항 | |---|---|---| | 엔진 오일 및 필터 | 거리 및 시간 | 가장 중요한 일상 항목; 매뉴얼의 오일 사양 사용 | | 점검 서비스 | 거리 및 시간 | 더 큰 서비스; 일반적으로 많은 점검 포함 | | 브레이크 오일 | 시간만(종종 2년) | 수분 흡수; 절대 연기 금지 | | 브레이크 점검 / 패드 / 디스크 | 거리 및 시간 | 고정 간격이 아닌 마모에 따라 교체 | | 냉각수 | 시간 또는 "수명" | 매년 겨울마다 수위 및 동결 방지 점검 | | 에어 필터, 캐빈 필터 | 거리 및 시간 | 먼지 또는 오염 지역에서 더 짧음 | | 점화 플러그(가솔린) | 거리 및 시간 | 최신 엔진에서 긴 간격 | | 타이밍 벨트(장착된 경우) | 거리 및 시간 | 고장 시 엔진 파괴 가능; 종종 워터 펌프와 함께 교체; 체인은 일반적으로 간격 없음 | | 타이어 | 마모 및 연식 | 매월 트레드 깊이 점검; 많은 제조사가 트레드와 관계없이 6-10년 후 교체 권장 | | 타이어 위치 교체 | 거리 | 매뉴얼이 권장하는 경우(전/후륜 크기가 다른 경우 제외) | | 차량 검사 | 시간(법적) | 첫 검사는 몇 년 후(예: 3년 또는 4년), 그 다음 국가에 따라 1-2년마다 | | 12V 배터리 | 상태 | 4-5년이 지나면 겨울 전에 테스트 | ## 법적 검사 규칙은 국가마다 다릅니다(영국 MOT, 독일 TUV/HU, 프랑스 CT, 터키 TUVTURK, 미국 주 검사). 국가의 첫 검사 지연을 `first_months`로, 반복 간격을 `months`로 입력하고 항목을 `"legal": true`로 표시하십시오. 많은 제도에서 날짜를 잃지 않고 약간 일찍 검사할 수 있습니다. 더 이른 방문에 묶기 전에 확인하십시오. ## 절대 연기하지 말아야 할 것 브레이크 및 브레이크 오일, 타이어(트레드, 손상, 연식), 조향 및 서스펜션 결함, 경고등(오일 압력, 브레이크, 엔진 온도) 및 법적 검사. 플래너는 이를 `safety` / `legal`로 표시하고 지연 시 높음으로 처리합니다. ## 입력 얻기 - 모든 방문의 주행거리계 및 날짜: 인보이스, 정비 기록부 도장, 딜러의 디지털 정비 기록, 법적 검사 증명서(주행거리계 기록). - 가격: 마지막 인보이스 또는 견적; 동시에 자주 교체되는 부품(와이퍼, 전구, 브레이크 패드)에 10-20% 추가. ## 점검 및 결과 코드 | 코드 | 심각도 | 점검 | 조치 | |---|---|---|---| | CS01 | 높음: 안전 또는 법적 항목 지연; 중간: 기타 항목 지연 | 만기일 또는 만기 거리 경과 | 예약하십시오; 안전 항목은 완료될 때까지 조심히 운전 | | CS02 | 중간 | `due_soon.days` 또는 `due_soon.km` 내 만기 | 다음 방문과 함께 예약 | | CS03 | 중간 | 로그에 항목 기록 없음: 최초 등록부터 계산 | 비용 지불 전에 정비 기록부 / 영수증 / 딜러 기록 확인 | | CS04 | 높음: 미래 날짜, 현재 주행거리계보다 높은 수치; 중간: 이전 수치보다 낮은 수치, 읽을 수 없는 주행거리계 | 로그 일관성 | 오타 수정; 거리 및 만기 계산에서 수치 무시 | | CS05 | 정보 | 두 간격의 60% 미만에서 마지막 수행 | 의도된 경우(혹독한 사용, 보증) 괜찮음; 그렇지 않으면 조기 지출 | | CS06 | 정보 | 혹독한 운행 조건 간격 사용 중 | 조건이 여전히 적용되는지 확인 | | CS07 | 정보 | 일정에 없는 로그 항목(수리, 추가) | 이력용으로 유지; 다른 이름의 일정 항목이면 별칭 추가 | | CS08 | 낮음 | 400 이상 비용 항목이 계획 기간 내 | 예산 책정; 조기 견적 요청 | | CS09 | 중간 | 주행거리계 수치 부족(최소 60일 전 수치): 기본 연간 거리 사용 | 수치 추가(검사 증명서, 연료 앱, 대시보드 사진) | ## 판정 - **안전/법적 포함 지연**: 지금 예약하십시오. - **지연**: 곧 예약하십시오. - **곧 만기** / **최신 상태**. ## 숫자 계산 방법 - 연간 거리: 오늘부터 최소 1년 전의 최신 로그 수치에서 오늘 수치까지(그렇게 오래된 것이 없으면 가장 이른 수치). - 다음 만기: 마지막 수행 날짜 + 개월, 및 마지막 수행 주행거리계 + km를 일일 거리로 날짜로 변환; 더 이른 것이 우선. 기록이 없는 항목은 0km에서 최초 등록부터 계산(`first_months`는 처음에 `months`를 대체, 예: 36개월 후 첫 법적 검사). - 기간: 각 항목은 수행된 것으로 가정된 날짜(만기일 또는 지연된 경우 오늘)부터 반복. - 방문: 방문의 첫 항목의 `bundle.days` 및 `bundle.km` 내 만기 항목이 해당 방문에 합류(앞당겨지고, 절대 늦춰지지 않음). - 비용: 각 발생의 `cost`가 해당 분기에 추가; 월별 적립금 = 기간 총액 / 기간 개월 수. ## 차량 JSON 키 `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 `date, odometer, item, cost, shop, notes` - 한 번의 방문에서 여러 항목은 `;`로 구분. 이름은 일정 항목 및 별칭과 대소문자 구분 없이 일치. 비용 열은 기록용; 예측은 일정 가격 사용. ## 템플릿 # 자동차 정비 계획: <차량, 등록> **날짜:** <YYYY-MM-DD> **주행거리계:** <83,400 km> **연간 거리:** <약 11,000 km> **일정:** <소유자 매뉴얼, 일반 / 혹독한 운행 조건> ## 지금 수행 | 항목 | 이유 | 예상 비용 | |---|---|---| | <브레이크 오일> | <4개월 지연(2년 간격) - 안전> | <85> | | <차량 검사> | <2026-11-20 만기 - 법적> | <60> | ## 다음 방문 | 시기(예약 기한) | 약 주행거리 | 항목 | 예상 비용 | |---|---|---|---| | <2027-02-14> | <86,500 km> | <오일 및 필터, 브레이크 점검> | <180> | ## 예산 - 다음 12개월: <1,220> <EUR> - 다음 24개월: <2,445> <EUR> -> 월 <102> 적립 - 향후 고가 항목: <타이밍 벨트 키트 및 워터 펌프, 2027년 11월, 약 680> ## 확인 사항 - [ ] <타이밍 벨트 기록 없음 - 정비 기록부 / 딜러 확인> - [ ] <이 엔진의 정확한 브레이크 오일 간격> - [ ] <보증: 딜러 정비 요구?> ## 공업사 체크리스트 - 차량: <제조사, 모델, 엔진, 요청 시 VIN>, 주행거리계 <...> - 수행 요청: <"지금 수행" 목록> - 교체 전 점검 및 견적 요청: <브레이크 패드/디스크, 타이어, 와이퍼, 배터리> - 정비 기록부 도장 / 디지털 기록 발송 및 인보이스에 주행거리계 기록 요청. *간격은 <출처> 기준; 비용은 추정치. 비정상적인 것(경고등, 소음, 쏠림, 진동)은 즉시 점검받으십시오.* ## 예시: 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"]} ] } ## 예시: 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 ## 예시: 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. ## Python script (car_service_plan.py) ```python #!/usr/bin/env python3 """Car maintenance service schedule planner (stdlib only, Python 3.8+). Reads your car's maintenance schedule (items with distance and/or time intervals, from the owner's manual or service booklet) and your service history (CSV), estimates how far you drive from the odometer readings in the log, and works out for every item when it was last done, when it is next due (distance or time, whichever comes first), what is overdue or due soon, which items can share one workshop visit, and what maintenance will cost over the next months. Usage: car_service_plan.py CAR.json SERVICE_LOG.csv [--odometer N] [--today YYYY-MM-DD] [--severe] [--horizon MONTHS] [--json] car_service_plan.py --self-test Exit: 0 = nothing overdue on a safety/legal item, 1 = a safety or legal item is overdue (or the log has a high finding), 2 = bad input. """ 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 must be YYYY-MM-DD, got %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 is not a number: %r" % (what, x)) def fk(x): return "{:,.0f}".format(x) def fm(x): return "{:,.2f}".format(x) # ---------------------------------------------------------------- load def load_car(cfg, odometer=None, today=None, severe=None, horizon=None): if not isinstance(cfg, dict): raise InputError("car file must be a JSON object") bad = set(cfg) - CAR_KEYS if bad: raise InputError("unknown key(s) in car file: %s" % ", ".join(sorted(bad))) v = cfg.get("vehicle") or {} odo = cfg.get("odometer") or {} drv = cfg.get("driving") or {} c = {"name": v.get("name", "Car"), "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"), "odometer reading") 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("give the current odometer reading (odometer.reading or --odometer)") for i, it in enumerate(cfg.get("schedule") or [], 1): name = it.get("item") if not name: raise InputError("schedule item %d has no 'item' name" % 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("schedule item '%s' needs km and/or months" % 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 + " cost"), "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("car file needs a non-empty '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("service log needs columns date, odometer, item (has %s)" % ", ".join(rows[0])) entries, readings = [], [] for n, r in enumerate(rows, 2): try: d = pdate(r.get("date"), "date (row %d)" % n) except InputError as e: F.append(("high", "CS04", "row %d: %s - row ignored" % (n, e))); continue if d > c["today"]: F.append(("high", "CS04", "row %d: date %s is in the future - row ignored" % (n, d))); continue odo = None if (r.get("odometer") or "").strip(): try: odo = num(r["odometer"], "odometer (row %d)" % n) except InputError as e: F.append(("medium", "CS04", "%s - distance ignored for this row" % 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", "row %d: odometer %s on %s is higher than the current reading %s - typo? reading ignored" % (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", "row %d: odometer %s on %s is lower than %s on %s - typo or wrong car? reading ignored" % (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 per day from the latest reading at least a year before today (else the earliest).""" 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", "not enough odometer readings to estimate your yearly distance - using the default %s %s/year" % (fk(c["default_per_year"]), c["unit"]))) return c["default_per_year"] / 365.0, "default" 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", "row %d: '%s' is not in your schedule - kept in the history only" % (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 at %s" % (last["date"], fk(last["odo"]) if last["odo"] is not None else "?") else: if not c["first"]: raise InputError("no record of '%s' and no vehicle.first_registered to count from" % it["item"]) base_d, base_km, base_note = c["first"], 0.0, "no record (counted from new, %s)" % c["first"] F.append(("medium", "CS03", "%s: no record in your log - counted from new; check the service book or receipts" % 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"], "unknown" 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 = "OVERDUE" if overdue else ("DUE SOON" if soon else "OK") if overdue: late = [] if due_d is not None and due_d < c["today"]: late.append("%d days" % (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 is overdue by %s%s - book it now" % (it["item"], " and ".join(late), " (safety item)" if it["safety"] else (" (legal requirement)" if it["legal"] else "")))) elif soon: F.append(("medium", "CS02", "%s is due soon: %s%s" % (it["item"], due_d if due_d and (due_d - c["today"]).days <= c["soon_days"] else "", (" / at %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 was last done after %s %s / %d months (under 60%% of the interval) - fine for severe use, otherwise money spent early" % (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 months" % 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": "legal" if it["legal"] else ("safety" if it["safety"] else ""), "severe": it["severe_used"]}) # occurrences in the horizon (each one assumed done on its due date) 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 (about %s) falls in the plan on %s - budget for it now" % (it["item"], fm(it["cost"]), o["date"]))) if c["severe"]: F.append(("info", "CS06", "severe-service intervals used%s" % ((" (" + ", ".join(c["severe_reasons"]) + ")") if c["severe_reasons"] else ""))) # visits: group occurrences within the bundle window 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]}) # costs by quarter 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} # ---------------------------------------------------------------- report 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"] == "OVERDUE"] soon = [r for r in res["rows"] if r["status"] == "DUE SOON"] L = ["# Service plan: %s" % c["name"], ""] L.append("Today %s, odometer %s %s. You drive about %s %s/year (%s). %s intervals, plan horizon %d months. Source: %s." % ( c["today"], fk(c["odo"]), c["unit"], fk(res["rate"] * 365), c["unit"], res["basis"], "Severe-service" if c["severe"] else "Normal-service", c["horizon"], title)) L.append("") if any(r["safety"] for r in ov): L.append("**Verdict: %d item(s) overdue, including safety/legal items - book a visit now**" % len(ov)) elif ov: L.append("**Verdict: %d item(s) overdue - book a visit soon**" % len(ov)) elif soon: L.append("**Verdict: nothing overdue, %d item(s) due soon**" % len(soon)) else: L.append("**Verdict: up to date**") L.append("") L.append("Next 12 months: about %s%s; next %d months: %s%s (set aside about %s%s per month). Findings: %d high, %d medium, %d low, %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 += ["", "## Items", "", "| Item | Interval | Last done | Due by date | Due at %s | Next (projected) | Trigger | Status | Est. cost |" % c["unit"], "|---|---|---|---|---|---|---|---|---|"] order = {"OVERDUE": 0, "DUE SOON": 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"] + (" (severe)" 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 += ["", "## Workshop visits (items within %d days / %s %s are bundled)" % (c["bundle_days"], fk(c["bundle_km"]), c["unit"]), "", "| # | Book by | At about | Items | Est. cost |", "|---|---|---|---|---|"] for i, v in enumerate(res["visits"], 1): L.append("| %d | %s%s | %s %s | %s | %s |" % (i, v["date"], " (now)" if v["date"] == c["today"] else "", fk(v["km"]), c["unit"], ", ".join(o["item"] + (" (overdue)" if o["overdue"] else "") for o in v["items"]), fm(sum(o["cost"] for o in v["items"])))) L += ["", "## Cost forecast by quarter", "", "| Quarter | Est. cost |", "|---|---|"] for q in sorted(res["quarters"]): L.append("| %s | %s |" % (q, fm(res["quarters"][q]))) L.append("| **Total %d months** | **%s** |" % (c["horizon"], fm(res["total"]))) L += ["", "## Findings", ""] L += ["- **%s %s** %s" % (s.upper(), code, m) for s, code, m in F] or ["- none"] L += ["", "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."] 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"]]}) # ---------------------------------------------------------------- self-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": "Oil", "km": 15000, "months": 12, "severe_km": 7500, "severe_months": 6, "cost": 100, "aliases": ["oil change"]}]} 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,Oil,90,X\n" r = run(car(), log) t("rate from readings: 10000 km/year", abs(r["rate"] * 365 - 10000) < 1) t("due by time first (12 months)", row(r, "Oil")["due_date"] == datetime.date(2026, 1, 1) and row(r, "Oil")["trigger"] == "time") t("due today counts as due soon", row(r, "Oil")["status"] == "DUE SOON") r = run(car(), "2025-06-01,40000,Oil,90,X\n") t("distance triggers first when driving a lot", row(r, "Oil")["due_km"] == 55000 and row(r, "Oil")["trigger"] == "distance") r = run(car(), "2024-06-01,30000,Oil,90,X\n") t("overdue by time and distance -> CS01", row(r, "Oil")["status"] == "OVERDUE" and ("medium", "CS01") in codes(r)) sched = [{"item": "Brake fluid", "months": 24, "cost": 80, "safety": True}] r = run(car(schedule=sched), "2023-06-01,30000,Brake fluid,80,X\n2025-01-01,40000,Other,0,X\n") t("overdue safety item -> CS01 high", ("high", "CS01") in codes(r)) t("item not in schedule -> CS07", ("info", "CS07") in codes(r)) r = run(car(), "2025-01-01,40000,oil change,90,X\n") t("aliases match case-insensitively", row(r, "Oil")["last"].startswith("2025-01-01")) r = run(car(), "2025-01-01,40000,Wipers,20,X\n") t("no record -> counted from new, CS03", ("medium", "CS03") in codes(r) and "no record" in row(r, "Oil")["last"]) r = run(car(), "2025-01-01,40000,Oil,90,X\n", severe=True) t("severe intervals used", row(r, "Oil")["due_date"] == datetime.date(2025, 7, 1) and ("info", "CS06") in codes(r)) r = run(car(), "2024-01-01,30000,Oil,90,X\n2024-06-01,20000,Oil,90,X\n2025-01-01,40000,Oil,90,X\n") t("odometer going down -> CS04, reading ignored", ("medium", "CS04") in codes(r)) r = run(car(), "2025-01-01,40000,Oil,90,X\n2027-01-01,60000,Oil,90,X\n") t("future date -> CS04 high", ("high", "CS04") in codes(r)) r = run(car(), "2025-01-01,90000,Oil,90,X\n") t("reading above current odometer -> CS04 high", ("high", "CS04") in codes(r)) r = run(car(), "") t("no readings -> default rate + CS09", ("medium", "CS09") in codes(r) and abs(r["rate"] * 365 - 12000) < 1) r = run(car(), "2025-01-01,40000,Oil,90,X\n2025-04-01,42000,Oil,90,X\n") t("done early -> 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,Other,0,X\n") t("first_months for first test from new", 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("items within the bundle window share a visit", len(r["visits"][0]["items"]) == 2) t("horizon repeats yearly items", sum(1 for o in r["occ"] if o["item"] == "A") == 2) t("12-month and total forecast", r["next12"] == 150 and r["total"] == 300) sched = [{"item": "Belt", "km": 120000, "months": 96, "cost": 650}] r = run(car(schedule=sched), "2025-01-01,40000,Other,0,X\n") t("big-ticket item in horizon -> CS08", ("low", "CS08") in codes(r)) t("add_months clamps month end", 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("item without interval rejected", ok) try: load_car(dict(car(), colour="red")); ok = False except InputError: ok = True t("unknown key rejected", ok) r = run(car(), log) t("report has verdict, visits and forecast", all(s in report(r, "t") for s in ("**Verdict:", "## Workshop visits", "## Cost forecast"))) for name, ok in tests: print(("PASS " if ok else "FAIL ") + name) passed = sum(ok for _, ok in tests) print("%d/%d passed" % (passed, len(tests))) return 0 if passed == len(tests) else 1 def main(argv=None): ap = argparse.ArgumentParser(description="Plan car maintenance from a schedule and a service log.") 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="use severe-service intervals") ap.add_argument("--horizon", type=int, help="months to plan (default from car file, else 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("car file is not valid 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("error: %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()) ```

전체 프롬프트를 보려면 로그인하세요

Continue with:

By logging in, you agree to our Terms of Use and Privacy Policy

사용법

이 프롬프트는 productivity와 함께 사용하도록 설계되었습니다. 위의 프롬프트 내용을 복사하여 원하는 AI 도구에 붙여넣으세요.

최상의 결과를 얻으려면 자리 표시자(대괄호 또는 대문자로 표시)를 특정 요구 사항으로 사용자 지정할 수 있습니다.

참고 자료

분류:productivity| prompts.chat| car-maintenance| service-schedule

토론