Files
ln-bi/docs/superpowers/specs/2026-04-16-smart-scheduling-design.md
kkfluous 9bf9bdd8ff docs: 智能调度模块设计规格
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 20:11:51 +08:00

8.4 KiB
Raw Blame History

智能调度模块设计

基于里程考核数据,通过贪心优先级匹配算法,生成车辆替换建议,帮助调度员优化车队里程分布,最大化达标车辆数。

业务背景

公司有多批次考核车辆40台普货、190台冷藏车等每批次有年度里程考核目标。车辆租赁给不同客户客户实际使用强度差异大。考核的是车辆本身的里程,因此需要通过替换车辆来均衡里程:

  • 高里程客户的已达标车换下来,换上里程缺口大的车(让新车追赶)
  • 低里程客户的无望达标车换下来给高里程客户(抢救),给低里程客户换上已达标的车

核心算法

车辆分类

tab_mileage_assessment_vehicle 获取所有考核车辆,按客户聚合计算客户日均里程(客户下所有车辆近 30 天日均里程的平均值),然后对每辆车计算:

预测年终里程 = 当前累计里程 + 客户日均里程 × 剩余天数
达标概率 = 预测年终里程 / 年度目标里程

分为三类:

类型 条件 含义
qualified currentYearIsQualified = true 或 达标概率 ≥ 120% 已完成或铁定完成
hopeless 达标概率 < 60% 按当前客户使用强度,年底肯定完不成
normal 60% ≤ 达标概率 < 120% 有希望但不确定,暂不干预

替换建议生成

场景 Areplace_qualified高里程客户的已达标车辆

  • 目标:把已达标的车换下来,换上里程缺口大的库存车
  • 候选池库存车rent_status='在库'+ 同车型 + 同区域
  • 排序:优先选剩余缺口最大但换后仍可达标的车
  • 校验:候选车当前累计 + 客户日均 × 剩余天数 ≥ 年度目标 才推荐

场景 Brescue_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 (可选) 按批次筛选,不传则全部

响应

{
  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 时排除已操作的建议。

数据查询流程

后端一次请求聚合以下数据:

  1. 所有考核车辆 — tab_mileage_assessment_vehicle(里程进度、达标状态)
  2. 所有考核目标 — tab_mileage_assessment_target(批次名称、年度目标)
  3. 库存车辆 — tab_truck WHERE truck_rent_status = 0(在库)+ 同表获取车型
  4. 车辆实时位置 — tab_truck_remote_sync_realtime_infoprovince, 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 组件