168 lines
6.7 KiB
Python
168 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
|
"""按三份GPS日里程文件处理2026年7月测试车辆考核源表。"""
|
|
from copy import copy
|
|
from collections import defaultdict
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
import re
|
|
import shutil
|
|
import openpyxl
|
|
from openpyxl.styles import PatternFill
|
|
|
|
BASE = Path('/Users/kkfluous/Downloads')
|
|
OUT = BASE / '7月核算结果'
|
|
SRC = BASE / '租赁任务考核_2026年7月 (1).xlsx'
|
|
TEST = Path('/Users/kkfluous/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_1704407055112_3af8/temp/RWTemp/2026-08/4340099467fbf08b4c6583c49adba4ee/7月测试车辆明细.xlsx')
|
|
GPS_DAILY = BASE / '智能管车_车辆里程日报-140106360.xlsx'
|
|
GPS_DETAIL = [
|
|
BASE / '里程统计[天][2026-07-01至2026-07-31].xlsx',
|
|
BASE / '里程统计[天][2026-07-01至2026-07-31] (1).xlsx',
|
|
]
|
|
EXCLUDED_TEST_PLATES = {'粤AG37316'}
|
|
ZERO_OVERRIDE = set()
|
|
DEPARTMENT_OVERRIDE = {'粤AGP6675': '业务六部', '粤AGP9757': '业务六部'}
|
|
|
|
def norm(v):
|
|
return re.sub(r'\s+', '', str(v or '')).upper()
|
|
|
|
def number(v):
|
|
try:
|
|
return float(v or 0)
|
|
except (TypeError, ValueError):
|
|
return 0.0
|
|
|
|
def read_test_plates():
|
|
wb = openpyxl.load_workbook(TEST, data_only=True)
|
|
ws = wb.active
|
|
plates = {norm(r[0]) for r in ws.iter_rows(min_row=2, values_only=True) if norm(r[0])}
|
|
wb.close()
|
|
return plates - EXCLUDED_TEST_PLATES
|
|
|
|
def read_daily_matrix():
|
|
wb = openpyxl.load_workbook(GPS_DAILY, data_only=True)
|
|
ws = wb.active
|
|
headers = [c.value for c in ws[1]]
|
|
day_cols = {}
|
|
for i, v in enumerate(headers):
|
|
m = re.fullmatch(r'07月(\d{2})日', str(v or ''))
|
|
if m:
|
|
day_cols[int(m.group(1))] = i
|
|
data = {}
|
|
for row in ws.iter_rows(min_row=2, values_only=True):
|
|
plate = norm(row[0])
|
|
if plate:
|
|
data[plate] = {d: number(row[i]) for d, i in day_cols.items()}
|
|
wb.close()
|
|
return data
|
|
|
|
def read_detail_files():
|
|
data = defaultdict(lambda: defaultdict(float))
|
|
for path in GPS_DETAIL:
|
|
wb = openpyxl.load_workbook(path, data_only=True)
|
|
ws = wb.active
|
|
headers = [c.value for c in ws[3]]
|
|
pcol = next(i for i, v in enumerate(headers) if v and '车牌' in str(v))
|
|
dcol = next(i for i, v in enumerate(headers) if str(v or '').strip() == '日期')
|
|
mcol = next(i for i, v in enumerate(headers) if v and '里程' in str(v) and 'km' in str(v).lower())
|
|
for row in ws.iter_rows(min_row=4, values_only=True):
|
|
plate = norm(row[pcol])
|
|
if not plate:
|
|
continue
|
|
try:
|
|
dt = row[dcol] if isinstance(row[dcol], datetime) else datetime.strptime(str(row[dcol])[:10], '%Y-%m-%d')
|
|
except (TypeError, ValueError):
|
|
continue
|
|
# 同一车辆同一天在两份明细中如重复,取较大值,避免重复累计。
|
|
data[plate][dt.day] = max(data[plate][dt.day], number(row[mcol]))
|
|
wb.close()
|
|
return {p: dict(days) for p, days in data.items()}
|
|
|
|
def copy_cell_style(src, dst):
|
|
dst._style = copy(src._style)
|
|
if src.has_style:
|
|
dst.number_format = src.number_format
|
|
dst.protection = copy(src.protection)
|
|
dst.alignment = copy(src.alignment)
|
|
|
|
def main():
|
|
OUT.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(SRC, OUT / '租赁任务考核_2026年7月_原版备份.xlsx')
|
|
tests = read_test_plates()
|
|
matrix = read_daily_matrix()
|
|
details = read_detail_files()
|
|
|
|
wb = openpyxl.load_workbook(SRC)
|
|
ws = wb['业务考核视图']
|
|
# 原表O列是实际里程。插入后统一为 O原TBOX、P实际、Q测试、R完成率、S是否达标。
|
|
ws.insert_cols(15, 1)
|
|
ws.insert_cols(17, 1)
|
|
ws['O1'] = '原TBOX里程(km)'
|
|
ws['Q1'] = '测试里程(km)'
|
|
copy_cell_style(ws['P1'], ws['O1'])
|
|
copy_cell_style(ws['P1'], ws['Q1'])
|
|
yellow = PatternFill(fill_type='solid', fgColor='FFFF00')
|
|
records = []
|
|
missing = []
|
|
for ri in range(2, ws.max_row + 1):
|
|
plate = norm(ws.cell(ri, 1).value)
|
|
if plate in DEPARTMENT_OVERRIDE:
|
|
ws.cell(ri, 4).value = DEPARTMENT_OVERRIDE[plate]
|
|
original = number(ws.cell(ri, 16).value)
|
|
ws.cell(ri, 15).value = round(original, 2)
|
|
copy_cell_style(ws.cell(ri, 16), ws.cell(ri, 15))
|
|
copy_cell_style(ws.cell(ri, 16), ws.cell(ri, 17))
|
|
if plate not in tests:
|
|
ws.cell(ri, 17).value = 0
|
|
continue
|
|
start = ws.cell(ri, 11).value
|
|
end = ws.cell(ri, 12).value
|
|
start_day = start.day if isinstance(start, datetime) else 1
|
|
end_day = end.day if isinstance(end, datetime) else 31
|
|
if plate in ZERO_OVERRIDE:
|
|
actual = 0.0
|
|
source = 'GPS缺失,业务确认暂按0'
|
|
elif plate in matrix:
|
|
actual = sum(matrix[plate].get(d, 0) for d in range(start_day, end_day + 1))
|
|
source = GPS_DAILY.name
|
|
elif plate in details:
|
|
actual = sum(details[plate].get(d, 0) for d in range(start_day, end_day + 1))
|
|
source = '里程统计日明细'
|
|
else:
|
|
missing.append(plate)
|
|
continue
|
|
target = number(ws.cell(ri, 14).value)
|
|
test_mileage = max(0.0, original - actual)
|
|
rate = actual / target * 100 if target > 0 else 0
|
|
ws.cell(ri, 16).value = round(actual, 2)
|
|
ws.cell(ri, 17).value = round(test_mileage, 2)
|
|
ws.cell(ri, 18).value = round(rate, 2)
|
|
ws.cell(ri, 19).value = '达标' if target > 0 and actual >= target else '未达标'
|
|
for cell in ws[ri]:
|
|
cell.fill = copy(yellow)
|
|
records.append((plate, ri, original, actual, test_mileage, source))
|
|
if missing:
|
|
raise RuntimeError('仍缺GPS车牌: ' + ','.join(sorted(set(missing))))
|
|
|
|
# 对齐5/6月的宽度、筛选和冻结窗格;保留原表其余样式。
|
|
ref = openpyxl.load_workbook(BASE / '租赁任务考核_2026年6月.xlsx', read_only=False)
|
|
rws = ref['业务考核视图']
|
|
for i in range(1, 24):
|
|
letter = openpyxl.utils.get_column_letter(i)
|
|
if rws.column_dimensions[letter].width:
|
|
ws.column_dimensions[letter].width = rws.column_dimensions[letter].width
|
|
ws.freeze_panes = rws.freeze_panes
|
|
ws.auto_filter.ref = f'A1:W{ws.max_row}'
|
|
ref.close()
|
|
|
|
out = OUT / '租赁任务考核_2026年7月.xlsx'
|
|
wb.save(out)
|
|
wb.close()
|
|
print(f'output={out}')
|
|
print(f'test_list={len(tests)} intervention_records={len(records)} intervention_plates={len(set(r[0] for r in records))}')
|
|
print(f'zero_override={sorted(ZERO_OVERRIDE)}')
|
|
print(f'department_override={DEPARTMENT_OVERRIDE}')
|
|
print(f'total_original={sum(r[2] for r in records):.2f} total_gps={sum(r[3] for r in records):.2f} total_test={sum(r[4] for r in records):.2f}')
|
|
|
|
if __name__ == '__main__':
|
|
main()
|