from collections import defaultdict from datetime import datetime from pathlib import Path from zipfile import ZipFile from xml.etree import ElementTree as ET import re from openpyxl import Workbook, load_workbook from openpyxl.styles import Alignment, Border, Font, PatternFill, Side from openpyxl.utils import get_column_letter BASE = Path("/Users/kkfluous/Downloads") ASSESSMENT = BASE / "7月核算结果/租赁任务考核_2026年7月.xlsx" PLATFORM_DIR = BASE / "广州新能源平台_纯氢纯电里程_2026年7月_按日" GPS_FILES = [ BASE / "里程统计[天][2026-07-01至2026-07-31].xlsx", BASE / "里程统计[天][2026-07-01至2026-07-31] (1).xlsx", ] OUTPUT = BASE / "7月核算结果/7月考核车辆多源里程汇总.xlsx" NS = "{http://schemas.openxmlformats.org/spreadsheetml/2006/main}" def normalize_plate(value): return re.sub(r"\s+", "", str(value or "")).upper() def parse_gps_file(path): result = {} with ZipFile(path) as zf: root = ET.fromstring(zf.read("xl/worksheets/sheet1.xml")) for row in root.findall(f".//{NS}row"): if int(row.get("r")) < 4: continue values = {} for cell in row.findall(f"{NS}c"): col = re.match(r"[A-Z]+", cell.get("r")).group() if cell.get("t") == "inlineStr": values[col] = "".join(t.text or "" for t in cell.findall(f".//{NS}t")) else: node = cell.find(f"{NS}v") values[col] = node.text if node is not None else None plate = normalize_plate(values.get("H")) date_text = str(values.get("O") or "").strip() if not plate or plate == "合计" or not re.fullmatch(r"2026-07-\d{2}", date_text): continue result[(plate, datetime.strptime(date_text, "%Y-%m-%d").date())] = float(values.get("P") or 0) return result platform = defaultdict(lambda: [0.0, 0.0]) platform_presence = set() for path in sorted(PLATFORM_DIR.glob("2026-07-??_纯氢纯电里程.xlsx")): wb = load_workbook(path, read_only=True, data_only=True) ws = wb.active for row in ws.iter_rows(min_row=2, values_only=True): plate = normalize_plate(row[5]) date_match = re.search(r"2026-07-\d{2}", str(row[6] or "")) if not plate or not date_match: continue date = datetime.strptime(date_match.group(), "%Y-%m-%d").date() electric = float(row[7] or 0) hydrogen = float(row[8] or 0) platform[(plate, date)][0] += hydrogen platform[(plate, date)][1] += electric platform_presence.add((plate, date)) wb.close() gps = {} for path in GPS_FILES: incoming = parse_gps_file(path) overlap = set(gps) & set(incoming) if overlap: raise RuntimeError(f"GPS来源存在重复车辆日期:{sorted(overlap)[:5]}") gps.update(incoming) source_wb = load_workbook(ASSESSMENT, read_only=True, data_only=True) source_ws = source_wb["业务考核视图"] headers = [c.value for c in source_ws[1]] idx = {name: i for i, name in enumerate(headers)} records = [] for row in source_ws.iter_rows(min_row=2, values_only=True): plate = normalize_plate(row[idx["车牌号"]]) if not plate: continue start = row[idx["考核开始日期"]].date() end = row[idx["考核结束日期"]].date() dates = [(start.fromordinal(start.toordinal() + i)) for i in range((end - start).days + 1)] p_dates = [d for d in dates if (plate, d) in platform_presence] if p_dates: hydrogen = sum(platform[(plate, d)][0] for d in p_dates) electric = sum(platform[(plate, d)][1] for d in p_dates) else: hydrogen = electric = None test_mileage = row[idx["测试里程(km)"]] is_test = isinstance(test_mileage, (int, float)) and test_mileage > 0 g_dates = [d for d in dates if (plate, d) in gps] gps_total = sum(gps[(plate, d)] for d in g_dates) if g_dates else None # 7月最终考核源表的“实际行驶里程”已按原处理链路写入测试车辆GPS里程: # 优先智能管车日报,缺失时用两份GPS日明细补充。补充明细未命中时, # 必须从最终源表回填,不能误标为“无来源”。 if gps_total is None and is_test: gps_total = row[idx["实际行驶里程(km)"]] records.append({ "plate": plate, "customer": row[idx["客户名称"]] or "", "start": start, "end": end, "days": (end - start).days + 1, "test": "是" if is_test else "否", "hydrogen": hydrogen, "electric": electric, "tbox": row[idx["原TBOX里程(km)"]] if row[idx["原TBOX里程(km)"]] is not None else None, "gps": gps_total, }) source_wb.close() wb = Workbook() ws = wb.active ws.title = "7月考核车辆里程汇总" columns = [ "序号", "车牌", "客户", "考核开始日期", "考核结束日期", "考核天数", "是否存在测试", "纯氢里程(km)", "纯电里程(km)", "广州平台总里程(km)", "TBOX里程(km)", "GPS里程(km)", ] ws.append(columns) for n, rec in enumerate(records, 1): row_num = ws.max_row + 1 ws.append([ n, rec["plate"], rec["customer"], rec["start"], rec["end"], rec["days"], rec["test"], rec["hydrogen"] if rec["hydrogen"] is not None else "无来源", rec["electric"] if rec["electric"] is not None else "无来源", round(rec["hydrogen"] + rec["electric"], 3) if rec["hydrogen"] is not None else "无来源", rec["tbox"] if rec["tbox"] is not None else "无来源", rec["gps"] if rec["gps"] is not None else "无来源", ]) ws.insert_rows(1) ws.merge_cells("A1:L1") ws["A1"] = ( "1. 纯氢里程(km)、纯电里程(km)来源于广州平台统计,TBOX里程来源于车机上报的仪表盘里程统计," "GPS里程优先按两份7月GPS日明细汇总;测试车辆未命中时,回填7月最终考核源表中已确认的GPS实际里程\n" "2. 广州平台总里程(km)=纯氢里程(km)+纯电里程(km)\n" "3. 已确认广州平台暂未接入86辆普货车数据;无对应记录统一标记为“无来源”" ) header_fill = PatternFill("solid", fgColor="1F4E78") platform_fill = PatternFill("solid", fgColor="DDEBF7") tbox_fill = PatternFill("solid", fgColor="E2F0D9") gps_fill = PatternFill("solid", fgColor="E4DFEC") test_fill = PatternFill("solid", fgColor="FFF2CC") missing_fill = PatternFill("solid", fgColor="F2F2F2") thin = Side(style="thin", color="B7B7B7") for cell in ws[2]: cell.fill = header_fill cell.font = Font(color="FFFFFF", bold=True) cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True) cell.border = Border(bottom=Side(style="medium", color="FFFFFF")) for row in ws.iter_rows(min_row=3, max_row=ws.max_row): is_test = row[6].value == "是" for cell in row: cell.border = Border(left=thin, right=thin, top=thin, bottom=thin) cell.alignment = Alignment(horizontal="center", vertical="center") row[2].alignment = Alignment(horizontal="left", vertical="center") if is_test: for cell in row: cell.fill = test_fill else: for cell in row[7:10]: cell.fill = platform_fill row[10].fill = tbox_fill row[11].fill = gps_fill for cell in row[7:12]: if cell.value == "无来源": cell.fill = missing_fill cell.font = Font(color="7F7F7F", italic=True) for row in range(3, ws.max_row + 1): ws.cell(row, 4).number_format = "yyyy-mm-dd" ws.cell(row, 5).number_format = "yyyy-mm-dd" for col in range(8, 13): ws.cell(row, col).number_format = "#,##0.0" widths = [8, 14, 34, 15, 15, 11, 14, 17, 17, 23, 17, 17] for col, width in enumerate(widths, 1): ws.column_dimensions[get_column_letter(col)].width = width ws["A1"].fill = PatternFill("solid", fgColor="FFF2CC") ws["A1"].font = Font(color="7F6000", italic=True) ws["A1"].alignment = Alignment(horizontal="left", vertical="center", wrap_text=True) ws.row_dimensions[1].height = 58 ws.row_dimensions[2].height = 36 ws.freeze_panes = "A3" ws.auto_filter.ref = f"A2:L{ws.max_row}" ws.sheet_view.showGridLines = False ws.sheet_properties.pageSetUpPr.fitToPage = True ws.page_setup.orientation = "landscape" ws.page_setup.fitToWidth = 1 ws.page_setup.fitToHeight = 0 ws.print_title_rows = "1:2" OUTPUT.parent.mkdir(parents=True, exist_ok=True) wb.save(OUTPUT) print({ "output": str(OUTPUT), "records": len(records), "test_records": sum(r["test"] == "是" for r in records), "platform_missing": sum(r["hydrogen"] is None for r in records), "tbox_missing": sum(r["tbox"] is None for r in records), "gps_missing": sum(r["gps"] is None for r in records), })