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

225 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 智能调度模块设计
基于里程考核数据,通过贪心优先级匹配算法,生成车辆替换建议,帮助调度员优化车队里程分布,最大化达标车辆数。
## 业务背景
公司有多批次考核车辆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 (可选) | 按批次筛选,不传则全部 |
**响应**
```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 组件