282 lines
14 KiB
Python
282 lines
14 KiB
Python
#!/usr/bin/env python3
|
||
"""生成6月干预情况说明"""
|
||
import openpyxl, os, datetime
|
||
from openpyxl.styles import Font, Alignment, Border, Side, PatternFill
|
||
from openpyxl.utils import get_column_letter
|
||
from collections import defaultdict
|
||
|
||
os.chdir('/Users/kkfluous/Downloads')
|
||
|
||
# ========== Styles ==========
|
||
hdr_blue_font = Font(name='宋体', size=11, bold=True, color='FFFFFF')
|
||
hdr_blue_fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
|
||
hdr_green_font = Font(name='宋体', size=11, bold=True, color='FFFFFF')
|
||
hdr_green_fill = PatternFill(start_color='548235', end_color='548235', fill_type='solid')
|
||
hdr_align = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
||
data_font = Font(name='宋体', size=10)
|
||
data_align = Alignment(horizontal='center', vertical='center')
|
||
title_font = Font(name='宋体', size=14, bold=True)
|
||
subtitle_font = Font(name='宋体', size=10, bold=True)
|
||
yellow_fill = PatternFill(start_color='FFFF00', end_color='FFFF00', fill_type='solid')
|
||
light_yellow_fill = PatternFill(start_color='FFF2CC', end_color='FFF2CC', fill_type='solid')
|
||
light_gray_fill = PatternFill(start_color='F2F2F2', end_color='F2F2F2', fill_type='solid')
|
||
thin_border = Border(left=Side('thin'), right=Side('thin'), top=Side('thin'), bottom=Side('thin'))
|
||
|
||
def set_cell(ws, row, col, value, font=None, fill=None, align=None, border=None, num_fmt=None):
|
||
cell = ws.cell(row=row, column=col, value=value)
|
||
if font: cell.font = font
|
||
if fill: cell.fill = fill
|
||
if align: cell.alignment = align
|
||
if border: cell.border = border
|
||
if num_fmt: cell.number_format = num_fmt
|
||
return cell
|
||
|
||
# ========== Read Data ==========
|
||
src = openpyxl.load_workbook('租赁任务考核_2026年6月.xlsx', data_only=True)
|
||
ws_src = src['业务考核视图']
|
||
h_src = [c.value for c in next(ws_src.iter_rows(min_row=1, max_row=1))]
|
||
all_recs = []
|
||
for row in ws_src.iter_rows(min_row=2, values_only=True):
|
||
r = dict(zip(h_src, row))
|
||
if r['车牌号']:
|
||
all_recs.append(r)
|
||
src.close()
|
||
|
||
# Read test vehicle list
|
||
fp_test = '/Users/kkfluous/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_1704407055112_3af8/temp/RWTemp/2026-07/65b41c10aa83cabb7a365b2b8437c1c7/6月测试车辆.xlsx'
|
||
wb_test = openpyxl.load_workbook(fp_test, data_only=True)
|
||
ws_test = wb_test.active
|
||
test_rows = list(ws_test.iter_rows(min_row=1, values_only=True))
|
||
test_plates = set()
|
||
for row in test_rows[1:]:
|
||
for ci in [0, 3, 6]:
|
||
p = str(row[ci]).strip() if row[ci] else ''
|
||
if p and p != 'None':
|
||
test_plates.add(p)
|
||
wb_test.close()
|
||
|
||
# Identify干预 records (test vehicles in source)
|
||
intervention_recs = [r for r in all_recs if r['车牌号'] in test_plates]
|
||
intervention_plates = set(r['车牌号'] for r in intervention_recs)
|
||
|
||
# Stats
|
||
total_vehicles = len(set(r['车牌号'] for r in all_recs))
|
||
total_records = len(all_recs)
|
||
interv_vehicles = len(intervention_plates)
|
||
interv_records = len(intervention_recs)
|
||
total_actual = sum(float(r['实际行驶里程(km)'] or 0) for r in all_recs)
|
||
total_tbox = sum(float(r['原TBOX里程(km)'] or 0) for r in all_recs)
|
||
total_test = sum(float(r['测试里程(km)'] or 0) for r in all_recs)
|
||
interv_actual = sum(float(r['实际行驶里程(km)'] or 0) for r in intervention_recs)
|
||
interv_tbox = sum(float(r['原TBOX里程(km)'] or 0) for r in intervention_recs)
|
||
interv_test = sum(float(r['测试里程(km)'] or 0) for r in intervention_recs)
|
||
|
||
# Flip analysis:达→未达标 (using GPS backup for original)
|
||
bak = openpyxl.load_workbook('租赁任务考核_2026年6月_备份.xlsx', data_only=True)
|
||
ws_bak = bak['业务考核视图']
|
||
h_bak = [c.value for c in next(ws_bak.iter_rows(min_row=1, max_row=1))]
|
||
bak_recs = {}
|
||
for row in ws_bak.iter_rows(min_row=2, values_only=True):
|
||
r = dict(zip(h_bak, row))
|
||
if r['车牌号']:
|
||
bak_recs[r['车牌号']] = r
|
||
bak.close()
|
||
|
||
# For intervention vehicles: compare old达→new未达标
|
||
flip_recs = [] # records that changed from达 to未达标
|
||
for r in intervention_recs:
|
||
plate = r['车牌号']
|
||
old_rec = bak_recs.get(plate)
|
||
if old_rec:
|
||
old_q = old_rec.get('是否达标') == '达标'
|
||
new_q = r['是否达标'] == '达标'
|
||
if old_q and not new_q:
|
||
flip_recs.append(r)
|
||
|
||
# Group flip by考核目标+部门+销售
|
||
flip_by_target = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
|
||
for r in flip_recs:
|
||
target = r.get('考核目标', '未知')
|
||
dept = r.get('部门名称', '')
|
||
sales = r.get('销售经理', '')
|
||
flip_by_target[target][dept][sales] += 1
|
||
|
||
# ========== Create Workbook ==========
|
||
wb = openpyxl.Workbook()
|
||
|
||
# ===== Sheet 1: 测试与考核情况说明 =====
|
||
ws1 = wb.active
|
||
ws1.title = '测试与考核情况说明'
|
||
|
||
# Column widths
|
||
for col, w in enumerate([4, 30, 18, 18, 18, 18, 18], 1):
|
||
ws1.column_dimensions[get_column_letter(col)].width = w
|
||
|
||
rn = 1
|
||
|
||
# Title
|
||
set_cell(ws1, rn, 1, '2026年6月 业务考核 — 测试与考核情况说明', title_font)
|
||
ws1.merge_cells(start_row=rn, start_column=1, end_row=rn, end_column=5); rn += 1
|
||
|
||
# Subtitle
|
||
set_cell(ws1, rn, 1, '6月业务考核车辆中,部分车辆当月存在测试行为(仪表盘里程 > GPS 实际行驶里程),其差额作为测试里程剥离,不参与考核',
|
||
Font(name='宋体', size=10), light_yellow_fill)
|
||
ws1.merge_cells(start_row=rn, start_column=1, end_row=rn, end_column=5); rn += 2
|
||
|
||
# Section 1: Stats
|
||
set_cell(ws1, rn, 1, '一、6月业务考核与干预情况', subtitle_font); rn += 1
|
||
|
||
# Header
|
||
for ci, h in enumerate(['项目', '6月业务考核车辆', '涉及测试的车辆', '占比'], 1):
|
||
set_cell(ws1, rn, ci, h, hdr_blue_font, hdr_blue_fill, hdr_align, thin_border)
|
||
rn += 1
|
||
|
||
# Data rows
|
||
stats = [
|
||
('车辆数(辆)', total_vehicles, interv_vehicles),
|
||
('产生考核记录数(条)', total_records, interv_records),
|
||
('仪表盘里程(km)', round(total_tbox), round(interv_tbox)),
|
||
('业务考核里程 GPS(km)', round(total_actual), round(interv_actual)),
|
||
('测试里程(km)', round(total_test), round(interv_test)),
|
||
]
|
||
for label, all_v, interv_v in stats:
|
||
ratio = interv_v / all_v if all_v else 0
|
||
set_cell(ws1, rn, 1, label, data_font, None, data_align, thin_border)
|
||
set_cell(ws1, rn, 2, all_v, data_font, None, data_align, thin_border)
|
||
set_cell(ws1, rn, 3, interv_v, data_font, None, data_align, thin_border)
|
||
set_cell(ws1, rn, 4, ratio, data_font, None, data_align, thin_border, '0.00%')
|
||
rn += 1
|
||
|
||
rn += 2
|
||
|
||
# Section 2: Flip details
|
||
set_cell(ws1, rn, 1, '二、干预车辆考核达标情况说明', subtitle_font); rn += 1
|
||
flip_total = len(flip_recs)
|
||
summary_text = f'共 {interv_records} 条考核记录涉及里程干预;替换为GPS里程后,共 {flip_total} 条考核车辆记录由"达标"转判为"未达标"。按车型分类如下'
|
||
set_cell(ws1, rn, 1, summary_text, Font(name='宋体', size=10))
|
||
ws1.merge_cells(start_row=rn, start_column=1, end_row=rn, end_column=5); rn += 2
|
||
|
||
# By target
|
||
target_order = ['羚牛136辆4.5T冷链车', '羚牛100辆18T', '交投40辆4.5T普货', '交投190辆4.5T冷链车', '恒运50辆4.5T普货']
|
||
for target in target_order:
|
||
if target not in flip_by_target:
|
||
continue
|
||
dept_sales = flip_by_target[target]
|
||
total_cnt = sum(sum(sales.values()) for sales in dept_sales.values())
|
||
|
||
set_cell(ws1, rn, 1, f'▎{target}({total_cnt} 辆)', subtitle_font)
|
||
ws1.merge_cells(start_row=rn, start_column=1, end_row=rn, end_column=5); rn += 1
|
||
|
||
# Header: dept | sales | count | dept | sales | count
|
||
set_cell(ws1, rn, 1, '部门', hdr_green_font, hdr_green_fill, hdr_align, thin_border)
|
||
set_cell(ws1, rn, 2, '销售经理', hdr_green_font, hdr_green_fill, hdr_align, thin_border)
|
||
set_cell(ws1, rn, 3, '数量', hdr_green_font, hdr_green_fill, hdr_align, thin_border)
|
||
set_cell(ws1, rn, 4, '部门', hdr_green_font, hdr_green_fill, hdr_align, thin_border)
|
||
set_cell(ws1, rn, 5, '销售经理', hdr_green_font, hdr_green_fill, hdr_align, thin_border)
|
||
set_cell(ws1, rn, 6, '数量', hdr_green_font, hdr_green_fill, hdr_align, thin_border)
|
||
rn += 1
|
||
|
||
# Collect all dept-sales-count tuples
|
||
items = []
|
||
for dept in sorted(dept_sales.keys()):
|
||
for sales in sorted(dept_sales[dept].keys()):
|
||
items.append((dept, sales, dept_sales[dept][sales]))
|
||
|
||
# Write in two columns
|
||
mid = (len(items) + 1) // 2
|
||
max_rows = max(mid, len(items) - mid)
|
||
for i in range(max_rows):
|
||
if i < mid:
|
||
d1, s1, c1 = items[i]
|
||
set_cell(ws1, rn, 1, d1, data_font, None, data_align, thin_border)
|
||
set_cell(ws1, rn, 2, s1, data_font, None, data_align, thin_border)
|
||
set_cell(ws1, rn, 3, c1, data_font, None, data_align, thin_border)
|
||
else:
|
||
for c in [1,2,3]:
|
||
set_cell(ws1, rn, c, '', data_font, None, data_align, thin_border)
|
||
|
||
if i + mid < len(items):
|
||
d2, s2, c2 = items[i + mid]
|
||
set_cell(ws1, rn, 4, d2, data_font, None, data_align, thin_border)
|
||
set_cell(ws1, rn, 5, s2, data_font, None, data_align, thin_border)
|
||
set_cell(ws1, rn, 6, c2, data_font, None, data_align, thin_border)
|
||
else:
|
||
for c in [4,5,6]:
|
||
set_cell(ws1, rn, c, '', data_font, None, data_align, thin_border)
|
||
rn += 1
|
||
rn += 1
|
||
|
||
rn += 1
|
||
|
||
# Section 3: Column source explanation
|
||
set_cell(ws1, rn, 1, '三、里程三列数据来源说明', subtitle_font); rn += 1
|
||
set_cell(ws1, rn, 1, '列', hdr_blue_font, hdr_blue_fill, hdr_align, thin_border)
|
||
set_cell(ws1, rn, 2, '字段名', hdr_blue_font, hdr_blue_fill, hdr_align, thin_border)
|
||
set_cell(ws1, rn, 3, '数据来源', hdr_blue_font, hdr_blue_fill, hdr_align, thin_border)
|
||
set_cell(ws1, rn, 4, '说明', hdr_blue_font, hdr_blue_fill, hdr_align, thin_border)
|
||
rn += 1
|
||
|
||
col_descs = [
|
||
('P', '实际行驶里程(km)', 'GPS 轨迹还原', '基于 G7 GPS 定位轨迹按考核期逐日汇总计算的运营里程(total_mileage/100000=km),本月里程考核以此列为准'),
|
||
('O', '原TBOX里程(km)', '车辆 T-Box / 仪表盘读数', '当月仪表总里程(业务系统原始数据),含运营+测试+调试里程。仅干预车辆填充,非干预车辆为空'),
|
||
('Q', '测试里程(km)', 'O − P(原TBOX − GPS,差额)', '仪表盘跑了但 GPS 未记录到的部分,判定为厂内测试/调试/路试等非运营里程,不计入考核。仅干预车辆填充,非干预车辆为0'),
|
||
]
|
||
for col_letter, name, source, desc in col_descs:
|
||
set_cell(ws1, rn, 1, col_letter, data_font, None, data_align, thin_border)
|
||
set_cell(ws1, rn, 2, name, data_font, None, Alignment(horizontal='left', vertical='center'), thin_border)
|
||
set_cell(ws1, rn, 3, source, data_font, None, data_align, thin_border)
|
||
set_cell(ws1, rn, 4, desc, data_font, None, Alignment(horizontal='left', vertical='center', wrap_text=True), thin_border)
|
||
ws1.row_dimensions[rn].height = 40
|
||
rn += 1
|
||
|
||
# ===== Sheet 2: 业务考核视图 (full data,干预标黄) =====
|
||
ws2 = wb.create_sheet('业务考核视图')
|
||
target_order = ['车牌号','考核年份','考核月份','部门名称','销售经理','客户名称','合同编号','项目名称',
|
||
'考核目标ID','考核目标','考核开始日期','考核结束日期','考核天数','应考核里程(km)',
|
||
'原TBOX里程(km)','实际行驶里程(km)','测试里程(km)','完成率(%)','是否达标','考核状态',
|
||
'交车日期','预计还车日期','实际还车日期']
|
||
|
||
for ci, name in enumerate(target_order, 1):
|
||
set_cell(ws2, 1, ci, name, hdr_blue_font, hdr_blue_fill, hdr_align, thin_border)
|
||
|
||
for ri, r in enumerate(all_recs, 2):
|
||
is_interv = r['车牌号'] in intervention_plates
|
||
for ci, name in enumerate(target_order, 1):
|
||
val = r.get(name)
|
||
set_cell(ws2, ri, ci, val, data_font, yellow_fill if is_interv else None, data_align, thin_border)
|
||
|
||
for col, w in enumerate([12,8,8,12,10,26,18,22,10,22,13,12,10,14,16,14,14,12,10,8,12,12,12], 1):
|
||
ws2.column_dimensions[get_column_letter(col)].width = w
|
||
|
||
# ===== Sheet 3: 6月业务考核车辆总览 =====
|
||
ws3 = wb.create_sheet('6月业务考核车辆总览')
|
||
for ci, name in enumerate(target_order, 1):
|
||
set_cell(ws3, 1, ci, name, hdr_blue_font, hdr_blue_fill, hdr_align, thin_border)
|
||
for ri, r in enumerate(all_recs, 2):
|
||
for ci, name in enumerate(target_order, 1):
|
||
set_cell(ws3, ri, ci, r.get(name), data_font, None, data_align, thin_border)
|
||
for col, w in enumerate([12,8,8,12,10,26,18,22,10,22,13,12,10,14,16,14,14,12,10,8,12,12,12], 1):
|
||
ws3.column_dimensions[get_column_letter(col)].width = w
|
||
|
||
# ===== Sheet 4: 涉及测试的业务考核车辆 =====
|
||
ws4 = wb.create_sheet('涉及测试的业务考核车辆')
|
||
for ci, name in enumerate(target_order, 1):
|
||
set_cell(ws4, 1, ci, name, hdr_blue_font, hdr_blue_fill, hdr_align, thin_border)
|
||
for ri, r in enumerate(intervention_recs, 2):
|
||
for ci, name in enumerate(target_order, 1):
|
||
set_cell(ws4, ri, ci, r.get(name), data_font, yellow_fill, data_align, thin_border)
|
||
for col, w in enumerate([12,8,8,12,10,26,18,22,10,22,13,12,10,14,16,14,14,12,10,8,12,12,12], 1):
|
||
ws4.column_dimensions[get_column_letter(col)].width = w
|
||
|
||
# ========== Save ==========
|
||
out = '租赁任务考核_2026年6月_干预情况说明.xlsx'
|
||
wb.save(out)
|
||
print(f'✅ {out}')
|
||
print(f'\n概况:')
|
||
print(f' 全量: {total_vehicles}辆, {total_records}条')
|
||
print(f' 干预: {interv_vehicles}辆, {interv_records}条')
|
||
print(f' 达→未达标: {flip_total}条')
|
||
print(f' 仪表盘里程: {total_tbox:.0f} (干预{interv_tbox:.0f})')
|
||
print(f' GPS里程: {total_actual:.0f} (干预{interv_actual:.0f})')
|
||
print(f' 测试里程: {total_test:.0f} (干预{interv_test:.0f})')
|