Files
mileage-bonus/gen_intervention_jun.py
T

282 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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})')