8.4 KiB
8.4 KiB
智能调度模块设计
基于里程考核数据,通过贪心优先级匹配算法,生成车辆替换建议,帮助调度员优化车队里程分布,最大化达标车辆数。
业务背景
公司有多批次考核车辆(40台普货、190台冷藏车等),每批次有年度里程考核目标。车辆租赁给不同客户,客户实际使用强度差异大。考核的是车辆本身的里程,因此需要通过替换车辆来均衡里程:
- 高里程客户的已达标车换下来,换上里程缺口大的车(让新车追赶)
- 低里程客户的无望达标车换下来给高里程客户(抢救),给低里程客户换上已达标的车
核心算法
车辆分类
从 tab_mileage_assessment_vehicle 获取所有考核车辆,按客户聚合计算客户日均里程(客户下所有车辆近 30 天日均里程的平均值),然后对每辆车计算:
预测年终里程 = 当前累计里程 + 客户日均里程 × 剩余天数
达标概率 = 预测年终里程 / 年度目标里程
分为三类:
| 类型 | 条件 | 含义 |
|---|---|---|
| qualified | currentYearIsQualified = true 或 达标概率 ≥ 120% |
已完成或铁定完成 |
| hopeless | 达标概率 < 60% | 按当前客户使用强度,年底肯定完不成 |
| normal | 60% ≤ 达标概率 < 120% | 有希望但不确定,暂不干预 |
替换建议生成
场景 A:replace_qualified(高里程客户的已达标车辆)
- 目标:把已达标的车换下来,换上里程缺口大的库存车
- 候选池:库存车(rent_status='在库')+ 同车型 + 同区域
- 排序:优先选剩余缺口最大但换后仍可达标的车
- 校验:
候选车当前累计 + 客户日均 × 剩余天数 ≥ 年度目标才推荐
场景 B:rescue_hopeless(低里程客户的无望达标车辆)
- 目标:把无望车换给高里程客户抢救,给低里程客户换上已达标/库存车
- 候选池:库存中已达标或将达标的同车型同区域车辆
- 排序:优先选已达标且里程最高的车(对低里程客户无影响)
车型匹配规则
| 源车型 | 可替换为 | 说明 |
|---|---|---|
| 4.5T冷链 | 4.5T冷链、4.5T普货 | 冷链不开空调可当普货用 |
| 4.5T普货 | 4.5T普货 | 不能反向替换冷链 |
| 18T | 18T | 同型号互换 |
| 49T | 49T | 同型号互换 |
| 挂车 | 挂车 | 同型号互换 |
区域匹配规则
复用已有 mapRegion() 函数,将 province/city 映射到大区(嘉兴/广东/北京/新疆/其他)。同一大区内可替换,跨大区不推荐。
优先级排序
干预清单排序:
- hopeless + 有可行替换方案 → priority: high(最紧急,还能抢救)
- qualified + 高里程客户 + 有库存可换 → priority: medium(释放达标车,让新车追赶)
后端 API
GET /api/scheduling/suggestions
获取调度建议列表。每次请求实时计算(不使用定时缓存),因为用户操作后需要立即看到最新结果。
请求参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| targetId | number (可选) | 按批次筛选,不传则全部 |
响应:
{
summary: {
qualifiedCount: number; // 已达标车辆数
hopelessCount: number; // 无望达标车辆数
suggestionCount: number; // 可干预建议数
estimatedGain: number; // 预计干预后可新增达标数
};
suggestions: SchedulingSuggestion[];
targets: { id: number; name: string; vehicleCount: number }[];
}
SchedulingSuggestion 结构:
{
id: string; // 建议唯一ID(如 "s-{plate}-{timestamp}")
priority: 'high' | 'medium';
type: 'replace_qualified' | 'rescue_hopeless';
currentVehicle: {
plateNumber: string;
targetId: number;
targetName: string; // 所属批次
vehicleType: string; // "4.5T冷链" / "18T" 等
totalMileage: number;
completionRate: number; // 0-1
yearTarget: number; // 年度目标里程
region: string; // 大区(嘉兴/广东等)
province: string; // 原始省份
customer: string;
customerAvgDaily: number; // 客户日均里程
predictedYearEnd: number; // 预测年终里程
daysLeft: number;
};
candidates: {
plateNumber: string;
targetId: number | null; // 库存车可能无批次
targetName: string | null;
vehicleType: string;
totalMileage: number;
completionRate: number;
yearTarget: number | null;
region: string;
province: string;
mileageGap: number; // 剩余缺口
predictedAfterSwap: number; // 换到该客户后预测年终里程
canQualifyAfterSwap: boolean;
}[];
reason: string; // 建议原因文案
}
POST /api/scheduling/notify
发送替换通知。成功后前端立即重新拉取 suggestions。
请求体:
{
suggestionId: string;
currentPlate: string;
candidatePlate: string;
}
操作人从 JWT auth 中获取。
响应:{ success: boolean; message: string }
行为:调用外部回调接口发送通知(具体回调 URL 后续配置)。成功后在本地记录已操作状态,后续 GET suggestions 时排除已操作的建议。
数据查询流程
后端一次请求聚合以下数据:
- 所有考核车辆 —
tab_mileage_assessment_vehicle(里程进度、达标状态) - 所有考核目标 —
tab_mileage_assessment_target(批次名称、年度目标) - 库存车辆 —
tab_truck WHERE truck_rent_status = 0(在库)+ 同表获取车型 - 车辆实时位置 —
tab_truck_remote_sync_realtime_info(province, city) - 合同/客户信息 — 复用
vehicle-info.ts已有的 JOIN 查询 - 客户日均里程 — 按客户聚合
v_vehicle_daily_stats近 30 天均值
前端结构
文件组织
src/modules/scheduling/
├── SchedulingModule.tsx // 主入口,状态管理和数据加载
├── SuggestionList.tsx // 干预建议清单列表
├── SuggestionDetail.tsx // 单条建议展开详情(含替换车辆对比)
├── api.ts // fetchSuggestions(), sendNotify()
└── types.ts // SchedulingSuggestion 等类型定义
后端:
src/server/routes/scheduling/
├── index.ts // 路由注册
├── suggestions.ts // GET /suggestions 算法核心
└── notify.ts // POST /notify 回调通知
页面层级
智能调度 Tab
├── 顶部:批次选择器(复用里程统计的批次 tabs,默认"全部")
├── 统计卡片区(3 个)
│ ├── 已达标车辆数(绿色)
│ ├── 无望达标车辆数(红色)
│ └── 可干预建议数 + 预计可新增达标数(蓝色)
├── 干预建议清单(主列表,按优先级排序)
│ ├── 每条:车牌、批次、客户、客户日均、完成率、区域、类型标签(已达标/无望)
│ └── 点击 → 展开干预详情
└── 干预详情(弹窗)
├── 当前车辆信息卡片
├── 推荐替换车辆列表(最多 5 辆)
│ └── 每辆显示对比:替换前后的区域、车型、里程、预测达标
├── 建议原因说明
└── 「发送替换通知」按钮 → notify 接口 → 成功后刷新列表
UI 设计要求
- 以原型
SmartSchedulingView组件为基础风格 - 使用 ui-ux-pro-max 优化视觉质量
- 适配移动端(竖屏卡片流)和 Web 端(landscape 横屏大表格)
- 干预详情弹窗需截图友好:完整卡片布局、替换前后对比一屏可见、关键数据醒目
- 统计卡片区保持与原型一致的三列 grid 布局
- 批次选择器横向滚动 pill 按钮样式
技术栈
复用项目已有:React 19 + Tailwind CSS + motion/react(动画)+ recharts(图表)+ lucide-react(图标)
约束与边界
- 替换仅为建议,不直接操作数据库修改车辆归属
- 不能推荐已租赁给其他客户的车辆,只从库存(在库)中推荐
- 跨批次可替换,但车型必须匹配(含冷链→普货单向规则)
- 同大区内替换,不跨大区
- notify 操作后数据立即更新(不使用定时缓存)
- 客户名称展示需使用已有的脱敏/Blur 组件