Files
mileage-bonus/prepare_july_source.py
T

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()