# 智能调度模块设计 基于里程考核数据,通过贪心优先级匹配算法,生成车辆替换建议,帮助调度员优化车队里程分布,最大化达标车辆数。 ## 业务背景 公司有多批次考核车辆(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 映射到大区(嘉兴/广东/北京/新疆/其他)。同一大区内可替换,跨大区不推荐。 ### 优先级排序 干预清单排序: 1. **hopeless + 有可行替换方案** → priority: high(最紧急,还能抢救) 2. **qualified + 高里程客户 + 有库存可换** → priority: medium(释放达标车,让新车追赶) ## 后端 API ### GET /api/scheduling/suggestions 获取调度建议列表。每次请求实时计算(不使用定时缓存),因为用户操作后需要立即看到最新结果。 **请求参数**: | 参数 | 类型 | 说明 | |------|------|------| | targetId | number (可选) | 按批次筛选,不传则全部 | **响应**: ```typescript { summary: { qualifiedCount: number; // 已达标车辆数 hopelessCount: number; // 无望达标车辆数 suggestionCount: number; // 可干预建议数 estimatedGain: number; // 预计干预后可新增达标数 }; suggestions: SchedulingSuggestion[]; targets: { id: number; name: string; vehicleCount: number }[]; } ``` `SchedulingSuggestion` 结构: ```typescript { 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。 **请求体**: ```typescript { suggestionId: string; currentPlate: string; candidatePlate: string; } ``` 操作人从 JWT auth 中获取。 **响应**:`{ success: boolean; message: string }` **行为**:调用外部回调接口发送通知(具体回调 URL 后续配置)。成功后在本地记录已操作状态,后续 GET suggestions 时排除已操作的建议。 ### 数据查询流程 后端一次请求聚合以下数据: 1. 所有考核车辆 — `tab_mileage_assessment_vehicle`(里程进度、达标状态) 2. 所有考核目标 — `tab_mileage_assessment_target`(批次名称、年度目标) 3. 库存车辆 — `tab_truck WHERE truck_rent_status = 0`(在库)+ 同表获取车型 4. 车辆实时位置 — `tab_truck_remote_sync_realtime_info`(province, city) 5. 合同/客户信息 — 复用 `vehicle-info.ts` 已有的 JOIN 查询 6. 客户日均里程 — 按客户聚合 `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 组件