#!/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})')