270 lines
14 KiB
Python
270 lines
14 KiB
Python
#!/usr/bin/env python3
|
||
"""用新GPS数据重新生成7月干预情况说明 — 测试里程/实际里程/翻转分析全部更新"""
|
||
import openpyxl, os
|
||
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')
|
||
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
|
||
|
||
# ── Step 1: Extract test plates from July test list ──
|
||
print("提取测试车辆清单...")
|
||
test_file = '/Users/kkfluous/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_1704407055112_3af8/temp/RWTemp/2026-08/4340099467fbf08b4c6583c49adba4ee/7月测试车辆明细.xlsx'
|
||
wb_old = openpyxl.load_workbook(test_file, data_only=True)
|
||
ws_old = wb_old.active
|
||
test_plates = set()
|
||
for row in ws_old.iter_rows(min_row=2, values_only=True):
|
||
p = row[0]
|
||
if p: test_plates.add(str(p).strip())
|
||
test_plates.discard('粤AG37316')
|
||
wb_old.close()
|
||
print(f" 测试车辆: {len(test_plates)} 辆")
|
||
|
||
# ── Step 2: Read NEW source data ──
|
||
print("读取新数据源...")
|
||
src_fp = '7月核算结果/租赁任务考核_2026年7月.xlsx'
|
||
src = openpyxl.load_workbook(src_fp, 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()
|
||
|
||
# ── Step 3: Read BACKUP for original comparison ──
|
||
print("读取备份(原始仪表盘)...")
|
||
bak = openpyxl.load_workbook('7月核算结果/租赁任务考核_2026年7月_原版备份.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[str(r['车牌号']).strip()] = r
|
||
bak.close()
|
||
|
||
# ── Step 4: Analyze ──
|
||
print("分析干预数据...")
|
||
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: old达→new未达标
|
||
flip_recs = []
|
||
for r in intervention_recs:
|
||
plate = r['车牌号']
|
||
old_rec = bak_recs.get(plate)
|
||
if old_rec:
|
||
old_q = str(old_rec.get('是否达标', '')).strip() == '达标'
|
||
new_q = str(r.get('是否达标', '')).strip() == '达标'
|
||
if old_q and not new_q:
|
||
flip_recs.append(r)
|
||
|
||
# Group flip by target
|
||
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
|
||
|
||
print(f" 全量: {total_vehicles}辆/{total_records}条")
|
||
print(f" 干预: {interv_vehicles}辆/{interv_records}条")
|
||
print(f" 达→未达标: {len(flip_recs)}条")
|
||
print(f" TBOX总计: {total_tbox:.0f}, GPS总计: {total_actual:.0f}, 测试总计: {total_test:.0f}")
|
||
|
||
# ── Step 5: Build workbook ──
|
||
wb = openpyxl.Workbook()
|
||
|
||
# ===== Sheet 1: 测试与考核情况说明 =====
|
||
ws1 = wb.active; ws1.title = '测试与考核情况说明'
|
||
for col, w in enumerate([4, 30, 18, 18, 18, 18, 18], 1):
|
||
ws1.column_dimensions[get_column_letter(col)].width = w
|
||
|
||
rn = 1
|
||
set_cell(ws1, rn, 1, '2026年7月 业务考核 — 测试与考核情况说明', title_font)
|
||
ws1.merge_cells(start_row=rn, start_column=1, end_row=rn, end_column=5); rn += 1
|
||
|
||
set_cell(ws1, rn, 1, '7月业务考核车辆中,测试车辆统一以指定GPS日里程按考核期汇总替换TBOX里程,差额作为测试里程剥离且最低取0。粤AG37316因GPS缺失,经业务确认暂按0核算。',
|
||
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, '一、7月业务考核与干预情况', subtitle_font); rn += 1
|
||
for ci, h in enumerate(['项目', '7月业务考核车辆', '涉及测试的车辆', '占比'], 1):
|
||
set_cell(ws1, rn, ci, h, hdr_blue_font, hdr_blue_fill, hdr_align, thin_border)
|
||
rn += 1
|
||
|
||
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
|
||
|
||
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
|
||
|
||
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
|
||
|
||
items = []
|
||
for dept in sorted(dept_sales.keys()):
|
||
for sales in sorted(dept_sales[dept].keys()):
|
||
items.append((dept, sales, dept_sales[dept][sales]))
|
||
|
||
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 = [
|
||
('O', '原TBOX里程(km)', '车辆 T-Box / 仪表盘读数', '当月仪表总里程(业务系统原始数据),含运营+测试+调试里程。仅干预车辆填充,非干预车辆为空'),
|
||
('P', '实际行驶里程(km)', '指定GPS日里程文件', '优先使用智能管车车辆里程日报;缺失车辆由两份里程统计日明细补充,并按考核期逐日汇总。粤AG37316经确认暂按0核算'),
|
||
('Q', '测试里程(km)', 'O − P(原TBOX − GPS,差额)', '仪表盘跑了但 GPS 未记录到的部分,判定为厂内测试/调试/路试等非运营里程,不计入考核。仅干预车辆填充,非干预车辆为0。若差额为负则取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_full = ['车牌号','考核年份','考核月份','部门名称','销售经理','客户名称','合同编号','项目名称',
|
||
'考核目标ID','考核目标','考核开始日期','考核结束日期','考核天数','应考核里程(km)',
|
||
'原TBOX里程(km)','实际行驶里程(km)','测试里程(km)','完成率(%)','是否达标','考核状态',
|
||
'交车日期','预计还车日期','实际还车日期']
|
||
for ci, name in enumerate(target_order_full, 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_full, 1):
|
||
set_cell(ws2, ri, ci, r.get(name), 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: 7月业务考核车辆总览 =====
|
||
ws3 = wb.create_sheet('7月业务考核车辆总览')
|
||
for ci, name in enumerate(target_order_full, 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_full, 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_full, 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_full, 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 = '7月核算结果/租赁任务考核_2026年7月_干预情况说明.xlsx'
|
||
wb.save(out)
|
||
|
||
print(f'\n✅ {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})')
|