chore: curate monthly assessment evidence and scripts
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Process May intervention: replace mileage with GPS, add TBOX/test columns."""
|
||||
import os, shutil, openpyxl, pymysql, warnings
|
||||
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||||
from openpyxl.utils import get_column_letter
|
||||
from datetime import datetime, date, timedelta
|
||||
from collections import defaultdict
|
||||
|
||||
warnings.filterwarnings('ignore')
|
||||
os.chdir('/Users/kkfluous/Downloads')
|
||||
|
||||
SRC = '租赁任务考核_2026年5月.xlsx'
|
||||
BAK = '租赁任务考核_2026年5月_原版备份.xlsx'
|
||||
OUT = SRC # overwrite
|
||||
|
||||
# ── Step 1: Backup ──
|
||||
if not os.path.exists(BAK):
|
||||
shutil.copy2(SRC, BAK)
|
||||
print(f"✅ Backup: {BAK}")
|
||||
else:
|
||||
print(f"跳过备份(已存在)")
|
||||
|
||||
# ── Step 2: Load intervention plates ──
|
||||
wb_i = openpyxl.load_workbook('标黄为5月干预车辆.xlsx')
|
||||
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()
|
||||
print(f"干预车辆: {len(yellow_plates)}")
|
||||
|
||||
# ── Step 3: Read source records ──
|
||||
wb_s = openpyxl.load_workbook(SRC, data_only=True)
|
||||
ws_s = wb_s['业务考核视图']
|
||||
src_h = [c.value for c in next(ws_s.iter_rows(min_row=1, max_row=1))]
|
||||
rows_raw = list(ws_s.iter_rows(min_row=2, values_only=True))
|
||||
wb_s.close()
|
||||
|
||||
records = []
|
||||
for row in rows_raw:
|
||||
if not row[0]:
|
||||
continue
|
||||
records.append(dict(zip(src_h, row)))
|
||||
print(f"源数据: {len(records)} 条")
|
||||
|
||||
# ── Step 4: GPS from DB ──
|
||||
print("查询数据库...")
|
||||
conn = pymysql.connect(host='101.133.130.65', user='root', password='ln123!@#',
|
||||
database='hydrogen_energy', charset='utf8mb4')
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT plate_number, dates, total_mileage FROM ln_vehicle_g7_mileage WHERE dates >= '2026-05-01' AND dates <= '2026-06-08'")
|
||||
db_rows = cur.fetchall()
|
||||
conn.close()
|
||||
|
||||
# Group: plate -> {date: mileage}
|
||||
pmap = defaultdict(lambda: defaultdict(int))
|
||||
for p, d, m in db_rows:
|
||||
dd = d if isinstance(d, date) else (d.date() if isinstance(d, datetime) else date.fromisoformat(str(d)[:10]))
|
||||
pmap[p][dd] = m or 0
|
||||
|
||||
print(f"DB plates loaded: {len(pmap)}")
|
||||
|
||||
# Compute GPS km per plate per考核期
|
||||
def get_gps_km(plate, start_v, end_v):
|
||||
"""GPS里程(km) = sum of daily total_mileage / 100000"""
|
||||
if plate not in pmap:
|
||||
return 0.0
|
||||
|
||||
# Parse dates
|
||||
if isinstance(start_v, datetime):
|
||||
sd = start_v.date()
|
||||
elif isinstance(start_v, date):
|
||||
sd = start_v
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
if isinstance(end_v, datetime):
|
||||
ed = end_v.date()
|
||||
elif isinstance(end_v, date):
|
||||
ed = end_v
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
total = 0
|
||||
d = sd
|
||||
daily = pmap[plate]
|
||||
while d <= ed:
|
||||
total += daily.get(d, 0)
|
||||
d += timedelta(days=1)
|
||||
return total / 100000.0
|
||||
|
||||
# Build plate -> gps for intervention vehicles
|
||||
plate_gps = {}
|
||||
for rec in records:
|
||||
p = str(rec['车牌号']).strip()
|
||||
if p not in yellow_plates:
|
||||
continue
|
||||
if p in plate_gps:
|
||||
continue
|
||||
km = get_gps_km(p, rec.get('考核开始日期'), rec.get('考核结束日期'))
|
||||
plate_gps[p] = round(km, 2)
|
||||
|
||||
print(f"GPS数据获取: {len(plate_gps)}/{len(yellow_plates)}")
|
||||
gps_zero = sum(1 for v in plate_gps.values() if v == 0)
|
||||
print(f"GPS=0: {gps_zero} 辆")
|
||||
|
||||
# ── Step 5: Generate new workbook ──
|
||||
YELLOW = PatternFill('solid', fgColor='FFFF00')
|
||||
RED = PatternFill('solid', fgColor='FF0000')
|
||||
REDF = Font(color='FFFFFF', bold=True)
|
||||
HFILL = PatternFill('solid', fgColor='4472C4')
|
||||
HFONT = Font(bold=True, color='FFFFFF', size=10)
|
||||
BORD = Border(Side('thin','B4B4B4'), Side('thin','B4B4B4'), Side('thin','B4B4B4'), Side('thin','B4B4B4'))
|
||||
CC = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
||||
NFONT = Font(size=10)
|
||||
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
ws.title = '业务考核视图'
|
||||
|
||||
# New header layout:
|
||||
# 车牌号(0) 考核年份(1) 考核月份(2) 部门名称(3) 销售经理(4) 客户名称(5) 合同编号(6) 项目名称(7)
|
||||
# 考核目标ID(8) 考核目标(9) 考核开始日期(10) 考核结束日期(11) 考核天数(12) 应考核里程(13)
|
||||
# 原TBOX里程(14) 实际行驶里程=GPS(15) 测试里程(16) 完成率(17) 是否达标(18) 考核状态(19) ...
|
||||
# Column map: src_col -> dst_col (0-indexed)
|
||||
# 0-13 stay same, 14(old actual) -> split into 14(TBOX) 15(new actual) 16(test), 15(old rate) -> 17, 16(old pass)->18, 17(status)->19, 18-20 -> 20-22
|
||||
|
||||
new_h = ['车牌号', '考核年份', '考核月份', '部门名称', '销售经理', '客户名称',
|
||||
'合同编号', '项目名称', '考核目标ID', '考核目标', '考核开始日期',
|
||||
'考核结束日期', '考核天数', '应考核里程(km)',
|
||||
'原TBOX里程(km)', '实际行驶里程(km)', '测试里程(km)',
|
||||
'完成率(%)', '是否达标', '考核状态',
|
||||
'交车日期', '预计还车日期', '实际还车日期']
|
||||
|
||||
for ci, h in enumerate(new_h):
|
||||
c = ci + 1
|
||||
cell = ws.cell(row=1, column=c, value=h)
|
||||
cell.fill = HFILL
|
||||
cell.font = HFONT
|
||||
cell.alignment = CC
|
||||
cell.border = BORD
|
||||
|
||||
# Write data
|
||||
for ri, rec in enumerate(records):
|
||||
r = ri + 2
|
||||
plate = str(rec['车牌号']).strip()
|
||||
is_intv = plate in yellow_plates
|
||||
|
||||
# Columns 0-13: copy directly (0-indexed in src_h)
|
||||
for si in range(14): # 车牌号 ~ 应考核里程(km)
|
||||
val = rec.get(src_h[si])
|
||||
if val is not None:
|
||||
ws.cell(row=r, column=si + 1, value=val)
|
||||
ws.cell(row=r, column=si + 1).border = BORD
|
||||
ws.cell(row=r, column=si + 1).alignment = CC
|
||||
ws.cell(row=r, column=si + 1).font = NFONT
|
||||
|
||||
# Column 14 (0-indexed): 原TBOX里程(km) - old actual mileage
|
||||
tbox = float(rec.get('实际行驶里程(km)', 0) or 0)
|
||||
ws.cell(row=r, column=15, value=tbox).border = BORD
|
||||
ws.cell(row=r, column=15).alignment = CC
|
||||
ws.cell(row=r, column=15).font = NFONT
|
||||
|
||||
# Column 15 (0-indexed:16): 实际行驶里程(km) - GPS for intv, TBOX for others
|
||||
target_km = float(rec.get('应考核里程(km)', 0) or 0)
|
||||
if is_intv:
|
||||
new_actual = plate_gps.get(plate, 0)
|
||||
else:
|
||||
new_actual = tbox
|
||||
|
||||
cell_actual = ws.cell(row=r, column=16, value=new_actual)
|
||||
cell_actual.border = BORD
|
||||
cell_actual.alignment = CC
|
||||
cell_actual.font = NFONT
|
||||
|
||||
# Column 16 (0-indexed:17): 测试里程(km) = TBOX - GPS
|
||||
test_km = round(tbox - new_actual, 2) if is_intv else 0
|
||||
ws.cell(row=r, column=17, value=test_km).border = BORD
|
||||
ws.cell(row=r, column=17).alignment = CC
|
||||
ws.cell(row=r, column=17).font = NFONT
|
||||
|
||||
# Column 17 (0-indexed:18): 完成率(%)
|
||||
rate = round(new_actual / target_km * 100, 2) if target_km > 0 else 0
|
||||
ws.cell(row=r, column=18, value=rate).border = BORD
|
||||
ws.cell(row=r, column=18).alignment = CC
|
||||
ws.cell(row=r, column=18).font = NFONT
|
||||
|
||||
# Column 18 (0-indexed:19): 是否达标
|
||||
new_pass = '达标' if new_actual >= target_km and target_km > 0 else '未达标'
|
||||
ws.cell(row=r, column=19, value=new_pass).border = BORD
|
||||
ws.cell(row=r, column=19).alignment = CC
|
||||
ws.cell(row=r, column=19).font = NFONT
|
||||
|
||||
# Column 19 (0-indexed:20): 考核状态
|
||||
val = rec.get('考核状态')
|
||||
ws.cell(row=r, column=20, value=val if val else '进行中').border = BORD
|
||||
ws.cell(row=r, column=20).alignment = CC
|
||||
ws.cell(row=r, column=20).font = NFONT
|
||||
|
||||
# Columns 20-22 (0-indexed): 交车日期, 预计还车日期, 实际还车日期
|
||||
for si in range(18, 21):
|
||||
val = rec.get(src_h[si])
|
||||
if val is not None:
|
||||
ws.cell(row=r, column=si + 1 + 5, value=val) # +5 offset for new cols
|
||||
else:
|
||||
ws.cell(row=r, column=si + 1 + 5, value='')
|
||||
ws.cell(row=r, column=si + 1 + 5).border = BORD
|
||||
ws.cell(row=r, column=si + 1 + 5).alignment = CC
|
||||
ws.cell(row=r, column=si + 1 + 5).font = NFONT
|
||||
|
||||
# ── Formatting: yellow for intervention, red for GPS=0 ──
|
||||
if is_intv:
|
||||
for c in range(1, len(new_h) + 1):
|
||||
cell = ws.cell(row=r, column=c)
|
||||
# Don't override red cells
|
||||
existing_fill = cell.fill
|
||||
is_red = (existing_fill.patternType == 'solid' and
|
||||
existing_fill.start_color and
|
||||
existing_fill.start_color.rgb and
|
||||
'FF0000' in str(existing_fill.start_color.rgb))
|
||||
if not is_red:
|
||||
cell.fill = YELLOW
|
||||
else:
|
||||
# Red stays, but also yellow background? No, red means GPS=0, keep it
|
||||
pass
|
||||
|
||||
# Red highlight for GPS=0
|
||||
if plate_gps.get(plate, 0) == 0:
|
||||
ws.cell(row=r, column=16).fill = RED # 实际行驶里程 column
|
||||
ws.cell(row=r, column=16).font = REDF
|
||||
|
||||
# Column widths
|
||||
widths = [12, 8, 8, 12, 10, 26, 18, 22, 10, 22, 13, 13, 10, 14, 16, 16, 14, 12, 10, 8, 12, 12, 12]
|
||||
for i, w in enumerate(widths):
|
||||
ws.column_dimensions[get_column_letter(i+1)].width = w
|
||||
|
||||
# ── Save ──
|
||||
wb.save(OUT)
|
||||
print(f"\n✅ 已保存: {OUT} ({len(records)} rows, {len(new_h)} cols)")
|
||||
|
||||
# ── Stats ──
|
||||
old_p = sum(1 for r in records if str(r.get('是否达标','')).strip() == '达标')
|
||||
new_p = 0
|
||||
flip_down = []
|
||||
flip_up = []
|
||||
for rec in records:
|
||||
p = str(rec['车牌号']).strip()
|
||||
tgt = float(rec.get('应考核里程(km)', 0) or 0)
|
||||
old_status = str(rec.get('是否达标','')).strip()
|
||||
if p in yellow_plates:
|
||||
actual = plate_gps.get(p, 0)
|
||||
else:
|
||||
actual = float(rec.get('实际行驶里程(km)', 0) or 0)
|
||||
n = '达标' if actual >= tgt and tgt > 0 else '未达标'
|
||||
if n == '达标':
|
||||
new_p += 1
|
||||
if old_status == '达标' and n == '未达标':
|
||||
flip_down.append((p, float(rec.get('实际行驶里程(km)',0) or 0), actual, tgt))
|
||||
elif old_status == '未达标' and n == '达标':
|
||||
flip_up.append((p, float(rec.get('实际行驶里程(km)',0) or 0), actual, tgt))
|
||||
|
||||
print(f"达标变化: {old_p} -> {new_p} ({new_p - old_p:+d})")
|
||||
print(f"达→未达标: {len(flip_down)} 辆")
|
||||
for p, tb, gp, tg in flip_down[:10]:
|
||||
print(f" {p} TBOX={tb:.1f} GPS={gp:.2f} 目标={tg:.0f}")
|
||||
print(f"未→达标: {len(flip_up)} 辆")
|
||||
for p, tb, gp, tg in flip_up[:10]:
|
||||
print(f" {p} TBOX={tb:.1f} GPS={gp:.2f} 目标={tg:.0f}")
|
||||
Reference in New Issue
Block a user