# 自动排线服务联调接口规范 | 项目 | 内容 | |---|---| | 文档版本 | 20261019-weekday-cache | | 协议标识 | V1.1;接收既有 V1.0 报文,语义差异以本规范为准 | | 服务地址 | https://cosineapi.com | | 在线文档 | https://cosineapi.com/api.md | | 数据与时间 | JSON;Asia/Shanghai;重量kg,体积m³,坐标GCJ-02 | | 适用范围 | 绝配业务服务与算法服务之间的排线 核查 回调 历史同步及配套运维 | 本文供绝配后端、前端和IT人员联调使用。四个业务接口沿用原Word路径和异步机制。首次排线分别传入车型标称能力、rules硬约束和solverParameters求解偏好;人工调整每次提交一条完整线路,算法保留订单及顺序,只核查约束和更新指标。 本次没有新增求解请求参数。道路缓存依次使用对应星期、前一天星期、30天内最新记录;仅无可用记录或记录过期时查询高德。历史学习继续支持近期不足补充旧历史及新增确认历史后更新模型。装载率仍取重量、体积装载率中的较大值;人工核查继续支持绝配自建虚拟线路。 ## 1 接口目录及责任 下表业务路径均以 `/openapi/routing` 为前缀。 | 接口 | 方法与相对路径 | 调用方向 | 使用人员 | |---|---|---|---| | 首次排线 | POST /plans/generated | 绝配→算法 | 用户发起 绝配后端提交 | | 人工单线核查 | POST /plans/recalculate | 绝配→算法 | 用户调线后提交 | | 统一结果回调 | POST /plans/callback | 算法→绝配 | 绝配后端接收 | | 历史同步 | POST /plans/history | 绝配→算法 | 后端定时或确认后触发 | | 任务状态 | GET /plans/{taskCode} | 绝配→算法 | 进度展示 | | 计算结果 | GET /plans/{taskCode}/result | 绝配→算法 | 回调兜底 | | 参考参数 | GET /profiles | 绝配→算法 | 用户参数参考 IT查询 | | 运行配置 | GET /runtime/config | 绝配→算法 | IT维护 | | 调整运行配置 | POST /runtime/config | 绝配→算法 | IT维护 | 健康检查为 `GET /health`,不带上表前缀。文档入口为 `/api.md` 和 `/docs`。道路缓存自动维护,缓存补采工具不是绝配日常接入步骤,不列入本规范。算法内部Gurobi工作量、候选槽位等不要求绝配前端开发。 ## 2 标识 认证及公共报文 ### 2.1 标识关系 | 字段 | 产生方 | 约定 | |---|---|---| | taskCode | 绝配 | 调用方内唯一的业务任务号;普通核查不换号 | | requestId | 发起本次请求的一方 | 一次操作的流水号;重试沿用,修改内容换号 | | routeId | 自动排线由算法生成;虚拟线路由绝配提供 | 新生成线路格式L+YYYYMMDD+五位日流水,例如L2026092200001;算法服务内跨任务、跨仓共用流水 | | routeSeq | 展示层 | 顺序编号,不能当线路ID | | adjustmentActions[].fromRouteId | 绝配 | 对应订单原所属线路,保留原Word位置 | | originalRequestId | 算法回显 | 指向绝配原请求;失败结果也提供 | | version | 算法 | 同任务计算记录序号,可能只对应一次单线核查 | | interfaceVersion | 协议 | 协议版本,与公司方案版本和计算记录序号不同 | 线路定位使用 `taskCode + routeId`;接口凭据还隔离调用方。首次排线在结果落库时统一分配线路号,按北京时间的编号生成日期递增,求解完成和超时保底均适用。人工核查原样保留,重试不重新编号。已有R-001等旧编号继续有效,不追溯改写。taskCode已使用后,新一轮整批排线应使用新的taskCode;单线核查继续使用原任务号。 日流水通过同一持久化数据库事务分配,并行任务和服务重启不会重复;同日最多99999个编号,耗尽时明确报错,不回绕。不要求流水无缺号,也不代表任务提交顺序。算法生成线路号由绝配原样保存引用;如果绝配其他业务也生成同格式线路号,必须共用一个编号来源,两个独立流水无法保证跨系统不冲突。多个独立数据库部署的算法实例也不能各自分配相同流水。 订单O3从A移到B:adjustmentActions中记录orderNo=O3、fromRouteId=A;routes[0].routeId=B,stops是调整后的B全线。算法核查B并回调B,不执行移单。A也需核查时另发一条请求。 ### 2.2 认证 除健康检查和文档外,使用HTTP Header `X-App-Key`、`X-App-Secret`。入站及回调凭据沿用原Word第三节双方约定,本文不列真实值。凭据由绝配后端保存,不放用户浏览器代码。 “用户/IT”是绝配产品权限分工建议;当前算法接口使用应用级凭据,不识别绝配系统内的人员角色。运行配置写入应由绝配后端仅向IT开放。 ### 2.3 公共外层 | 字段 | 类型 | 填写要求 | |---|---|---| | requestId | string | 必填,1~128字符,无首尾空格 | | interfaceVersion | string | 标准请求填写V1.1;兼容V1.0 | | timestamp | string | ISO 8601,例如2026-09-20T08:00:00+08:00 | | data | object | 具体业务数据 | 相同requestId、相同业务内容返回原受理记录;相同requestId、不同业务内容返回409。code=0表示接口处理成功,ACCEPTED只表示已受理,不表示线路合规。结构错误返回422,鉴权失败401。请求体上限10MB,首次最多5000条订单。 ## 3 车型参数与求解配置 车型自身容量仍是maxWeightKg、maxVolumeM3。硬上限比例沿用rules.maxWeightRate/maxVolumeRate;新增偏好统一位于data.solverParameters。旧vehicleTypes偏好字段仅兼容接收,两处同时传入不同值返回422。 | 层次 | 字段 | 单位及含义 | 来源 | |---|---|---|---| | 标称重量 | vehicleTypes[].maxWeightKg | kg,车型标称能力,>0 | 原Word | | 标称体积 | vehicleTypes[].maxVolumeM3 | m³,车型标称能力,>0 | 原Word | | 重量硬比例 | rules.maxWeightRate | %,0<值≤100 | 原Word | | 体积硬比例 | rules.maxVolumeRate | %,0<值≤100 | 原Word | | 重量偏好 | solverParameters.preferredWeightKg | kg,可选,null关闭,不高于硬上限 | 新增 | | 体积偏好 | solverParameters.preferredVolumeM3 | m³,可选,null关闭,不高于硬上限 | 新增 | 重量硬上限=maxWeightKg×maxWeightRate/100;体积硬上限=maxVolumeM3×maxVolumeRate/100。偏好使用绝对值,不再次乘比例。 示例:车型标称5000kg/10m³,硬比例96%/85%,得到4800kg/8.5m³;体积偏好8m³。8.3m³属于容量合规但超偏好,8.7m³属于超硬容量。时间窗和门店数仍须另外核查。 公司继续维护车型档案。车型对象只描述车辆自身能力和费用;硬约束属于rules,偏好属于solverParameters。算法保存每次任务的车型和生效求解配置。vehicleTypeCode和vehicleTypeName可传并回显,不作为计算必需条件,不需要先调用车型注册接口。相同容量但费用或规则不同的配置不能互相替代。 通常vehicleTypes只传一个对象。多车型目前按整批单车型方案比较,不提供混合车队联合优化。新增count可限制车数,0表示不限;costPerKm沿用原Word,fixedCost为可选扩展、默认0。不提供costPerKm时费用为null,不使用隐藏运价;COST_FIRST必须提供运价。 ## 4 参数开放范围及新增清单 ### 4.1 开放给用户的排线效果参数 下列参数均为原Word以外的扩展;业务硬上限、发车时间、最多门店、时间窗等原参数也继续开放给用户。 | 字段路径 | 类型与范围 | 缺省行为 | 用途及前端建议 | |---|---|---|---| | solverParameters.preferredVolumeM3 | number/null,>0且≤体积硬上限 | 通用请求不启用 | 展示为体积偏好上限,超过仍可合规 | | solverParameters.preferredWeightKg | number/null,>0且≤重量硬上限 | 不启用 | 展示为重量偏好上限 | | solverParameters.preferredLastStopMin | number/null,分钟,>0且≤末店硬时限 | 通用请求不启用 | 希望发车后多少分钟内完成末店 | | solverParameters.minimumAverageLoadRate | number/null,0~100 | 通用请求不启用;0/null关闭 | 通用V2主流程的全批容积装载目标,不是每线硬下限;有无时间窗均使用相同目标 | | rules.maxTimeToLastStopMin | number,分钟,>0 | 通用请求取maxDurationHours×60 | 需要独立限制末店完成时使用;与配送总时长分开 | | rules.defaultWaitDurationMin | number,分钟,≥0 | 通用请求5 | 门店固定操作等待默认值,不是早到等待 | | rules.defaultUnloadDurationMin | number,分钟,≥0 | 通用请求10 | 逐单卸货值缺失时的默认值 | | orders[].waitDurationMin | number,分钟,≥0 | 使用任务默认值 | 单店特殊等待覆盖 | 默认软偏好不会把未知仓自动套成海星或北京。配置为空表示不启用,不代表没有硬上限。偏好只影响可行方案之间的选择,不能突破硬容量和时间窗。 preferredLastStopMin例如480、末店硬上限510,表示希望8小时内完成、允许最晚8.5小时;不是算法允许运行480分钟。软容量已接入通用V2分配目标,可能改变组合,但不保证达到所有偏好或全局最优。 ### 4.2 建议由IT维护的执行与服务器参数 | 字段或接口 | 类型与范围 | 默认值 | 作用 | |---|---|---|---| | data.solverParameters.maxSolveSeconds | number,1~3600秒 | 600或IT配置值 | 单次执行预算,排队不计入;不建议普通用户随意放大 | | POST runtime/config的defaultSolveBudgetSeconds | number,1~3600秒 | 600 | 调整后续新任务默认预算,已受理任务不变 | | POST runtime/config的maxConcurrentJobs | integer,1~8 | 按部署配置,当前目标4 | 同时执行的求解任务上限,超出排队 | | data.solverParameters.maxOnlineRequests | integer,0~100000次 | min(100000,max(5000,40×订单数)) | 每任务新增道路查询预算;973单默认38920次。显式传值优先,0只用缓存,缓存命中不计数 | | data.solverParameters.historyMode | auto/required/disabled/refresh | auto | 自动复用或随确认历史更新、必须有历史、关闭、强制重学 | | data.solverParameters.historyDays | integer,1~3650天 | 60 | 训练最多选取的有效配送日数,日期越新越优先;近期不足时补充较旧配送日。修改后立即重学可使用refresh | maxSolveSeconds是算法执行预算;返回的solveDurationMs是实际执行毫秒数,只读;原rules.maxDurationHours是车辆配送时长。三者不是同一个参数。请求不使用solveBudgetSec;solveBudgetSeconds是结果回显的预算。 Gurobi工作量、搜索轮数、候选槽位和候选车型上限保留为算法内部参数,不进入绝配普通用户配置表。业务侧通常一个车型,无需maxCandidateVehicleTypes。 ### 4.3 不需要新增的业务输入 不要求profile、historyMatchWeekday、独立历史模型ID、routeInstanceId或isOuterArea。仓库身份使用原startStation.stationCode。温区当前可保留temperatureZones标记,缺失按假设提示;不以缺失字段证明真实温区可行性。车辆默认支持冷藏。 ## 5 首次排线接口 `POST /openapi/routing/plans/generated` ### 5.1 data结构 | 字段 | 类型 | 标准报文要求 | |---|---|---| | batch.taskCode | string | 必填,唯一任务号 | | batch.planDispatchDate | string | 配送日期YYYY-MM-DD;原Word示例已有 | | batch.totalOrders | integer | 订单总数,与明细核对 | | batch.totalWeightKg/totalVolumeM3 | number | kg/m³,与明细核对 | | startStation | object | 原仓号、名称、城市、地址及GCJ-02经纬度 | | vehicleTypes | array | 必填,通常一个车型;标称重量/体积必填;偏好放入solverParameters | | rules | object | 本次业务规则 | | orders | array | 必填,非空完整订单集合 | | solverParameters | object | 可省略,用户求解偏好及IT维护的执行预算 | 旧请求缺少部分可选数据可宽松接收并提示,但绝配标准报文应填写真实配送日期、车型及规则,不能依赖测试仓默认值。汇总不符时明细为准并返回警告。 ### 5.2 原业务规则 | 字段 | 类型 | 含义及实现边界 | |---|---|---| | ruleSetId/ruleSetName | string | 规则标识与名称,保留 | | departureTime | string | HH:mm:ss,通用缺省10:00:00 | | strategyMode | string | TIME_FIRST/LOAD_FIRST/COST_FIRST;成本模式只用于候选方案比较 | | maxStopsPerRoute | integer,>0 | 每线最多门店访问次数,通用缺省20 | | maxDurationHours | number,>0 | 配送总时长小时,通用缺省12;包含等待卸货,returnTrip=true时含返仓 | | maxWeightRate/maxVolumeRate | number,0<值≤100 | 硬容量比例%,建议明确传入;通用缺省100 | | avgSpeedKmH | number,>5且≤130 | 初排参考速度,缺省45;终检使用道路时间 | | maxDistrictCount | integer,1~1000 | 无历史时的聚类模板数量提示,不是每线区县数上限 | | allowMixedTemperature | boolean | 是否允许混温,缺省true;依赖明确温区数据 | | categoryMutex | boolean | 食品/非食品互斥,缺省false | | burstPriority | boolean | 保留优先级,不能突破硬约束;不保证各分支按等级先后送达 | | hardTimeWindow | boolean | 有窗口时要求true;当前不支持带窗口且false的软窗模式 | | peakAvoid | boolean | 使用已有时段缓存,不提供未来交通预测 | | outerOneWayLimitHours | number | 兼容旧正数值,停止独立计算;限制由有效timeWindowText表达 | | returnTrip | boolean | 是否计算返仓,缺省false | 时间窗格式兼容英文/中文逗号及两侧空格,时段内部推荐使用连字符(06:00-10:00);旧单时段起止使用~的写法继续接收,旧多段06:00-10:00~14:00-16:00也兼容。未带日期的多段时钟按同一日窗口集合解释:当天尚有可用段时不单独把已过的上午窗口移到次日;全部已过时整体顺延到下一次窗口。固定日期时间段不顺延。时间窗是允许配送的时段,禁收货文字仍需由业务转换为允许时段,不能直接当作可送窗口。 窗口截止统一按卸货完成。相邻同店多单按门店操作时长聚合,默认取最大值;内部兼容serviceDurationBasis=per_order时求和。A→B→A仍是三次访问。多个可选配送时段用逗号分隔,如06:00-10:00,14:00-16:00;必须在其中一个时段内完成服务,不能跨越中间禁送间隔。提前到达可以等待,等待计入配送运行时长。同店多单合并配送时取各订单可选窗口集合的交集。 ### 5.3 订单字段 | 字段 | 类型 | 要求 | |---|---|---| | orderNo/storeCode/storeName | string | 订单号、真实门店编号和名称;orderNo批次内唯一 | | province/city/district/address | string | 省市区及地址,沿用原Word | | longitude/latitude | number | GCJ-02有效经纬度,不能以0,0代替缺失 | | pieces | integer,≥0 | 件数 | | weightKg/volumeM3 | number,≥0 | 订单实际重量/体积 | | timeWindowText | string | 如09:00-16:00或06:00-10:00,14:00-16:00;空串表示无额外窗口 | | unloadDurationMin | number,≥0 | 卸货分钟数,原Word已有;逐单值优先 | | category | string,可选 | FOOD/NON_FOOD,缺省UNKNOWN | | burstLevel | string,可选 | HIGH/MEDIUM/LOW,缺省LOW | | waitDurationMin | number,可选 | 新增固定操作等待分钟数 | | temperatureZones/remark | array/string,可选 | 温区标记和操作备注 | 跨日可以使用22:00-02:00,或明确日期2026-09-20 22:00~2026-09-21 02:00。不带日期的时段取发车后最近可用时段,固定日期请显式填写。等待计入配送时长,不自动放宽硬时限。 ### 5.4 完整请求示例 仓库、订单和运价均为格式示例,不代表真实公司仓库绑定。 ```json { "requestId": "JP-GEN-001", "interfaceVersion": "V1.1", "timestamp": "2026-09-20T08:00:00+08:00", "data": { "batch": { "taskCode": "TASK-001", "planDispatchDate": "2026-09-20", "totalOrders": 2, "totalWeightKg": 700, "totalVolumeM3": 3.5 }, "startStation": { "stationCode": "COMPANY-W001", "stationName": "示例仓", "city": "北京市", "address": "示例仓地址", "longitude": 116.5, "latitude": 40.0 }, "vehicleTypes": [ { "maxWeightKg": 5000, "maxVolumeM3": 10, "costPerKm": 4.2 } ], "rules": { "departureTime": "09:00:00", "strategyMode": "TIME_FIRST", "maxStopsPerRoute": 10, "maxDurationHours": 10, "avgSpeedKmH": 45, "maxWeightRate": 96, "maxVolumeRate": 85, "hardTimeWindow": true, "returnTrip": false, "allowMixedTemperature": true, "categoryMutex": false, "burstPriority": false, "peakAvoid": false }, "solverParameters": { "preferredVolumeM3": 8, "preferredWeightKg": 4500, "maxSolveSeconds": 300 }, "orders": [ { "orderNo": "O001", "storeCode": "S001", "storeName": "示例店一", "province": "北京市", "city": "北京市", "district": "朝阳区", "address": "示例地址一", "longitude": 116.45, "latitude": 39.95, "pieces": 20, "weightKg": 300, "volumeM3": 1.5, "timeWindowText": "09:00-16:00", "unloadDurationMin": 10 }, { "orderNo": "O002", "storeCode": "S002", "storeName": "示例店二", "province": "北京市", "city": "北京市", "district": "朝阳区", "address": "示例地址二", "longitude": 116.46, "latitude": 39.96, "pieces": 30, "weightKg": 400, "volumeM3": 2, "timeWindowText": "09:00-16:00", "unloadDurationMin": 10 } ] } } ``` ### 5.5 受理响应 ```json { "code": 0, "message": "success", "data": { "taskCode": "TASK-001", "version": 1, "status": "ACCEPTED", "solveBudgetSeconds": 300, "idempotentReplay": false, "inputIssues": [], "warnings": [] } } ``` 不在此响应中等待最终方案。estimatedDurationMs没有可靠预测时省略,不能直接用预算冒充预计耗时。订单明细异常在inputIssues立即提示,最终列入unassignedOrders;结构错误直接422。 ## 6 人工单线核查接口 `POST /openapi/routing/plans/recalculate` ### 6.1 请求字段 | 字段 | 要求 | 与原Word差异 | |---|---|---| | data.taskCode | 原任务号;首次虚拟线路使用绝配提供的新任务号 | 保留字段,扩展首次建档能力 | | data.routes | 必填,仅一条完整线路 | 多线局部优化改为单线核查 | | routes[].routeId | 当前核查线路ID | 原示例已有,任务内稳定 | | routes[].stops | 调整后完整订单、当前属性与顺序 | 保留结构,不执行重新排序 | | stops[].sequence | 全部提供时为唯一正整数;或全部省略按数组顺序 | 明确顺序口径 | | data.adjustmentActions | V1.1提供数组;或使用线路级fromRouteId。纯规则/车型核查可为空数组 | 保留原结构,仅记录和回显 | | adjustmentActions[].orderNo | 本次调整的订单 | 保留 | | adjustmentActions[].fromRouteId | 来源线路ID;全新无来源订单显式null | 保留原字段,不改成线路级必填 | | data.vehicleTypes | 不换车可省略;换车传完整配置 | 原必填改可继承 | | data.rules | 不变可省略;提供则覆盖指定字段 | 原必填改可继承 | | data.solverParameters | 可省略,继承首次任务预算;显式覆盖按本次 | 新增,IT维护 | | data.baseVersion | 可选;自动任务引用首次方案版本,虚拟线路引用该线路最近受理版本 | 用于检测基准变化;新任务不传 | 未换车时继承首次对应线路的车型及求解、费用和规则快照。改变硬比例时偏好超过新硬上限会返回422,不偷偷缩放。换车时传完整vehicleTypes,偏好在solverParameters中单独填写,未提供偏好则关闭,未提供费用则不计算费用;其他线路不会继承本次特殊配置。 推荐直接使用原vehicleTypes和rules结构;此前capacityWeightKg/capacityVolumeM3仅作兼容输入,不能同时传互相冲突的两套容量。线路级fromRouteId原样回显;首次虚拟线路可与routeId相同。涉及多个订单来源线路时仍使用adjustmentActions逐单表达。 同一taskCode的多条核查可以分别入队;每条请求须用不同requestId。新增人工线路可使用任务内未重复的新ID。已知来源ID须属于当前调用方的任务。 单线核查无法单独证明全批车数或跨线订单不重复,绝配应维护整批方案一致性。空线作废在业务侧处理。 ### 6.2 虚拟线路首次与后续核查 无需先调用generated。绝配首次传入新的data.taskCode,routes[0].routeId与fromRouteId使用同一个自有线路编号,算法原样保存并回传,不替换为新编号。服务仍先返回ACCEPTED,完成后异步回调;状态和结果继续按同一taskCode查询,version随每次新请求递增。 首次没有可继承配置,须提供startStation、dispatchDate、vehicleTypes和rules;rules至少明确departureTime、maxDurationHours、maxStopsPerRoute。其余规则及求解偏好沿用既有字段,没有新增求解参数。后续同taskCode和routeId可省略仓库、配送日期、车型及规则,从该虚拟线路最近已受理请求继承,服务重启后也可复用;显式提供的规则覆盖本次配置。另一条虚拟线路不会自动继承前一条线路的特殊配置。已有自动排线任务仍继承首次自动排线基准。 每次stops均为调整后的完整线路快照,按数组或sequence给定顺序核查,不自动追加旧订单,也不重新分线或排序。修改内容使用新requestId,重试保持原requestId。与其他任务冲突的已登记L格式线路号返回409,来源线路不能跨任务引用。 首次请求: ```json { "requestId": "JP-VIRTUAL-001", "interfaceVersion": "V1.1", "timestamp": "2026-10-17T08:00:00+08:00", "data": { "taskCode": "VIRTUAL-TASK-001", "dispatchDate": "2026-10-17", "startStation": { "stationCode": "COMPANY-W001", "stationName": "示例仓", "city": "北京市", "address": "示例仓地址", "longitude": 116.5, "latitude": 40.0 }, "vehicleTypes": [ { "maxWeightKg": 5000, "maxVolumeM3": 10, "costPerKm": 4.2 } ], "rules": { "departureTime": "09:00:00", "strategyMode": "TIME_FIRST", "maxStopsPerRoute": 10, "maxDurationHours": 10, "avgSpeedKmH": 45, "maxWeightRate": 96, "maxVolumeRate": 85, "hardTimeWindow": true, "returnTrip": false, "allowMixedTemperature": true, "categoryMutex": false, "burstPriority": false, "peakAvoid": false }, "solverParameters": { "preferredVolumeM3": 8, "preferredWeightKg": 4500, "maxSolveSeconds": 300 }, "routes": [ { "routeId": "VIRTUAL-LINE-001", "fromRouteId": "VIRTUAL-LINE-001", "stops": [ { "orderNo": "O001", "storeCode": "S001", "storeName": "示例店一", "province": "北京市", "city": "北京市", "district": "朝阳区", "address": "示例地址一", "longitude": 116.45, "latitude": 39.95, "pieces": 20, "weightKg": 300, "volumeM3": 1.5, "timeWindowText": "09:00-16:00", "unloadDurationMin": 10 }, { "orderNo": "O002", "storeCode": "S002", "storeName": "示例店二", "province": "北京市", "city": "北京市", "district": "朝阳区", "address": "示例地址二", "longitude": 116.46, "latitude": 39.96, "pieces": 30, "weightKg": 400, "volumeM3": 2, "timeWindowText": "09:00-16:00", "unloadDurationMin": 10 } ] } ] } } ``` 后续请求(沿用同一任务号、线路号,省略已存配置): ```json { "requestId": "JP-VIRTUAL-002", "interfaceVersion": "V1.1", "timestamp": "2026-10-17T08:05:00+08:00", "data": { "taskCode": "VIRTUAL-TASK-001", "routes": [ { "routeId": "VIRTUAL-LINE-001", "fromRouteId": "VIRTUAL-LINE-001", "stops": [ { "orderNo": "O002", "storeCode": "S002", "storeName": "示例店二", "province": "北京市", "city": "北京市", "district": "朝阳区", "address": "示例地址二", "longitude": 116.46, "latitude": 39.96, "pieces": 30, "weightKg": 400, "volumeM3": 2, "timeWindowText": "09:00-16:00", "unloadDurationMin": 10 }, { "orderNo": "O001", "storeCode": "S001", "storeName": "示例店一", "province": "北京市", "city": "北京市", "district": "朝阳区", "address": "示例地址一", "longitude": 116.45, "latitude": 39.95, "pieces": 20, "weightKg": 300, "volumeM3": 1.5, "timeWindowText": "09:00-16:00", "unloadDurationMin": 10 } ] } ] } } ``` ### 6.3 已有自动排线任务核查示例 下例只改变顺序,继承首次车型及规则。 ```json { "requestId": "JP-CHECK-001", "interfaceVersion": "V1.1", "timestamp": "2026-09-20T09:05:00+08:00", "data": { "taskCode": "TASK-001", "adjustmentActions": [ { "orderNo": "O002", "fromRouteId": "L2026092000001" } ], "solverParameters": { "maxSolveSeconds": 30 }, "routes": [ { "routeId": "L2026092000001", "stops": [ { "orderNo": "O002", "storeCode": "S002", "storeName": "示例店二", "province": "北京市", "city": "北京市", "district": "朝阳区", "address": "示例地址二", "longitude": 116.46, "latitude": 39.96, "pieces": 30, "weightKg": 400, "volumeM3": 2, "timeWindowText": "09:00-16:00", "unloadDurationMin": 10, "sequence": 1 }, { "orderNo": "O001", "storeCode": "S001", "storeName": "示例店一", "province": "北京市", "city": "北京市", "district": "朝阳区", "address": "示例地址一", "longitude": 116.45, "latitude": 39.95, "pieces": 20, "weightKg": 300, "volumeM3": 1.5, "timeWindowText": "09:00-16:00", "unloadDurationMin": 10, "sequence": 2 } ] } ] } } ``` ### 6.3 受理及结果 ```json { "code": 0, "message": "success", "data": { "taskCode": "TASK-001", "version": 2, "status": "ACCEPTED", "solveBudgetSeconds": 30, "idempotentReplay": false, "inputIssues": [], "warnings": [] } } ``` 结果继续异步回调。超重、超体积、超窗、超时均保留提交线路,返回INVALID和违规明细。仅超过软偏好在warnings中提示,不因此判硬违规。 ## 7 统一结果回调 `POST /openapi/routing/plans/callback`,算法调用绝配接收端。具体回调基址由部署配置指定,禁止每个请求任意指定回调URL。 ### 7.1 data字段 | 字段 | 含义 | |---|---| | taskCode/version | 任务号、本次计算记录序号 | | originalRequestId | 对应绝配请求号,正常与失败均提供 | | stationCode/stationName/dispatchDate | 始发仓及实际配送日期 | | resultScope | FULL_PLAN整批结果,SINGLE_ROUTE单线结果 | | operation | GENERATE或VALIDATE_ONLY | | status/completion/valid | 当前结果状态、完整性及合规性;不能只看SUCCESS判断可执行 | | routes/metrics/unassignedOrders | 线路、本次范围汇总、未成线订单及原因 | | adjustmentActions | 单线请求动作原样回显,保留逐订单fromRouteId | | targetRouteId | 无线路结果的失败核查保留目标线路ID | | budgetExhausted/optimizationCompleted | 是否预算耗尽、流程是否正常结束;不表示全局最优证明 | | queueDurationMs/solveDurationMs | 排队和执行毫秒数,分别统计 | | solveBudgetSeconds | 生效执行预算,秒,只读 | | resultSource/remark/warnings | 结果来源、终止原因及警告 | SUCCESS表示有结果返回,仍可能PARTIAL或含INVALID线。绝配按路线valid/routeStatus判断可执行性。预算耗尽可以返回走廊初排草案,但违规和道路未核验必须可见。单线失败且没有可用候选时routes为空,通过targetRouteId和originalRequestId关联。 ### 7.2 线路字段及容量回显 | 字段 | 含义 | |---|---| | routeId/routeSeq/routeName | 线路ID、展示顺序和名称 | | valid/routeStatus | 合规布尔值;EXECUTABLE/INVALID | | constraintViolations | string[],硬违规和未核验事项 | | warnings | string[],超过软偏好等提示 | | vehicleCapacity | 实际采用的车型参数与求解配置 | | vehicleTypeCode/vehicleTypeName | 公司提供时原样回显,可选 | | polyline/polylineProvider | [[经度,纬度],...]道路折线数组及来源;草案可为空 | | roadTimeVerified | 耗时是否全部经过道路数据核验,不等于货车通行许可认证 | | stops | 订单顺序、预计到达/开始服务/离开时刻,包含配送日期 | ```json { "vehicleCapacity": { "maxWeightKg": 5000, "maxVolumeM3": 10, "maxWeightRate": 96, "maxVolumeRate": 85, "weightKg": 4800, "volumeM3": 8.5, "preferredWeightKg": 4500, "preferredVolumeM3": 8 } } ``` | metrics字段 | 单位与口径 | |---|---| | pointCount/linePieces | 实际门店访问次数/件数 | | lineWeightKg/lineVolumeM3 | kg/m³,线路实际货量 | | capacityWeightKg/capacityVolumeM3 | kg/m³,有效硬容量 | | remainingWeightKg/remainingVolumeM3 | 硬容量减货量,负值表示超载 | | weightLoadRate/volumeLoadRate | 百分数,实际货量除以对应有效硬容量 | | loadRate | max(weightLoadRate, volumeLoadRate),百分数 | | avgLoadRate(顶层metrics) | 本次各线路loadRate的算术平均;不是两种平均装载率再取最大值 | | distanceKm/returnDistanceKm | 总里程/返仓里程,km | | deliveryDurationHours | 发车至末店卸货完成,不含返仓 | | durationHours | 总时长,按returnTrip决定是否含返仓 | | lineCost | 元;无运价为null,包含每车固定费用 | 顶层metrics只汇总本次返回范围,包括总线数、点数、件数、里程、时长、平均装载率、费用、已排/未排订单数和不可执行线数。单线结果不能直接覆盖整批汇总。站点不再重复回传category/burstLevel,公司从订单库读取;首次输入仍保留这些字段。 ### 7.3 核查回调示例 下面为回调片段和模拟道路指标,省略stops与顶层汇总metrics,不表示真实道路运行结果。完整字段沿用前述定义。 ```json { "requestId": "HKUEXAMPLE", "interfaceVersion": "V1.1", "timestamp": "2026-09-20T09:05:02+08:00", "data": { "taskCode": "TASK-001", "stationCode": "COMPANY-W001", "stationName": "示例仓", "dispatchDate": "2026-09-20", "version": 2, "resultScope": "SINGLE_ROUTE", "operation": "VALIDATE_ONLY", "valid": true, "status": "SUCCESS", "completion": "COMPLETE", "solveDurationMs": 1200, "remark": "计算完成:1条线路,已排2/2单,未成线0单,不可执行线路0条", "routes": [ { "routeId": "L2026092000001", "routeSeq": 1, "routeName": "线路01", "valid": true, "routeStatus": "EXECUTABLE", "isLocked": false, "vehicleCapacity": { "weightKg": 4800.0, "volumeM3": 8.5, "maxWeightKg": 5000.0, "maxVolumeM3": 10.0, "maxWeightRate": 96.0, "maxVolumeRate": 85.0, "preferredWeightKg": 4500.0, "preferredVolumeM3": 8.0 }, "metrics": { "pointCount": 2, "linePieces": 50.0, "distanceKm": 12.0, "returnDistanceKm": 0.0, "durationHours": 1.1667, "lineWeightKg": 700.0, "lineVolumeM3": 3.5, "weightLoadRate": 14.58, "deliveryDurationHours": 1.1667, "capacityWeightKg": 4800.0, "capacityVolumeM3": 8.5, "remainingWeightKg": 4100.0, "remainingVolumeM3": 5.0, "volumeLoadRate": 41.18, "loadRate": 41.18, "lineCost": 50.4 }, "polyline": [ [ 116.5, 40.0 ], [ 116.46, 39.96 ], [ 116.45, 39.95 ] ], "polylineProvider": "amap", "roadTimeVerified": true, "constraintViolations": [], "warnings": [] } ], "adjustmentActions": [ { "orderNo": "O002", "fromRouteId": "L2026092000001" } ], "originalRequestId": "JP-CHECK-001", "queueDurationMs": 500, "solveBudgetSeconds": 30, "budgetExhausted": false, "optimizationCompleted": true, "resultSource": "SOLVER_RESULT" } } ``` 公司按taskCode+routeId更新线路,按originalRequestId匹配操作。收到旧核查回调时,按对应线路记录的最新请求/version防止覆盖;不能用另一条线路更高的version丢弃本条线路结果。 ### 7.4 公司接收响应 ```json { "code": 0, "message": "success", "data": { "taskCode": "TASK-001", "status": "ACCEPTED" } } ``` 继续接受原Word的ACCEPTED。回调失败最多尝试5次,同一次回调重试沿用相同requestId和报文。结果先保存在算法服务,再通知公司;未收到时可查结果接口。 ## 8 历史同步接口 `POST /openapi/routing/plans/history` 本接口由算法服务提供,绝配后端在最终确认发车后主动调用,也可每日定时补同步。绝配应按已确认的结果格式发送stationCode、dispatchDate、taskCode、routes;测试或未采用的线路不得作为正式历史推送。算法接收并保存,不启动新的排线,不要求Excel上传。原Word只传historyTimeStart/historyTimeEnd无法传递历史样本;只传起止日期仍会提示导入0条,不触发远程拉取。 ```json { "requestId": "JP-HISTORY-001", "interfaceVersion": "V1.1", "timestamp": "2026-09-20T18:00:00+08:00", "data": { "stationCode": "COMPANY-W001", "dispatchDate": "2026-09-20", "taskCode": "TASK-001", "routes": [ { "routeId": "L2026092000001", "routeStatus": "EXECUTABLE", "stops": [ { "orderNo": "O002", "storeCode": "S002", "longitude": 116.46, "latitude": 39.96, "pieces": 30, "weightKg": 400, "volumeM3": 2, "sequence": 1 }, { "orderNo": "O001", "storeCode": "S001", "longitude": 116.45, "latitude": 39.95, "pieces": 20, "weightKg": 300, "volumeM3": 1.5, "sequence": 2 } ] } ] } } ``` stops每项至少包括订单/门店标识、坐标及件重方,格式同结果明细;顺序使用sequence。dispatchDate是实际配送日期,不是同步日期。只有可执行确认线路进入该格式的历史学习;INVALID线路不导入。 ```json { "code": 0, "message": "success", "data": { "importedRecords": 2, "rejectedRecords": [], "warnings": [], "historyTimeStart": null, "historyTimeEnd": null } } ``` 按调用方、仓库、日期、订单维护记录;换线更新归属,未提交记录保留,不代表删除整个旧批次。重发同一请求号幂等。自动排线、人工核查和回调接收记录不会自行进入历史学习库,必须通过本接口同步最终确认线路。 历史选择以配送日前的数据为准,不包含当天及未来数据。默认最多使用最近60个有历史记录的配送日:先选近期日期,数量不足时向前补充旧日期。新日期进入后,超过60日时最旧日期退出训练集合,原始历史仍保留。例如已有60日旧历史,新增1日确认历史后,使用1日新历史和最近59日旧历史。 首次没有模型时训练;已有模型且该仓确认历史发生新增或修改时,在下一次自动排线开始阶段自动检查并更新。没有变化时直接复用,不重复读取全量样本;变化不影响所选样本时也不重复训练。多个同仓任务共用训练锁。historyMode=refresh仍可强制重学,disabled不使用历史。更新模型的耗时计入该次执行预算。结果learningModel.historySelection可查看近期与补充旧历史的配送日数量。 仓库必须使用同一个真实stationCode,模型同时匹配始发仓坐标;预置海星资料在完成真实仓号登记前不会自动借给其他仓。 ## 9 查询和IT维护接口 ### 9.1 状态及结果查询 GET `/openapi/routing/plans/{taskCode}` 查询状态;GET `/openapi/routing/plans/{taskCode}/result` 查询结果。两者均可带 `?version=2` 指定受理版本。没有version时返回最新计算记录,单线核查之后可能只是一条线,不是自动合并好的整批方案。 ```json { "code": 0, "message": "success", "data": { "taskCode": "TASK-001", "version": 2, "status": "ACCEPTED", "queuePosition": 1, "queueDurationMs": 1200, "solveStartedAt": null, "maxConcurrentJobs": 4, "callbackStatus": "NONE", "callbackAttempts": 0 } } ``` 结果未就绪返回409,任务不存在404。查询到结果后data与回调data一致。 ### 9.2 默认执行预算及并发 GET `/openapi/routing/runtime/config` 查询maxConcurrentJobs、runningJobs、queuedJobs、defaultSolveBudgetSeconds、允许范围和资源信息。POST同路径修改以下字段之一或两者: ```json { "requestId": "JP-IT-001", "interfaceVersion": "V1.1", "timestamp": "2026-09-20T08:00:00+08:00", "data": { "maxConcurrentJobs": 4, "defaultSolveBudgetSeconds": 300 } } ``` ```json { "code": 0, "message": "success", "data": { "appliedConfiguration": { "maxConcurrentJobs": 4, "defaultSolveBudgetSeconds": 300 }, "idempotentReplay": false } } ``` 实际响应另带updatedAt和currentConfiguration。配置持久化,重启保留。降低并发不终止正在运行的任务,只限制之后出队;提高并发允许更多任务执行。修改默认预算只影响后续新请求,单次请求显式maxSolveSeconds优先。原重算预算继承首次参数,不受随后全局配置漂移影响。 ### 9.3 参考参数及健康检查 GET `/openapi/routing/profiles` 返回海星、北京参考配置及parameterRoles,供查看,不是公司必须提交的profile开关。GET `/health` 检查服务存活;不能单独据此证明每次求解、回调和外部依赖全部可用。 ## 10 与原Word的变更对照 | 项目 | 原Word | 本版 | |---|---|---| | 四业务路径及外层 | 已有 | 保留 | | 首次与重算异步 | ACCEPTED后回调 | 保留,不改同步 | | 车型标称与硬比例 | maxWeightKg/maxVolumeM3及maxWeightRate/maxVolumeRate | 保留名称及单位 | | 车型编号 | 请求必填 | 可选保存回显,算法按数值求解 | | 软容量偏好 | 无 | 新增preferredVolumeM3/preferredWeightKg,建议用户配置 | | 执行时限 | 无 | 新增maxSolveSeconds,建议IT维护 | | 其他用户扩展 | 无完整定义 | 第4.1节单独列出,不与服务器参数混写 | | 重算内容 | 多线路局部重排 | 一条线固定订单与顺序核查 | | fromRouteId | adjustmentActions订单来源 | 原位置保留,逐动作回显,不改成整线来源 | | 重算车型与规则 | 每次全量必填 | 未变可继承首次快照 | | 回调范围 | 全量方案 | FULL_PLAN/SINGLE_ROUTE,接收端按范围处理 | | 回调routeId | 已引用但字段表不完整 | 明确taskCode+routeId关联 | | 轨迹 | 正文要求但无字段细则 | polyline数组及来源明确 | | 违规及超时 | 说明不充分 | valid、违规、预算耗尽及草案标记 | | 历史请求 | 只有起止时间 | 补充实际线路明细与配送日期 | | 状态/结果/参数查询 | 无这些算法侧路径 | 按本文新增配套能力 | ## 11 海星和北京填写参考 下表来自既有测试口径,业务应填写真实参数;不通过仓号或城市名称自动套用。 | 参数 | 海星参考 | 北京参考 | |---|---:|---:| | maxWeightKg | 5000 | 5000 | | maxVolumeM3 | 12 | 10 | | maxWeightRate | 96 | 96 | | maxVolumeRate | 87.5 | 85 | | 有效重量硬上限 | 4800kg | 4800kg | | 有效体积硬上限 | 10.5m³ | 8.5m³ | | preferredVolumeM3 | null | 8 | | preferredWeightKg | null | null | | departureTime | 10:00:00 | 09:00:00 | | maxStopsPerRoute | 20 | 10 | | maxDurationHours且不返仓 | 8.5 | 10 | | preferredLastStopMin | 480 | null | | minimumAverageLoadRate | 85 | null | | 默认等待/卸货分钟 | 5/5 | 5/10 | | 历史样本上限 | 60个有效配送日,近期优先、旧历史补充 | 60个有效配送日,近期优先、旧历史补充 | ## 12 错误及联调检查 | HTTP或字段 | 含义与处理 | |---|---| | 401 | 鉴权失败,核对调用方向与Header | | 404 | 任务/基准不存在 | | 409 | 请求号冲突、首次仍在计算、基准不符或结果未就绪;根据message区分 | | 413 | 请求体超过限制 | | 422 | 字段类型、范围、车型及生效规则冲突、顺序、重复订单或来源错误 | | inputIssues | 已识别的订单字段问题,最终在未排原因中保留 | | INVALID | 已返回线路但硬约束不满足或道路未核验,不可自动当可执行方案 | 联调至少验证:同请求重试幂等、不同taskCode同routeId隔离、同任务多条核查排队、未换车参数继承、换车费用与偏好、超软不超硬、超硬违规、原顺序保持、动作来源回显、预算保底、失败结果关联,以及公司单线回调不覆盖整批方案。 ## 在线道路查询预算 未传solverParameters.maxOnlineRequests时,按本次订单数自动确定:min(100000,max(5000,40×订单数))。这是候选道路查询余量,不会预先发满,也不进行全量两两查询;已经缓存的道路优先复用。显式传入0至100000仍优先采用,属于IT成本及性能参数。受理响应增加onlineRequestBudget和onlineRequestBudgetSource(order_count/explicit),便于调用方确认实际额度。 600秒求解时限、全服务QPS限制及月度预算继续独立生效,不因增加道路额度而放宽。增加额度能避免大型冷缓存任务过早撞到固定5000次上限,但不保证硬约束一定可行或预算内完成。缓存实时持久化,任务中断不丢失已成功查询的道路。 在线额度耗尽时正常返回已保存候选方案,onlineRequestBudgetExhausted=true、optimizationCompleted=false、terminationCode=ONLINE_REQUEST_BUDGET_EXHAUSTED,并提供具体提示、onlineRequestBudget和cacheMetrics。budgetExhausted仍表示求解时间预算,两个上限需区分;月度额度耗尽也使用道路预算终止码,具体范围见提示。单条线路是否可执行仍看valid/constraintViolations,整批是否排完看completion及unassignedOrders。 ## 通用 V2 主流程 各仓统一使用历史/地理走廊初排、历史跨区关系约束、Gurobi候选分配与单线路序优化。订单时间窗只增加路序和到店服务的可行性条件,不再触发另一套整仓初排流程。profile仅兼容旧请求的默认业务参数,不再决定算法分支或历史选择策略;同一组有效参数和数据采用同一主流程。 各仓历史均优先复用对应星期的模型,无对应星期模型时使用训练期汇总模型。人工核查仍保持订单和顺序,不进入重新排线。未增加必须由绝配前端提交的算法模式字段。minimumAverageLoadRate在有无时间窗时均进入统一V2的全批容积装载目标。GET /profiles及正常结果solverDiagnostics可查看solverFramework=v2_corridor_strong_admin。 此统一流程尚在本地复测,线上部署版本以/health为准;本次两地离线质量试验使用1200秒预算及现有完整模型授权,不代表线上受限模型规模、600秒冷缓存条件下的表现。 ## 11 部署配置与道路缓存 服务首页提供运行状态、并行数及默认求解预算管理。GET/POST `/openapi/routing/runtime/config` 同样支持查询和修改这两个参数,修改后持久化。业务容量、时间窗、偏好值由绝配在排线请求中传入;`/profiles` 为只读参考,不是规则编辑接口。 高德Key使用环境变量 `AMAP_WEB_SERVICE_KEY`;回调完整地址使用 `ROUTING_CALLBACK_URL`,回调鉴权使用 `ROUTING_CALLBACK_APP_KEY`、`ROUTING_CALLBACK_APP_SECRET`。更换测试/正式地址不改代码,修改部署目录的 `.env` 后重建容器。API和页面已保存的并行数、默认预算优先于 `.env` 初始值;单次请求的 `solverParameters.maxSolveSeconds` 再覆盖默认预算。 道路缓存按配送日期的星期匹配:先在所需时段查对应星期,再查前一天星期,然后使用30天内最新记录;无匹配时以相同规则查全天数据,仍无可用记录才请求高德。`ROUTE_CACHE_TTL_DAYS` 与 `ROUTE_CACHE_PEAK_TTL_DAYS` 均默认30,按北京时间自然日判断;只要所需道路/时段有30天内可用记录,就不为更新周期再次请求高德。超过30天的记录不参与匹配。实时查询按真实查询日期和星期保存,不能伪装为计划配送日路况。实际观测不是未来交通预测。 排线和人工核查会自动保存有效的仓库、门店坐标及最新名称、地址,并保存道路距离、耗时和折线;地点缓存按调用方和仓库隔离。地点记录保留最新值,30天有效期只作用于道路观测。当前请求仍需提供有效坐标,系统不会用旧地址猜测缺失坐标。完整线路、顺序和指标保存在对应任务结果里。 缓存命中不占在线额度;高德HTTP失败尝试和重试均计入额度。单任务额度仍按 `min(100000,max(5000,40×订单数))`,可由 `solverParameters.maxOnlineRequests` 显式覆盖。共享缓存数据库内的全局QPS由 `ROUTE_CACHE_QPS` 控制,月度预算由 `ROUTE_CACHE_MONTHLY_API_BUDGET` 控制;它们不是高德账号自身的授权额度。GET `/openapi/routing/cache/stats` 可查询缓存记录数、地点数、月度请求统计和当前匹配策略。缓存按需求写入,不每月全量预热,不后台查询所有门店组合;持久化数据卷保留时,重启和升级不会丢失。