# 海外仓业务船节点标准化契约 > 状态:已确认 > > 确认日期:2026-08-21 > > 适用范围:国内确认出仓后的船司轨迹查询、目的港提柜及还柜事实闭环 ## 1. 目的与适用边界 本契约统一不同船司、港口和轨迹供应商的节点语义,防止后续逐家接入时因原始文案、字段数量或页面结构不同而改变平台业务含义。 - 每家供应商保留自己的原始节点编码和文案,再映射到本契约的标准节点。 - 供应商没有提供的标准节点保持缺失,不推测、不补造,也不要求所有船司返回相同数量的节点。 - 海外仓业务船节点从国内确认出仓开始,持续记录至已还柜 `EMPTY_RETURNED` 后停止。 - `PICKED_UP`、`EMPTY_RETURNED` 在本契约中只是货柜轨迹事实,不代表启用海外派送、海外仓出库或库存离仓业务。 - Firstleg 拥有查询任务、标准节点、异常事实和停止条件;Integration Service 只负责供应商访问、解析、标准化和错误归一化。 ## 2. 标准节点 标准节点使用固定递增序号。新增供应商只能映射这些节点;需要新增或修改节点时,必须先修改本契约并完成跨端同步。 | 序号 | 标准节点 | 中文定义 | 实际进度说明 | |---:|---|---|---| | 10 | `DEPARTED_WAREHOUSE` | 已离开国内仓库 | 国内出仓确认产生的首个实际节点 | | 20 | `AT_ORIGIN_PORT` | 重箱已进入起运港或码头 | 货柜已进入起运港区,但未必装船 | | 30 | `LOADED_ON_VESSEL` | 已装船 | 货柜已装上船舶,但船舶未必已经开航 | | 40 | `DEPARTED_ORIGIN_PORT` | 船舶已离开起运港 | 必须有实际离港事实,不得用装船或预计开航代替 | | 50 | `IN_TRANSIT` | 海运途中 | 船舶处于起运港至目的港之间的海运阶段 | | 60 | `ARRIVED_DESTINATION_PORT` | 船舶已到目的港 | 仅表示船舶到港,货柜未必已经卸船 | | 70 | `DISCHARGED_FROM_VESSEL` | 已卸船、未清关 | 货柜已从船上卸下,不代表已经清关或可提柜 | | 80 | `PICKUP_READY` | 已清关、可提柜 | 必须有清关完成、放行或码头明确可提柜事实 | | 90 | `PICKED_UP` | 已提柜 | 重箱已被提离码头,不得用“可提柜”代替 | | 100 | `EMPTY_RETURNED` | 已还柜 | 空箱已归还,船节点查询闭环完成并停止 | ### 2.1 旧节点调整 | 旧节点 | 处理方式 | |---|---| | `DEPARTED_WAREHOUSE` | 保持不变 | | `AT_ORIGIN_PORT` | 保持不变 | | `DEPARTED_ORIGIN_PORT` | 保持不变 | | `IN_TRANSIT` | 保持不变 | | `ARRIVED` | 改名为 `ARRIVED_DESTINATION_PORT`,消除“船到港”与“货柜卸船”的歧义 | | `PICKUP_READY` | 名称保持不变,语义收敛为“已清关、可提柜” | 新增节点为 `LOADED_ON_VESSEL`、`DISCHARGED_FROM_VESSEL`、`PICKED_UP`、`EMPTY_RETURNED`。当前项目尚未上线,不保留旧 `ARRIVED` 的兼容枚举或双写逻辑。 ## 3. 时间语义与状态推进 每条供应商节点事实可同时包含以下时间: | 字段 | 含义 | 是否推进实际节点 | |---|---|---| | `estimatedAt` | 供应商提供的预计发生时间,例如预离、预抵 | 否 | | `actualAt` | 供应商确认的实际发生时间 | 是 | | `observedAt` | 平台本次查询到该事实的时间或供应商声明的观测时间 | 否 | | `createdAt` | 标准化事实写入平台的时间 | 否 | 规则: 1. 只有存在 `actualAt` 的标准节点才能推进任务的最新实际节点。 2. 只有预计时间时可以保存预计事实,但不得把任务推进为已开航、已到港、已卸船或已清关。 3. 节点按固定序号单调推进;迟到的较早节点仍保存为历史事实,但不能回退最新实际节点。 4. 同一供应商事件使用稳定 `providerEventId` 幂等;重复查询不得重复插入。 5. 一个供应商事件只映射一个最精确的标准节点,不因中间节点缺失而自动补齐整条链路。 6. 柜级实际到港时间在预约日历、清关提柜列表与详情中统一读取 `ARRIVED_DESTINATION_PORT` 的最新非空实际事实:按 `observedAt` 降序,同观测时间按事实记录顺序降序选一条。后续观测可能修正为更早的实际日期,不得用历史 `actualAt` 最大值覆盖新观测;仅有预计时间的观测不覆盖已取得的实际到港时间。本规则不改变节点序号推进或已保存事实。 ## 4. 异常事实 扣留、异常和限制不属于单调业务节点,必须与标准节点分开保存和展示,不能覆盖最近正常节点。 | 异常编码 | 中文定义 | |---|---| | `CUSTOMS_HOLD` | 海关扣留或等待海关处理 | | `TERMINAL_HOLD` | 码头、堆场或港区扣留 | | `DOCUMENT_HOLD` | 单证、放货文件或提单问题导致扣留 | | `UNKNOWN_HOLD` | 无法进一步分类的其他扣留 | 异常事实至少保存供应商编码、原始状态、观测时间、地点、来源引用和原始响应哈希。异常解除时保存解除事实,不删除原异常记录。 ## 5. 供应商原始字段保存规则 标准化节点不得丢失供应商来源,节点事实至少包含: - `providerCode`:供应商或船司适配器编码。 - `providerEventId`:供应商事件 ID;无原生 ID 时使用稳定字段组合的 SHA-256。 - `providerNodeCode`:供应商原始节点编码或规范化原始文案。 - `standardNode`:本契约定义的标准节点。 - `observedAt`、`estimatedAt`、`actualAt`。 - `locationCode`、`locationName`,供应商缺失时允许为空。 - `sourceReference`:不含密钥和敏感参数的可审计来源引用。 - `rawPayloadHash`:原始响应的 SHA-256;业务表不保存完整网页或敏感原文。 ### 5.1 响应审计与事件幂等摘要 `rawPayloadHash` 仍是本次供应商整份原始响应的审计摘要,不作为同一 `providerEventId` 的不可变事件摘要。追加其他节点、调整其他节点的预计时间、改变网页排版或再次查询,均可能改变整份响应摘要;不得因此阻断未变化的旧事件及后续新事件。 Firstleg 对已持久化事实和本次标准化事实采用相同字段顺序计算 SHA-256 事件事实摘要并比较:节点包含柜次、供应商/事件标识、原始节点码、标准节点、预计/实际时间、地点编码/名称和来源引用;异常包含查询任务、供应商/事件标识、原始状态、异常编码、是否生效、地点编码/名称和来源引用。查询观测时间 `observedAt`、本地写入时间与整份响应 `rawPayloadHash` 不参与该稳定事件摘要。字段空值保持空值,不用查询时间或其他字段补齐。 同 ID 同事实重复查询不新增节点、异常、历史或业务事件,保留首次写入的观测时间和原始响应审计哈希;同 ID 变更上述任何事实字段必须冲突,即使供应商沿用了相同原始响应哈希也不能绕过。现有适配器的事件 ID 生成规则保持不变;预计时间变化按原规则形成独立预计事实,不推进实际节点。不得通过每轮换 ID、吞掉事实冲突或新增未知供应商状态/解除映射完成重放。 预计/实际时间参与摘要前统一按现有 `DATETIME(3)` 毫秒精度四舍五入(含跨秒进位),对输入与持久化事实使用同一规则;不采用时间差容忍窗口,仍拒绝不同毫秒事实。 供应商返回空字段时保持空值,不使用 `0`、当前时间、预计时间或其他字段伪造实际事实。 ## 6. 已确认供应商映射 ### 6.1 美森 MATSON 美森通过公开查询页动态取得网页会话和 `ttr`,再调用其网页查询代理。以下映射只有在响应出现对应原始状态时生效: | 美森原始状态 | 标准化结果 | |---|---| | `INGATE` | `AT_ORIGIN_PORT` | | `LOAD TO VESSEL` | `LOADED_ON_VESSEL` | | 明确的实际离港状态,例如 `VESSEL DEPARTURE` | `DEPARTED_ORIGIN_PORT` | | `IN TRANSIT` | `IN_TRANSIT` | | 明确的船舶到达目的港状态 | `ARRIVED_DESTINATION_PORT` | | `DISCHARGE FROM VESSEL` | `DISCHARGED_FROM_VESSEL` | | `AVAILABLE FOR PICKUP` | `PICKUP_READY` | | 明确的重箱出场状态 | `PICKED_UP` | | 明确的空箱归还状态 | `EMPTY_RETURNED` | | `CONTAINER ON HOLD - Customs Clearance` | `CUSTOMS_HOLD`,不推进标准节点 | `vesselETA` 仅可作为 `ARRIVED_DESTINATION_PORT.estimatedAt`,不得生成实际到港节点。未在真实响应样本中确认的美森文案不得仅凭名称猜测映射。 ### 6.2 合德 HEDE 合德返回 UTF-8 HTML。适配器从“提单箱时间线信息”主表精确匹配目标柜号,当前不对可能包含多个柜子的提单查询自动选柜。 | 合德字段 | 标准化结果 | |---|---| | 预离 | `DEPARTED_ORIGIN_PORT.estimatedAt`,不推进实际节点 | | 预抵 | `ARRIVED_DESTINATION_PORT.estimatedAt`,不推进实际节点 | | 空箱出场 | 不进入当前标准节点 | | 重箱进场 | `AT_ORIGIN_PORT` | | 装船 | `LOADED_ON_VESSEL` | | 卸船 | `DISCHARGED_FROM_VESSEL` | | 重箱出场 | `PICKED_UP` | | 空箱还回 | `EMPTY_RETURNED` | 合德当前没有明确的实际离港、海运途中、船舶到达或清关完成字段,因此不得生成 `DEPARTED_ORIGIN_PORT`、`IN_TRANSIT`、`ARRIVED_DESTINATION_PORT` 或 `PICKUP_READY` 的实际节点。 ## 7. 查询停止规则 1. 自动查询在写入实际 `EMPTY_RETURNED` 后幂等停止,停止原因固定记录为“已还柜”。 2. 供应商不提供还柜节点时,只能由可信内部业务事件或具备权限的人工操作确认还柜并停止。 3. `ARRIVED_DESTINATION_PORT`、`DISCHARGED_FROM_VESSEL`、`PICKUP_READY` 和 `PICKED_UP` 均不是默认停止条件。 4. 停止后的迟到供应商回调不得重新激活任务;如需恢复必须由独立、可审计的恢复契约处理。 5. 长期缺少节点、供应商失败或网页结构变化只进入失败/人工状态,不得伪造 `EMPTY_RETURNED`。 ## 8. 新增船司接入门禁 每新增一家船司或轨迹供应商,必须逐项完成: 1. 保存至少一份脱敏真实成功样本,以及空结果、无效号码、限流和结构异常样本。 2. 列出所有实际出现的原始字段、状态文案、时间格式、时区和地点字段。 3. 建立“原始状态 → 标准节点或异常事实”映射表;无法确认的状态保持未映射。 4. 明确哪些时间是预计、实际或观测时间,不允许混用。 5. 验证同一业务事件重复查询的幂等键稳定。 6. 配置连接超时、请求超时、最小访问间隔、限流和重试归一化。 7. 对 HTML 页面验证字符集、目标行选择和页面结构变化失败关闭;不得按固定行号或整票第一条猜测目标柜。 8. 对 JSON 接口验证目标业务键与请求一致,防止返回错误柜号、提单或订舱结果。 9. 默认关闭真实外部调用,通过环境开关显式启用;页面和任务能力描述必须与部署开关一致。 10. 完成 Provider 单元测试、Firstleg 标准节点测试、OpenAPI/SQL 合同、真实环境 HTTP 及 Admin 展示验收。 ## 9. 跨端同步清单 本契约的节点、时间或异常编码变化时,必须同步核对: - Integration Service 的标准节点枚举、Provider DTO、各船司适配器和配置。 - Firstleg 的标准节点枚举、任务状态推进、节点事实、查询历史、停止条件、SQL 约束和 OpenAPI。 - Admin 的 TypeScript 类型、节点中文文案、时间展示、状态标签和页面测试。 - HTTP 验收脚本中的节点枚举、详情和历史断言。 - 海外仓业务 README、相关 `AGENTS.md` 和新增供应商映射证据。 不得只修改某一家适配器而跳过统一模型,也不得为单一供应商新增含义重复的业务节点。