148 lines
7.5 KiB
Python
148 lines
7.5 KiB
Python
#!/usr/bin/env python3
|
||
"""复刻6月干预说明配色,并将达标分类改为一行一人。"""
|
||
from copy import copy
|
||
from collections import defaultdict
|
||
from pathlib import Path
|
||
import openpyxl
|
||
|
||
BASE = Path('/Users/kkfluous/Downloads')
|
||
JUL = BASE / '7月核算结果/租赁任务考核_2026年7月_干预情况说明.xlsx'
|
||
JUN = BASE / '6月核算结果/租赁任务考核_2026年6月_干预情况说明.xlsx'
|
||
SRC = BASE / '7月核算结果/租赁任务考核_2026年7月.xlsx'
|
||
BAK = BASE / '7月核算结果/租赁任务考核_2026年7月_原版备份.xlsx'
|
||
TEST = Path('/Users/kkfluous/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_1704407055112_3af8/temp/RWTemp/2026-08/4340099467fbf08b4c6583c49adba4ee/7月测试车辆明细.xlsx')
|
||
|
||
def recs(path):
|
||
wb = openpyxl.load_workbook(path, data_only=True)
|
||
ws = wb['业务考核视图']
|
||
h = [c.value for c in ws[1]]
|
||
out = [dict(zip(h, r)) for r in ws.iter_rows(min_row=2, values_only=True) if r[0]]
|
||
wb.close()
|
||
return out
|
||
|
||
def clone_style(src, dst):
|
||
dst.font = copy(src.font)
|
||
dst.fill = copy(src.fill)
|
||
dst.border = copy(src.border)
|
||
dst.alignment = copy(src.alignment)
|
||
dst.protection = copy(src.protection)
|
||
dst.number_format = src.number_format
|
||
|
||
def style_row(ws, row, cols, ref_ws, ref_row, ref_cols=None):
|
||
ref_cols = ref_cols or cols
|
||
for c, rc in zip(cols, ref_cols):
|
||
clone_style(ref_ws.cell(ref_row, rc), ws.cell(row, c))
|
||
|
||
new = recs(SRC)
|
||
old = recs(BAK)
|
||
old_map = {}
|
||
for r in old:
|
||
old_map[r['车牌号']] = r
|
||
|
||
twb = openpyxl.load_workbook(TEST, data_only=True)
|
||
test_plates = {str(r[0]).strip() for r in twb.active.iter_rows(min_row=2, values_only=True) if r[0]}
|
||
test_plates.discard('粤AG37316')
|
||
twb.close()
|
||
intervention = [r for r in new if r['车牌号'] in test_plates]
|
||
flip = []
|
||
for r in intervention:
|
||
old_r = old_map.get(r['车牌号'])
|
||
if old_r and old_r.get('是否达标') == '达标' and r.get('是否达标') != '达标':
|
||
flip.append(r)
|
||
|
||
groups = defaultdict(lambda: defaultdict(int))
|
||
for r in flip:
|
||
groups[r.get('考核目标') or '未知'][(r.get('部门名称') or '', r.get('销售经理') or '')] += 1
|
||
|
||
wb = openpyxl.load_workbook(JUL)
|
||
ref = openpyxl.load_workbook(JUN)
|
||
ws = wb['测试与考核情况说明']
|
||
rws = ref['测试与考核情况说明']
|
||
|
||
# 清空原说明页后,按6月视觉规范重建。
|
||
for merged in list(ws.merged_cells.ranges):
|
||
ws.unmerge_cells(str(merged))
|
||
ws.delete_rows(1, ws.max_row)
|
||
for col in range(1, 8):
|
||
letter = openpyxl.utils.get_column_letter(col)
|
||
ws.column_dimensions[letter].width = rws.column_dimensions[letter].width
|
||
|
||
total_plates = len({r['车牌号'] for r in new})
|
||
interv_plates = len({r['车牌号'] for r in intervention})
|
||
total_tbox = sum(float(r.get('原TBOX里程(km)') or 0) for r in new)
|
||
total_actual = sum(float(r.get('实际行驶里程(km)') or 0) for r in new)
|
||
total_test = sum(float(r.get('测试里程(km)') or 0) for r in new)
|
||
interv_tbox = sum(float(r.get('原TBOX里程(km)') or 0) for r in intervention)
|
||
interv_actual = sum(float(r.get('实际行驶里程(km)') or 0) for r in intervention)
|
||
interv_test = sum(float(r.get('测试里程(km)') or 0) for r in intervention)
|
||
|
||
# 标题及说明:深蓝标题、浅黄说明,与6月一致。
|
||
ws.merge_cells('A1:G1'); ws['A1'] = '2026年7月 业务考核 — 测试与考核情况说明'; clone_style(rws['A1'], ws['A1'])
|
||
ws.row_dimensions[1].height = rws.row_dimensions[1].height
|
||
ws.merge_cells('A2:G3'); ws['A2'] = ('7月业务考核车辆中,测试车辆统一以指定GPS日里程按考核期汇总替换TBOX里程,差额作为测试里程剥离且最低取0。\n'
|
||
'粤AG37316因GPS缺失,经业务确认暂按0核算。'); clone_style(rws['A2'], ws['A2'])
|
||
ws.row_dimensions[2].height = rws.row_dimensions[2].height
|
||
|
||
ws.merge_cells('A5:G5'); ws['A5'] = '一、7月业务考核与干预情况'; clone_style(rws['A5'], ws['A5'])
|
||
headers = ['项目', '7月业务考核车辆', '涉及测试的车辆', '占比']
|
||
for c, v in enumerate(headers, 1): ws.cell(6,c,v); clone_style(rws.cell(6,c), ws.cell(6,c))
|
||
stats = [
|
||
('车辆数(辆)', total_plates, interv_plates),
|
||
('产生考核记录数(条)', len(new), len(intervention)),
|
||
('仪表盘里程(km)', round(total_tbox), round(interv_tbox)),
|
||
('业务考核里程 GPS(km)', round(total_actual), round(interv_actual)),
|
||
('测试里程(km)', round(total_test), round(interv_test)),
|
||
]
|
||
for rr, (label, allv, partv) in enumerate(stats, 7):
|
||
for c, v in enumerate([label, allv, partv, partv/allv if allv else 0], 1):
|
||
ws.cell(rr,c,v); clone_style(rws.cell(7 + ((rr-7)%2),c), ws.cell(rr,c))
|
||
|
||
# “测试里程”比例改为测试车辆剔除率,避免原100%被理解成车辆全部里程均为测试。
|
||
ws['D6'] = '占比/剔除率'
|
||
ws['D11'] = interv_test / interv_tbox if interv_tbox else 0
|
||
negative_floor_adjustment = sum(max(0, float(r.get('实际行驶里程(km)') or 0) - float(r.get('原TBOX里程(km)') or 0)) for r in intervention)
|
||
ws.merge_cells('A12:G12')
|
||
ws['A12'] = (f'勾稽:测试里程 {round(interv_tbox):,} − {round(interv_actual):,} + '
|
||
f'{round(negative_floor_adjustment):,}(负差归0调整)= {round(interv_test):,} km')
|
||
clone_style(rws['A2'], ws['A12'])
|
||
ws.row_dimensions[12].height = 22
|
||
|
||
ws.merge_cells('A13:G13'); ws['A13']='二、干预车辆考核达标情况说明'; clone_style(rws['A13'],ws['A13'])
|
||
ws.merge_cells('A14:G14'); ws['A14']=f'共 {len(intervention)} 条考核记录涉及里程干预;替换为GPS里程后,共 {len(flip)} 条考核车辆记录由“达标”转判为“未达标”。按车型分类如下:'; clone_style(rws['A14'],ws['A14'])
|
||
|
||
target_order = ['羚牛136辆4.5T冷链车','羚牛100辆18T','交投40辆4.5T普货','交投190辆4.5T冷链车','恒运50辆4.5T普货','恒运50辆4.5T 普货']
|
||
rn = 16
|
||
for target in target_order:
|
||
items = sorted(groups.get(target, {}).items())
|
||
if not items: continue
|
||
total = sum(v for _, v in items)
|
||
ws.merge_cells(start_row=rn,start_column=1,end_row=rn,end_column=3)
|
||
ws.cell(rn,1,f'◎{target}({total} 辆)'); clone_style(rws['A15'],ws.cell(rn,1)); rn += 1
|
||
for c,v in enumerate(['部门','销售经理','数量'],1): ws.cell(rn,c,v); clone_style(rws.cell(16,c),ws.cell(rn,c))
|
||
rn += 1
|
||
for idx, ((dept,sales),cnt) in enumerate(items):
|
||
for c,v in enumerate([dept,sales,cnt],1): ws.cell(rn,c,v); clone_style(rws.cell(17 + idx%2,c),ws.cell(rn,c))
|
||
rn += 1
|
||
rn += 1
|
||
|
||
# 数据来源说明沿用6月蓝色区域。
|
||
ws.merge_cells(start_row=rn,start_column=1,end_row=rn,end_column=7); ws.cell(rn,1,'三、里程三列数据来源说明'); clone_style(rws['A13'],ws.cell(rn,1)); rn += 1
|
||
for c,v in enumerate(['列','字段名','数据来源','说明'],1): ws.cell(rn,c,v); clone_style(rws.cell(6,c),ws.cell(rn,c))
|
||
rn += 1
|
||
descs = [
|
||
('O','原TBOX里程(km)','车辆 T-Box / 仪表盘读数','业务系统原始TBOX里程,包含运营、测试及调试里程。'),
|
||
('P','实际行驶里程(km)','指定GPS日里程文件','按考核日期逐日汇总GPS里程;粤AG37316经确认暂按0核算。'),
|
||
('Q','测试里程(km)','O − P(最低取0)','原TBOX里程与GPS里程的非负差额,不计入考核。'),
|
||
]
|
||
for idx,row in enumerate(descs):
|
||
for c,v in enumerate(row,1): ws.cell(rn,c,v); clone_style(rws.cell(28 + idx,c),ws.cell(rn,c))
|
||
ws.row_dimensions[rn].height = rws.row_dimensions[28 + idx].height
|
||
rn += 1
|
||
|
||
ws.sheet_view.showGridLines = rws.sheet_view.showGridLines
|
||
ws.freeze_panes = rws.freeze_panes
|
||
wb.save(JUL)
|
||
wb.close(); ref.close()
|
||
print(f'updated={JUL}')
|
||
print(f'rows={rn-1} flip={len(flip)} groups={sum(len(v) for v in groups.values())}')
|