Files
mileage-bonus/regenerate_intervention_jul.py
T

270 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
"""用新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})')