Files
mileage-bonus/_gen_intervention_v2.py

331 lines
13 KiB
Python
Raw Permalink 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
"""Generate 租赁任务考核_2026年5月_干预情况说明.xlsx — GPS干预后的完整说明"""
import os, openpyxl
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
from collections import defaultdict
os.chdir('/Users/kkfluous/Downloads')
SRC = '租赁任务考核_2026年5月.xlsx'
BAK = '租赁任务考核_2026年5月_原版备份.xlsx'
INTV = '标黄为5月干预车辆.xlsx'
# Styles
HEAD = PatternFill('solid', fgColor='4472C4')
HFONT = Font(bold=True, color='FFFFFF', size=10)
SUB = PatternFill('solid', fgColor='D9E1F2')
ALT = PatternFill('solid', fgColor='F2F2F2')
TITLE_FONT = Font(bold=True, size=14)
SEC_FONT = Font(bold=True, size=12, color='1F4E78')
NOTE_FONT = Font(size=10)
BOLD_FONT = Font(bold=True, size=10)
RED_FONT = Font(size=10, color='C00000')
BORD = Border(Side('thin','B4B4B4'), Side('thin','B4B4B4'), Side('thin','B4B4B4'), Side('thin','B4B4B4'))
CC = Alignment(horizontal='center', vertical='center', wrap_text=True)
LL = Alignment(horizontal='left', vertical='center', wrap_text=True)
# ── Load intervention plates ──
wb_i = openpyxl.load_workbook(INTV)
ws_i = wb_i['Sheet1']
yellow_plates = set()
for row in ws_i.iter_rows(min_row=2, values_only=False):
cell = row[0]
fill = cell.fill
is_yellow = (fill and fill.patternType == 'solid' and fill.start_color
and fill.start_color.rgb and 'FFFF00' in str(fill.start_color.rgb))
if is_yellow:
p = str(cell.value).strip() if cell.value else ''
if p:
yellow_plates.add(p)
wb_i.close()
# ── Load source (post-intervention) and backup (pre-intervention) ──
def load_records(fp):
wb = openpyxl.load_workbook(fp, data_only=True)
ws = wb['业务考核视图']
h = [c.value for c in next(ws.iter_rows(min_row=1, max_row=1))]
recs = []
for row in ws.iter_rows(min_row=2, values_only=True):
if row[0]:
recs.append(dict(zip(h, row)))
wb.close()
return recs
src_recs = load_records(SRC)
bak_recs = load_records(BAK)
# Build plate -> {backup rec, source rec}
bak_by_plate = {}
for r in bak_recs:
p = str(r['车牌号']).strip()
# For duplicate plates, keep last
key = (p, str(r.get('销售经理','')), str(r.get('项目名称','')))
bak_by_plate[key] = r
src_by_plate = {}
for r in src_recs:
p = str(r['车牌号']).strip()
key = (p, str(r.get('销售经理','')), str(r.get('项目名称','')))
src_by_plate[key] = r
# ── Analyze intervention impact ──
# For each source record that's an intervention vehicle, find its backup
intv_records = []
for key, sr in src_by_plate.items():
p = key[0]
if p not in yellow_plates:
continue
br = bak_by_plate.get(key)
tbox = sr.get('原TBOX里程(km)')
gps = sr.get('实际行驶里程(km)')
test = sr.get('测试里程(km)')
old_actual = float(br['实际行驶里程(km)'] or 0) if br else 0
old_pass = str(br['是否达标']).strip() if br else '?'
new_pass = str(sr['是否达标']).strip() if sr else '?'
target = float(sr.get('应考核里程(km)', 0) or 0)
tbox_val = float(tbox or 0)
gps_val = float(gps or 0)
test_val = float(test or 0)
flip = ''
if old_pass == '达标' and new_pass == '未达标':
flip = '达→未达标'
elif old_pass == '未达标' and new_pass == '达标':
flip = '未→达标'
has_gps = '有' if gps_val > 0 else '无GPS数据'
intv_records.append({
'plate': p, 'dept': sr.get('部门名称', ''), 'sales': sr.get('销售经理', ''),
'client': sr.get('客户名称', ''), 'target_name': sr.get('考核目标', ''),
'target_km': target, 'old_actual': old_actual, 'tbox': tbox_val,
'gps': gps_val, 'test': test_val, 'old_pass': old_pass, 'new_pass': new_pass,
'flip': flip, 'has_gps': has_gps
})
print(f"Intervention records: {len(intv_records)}")
flip_down = [r for r in intv_records if r['flip'] == '达→未达标']
flip_up = [r for r in intv_records if r['flip'] == '未→达标']
no_gps = [r for r in intv_records if r['gps'] == 0]
print(f"达→未达标: {len(flip_down)}, 未→达标: {len(flip_up)}, 无GPS: {len(no_gps)}")
# ── Intervention vehicles by target type ──
by_target = defaultdict(list)
for r in intv_records:
by_target[r['target_name']].append(r)
# ── Generate workbook ──
wb = openpyxl.Workbook()
def style_header(ws, row, ncols):
for c in range(1, ncols+1):
cell = ws.cell(row=row, column=c)
cell.fill = HEAD; cell.font = HFONT; cell.alignment = CC; cell.border = BORD
def style_row(ws, row, ncols, alt=False):
for c in range(1, ncols+1):
cell = ws.cell(row=row, column=c)
cell.border = BORD; cell.font = Font(size=10); cell.alignment = CC
if alt: cell.fill = ALT
# ── Sheet 1: 测试与考核情况说明 ──
ws1 = wb.active
ws1.title = '测试与考核情况说明'
ws1.sheet_properties.tabColor = '4472C4'
ws1.merge_cells('A1:H1')
ws1.cell(row=1, column=1, value='2026年5月 业务考核 — GPS干预与考核情况说明').font = TITLE_FONT
ws1.cell(row=1, column=1).alignment = CC
ws1.row_dimensions[1].height = 32
desc = (
f'5月共计{len(src_recs)}条考核记录,其中{len(yellow_plates)}辆车/{len(intv_records)}条记录涉及GPS里程干预。\n'
'干预规则:对于标黄车辆,使用G7平台GPS每日里程(考核期内逐日累加)替代原仪表盘里程作为实际行驶里程参与考核。\n'
f'GPS数据来源:hydrogen_energy.ln_vehicle_g7_mileage101.133.130.65),按考核起止时间汇总每日total_mileage/100000=km。\n'
f'干预结果:{len(flip_down)}条由达标翻转为未达标,{len(flip_up)}条由未达标翻转为达标,{len(no_gps)}条车辆无GPS数据(里程记为0)。'
)
ws1.merge_cells('A2:H2')
ws1.cell(row=2, column=1, value=desc).font = NOTE_FONT
ws1.cell(row=2, column=1).alignment = Alignment(horizontal='left', vertical='top', wrap_text=True)
ws1.row_dimensions[2].height = 80
r = 4
ws1.merge_cells(start_row=r, start_column=1, end_row=r, end_column=8)
ws1.cell(row=r, column=1, value='一、5月考核概况(GPS干预后)').font = SEC_FONT
r += 1
# Summary table
summary_headers = ['指标', '干预前', '干预后', '变化']
for c, h in enumerate(summary_headers, 1):
ws1.cell(row=r, column=c, value=h)
style_header(ws1, r, 4)
r += 1
old_pass_count = sum(1 for r in bak_recs if str(r.get('是否达标','')).strip() == '达标')
new_pass_count = sum(1 for r in src_recs if str(r.get('是否达标','')).strip() == '达标')
old_fail = len(bak_recs) - old_pass_count
new_fail = len(src_recs) - new_pass_count
summary_rows = [
('考核记录数(条)', len(bak_recs), len(src_recs), ''),
('达标记录数(条)', old_pass_count, new_pass_count, f'{new_pass_count - old_pass_count:+d}'),
('未达标记录数(条)', old_fail, new_fail, f'{new_fail - old_fail:+d}'),
('达标率', f'{old_pass_count/len(bak_recs)*100:.1f}%', f'{new_pass_count/len(src_recs)*100:.1f}%', ''),
('干预车辆数', len(yellow_plates), len(yellow_plates), ''),
('涉及考核记录', len(intv_records), len(intv_records), ''),
('有GPS数据', '', sum(1 for r in intv_records if r['gps'] > 0), ''),
('无GPS数据(里程=0)', '', len(no_gps), ''),
]
for row_data in summary_rows:
for c, val in enumerate(row_data, 1):
ws1.cell(row=r, column=c, value=val)
style_row(ws1, r, 4)
r += 1
r += 1
ws1.merge_cells(start_row=r, start_column=1, end_row=r, end_column=8)
ws1.cell(row=r, column=1, value='二、干预车型达标翻转统计').font = SEC_FONT
r += 1
# By target type
flip_headers = ['考核目标', '干预车辆数', '达→未达标', '未→达标', '有GPS数据', '无GPS数据']
for c, h in enumerate(flip_headers, 1):
ws1.cell(row=r, column=c, value=h)
style_header(ws1, r, 6)
r += 1
# Sort: most affected first
target_order = sorted(by_target.keys(), key=lambda t: -len(by_target[t]))
for i, tgt in enumerate(target_order):
recs = by_target[tgt]
ws1.cell(row=r, column=1, value=tgt)
ws1.cell(row=r, column=2, value=len(recs))
ws1.cell(row=r, column=3, value=sum(1 for x in recs if x['flip'] == '达→未达标'))
ws1.cell(row=r, column=4, value=sum(1 for x in recs if x['flip'] == '未→达标'))
ws1.cell(row=r, column=5, value=sum(1 for x in recs if x['gps'] > 0))
ws1.cell(row=r, column=6, value=sum(1 for x in recs if x['gps'] == 0))
style_row(ws1, r, 6, alt=(i%2==1))
r += 1
col_widths1 = [14, 15, 15, 15, 18, 18, 18, 18]
for i, w in enumerate(col_widths1, 1):
ws1.column_dimensions[get_column_letter(i)].width = w
# ── Sheet 2: 干预车辆明细 ──
ws2 = wb.create_sheet('干预车辆明细')
ws2.sheet_properties.tabColor = 'ED7D31'
ws2.merge_cells('A1:N1')
ws2.cell(row=1, column=1, value='5月干预车辆 — 原仪表盘里程 vs GPS里程 逐条对比').font = TITLE_FONT
ws2.cell(row=1, column=1).alignment = CC
ws2.row_dimensions[1].height = 28
detail_headers = ['车牌号', '部门', '销售经理', '客户名称', '考核目标',
'考核天数', '应考核里程(km)', '原仪表盘里程(km)', 'GPS行驶里程(km)',
'测试里程(km)', '原是否达标', '干预后达标', '翻转', 'GPS数据']
r2 = 3
for c, h in enumerate(detail_headers, 1):
ws2.cell(row=r2, column=c, value=h)
style_header(ws2, r2, 14)
r2 += 1
# Sort: flip-down first, then no-GPS, then others
intv_records.sort(key=lambda r: (0 if r['flip'] == '达→未达标' else 1 if r['gps'] == 0 else 2, r['plate']))
for i, rec in enumerate(intv_records):
ws2.cell(row=r2, column=1, value=rec['plate'])
ws2.cell(row=r2, column=2, value=rec['dept'])
ws2.cell(row=r2, column=3, value=rec['sales'])
ws2.cell(row=r2, column=4, value=rec['client'])
ws2.cell(row=r2, column=5, value=rec['target_name'])
ws2.cell(row=r2, column=6, value=rec.get('days', ''))
ws2.cell(row=r2, column=7, value=round(rec['target_km'], 2))
ws2.cell(row=r2, column=8, value=round(rec['tbox'], 2))
ws2.cell(row=r2, column=9, value=round(rec['gps'], 2))
ws2.cell(row=r2, column=10, value=round(rec['test'], 2))
ws2.cell(row=r2, column=11, value=rec['old_pass'])
ws2.cell(row=r2, column=12, value=rec['new_pass'])
# Flip column with color
flip_cell = ws2.cell(row=r2, column=13, value=rec['flip'])
if rec['flip'] == '达→未达标':
flip_cell.font = RED_FONT
ws2.cell(row=r2, column=14, value=rec['has_gps'])
style_row(ws2, r2, 14, alt=(i%2==1))
r2 += 1
col_widths2 = [12, 10, 10, 26, 20, 8, 14, 16, 16, 14, 10, 10, 10, 10]
for i, w in enumerate(col_widths2, 1):
ws2.column_dimensions[get_column_letter(i)].width = w
# ── Sheet 3: 业务考核视图 (current, post-intervention) ──
ws3 = wb.create_sheet('业务考核视图')
ws3.sheet_properties.tabColor = '70AD47'
src_h = list(src_recs[0].keys())
for c, h in enumerate(src_h, 1):
ws3.cell(row=1, column=c, value=h)
style_header(ws3, 1, len(src_h))
for i, rec in enumerate(src_recs):
for c, h in enumerate(src_h, 1):
val = rec.get(h)
if val is not None:
ws3.cell(row=i+2, column=c, value=val)
style_row(ws3, i+2, len(src_h), alt=(i%2==1))
for i in range(1, len(src_h)+1):
ws3.column_dimensions[get_column_letter(i)].width = 12
# ── Sheet 4: 干预车辆汇总(按部门+销售) ──
ws4 = wb.create_sheet('干预汇总(按部门销售)')
ws4.sheet_properties.tabColor = 'FFC000'
ws4.merge_cells('A1:F1')
ws4.cell(row=1, column=1, value='干预车辆 — 按部门+销售经理汇总').font = TITLE_FONT
ws4.cell(row=1, column=1).alignment = CC
# Aggregate
by_dept_sales = defaultdict(lambda: {'total': 0, 'flip_down': 0, 'flip_up': 0, 'no_gps': 0, 'has_gps': 0, 'gps_amount': 0})
for rec in intv_records:
key = (rec['dept'], rec['sales'])
by_dept_sales[key]['total'] += 1
if rec['flip'] == '达→未达标':
by_dept_sales[key]['flip_down'] += 1
elif rec['flip'] == '未→达标':
by_dept_sales[key]['flip_up'] += 1
if rec['gps'] > 0:
by_dept_sales[key]['has_gps'] += 1
by_dept_sales[key]['gps_amount'] += rec['gps']
else:
by_dept_sales[key]['no_gps'] += 1
agg_headers = ['部门', '销售经理', '干预车辆数', '达→未达标', '未→达标', '无GPS数据']
r4 = 3
for c, h in enumerate(agg_headers, 1):
ws4.cell(row=r4, column=c, value=h)
style_header(ws4, r4, 6)
r4 += 1
for (dept, sales), data in sorted(by_dept_sales.items()):
ws4.cell(row=r4, column=1, value=dept)
ws4.cell(row=r4, column=2, value=sales)
ws4.cell(row=r4, column=3, value=data['total'])
ws4.cell(row=r4, column=4, value=data['flip_down'])
ws4.cell(row=r4, column=5, value=data['flip_up'])
ws4.cell(row=r4, column=6, value=data['no_gps'])
style_row(ws4, r4, 6)
r4 += 1
col_widths4 = [14, 12, 12, 12, 12, 12]
for i, w in enumerate(col_widths4, 1):
ws4.column_dimensions[get_column_letter(i)].width = w
# ── Save ──
out_path = 'new-kpi-202605_1/租赁任务考核_2026年5月_干预情况说明.xlsx'
wb.save(out_path)
print(f'✅ {out_path} ({len(wb.sheetnames)} sheets)')