chore: curate monthly assessment evidence and scripts
This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
#!/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_mileage(101.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)')
|
||||
Reference in New Issue
Block a user