You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
chajia/docs/查价系统-PRD.md

85 KiB

查价系统 PRD v0.6

版本 日期 作者 说明
v0.1 2026-06-16 产品 背景与核心功能初稿(第 17 章)
v0.2 2026-06-16 产品负责人/架构师/技术PM 可开工版矛盾收敛、全章节补齐112 章)
v0.3 2026-06-16 QA负责人/SRE/架构师 补齐第 11 章异常韧性设计、第 12 章验收标准(可自动化)
v0.4 2026-06-16 产品/架构 对齐 Mothership 实测:四档报价、地址联想、托盘数、单位换算、嵌入宿主、官方尺寸限制
v0.5 2026-06-17 产品/架构 明确 Mothership 公开报价页匿名查价RPA 默认真实抓取Mock 仅 CI/单测
v0.6 2026-06-17 产品/架构/SRE RPA 入口 URL 多候选探测、Selector 录制规范、匿名 storageState、入口/抓取失败分类
v0.7 2026-07-13 产品/架构 新增 Flock Freight 报价源模块§14扩展点与硬限对齐官网实测

本版业务权威为 v0.7(在 v0.6 基线上增量。Mothership 相关规则无变更处仍以 v0.6 为准。技术栈与实现细节见 查价系统-技术设计.md;界面与交互见 查价系统-UI设计.mdRPA 解阻塞参考见 参考-Mothership-RPA-开源借鉴与解阻塞.md。异常处理见第 11 章;验收与上线门禁见第 12 章Flock Freight 见第 14 章。


工程收敛决策(全局生效)

编号 决策 唯一规则
GD-1 RPA 模型 一次询价 = 1 次 RPA单次会话内抓取 4 档报价standard/guaranteed × lowest/fastest严禁多次 RPA
GD-2 缓存模型 三层固定优先级L1 request_id(24h 幂等) > L2 cargo_hash(3min 热缓存) > L3 stale(30min仅 RPA 失败降级)
GD-3 service_level / rate_option 均不进入 cache key缓存仅按货物维度四档一次返回
GD-4 成功率指标 拆分三指标API success rate含缓存/ RPA success rate真实外呼/ realtime rate真实 RPA 占比);不使用单一 99.5%
GD-5 询价交互模型 异步:POST /quotes 立即返回 quote_id + 状态;客户端轮询 GET /quotes/{quote_id}
GD-6 加价基准 仅对运费 raw_freight 按百分比加价;上限 30%;未配置客户按 0%
GD-7 币种 本期仅 USD多币种字段保留但不启用
GD-8 货物类型 本期枚举固定为 9 项(见 §4.3.6);不做「实施时再抓」
GD-9 地址校验 Mothership 报价页为地址联想下拉,禁止仅填街道后 Enter须携带已选联想项place_id 或等效选中快照)方可询价
GD-15 RPA 报价入口 MOTHERSHIP_QUOTE_URLS 须为 headed 录制验证 的匿名公开报价 URL 列表(逗号分隔、按序探测);禁止默认或示例使用 dashboard.mothership.comwww.mothership.com/quote
GD-11 嵌入形态 查价为宿主系统内嵌能力,不自建客户/运营登录;本期仅保留管理端(预警/RPA/开发进度)
GD-12 货物计量 使用托盘数pallet_count),对齐 Mothership freight[].quantitytype=Pallet禁止「件数」
GD-13 单位换算 前端/API 可接收 kg/cm落库与 RPA 前强制换算为 lb/inGD-10 精度不变)
GD-14 尺寸重量上限 仅以 Mothership Help Center 公布值为硬校验;禁止自定义宽范围
GD-10 金额精度 一律 DECIMAL(12,2),四舍五入 ROUND_HALF_UP,保留 2 位
GD-16 Flock Freight 第二报价源Phase 扩展);入口 https://app.flockfreight.com/get-a-quote;硬限与结果档位见 §14;校验口径与 Mothership 分源独立,禁止混用上限

第 1 章 背景与目标

1.1 目的

为客户提供卡派LTL自助查价能力以美美与共作为报价中台通过 RPA 从 Mothership 获取报价,叠加运营加价后返回客户。本章定义业务动机、目标与边界。

1.2 现状与痛点

痛点 表现 影响
人工查价慢 客服手工登录 Mothership 录入并回传 单次 310 分钟,高峰排队
报价不一致 操作习惯差异、in/cm 换算错误 预算与实付偏差,客诉
难规模化 无法并发响应 限制业务增长

1.3 建设目标与衡量

目标 定义 衡量指标(见 §10、§12
提速 客户自助,无需人工 查价响应 P95 < 30s
降本 减少人工查价 RPA 调用次数下降(缓存命中率)
减少偏差 RPA 与人工一致 RPA vs 人工偏差 ≤ 5%

1.4 系统定位

宿主业务系统(美美与共等)
   ├─ 内嵌查价组件 / 调用查价 API宿主鉴权透传 customer_id
   └─ 运营加价由宿主配置或 API 写入(本期无独立运营端)
      │ HTTP
美美与共 报价中台(本期建设主体)
   ├─ 查价编排服务 Quote Orchestrator
   ├─ 缓存层 RedisL1/L2/L3
   ├─ RPA 队列 BullMQ + Worker PoolPlaywright
   ├─ 加价引擎 Pricing Engine
   ├─ 预警模块 Alert
   ├─ 管理端(仅管理员:预警/RPA/开发进度)
   └─ 持久层 MySQLquote_record / idempotency / markup_config / alert_log / quote_cache_meta
      │ RPA
Mothership外部报价源仅 RPA
  • 美美与共:报价中台,负责编排/加价/缓存/预警/落库;查价 UI 嵌入宿主,不单独面向终端客户开户
  • CC本期不接Phase 2 通过 API 对接(接口与字段已预留)。
  • Mothership唯一报价源仅 RPA报价页地址须从联想列表点选结果含 standard/guaranteed 各 最低价格 / 最快递送 选项。

1.5 MVP 边界

本期包含:宿主内嵌查价 API/组件、Mothership RPA四档报价、运营加价宿主/API 配置)、询价记录、三层缓存、缓存降级、偏差预警、管理端(预警中心 + RPA/开发进度,无独立客户/运营登录页)。

本期不包含CC 对接、Mothership API、嵌入私仓预报、Priority1、Flock Freight 实现(口径见 §14本期仅文档、下单、对账、多币种、独立客户查价 H5 账号体系独立运营管理端登录

交付约束3 周;团队 = 1 技术 + Cursor + 1 产品经理。


第 2 章 用户与场景

2.1 角色与鉴权

角色 来源 鉴权 权限
宿主系统 业务系统内嵌查价或服务端调用 宿主签发 Service Token / API Key(或宿主 JWT 经网关换票);请求体携带 customer_id 代终端用户提交询价、查历史、配置加价(若宿主开放)
管理员 报价中台管理端 JWT + RBAC alert:read rpa:operate 查看/处理预警、RPA 状态、开发进度指标;本期唯一需登录的角色

鉴权规则:

  • 查价类 APIAuthorization: Bearer <宿主 token>customer_id 由宿主传入,中台校验 token 与租户绑定关系,越权 403。
  • 管理端 API管理员 JWT与宿主 token 隔离。
  • 本期不提供 customer_demo / operator_demo 等独立业务账号Demo/联调使用宿主模拟 customer_id + 管理端 admin 账号。

2.2 场景一:宿主内嵌查价(主路径,异步模型)

前置:用户在宿主业务系统内操作;宿主前端或 BFF 持有 Service Token。

  1. 宿主页面加载内嵌查价组件(或跳转宿主路由)。
  2. 填写提货/派送地址(须从地址联想列表点选,见 §4.2.1)、单托重量与尺寸(可用 kg/cm 录入)、托盘数、货物类型。
  3. 宿主调用 POST /api/quotes(前端生成 request_id)。
  4. 服务端换算为 lb/in → 校验 Mothership 官方范围 → 返回 quote_id + processing(或缓存命中 done)。
  5. 轮询 GET /api/quotes/{quote_id}2s最长 30s
  6. status=done:展示 4 档报价standard/guaranteed × lowest/fastest默认 standard + lowest3 分钟倒计时。
  7. 3 分钟内同货物重复查价:命中 L2不触发 RPA。
  8. 倒计时归零:宿主弹窗确认后以新 request_id 重询。

示例输入LA 已选联想地址 → Dallas 已选联想地址,单托 500 lb48×40×48 in2 托盘general_freight。

示例输出(节选 2 档,实际返回 4 档):

项目 standard · lowest guaranteed · fastest
rate_option 最低价格 最快递送
运费 raw_freight $320.00 $410.00
客户价 final_total加价 10% $352.00 $451.00
时效 预计 57 天 保障 3 天内

2.3 场景二:宿主配置加价

  1. 宿主运营后台或 PUT /api/markup-configs/{customer_id}Service Token + pricing:markup:write)。
  2. 设置运费加价比例 030%(步长 0.1)。
  3. 仅影响该 customer_id 之后的新询价;历史不重算。
  4. 报价中台管理端不提供运营加价 UI(仅管理员查看预警/RPA

2.4 场景三:管理员处理预警

  1. 「预警中心」列表展示预警(站内)。
  2. 查看详情:quote_id、类型、原始价、对比价、偏差%、时间。
  3. 操作:标记已处理 / 暂停 RPA Worker / 触发人工兜底录入。

2.5 场景四RPA 失败降级(异常路径)

失败分类v0.6

类型 典型原因 错误码/告警 是否走 L3
入口错误 URL 错误、404、登录页且无凭据、preCheck 无表单 STRUCT_CHANGE / QUOTE_ENTRY_UNAVAILABLE 是(若有 stale不消耗 RPA 重试配额
抓取错误 超时、验证码、DOM 变更 mid-flow、四档不全、价格 ≤0 RPA_CAPTCHA / RPA_DATA_INVALID / QUOTE_TIMEOUT
地址未锁定 RPA 补选联想失败 ADDRESS_SUGGESTION_NOT_FOUND 宿主路径 400Worker 路径走 L3

主路径

  1. RPA 超时 / 验证码 / 封禁 / 入口错误 / 抓取错误。
  2. 按 GD-2 读取 L3 stale≤30min
  3. 命中:返回该报价,is_realtime=false,写 STALE_FALLBACK 预警。
  4. 未命中:status=failed,错误 QUOTE_UNAVAILABLE,写 RPA_FAILED 预警,提示宿主稍后重试。

第 3 章 范围与扩展点

3.1 功能范围矩阵

模块 本期 说明
独立查价页 宿主内嵌组件/API,非独立 C 端账号
询价输入校验 §4含地址联想、托盘数、Mothership 官方尺寸)
四档输出(异步) §5、GD-1、GD-5
运营加价(按客户、运费% §6由宿主/API 配置
三层缓存 + 生命周期 §7、GD-2
询价历史记录 §9预留 CC 字段
Mothership RPA四档 GD-1
缓存降级 §7.6
偏差/失败预警(站内) §7.5
管理端预警 + RPA/开发进度 §2.4无客户/运营登录
Mothership API 不使用
CC 对接 Phase 2
嵌入私仓预报 Phase 3
Priority1 Phase 4保留 QuoteProvider
Flock Freight RPA §14;默认关,FLOCK_RPA_ENABLED / Mock 启用
多币种 / 下单 / 对账 后续

3.2 扩展点

扩展点 预留方式
Priority1 QuoteProvider 接口,新增实现类
Flock Freight QuoteProviderFlockFreightRPAProvider;独立校验常量 FLOCK_LIMITS§14.3);独立队列/env技术设计 §7.6
CC 对接 询价 API 契约稳定;quote_record 预留 cc_customer_idforecast_id
嵌入预报 查价为独立组件,request_id 可关联预报单

第 4 章 询价输入

4.1 功能点

ID 描述
F4.1 提货/派送地址录入(须完成联想点选,见 §4.2.1
F4.2 单托重量录入lb/kg提交前换算 lb
F4.3 单托尺寸录入in/cm提交前换算 in
F4.4 托盘数录入(pallet_count,非件数)
F4.5 货物类型选择(固定枚举)
F4.6 默认展示档位:service_level=standard + rate_option=lowest(仅影响 UI 默认 Tab
F4.7 单位换算API 可收 kg/cm服务端与 RPA 统一 lb/in
F4.8 输入校验Mothership 官方上限§4.2.8

4.2 字段规格

4.2.1 地址pickup_address / delivery_address

Mothership 报价页行为(实测):地址输入框为联想搜索,输入 1234 Warehouse Blvd 后按 Enter 不能确认地址;须在输入框上方出现的候选列表中点击一项,表单才锁定为有效地址。

字段结构

{
  "street": "1234 Warehouse Blvd",
  "city": "Los Angeles",
  "state": "CA",
  "zip": "90001",
  "place_id": "ChIJxxxx",
  "formatted_address": "1234 Warehouse Blvd, Los Angeles, CA 90001, USA",
  "selected_from_suggestions": true
}
字段 必填 说明
street, city, state, zip state 2 字母美国州码;zip 正则 ^\d{5}(-\d{4})?$
place_id 是(宿主/API 路径) Google Place ID 或 Mothership 联想项 ID对齐 Mothership APIplaceId
formatted_address 用户点选后的完整展示串
selected_from_suggestions 必须为 truefalse 拒绝询价

解决方案(二选一,宿主择一实现)

方案 说明 适用
A. 宿主侧地址组件 宿主复用与 Mothership 同源的 Places 联想(或自研等价组件),用户点选后把 place_id + 结构化地址传给中台 推荐;嵌入形态默认
B. RPA 侧补选 入参仅有街道文本时Worker 输入触发联想 → waitFor 下拉 → 点击首条/匹配项 → 校验表单已锁定 仅当宿主无法提供 place_id 的过渡方案;成功率低于 A

校验失败:400 VALIDATION_FAILED,提示「请从地址列表中选择有效地址」。

4.2.2 重量weight— 单托重量

  • { value: number, unit: "lb"|"kg" },必填,默认 lb
  • 表示每一托盘的重量(对齐 Mothership freight[].weight
  • 换算:lb = kg × 2.20462ROUND_HALF_UP 保留 2 位
  • 范围:见 §4.2.8禁止沿用 150000 lb 等自定义宽范围)

4.2.3 尺寸dimensions— 单托尺寸

  • { length, width, height, unit: "in"|"cm" },必填,默认 in
  • 表示每一托盘外廓尺寸
  • 换算:in = cm ÷ 2.54,保留 2 位
  • 范围:见 §4.2.8

4.2.4 托盘数pallet_count

  • 正整数,必填
  • 语义:托盘数量;映射 Mothership freight[].quantitytype: "Pallet"
  • 禁止使用「件数」「箱数」等非托盘字段
  • 范围:见 §4.2.8

4.2.5 货物类型cargo_type— 固定枚举GD-8

枚举值 显示名
general_freight 普通货物
machinery 机械设备
furniture 家具
electronics 电子产品
building_materials 建材
auto_parts 汽车配件
food_nonperishable 食品(非生鲜)
apparel 服装
other 其他

默认 general_freight

4.2.6 服务等级service_level与报价选项rate_option

字段 枚举 默认 说明
service_level standard | guaranteed standard Mothership 结果页两大服务类型
rate_option lowest | fastest lowest 各 service_level 下的子选项:最低价格 / 最快递送
  • 仅决定结果页默认 Tab一次 RPA 仍返回 4 档GD-1
  • service_levelrate_option 均不进入 cache keyGD-3

4.2.7 单位偏好display_unit

  • "imperial"|"metric",非必填,默认 imperial
  • 仅影响宿主 UI 展示POST /quotes 时服务端按 weight.unit / dimensions.unit 换算,RPA 仅使用 lb/in

4.2.8 Mothership 官方货物限制硬校验GD-14

来源:Will my freight fit? · Getting Ratesfreight[].type=Pallet

本期查价货物详情硬校验口径National LTL单次最多 25 托):

字段 换算后规则lb/in 说明
pallet_count 整数 125 单次询价托盘数上限
length 1999 in 单托长度上限 999 in
width 199 in 单托宽度上限 99 in
height 199 in 单托高度上限 99 in
weight单托 0.019,999 lb 单托重量上限 9,999 lb
整票总重 weight_lb × pallet_count249,975 lb 9,999 × 25 硬顶

非本期默认路径(仅文档备案,不扩校验除非产品切换车型):

车型 托盘上限 总重上限
26-foot box truck同城 12 9,50010,000 lb
53-foot dry van 28 44,000 lb

超限:400 VALIDATION_FAILED,提示「{字段}超出 Mothership 允许范围」。

4.3 请求体POST /quotes

{
  "request_id": "550e8400-e29b-41d4-a716-446655440000",
  "customer_id": "CUST_001",
  "pickup_address": {
    "street": "1234 Warehouse Blvd",
    "city": "Los Angeles",
    "state": "CA",
    "zip": "90001",
    "place_id": "ChIJxxxx_pickup",
    "formatted_address": "1234 Warehouse Blvd, Los Angeles, CA 90001, USA",
    "selected_from_suggestions": true
  },
  "delivery_address": {
    "street": "5678 Distribution Dr",
    "city": "Dallas",
    "state": "TX",
    "zip": "75201",
    "place_id": "ChIJxxxx_delivery",
    "formatted_address": "5678 Distribution Dr, Dallas, TX 75201, USA",
    "selected_from_suggestions": true
  },
  "weight": { "value": 500, "unit": "lb" },
  "dimensions": { "length": 48, "width": 40, "height": 48, "unit": "in" },
  "pallet_count": 2,
  "cargo_type": "general_freight",
  "service_level": "standard",
  "rate_option": "lowest",
  "display_unit": "imperial"
}

4.4 业务规则

ID 规则
BR-4.1 入参换算为 in/lb 后再生成 cargo_hash 与调用 RPA
BR-4.2 request_id 必填、UUID v4服务端按 GD-2 L1 做 24h 幂等
BR-4.3 地址须 selected_from_suggestions=true 且含 place_id(方案 A或 RPA 补选成功(方案 B
BR-4.4 cargo_hash = MD5(pickup+delivery+weight_lb+dims_in+pallet_count+cargo_type),不含 service_levelrate_option
BR-4.5 pallet_count 为托盘数;禁止 quantity 件数字段
BR-4.6 重量/尺寸为单托值;整票总重 = weight_lb × pallet_count

4.5 异常

异常 触发 行为 提示
E4.1 必填缺失 400 拒绝 「请填写{字段}」
E4.2 超范围 400 拒绝 「{字段}超出允许范围」
E4.3 地址未点选联想项 / 缺 place_id 400 拒绝 「请从地址列表中选择有效地址」
E4.4 单位非法 400 拒绝 「请选择有效的单位」
E4.5 request_id 非 UUID 400 拒绝 「请求标识无效」
E4.6 超 Mothership 官方范围§4.2.8 400 拒绝 「{字段}超出 Mothership 允许范围」
E4.7 使用 quantity/件数字段 400 拒绝 「请使用托盘数 pallet_count」

第 5 章 询价输出

5.1 功能点

ID 描述
F5.1 一次返回 4 档报价standard/guaranteed × lowest/fastestGD-1
F5.2 默认展示 standard + lowest
F5.3 价格明细(运费/附加费/加价/总价)
F5.4 加价后客户价
F5.5 3 分钟有效期与倒计时
F5.6 来源/实时性/可信度标识

5.2 查询响应GET /quotes/{quote_id}

{
  "quote_id": "QTE_20260616_0001",
  "request_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "done",
  "valid_until": "2026-06-16T14:32:00Z",
  "source_type": "rpa",
  "is_realtime": true,
  "confidence_score": 0.95,
  "currency": "USD",
  "quotes": [
    {
      "service_level": "standard",
      "rate_option": "lowest",
      "carrier": "Mothership",
      "transit_days": "5-7",
      "transit_description": "最低价格 · 预计 5-7 天送达",
      "raw_freight": 320.00,
      "surcharges": 0.00,
      "raw_total": 320.00,
      "markup_percent": 10.0,
      "markup_amount": 32.00,
      "final_total": 352.00,
      "breakdown": [
        { "item": "运费", "amount": 320.00 },
        { "item": "附加费", "amount": 0.00 },
        { "item": "运营加价(10.0%)", "amount": 32.00 }
      ]
    },
    {
      "service_level": "standard",
      "rate_option": "fastest",
      "carrier": "Mothership",
      "transit_days": "3-4",
      "transit_description": "最快递送 · 预计 3-4 天送达",
      "raw_freight": 365.00,
      "surcharges": 0.00,
      "raw_total": 365.00,
      "markup_percent": 10.0,
      "markup_amount": 36.50,
      "final_total": 401.50,
      "breakdown": [
        { "item": "运费", "amount": 365.00 },
        { "item": "附加费", "amount": 0.00 },
        { "item": "运营加价(10.0%)", "amount": 36.50 }
      ]
    },
    {
      "service_level": "guaranteed",
      "rate_option": "lowest",
      "carrier": "Mothership",
      "transit_days": "4-5",
      "transit_description": "保证送达 · 最低价格",
      "raw_freight": 380.00,
      "surcharges": 0.00,
      "raw_total": 380.00,
      "markup_percent": 10.0,
      "markup_amount": 38.00,
      "final_total": 418.00,
      "breakdown": [
        { "item": "运费", "amount": 380.00 },
        { "item": "附加费", "amount": 0.00 },
        { "item": "运营加价(10.0%)", "amount": 38.00 }
      ]
    },
    {
      "service_level": "guaranteed",
      "rate_option": "fastest",
      "carrier": "Mothership",
      "transit_days": "3",
      "transit_description": "保证送达 · 最快递送",
      "raw_freight": 410.00,
      "surcharges": 0.00,
      "raw_total": 410.00,
      "markup_percent": 10.0,
      "markup_amount": 41.00,
      "final_total": 451.00,
      "breakdown": [
        { "item": "运费", "amount": 410.00 },
        { "item": "附加费", "amount": 0.00 },
        { "item": "运营加价(10.0%)", "amount": 41.00 }
      ]
    }
  ]
}

5.3 字段说明

字段 类型 说明
service_level enum standard / guaranteed
rate_option enum lowest(最低价格)/ fastest(最快递送)
status enum processing / done / failed / expired
valid_until ISO8601 报价生成时间 + 3 分钟
source_type enum rpa / cacheL2 命中)/ staleL3 降级)
is_realtime bool rpa/cache=truestale=false
confidence_score decimal rpa/cache=0.95stale=0.70
currency string 固定 USD
markup_percent decimal 应用比例(基于运费)
final_total decimal raw_total + raw_freight × markup% ROUND_HALF_UP

5.4 展示规则

ID 规则
BR-5.1 默认选中 standard + lowest可切换 guaranteed / fastest
BR-5.2 倒计时剩余 <30s 高亮
BR-5.3 is_realtime=false 显示角标「非实时报价,仅供参考」
BR-5.4 仅展示 USD
BR-5.5 quotes 数组长度固定为 4(四档齐全才 done

5.5 异常输出

场景 HTTP 响应
处理中 200 status=processing
完成 200 status=done + quotes
RPA 失败有 stale 200 status=done + source_type=stale + is_realtime=false
RPA 失败无 stale 200 status=failed + error_code=QUOTE_UNAVAILABLE
超时(>30s 仍处理) 200 status=failed + error_code=QUOTE_TIMEOUT
quote_id 不存在 404 error_code=QUOTE_NOT_FOUND
越权访问 403 error_code=FORBIDDEN

5.6 错误码全集

error_code 含义 来源
VALIDATION_FAILED 入参校验失败 输入
QUOTE_UNAVAILABLE RPA 失败且无 stale RPA
QUOTE_TIMEOUT 30s 内未完成 RPA
QUOTE_NOT_FOUND quote_id 不存在 查询
FORBIDDEN 越权/无权限 鉴权
RATE_LIMITED 触发限流 网关
CARRIER_NO_CAPACITY Mothership 返回无运力 业务
ADDRESS_NOT_SUPPORTED 邮编不支持 业务
ADDRESS_SUGGESTION_NOT_FOUND RPA 地址联想点选失败 RPA / 输入
QUOTE_ENTRY_UNAVAILABLE 所有报价入口 URL preCheck 失败 RPA
STRUCT_CHANGE 页面结构变更 / 404 / 登录页且无凭据 RPA
INTERNAL_ERROR 系统异常 系统

第 6 章 加价规则

6.1 功能点

ID 描述
F6.1 按客户独立配置
F6.2 仅按运费百分比加价GD-6
F6.3 上限 30%
F6.4 未配置默认 0%
F6.5 配置审计(操作人、时间)

6.2 配置项

字段 类型 必填 说明
customer_id string 客户唯一标识
markup_percent decimal(5,1) 030.0,步长 0.1
operator_id string 操作人
remark string 备注

6.3 计算(唯一公式)

markup_amount = ROUND_HALF_UP(raw_freight × markup_percent / 100, 2)
final_total   = ROUND_HALF_UP(raw_total + markup_amount, 2)

示例raw_freight=380.00surcharges=0raw_total=380.00markup=10.0% → markup_amount=38.00final_total=418.00。 含附加费示例raw_freight=320surcharges=15raw_total=335markup=10% → markup_amount=32.00final_total=367.00。

6.4 业务规则

ID 规则
BR-6.1 pricing:markup:write 可写
BR-6.2 markup_percent ∈ [0, 30.0]
BR-6.3 未配置客户按 0%
BR-6.4 配置变更仅影响新询价,历史不重算

6.5 异常

异常 处理
比例 >30 400「加价比例不能超过 30%」
客户不存在 400「客户不存在」
无权限 403 FORBIDDEN

第 7 章 报价生命周期与缓存

7.1 状态机

[创建 processing]
   → 命中 L1 → [done](原响应)
   → 命中 L2 → [done]source=cache
   → 未命中 → 入 RPA 队列
        → RPA 成功 → 写 L1/L2落库 → [done source=rpa]
        → RPA 失败 → 查 L3 stale
              → 命中 → [done source=stale, is_realtime=false] + 预警
              → 未命中 → [failed QUOTE_UNAVAILABLE] + 预警
   → 30s 未完成 → [failed QUOTE_TIMEOUT]
[done] --3 分钟到期--> [expired](客户端提示重询)

7.2 三层缓存GD-2唯一规则

Key 存储 TTL 用途 优先级
L1 idem:{request_id} 完整响应快照 24h 幂等:相同请求返同一结果 1最高
L2 quote:{cargo_hash} 四档报价(未加价原始结果) 3min 去重 RPA 2
L3 stale:{cargo_hash} 同 L2 内容 30min 仅 RPA 失败时降级只读 3最低

写入规则RPA 成功后同时写 L1带 request_id、L2、L3。命中 L2 后仍按当前请求的客户加价比例重算 final_total 并写 L1。

读取顺序固定L1 → L2 →RPA→ 失败时 L3。L3 永不用于正常路径,只在 RPA 失败时读取。

cargo_hash 定义见 BR-4.4不含 service_level、不含 customer_id(加价在读出后按客户重算)。

7.3 幂等规则

条件 行为
相同 request_id 24h 内再次 POST /quotes 返回 L1 快照对应的同一 quote_id,不触发新 RPA
L1 过期后相同 request_id 视为新请求,重新走流程

7.4 有效期与复用

报价有效期 3 分钟(valid_until
3min 内同 cargo 再询 命中 L2无新 RPA
过期 客户端弹「是否重新询价」,确认后新 request_id

7.5 偏差预警

条件 基准 动作
本次 RPA raw_total 与 L3 中上一次同 cargo 报价偏差 ≥ 5% 上一次报价 alert_log 类型 PRICE_DEVIATION,站内通知管理员
首次询价(无历史基准) 不预警

偏差计算:abs(new - prev) / prev ≥ 0.05;恰好等于 5% 触发()。

7.6 降级

RPA 失败按 §2.5 / §7.1 执行:优先 L3 staleSTALE_FALLBACK;无 stale 则 failedRPA_FAILED

7.7 预警类型

type 触发 通知
PRICE_DEVIATION 偏差 ≥5% 管理员站内
STALE_FALLBACK 使用 L3 降级 管理员站内
RPA_FAILED RPA 失败无 stale 管理员站内
RPA_CAPTCHA 遇验证码 管理员站内 + 暂停该 Worker

第 8 章 技术架构(可部署级)

8.1 架构组件

[客户端/管理端]
     │ REST(JWT)
[API 网关层]  限流(令牌桶) + 鉴权 + 路由
     │
[Quote Orchestrator 查价编排服务]
   ├─ ValidationModule      入参校验 + 单位换算
   ├─ CacheModule           L1/L2/L3 读写(Redis)
   ├─ IdempotencyModule     request_id 幂等
   ├─ PricingEngine         按客户加价
   ├─ AlertModule           预警写入
   └─ QuoteProvider(接口)
         └─ MothershipRPAProvider本期唯一实现
     │ 入队
[BullMQ 队列(Redis)] ── [RPA Worker Pool(Playwright)]
     │                         │ 1 Worker = 1 IP + 1 指纹 + 1 Session
[MySQL]                    [Mothership]
 quote_record / idempotency_record
 markup_config / alert_log / quote_cache_meta

8.2 RPA 执行流程GD-1单次会话四档报价

1. Worker 从 BullMQ 取 job(quote_id, cargo 标准化输入)
2. QuotePageAdapter.resolveQuoteEntry()
   - 读取 env MOTHERSHIP_QUOTE_URLS逗号分隔按序
   - 对每个 URLgoto → preCheck非 login/captcha、报价表单 selector 可见)
   - 首个通过者写入 activeUrl全部失败 → QUOTE_ENTRY_UNAVAILABLE / STRUCT_CHANGE
   - **禁止**内置默认 URL禁止 `dashboard.mothership.com`、`www.mothership.com/quote`(已 404 或跳转登录)
3. 加载 RPA_STORAGE_STATE_PATH若存在匿名 Cookie成功路径写回 storageState
4. 若页面为 login 且已配置 MOTHERSHIP_EMAIL/PASSWORD → 重登 1 次;若 login 且无凭据 → STRUCT_CHANGE**非** SESSION_EXPIRED
5. preCheck404 文案 / 登录页且无凭据 → STRUCT_CHANGE关键表单元素不可见 → STRUCT_CHANGE
6. 地址:输入街道 → wait 联想下拉 → click 含 zip 项 → 断言下拉消失且地址已锁定GD-9失败 → ADDRESS_SUGGESTION_NOT_FOUND
7. 填表pickup/delivery、单托 weight(lb)/dims(in)、pallet_count、cargo_type
8. 提交一次;在结果页分别选择 standard/guaranteed各切换 lowest/fastest抓取 4 组价格与时效
9. 一致性校验:价格 >0、四档均存在
10. 标准化为 quotes[4],回写 Orchestrator
11. 异常:超时/验证码/结构变更/地址未锁定 → 抛分类错误 → 进入 fallback§8.4
重试:单 job 最多重试 1 次;连续失败 ≥3 触发熔断 10 分钟

8.2.1 RPA 环境变量GD-15

变量 必填 说明
MOTHERSHIP_QUOTE_URLS 逗号分隔匿名报价入口 URL按序探测
RPA_STORAGE_STATE_PATH Playwright storageState 文件路径(默认 .rpa/mothership-storage.json
RPA_SELECTOR_* 由 Playwright codegen 录制产出,见 §8.2.3
MOTHERSHIP_EMAIL / MOTHERSHIP_PASSWORD 仅当入口跳转 login 且需账号时使用
RPA_MOCK_MODE 仅 CI/单测为 true;验收与生产为 false
RPA_HEADLESS 默认 trueprobe 失败时可设 false 对比

8.2.2 入口 URL 禁止清单

URL 原因
https://dashboard.mothership.com/ 非公开报价页,跳转 login
https://www.mothership.com/quote 2026-06 实测 404

8.2.3 RPA Selector 维护规范

  1. 使用 Playwright codegenheaded 录制人工匿名查价路径LA→Dallas 标准样例)。
  2. 从录制脚本提取 goto URL → 写入 MOTHERSHIP_QUOTE_URLS
  3. 从录制脚本提取 selector → 写入 RPA_SELECTOR_*(见技术设计 §7.2.3 清单)。
  4. 每次 Mothership 页面改版后:重新录制 → 更新 env → 运行 scripts/probe-rpa.ts 连续 3 次 exit 0。
  5. 禁止手猜 selector 上线;禁止 LLM 运行时自愈替代生产热路径GD-1 确定性要求)。

验证命令

RPA_MOCK_MODE=false RPA_HEADLESS=false npx tsx scripts/probe-rpa.ts

8.3 Cache 流程

POST /quotes(request_id, cargo):
  if L1[request_id] 存在: 返回其 quote_id (done)
  cargo_hash = MD5(标准化货物)
  if L2[cargo_hash] 存在:
     按客户加价重算 → 落库 → 写 L1 → 返回 done(source=cache)
  else:
     创建 quote_record(processing) → 入队 RPA → 返回 quote_id(processing)

RPA 成功:
  写 L2(3min)、L3(30min)、L1(24h) → 加价 → 落库 done(source=rpa)
  与 L3 上次比对偏差≥5% 写 PRICE_DEVIATION

8.4 Fallback 流程

RPA 失败:
  if L3[cargo_hash] 存在:
     读出 → 按客户加价 → done(source=stale,is_realtime=false)
     写 STALE_FALLBACK 预警
  else:
     failed(QUOTE_UNAVAILABLE) + 写 RPA_FAILED 预警
验证码:
  停止该 Worker 重试 → 标记 IP/指纹不可用 → 写 RPA_CAPTCHA → 走上面 L3 分支

8.5 接口清单

方法 路径 权限 说明
POST /api/quotes 宿主 Service Token 提交询价,返回 quote_id
GET /api/quotes/{quote_id} 宿主(本人 customer_id 轮询结果
GET /api/quotes/history 宿主(本人 customer_id 分页历史(必传分页)
PUT /api/markup-configs/{customer_id} 宿主(pricing:markup:write 新增/更新加价
GET /api/alerts 管理员 预警列表(分页)
POST /api/alerts/{id}/resolve 管理员 标记处理

统一响应包络:{ "code": 0, "message": "ok", "data": {...} };错误 code 取 §5.6。


第 9 章 数据模型(可建表)

MySQL 8.0InnoDButf8mb4。金额 DECIMAL(12,2)。所有删除为逻辑删除(is_deleted。DDL 需人工确认后执行 migration。

9.1 quote_record 询价记录

CREATE TABLE quote_record (
  id              BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
  quote_id        VARCHAR(32)  NOT NULL,
  request_id      VARCHAR(36)  NOT NULL,
  customer_id     VARCHAR(32)  NOT NULL,
  cargo_hash      CHAR(32)     NOT NULL,
  status          VARCHAR(16)  NOT NULL DEFAULT 'processing',
  source_type     VARCHAR(16)  NULL,
  is_realtime     TINYINT(1)   NOT NULL DEFAULT 1,
  confidence_score DECIMAL(3,2) NULL,
  currency        VARCHAR(8)   NOT NULL DEFAULT 'USD',
  -- 输入快照
  pickup_json     JSON         NOT NULL,
  delivery_json   JSON         NOT NULL,
  weight_lb       DECIMAL(12,2) NOT NULL,
  dim_l_in        DECIMAL(8,2) NOT NULL,
  dim_w_in        DECIMAL(8,2) NOT NULL,
  dim_h_in        DECIMAL(8,2) NOT NULL,
  pallet_count    INT          NOT NULL COMMENT '托盘数,对齐 Mothership freight.quantity',
  cargo_type      VARCHAR(32)  NOT NULL,
  -- 四档结果快照
  quotes_json     JSON         NULL,
  markup_percent  DECIMAL(5,1) NOT NULL DEFAULT 0.0,
  valid_until     DATETIME     NULL,
  error_code      VARCHAR(32)  NULL,
  -- CC 预留
  cc_customer_id  VARCHAR(32)  NULL,
  forecast_id     VARCHAR(32)  NULL,
  is_deleted      TINYINT(1)   NOT NULL DEFAULT 0,
  created_at      DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at      DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uk_quote_id (quote_id),
  KEY idx_customer_created (customer_id, created_at),
  KEY idx_cargo_hash (cargo_hash),
  KEY idx_request (request_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

9.2 idempotency_record 幂等表

CREATE TABLE idempotency_record (
  id           BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
  request_id   VARCHAR(36) NOT NULL,
  customer_id  VARCHAR(32) NOT NULL,
  quote_id     VARCHAR(32) NOT NULL,
  expire_at    DATETIME    NOT NULL,
  created_at   DATETIME    NOT NULL DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uk_request_id (request_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

说明Redis L1 为热路径;本表为持久兜底,保证重启后 24h 内幂等仍成立。request_id 唯一键防并发重复写。

9.3 quote_cache_meta 缓存元数据(偏差比对/审计)

CREATE TABLE quote_cache_meta (
  id           BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
  cargo_hash   CHAR(32)     NOT NULL,
  raw_total_standard   DECIMAL(12,2) NOT NULL,
  raw_total_guaranteed DECIMAL(12,2) NOT NULL,
  last_quotes_json     JSON   NOT NULL,
  refreshed_at DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uk_cargo_hash (cargo_hash)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

用途:持久记录每个 cargo_hash 上一次原始报价,供 §7.5 偏差比对(即使 Redis L3 已过期)。

9.4 markup_config 加价配置

CREATE TABLE markup_config (
  id             BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
  customer_id    VARCHAR(32)  NOT NULL,
  markup_percent DECIMAL(5,1) NOT NULL DEFAULT 0.0,
  operator_id    VARCHAR(32)  NOT NULL,
  remark         VARCHAR(255) NULL,
  is_deleted     TINYINT(1)   NOT NULL DEFAULT 0,
  created_at     DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at     DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uk_customer (customer_id),
  CONSTRAINT chk_markup CHECK (markup_percent >= 0 AND markup_percent <= 30.0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

9.5 alert_log 预警日志

CREATE TABLE alert_log (
  id           BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
  alert_type   VARCHAR(32)  NOT NULL,
  quote_id     VARCHAR(32)  NULL,
  cargo_hash   CHAR(32)     NULL,
  detail_json  JSON         NULL,
  status       VARCHAR(16)  NOT NULL DEFAULT 'open',
  resolver_id  VARCHAR(32)  NULL,
  resolved_at  DATETIME     NULL,
  created_at   DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
  KEY idx_type_status (alert_type, status),
  KEY idx_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

9.6 字段映射(响应 ← 表)

响应字段 来源
quote_id / status / source_type / is_realtime quote_record
quotes[] quote_record.quotes_json
markup_percent markup_config查询时
valid_until quote_record.valid_until

第 10 章 非功能需求

10.1 性能

指标 目标
查价完成doneP95 < 30s含 RPA
L1/L2 命中响应 P95 < 300ms
GET 轮询响应 P95 < 200ms
加价配置/预警接口 P95 < 500ms

10.2 成功率指标GD-4拆分

指标 定义 目标
API success rate done(含 cache/stale) / 总 POST ≥ 99.5%
RPA success rate RPA done / 触发 RPA 的请求 ≥ 85%
realtime rate source∈{rpa,cache} / done ≥ 90%
缓存命中率 (L1+L2) 命中 / 总 POST ≥ 90%3min 内重复场景)
重询率 过期后重新询价 / done ≤ 5%
RPA vs 人工偏差 抽检 ≤ 5%

10.3 可用性

规则
RPA 熔断 连续失败 ≥3 次熔断 10 分钟,期间全部走 L3/失败
队列 BullMQ 持久化;服务重启 job 不丢
降级 RPA 不可用时 L3 stale 兜底,标记非实时
限流 单客户 60 次/分钟,超限 RATE_LIMITED

10.4 幂等性

场景 保证
重复 POST 相同 request_id L1 + idempotency_record 双保险,返回同一 quote_id
并发相同 request_id 唯一键冲突 → 读已存在记录返回
RPA 重试 同 quote_id 不重复落库(按 quote_id 更新)

10.5 一致性

规则
缓存与 DB RPA 成功后先落库再写缓存;缓存失效不影响 DB 真值
加价 命中 L2 时按当前客户配置实时重算,不缓存加价后价
金额 全链路 DECIMAL(12,2)ROUND_HALF_UP
偏差基准 quote_cache_meta 持久记录,避免 Redis 过期丢基准

10.6 安全

规则
鉴权 所有接口 JWT客户仅访问本人资源
RBAC 加价 pricing:markup:write;预警 alert:read
越权 403 FORBIDDEN
审计 加价变更、预警处理记录操作人与时间
日志 所有 async 必须 log error不得吞异常

第 11 章 异常场景与系统韧性设计

本章定义全链路失败点的唯一系统行为,与第 7 章生命周期、第 8 章 fallback 流程、第 12 章验收用例一一对应。每条均含:异常描述、系统行为、降级策略、告警、用户可见结果。

11.1 输入层异常处理

异常:必填字段为空或半空
系统行为400 VALIDATION_FAILED拒绝入队不生成 quote_id
是否允许降级:否
是否触发告警:否
用户看到的结果:「请填写{字段名}」

异常:重量/尺寸/托盘数超 Mothership 官方范围§4.2.8
系统行为400 VALIDATION_FAILED拒绝入队
是否允许降级:否
是否触发告警:否
用户看到的结果:「{字段}超出允许范围」

异常地址未从联想列表点选selected_from_suggestions=false 或缺 place_id
系统行为400 VALIDATION_FAILED拒绝入队
是否允许降级:否
是否触发告警:否
用户看到的结果:「请从地址列表中选择有效地址」

异常:地址邮编格式非法(非 5 位或 5+4
系统行为400 VALIDATION_FAILED拒绝入队
是否允许降级:否
是否触发告警:否
用户看到的结果:「地址无效,请检查州与邮编」

异常:单位错配(数值与 unit 语义不一致,如 500kg 标为 lb
系统行为:按 BR-4.1 强制按声明 unit 换算为 in/lb 后处理,不报错
是否允许降级:否
是否触发告警:否
用户看到的结果:正常进入处理流程

异常:地址含 SQL/脚本注入字符
系统行为400 VALIDATION_FAILED拒绝入队记录 audit log
是否允许降级:否
是否触发告警:是(安全类告警)
用户看到的结果:「地址无效,请检查」

异常:货物类型不在 9 项枚举内
系统行为400 VALIDATION_FAILED拒绝入队
是否允许降级:否
是否触发告警:否
用户看到的结果:「请选择有效的货物类型」

异常request_id 非 UUID v4
系统行为400 VALIDATION_FAILED拒绝入队
是否允许降级:否
是否触发告警:否
用户看到的结果:「请求标识无效」

11.2 行为与并发控制

异常:重复点击提交按钮
系统行为:前端 button disabled 3000ms后端 L1 命中直接返回已有 quote_id
是否允许降级L1
是否触发告警:否
用户看到的结果:直接展示已有报价或 processing 状态

异常:同一 request_id 并发 10 次提交
系统行为Redis L1 SETNX + DB 唯一键冲突,返回首次 quote_id其余请求丢弃
是否允许降级L1
是否触发告警:否
用户看到的结果:所有请求返回同一 quote_id

异常:页面刷新后重复提交
系统行为request_id 不变L1 24h 内幂等生效
是否允许降级L1
是否触发告警:否
用户看到的结果:返回原 quote_id

异常:多标签页同时对同一 request_id 操作
系统行为:按 request_id 隔离,各自独立轮询 GET
是否允许降级L1
是否触发告警:否
用户看到的结果:各标签页状态一致

异常:浏览器前进/后退导致状态错乱
系统行为:客户端以 quote_id 为准GET /quotes/{id} 实时拉取
是否允许降级L3
是否触发告警:否
用户看到的结果:显示当前真实状态

11.3 网络与通信容错

异常:客户端网络断开后恢复
系统行为:服务端继续 RPA客户端重连后轮询 GET 可取结果
是否允许降级L3
是否触发告警:否
用户看到的结果:轮询恢复后显示 done 或 failed

异常:请求已发送但响应丢失
系统行为:客户端指数退避重试 GET最多 15 次30s 内)
是否允许降级L1/L3
是否触发告警:否
用户看到的结果:最终获得状态或超时提示

异常CDN 缓存脏数据
系统行为:所有查价接口加 Cache-Control: no-storeAPI 网关不走 CDN
是否允许降级:否
是否触发告警:否
用户看到的结果:始终获取实时响应

异常:客户端弱网(>500ms RTT
系统行为:客户端超时 30s 后提示「查询超时,请重试」
是否允许降级L3
是否触发告警:否
用户看到的结果:提示重试或展示 stale 报价

11.4 后端 API 容错策略

异常API 处理超时(>30s 仍未 done
系统行为:定时任务标记 status=failed + QUOTE_TIMEOUT,不阻塞线程
是否允许降级L3
是否触发告警RPA 超时类)
用户看到的结果:「查询超时,请重试」

异常HTTP 5xx数据库/Redis 抖动)
系统行为:网关 retry 1 次,仍失败返回 503 + INTERNAL_ERROR
是否允许降级L3
是否触发告警:是
用户看到的结果:「服务暂时不可用,请稍后重试」

异常响应字段缺失quotes 数组为空)
系统行为:按 RPA_DATA_INVALID 处理,走 L3 fallback
是否允许降级L3
是否触发告警:是
用户看到的结果:展示 stale 或「暂时无法获取报价」

异常:响应结构变化(新增/删除关键字段)
系统行为Provider preCheck 失败,抛 STRUCT_CHANGE走 L3 或 failed
是否允许降级L3
是否触发告警STRUCT_CHANGE
用户看到的结果:按 fallback 处理

异常幂等冲突request_id 已存在但 cargo 不同)
系统行为L1 优先,返回原 quote_id忽略新 cargo
是否允许降级L1
是否触发告警:否
用户看到的结果:返回原报价

11.5 RPA 外部依赖容错

异常Mothership DOM 结构变化 / 报价入口不可用
系统行为preCheck 失败404、login 且无凭据、表单 selector 不可见),抛 STRUCT_CHANGEQUOTE_ENTRY_UNAVAILABLE,重试 0 次,走 L3 fallback
是否允许降级L3
是否触发告警STRUCT_CHANGE
用户看到的结果:展示 stale 报价或「暂时无法获取报价」

异常storageState 失效Cookie 过期)
系统行为:删除失效 storageState 文件,重新匿名访问;若仍 login 且无凭据 → STRUCT_CHANGE若有凭据则重登 1 次
是否允许降级L3
是否触发告警:是
用户看到的结果:按 fallback 处理

异常:地址联想点选失败
系统行为:抛 ADDRESS_SUGGESTION_NOT_FOUND不重试Worker 路径走 L3宿主 API 路径返回 400
是否允许降级L3Worker400宿主直调
是否触发告警:是
用户看到的结果:提示修正地址或 stale 报价

异常:验证码 / Cloudflare 拦截
系统行为:抛 RPA_CAPTCHA暂停该 IP+指纹 Worker 10 分钟,写告警,走 L3
是否允许降级L3
是否触发告警RPA_CAPTCHA
用户看到的结果:展示 stale 报价或「暂时无法获取报价」

异常:登录失效 / session 过期(已登录账号场景)
系统行为:若页面为 login 且已配置 MOTHERSHIP_EMAIL/PASSWORDWorker 自动重新登录 1 次,失败后抛 SESSION_EXPIRED走 L3若 login 且无凭据 → STRUCT_CHANGE禁止误报 SESSION_EXPIRED
是否允许降级L3
是否触发告警:是
用户看到的结果:按 fallback 处理

异常RPA 返回不完整数据standard 存在guaranteed 缺失)
系统行为:一致性校验失败,抛 RPA_DATA_INVALID走 L3
是否允许降级L3
是否触发告警:是
用户看到的结果:展示 stale 报价

异常:页面加载超时(>15s
系统行为:超时抛 PAGE_LOAD_TIMEOUT走 L3
是否允许降级L3
是否触发告警:是
用户看到的结果:按 fallback 处理

异常RPA 抓取成功但数据错误(价格 ≤0 或负数)
系统行为:一致性校验失败,抛 RPA_DATA_INVALID走 L3不落库错误价
是否允许降级L3
是否触发告警:是
用户看到的结果:展示 stale 报价

异常RPA 连续失败 ≥3 次
系统行为:熔断 10 分钟,期间全部走 L3 或 failed
是否允许降级L3
是否触发告警:是
用户看到的结果:按 fallback 处理或「暂时无法获取报价」

11.6 缓存一致性与防击穿设计

异常cache 击穿(同一 cargo_hash 同时 100+ miss
系统行为L2 写操作加 Redis SETNX + 5s expire 锁,只允许 1 个 RPA其余等待 2s 后读 L2
是否允许降级L3
是否触发告警:否
用户看到的结果:最终命中 L2 或 L3

异常cache 雪崩3min TTL 同时到期)
系统行为L2 TTL 写入时加 030s 随机 jitter
是否允许降级L3
是否触发告警:否
用户看到的结果:平滑过渡

异常cache 不一致L2 旧报价 vs 新 RPA 结果)
系统行为L2 3min 固定 TTL不被新 RPA 覆盖;新 RPA 写新 L2
是否允许降级L2
是否触发告警:否
用户看到的结果3min 内看到旧报价,之后看到新报价

异常stale 数据被错误展示为实时
系统行为:所有 L3 命中必须返回 is_realtime=false + 角标「非实时报价,仅供参考」
是否允许降级L3
是否触发告警STALE_FALLBACK
用户看到的结果:带角标展示

异常request_id 与 cargo_hash 冲突
系统行为L1 优先级最高request_id 命中直接返回,不再查 L2
是否允许降级L1
是否触发告警:否
用户看到的结果:返回原 quote_id

11.7 并发一致性与幂等设计

异常:同一用户并发 10 次不同 request_id 询价
系统行为API 网关令牌桶限流 60 次/分钟,超限 429 RATE_LIMITED
是否允许降级:否
是否触发告警:是(限流)
用户看到的结果:「请求过于频繁,请稍后重试」

异常:多用户同时请求同一货物
系统行为L2 共享,各自按自己 customer_id 加价重算 final_total无覆盖写
是否允许降级L2
是否触发告警:否
用户看到的结果:各自看到自己加价后的价格

异常:价格竞争导致覆盖写
系统行为quote_record 更新按 quote_id 主键RPA 成功后先 UPDATE 再写缓存
是否允许降级L2
是否触发告警:否
用户看到的结果:最终以 DB 为准

异常:幂等失效导致重复 RPA
系统行为Redis L1 + DB 唯一键双保险,冲突即返回已有 quote_id
是否允许降级L1
是否触发告警:否
用户看到的结果:不重复执行 RPA

11.8 数据计算安全

异常:浮点精度误差(加价计算)
系统行为:全程使用 DECIMAL(12,2),加价公式 ROUND_HALF_UP(raw_freight × markup / 100, 2)
是否允许降级:否
是否触发告警:否
用户看到的结果:金额精确到分,无误差

异常surcharge 缺失
系统行为:按 0 处理,raw_total = raw_freight
是否允许降级:否
是否触发告警:否
用户看到的结果:正常展示

异常rounding 规则不一致
系统行为:全局唯一 ROUND_HALF_UPDB/缓存/响应一致
是否允许降级:否
是否触发告警:否
用户看到的结果:各端金额一致

11.9 权限与安全控制

异常:越权访问其他客户 quote_id
系统行为JWT customer_id 校验失败403 FORBIDDEN记录 audit log
是否允许降级:否
是否触发告警:是(安全)
用户看到的结果403 FORBIDDEN

异常:伪造 customer_idJWT 无效)
系统行为JWT 签名校验失败401 UNAUTHORIZED
是否允许降级:否
是否触发告警:是(安全)
用户看到的结果401 UNAUTHORIZED

异常:无权限修改加价规则
系统行为RBAC 校验失败403 FORBIDDEN
是否允许降级:否
是否触发告警:是(安全)
用户看到的结果403 FORBIDDEN

异常API 被批量刷(>60 次/分钟/客户 或 >1000 次/分钟/IP
系统行为:网关限流 + IP 封禁 10 分钟,写安全告警
是否允许降级:否
是否触发告警:是(安全)
用户看到的结果429 RATE_LIMITED 或封禁提示

11.10 状态机与生命周期保护

异常询价已过期valid_until 过去)仍被展示
系统行为:客户端 GET 时检查 valid_until过期返回 status=expired,不展示价格
是否允许降级:否
是否触发告警:否
用户看到的结果:「报价已过期,是否重新询价?」

异常cache 未过期但 RPA 更新更高优先级
系统行为L2 3min 固定 TTL新 RPA 只写新 L2不覆盖旧 L2
是否允许降级L2
是否触发告警:否
用户看到的结果3min 内看到旧报价

异常fallback 状态未标识
系统行为:所有 L3 命中强制返回 is_realtime=false + 角标
是否允许降级L3
是否触发告警STALE_FALLBACK
用户看到的结果:带角标展示

异常quote_id 重复使用(生成冲突)
系统行为DB quote_id 唯一键冲突,拒绝写入,抛 INTERNAL_ERROR
是否允许降级:否
是否触发告警:是
用户看到的结果:服务异常提示

异常processing 状态超过 30s 未更新
系统行为:定时任务标记为 failed + QUOTE_TIMEOUT
是否允许降级L3
是否触发告警:是
用户看到的结果:轮询后得到 failed 状态


第 12 章 验收标准Acceptance Criteria

本章所有 TC 可直接转换为自动化脚本PASS 条件均为可量化判断。与第 11 章异常场景一一对应。

12.1 功能验收

TC-101 正常询价流程(首次提交)

  • 类型Functional
  • 前置条件:宿主 Service Token 有效customer_id=CUST_001 未配置加价;地址含有效 place_id
  • 输入数据request_id=uuid-v4pickup/delivery 含 place_id单托 weight=500lbdims=48×40×48inpallet_count=2cargo_type=general_freightservice_level=standardrate_option=lowest
  • 操作步骤POST /quotes → 轮询 GET /quotes/{quote_id} 直至 done
  • 预期结果:
    • HTTP状态码200
    • 响应字段status=donesource_type=rpais_realtime=truequotes 数组长度=4final_total=raw_totalmarkup=0
    • 数据库变化quote_record 新增 1 条status=donesource_type=rpaidempotency_record 新增 1 条
    • 是否触发告警:否
    • 是否命中cache
  • PASS标准响应 200status=donequotes 含 standard/guaranteed × lowest/fastest 共 4 档DB 写入 1 条 quote_record + 1 条 idempotency_record耗时 <30s

TC-102 四档报价返回正确性

  • 类型Functional
  • 前置条件RPA 已成功抓取
  • 输入数据:同 TC-101
  • 操作步骤GET /quotes/{quote_id}
  • 预期结果:
    • HTTP状态码200
    • 响应字段quotes 含 4 条;每条含 service_level + rate_optionstandard+lowest 的 transit_description 含「最低价格」guaranteed+fastest 含「最快递送」或「保障」
    • 数据库变化quotes_json 存储四档数据
    • 是否触发告警:否
    • 是否命中cache首次不命中
  • PASS标准quotes 数组恰好 4 条service_level × rate_option 组合齐全

TC-103 加价计算正确性(含 surcharge

  • 类型Functional
  • 前置条件markup_config 中 CUST_002 配置 10.0%
  • 输入数据raw_freight=320.00surcharges=15.00markup=10.0%
  • 操作步骤RPA 返回后 PricingEngine 计算
  • 预期结果:
    • HTTP状态码200
    • 响应字段markup_amount=32.00final_total=367.00ROUND_HALF_UP
    • 数据库变化quote_record.markup_percent=10.0quotes_json 中 breakdown 含 3 项
    • 是否触发告警:否
    • 是否命中cache
  • PASS标准markup_amount=32.00final_total=367.00,金额精确到分,无浮点误差

TC-104 L2 cache 命中逻辑

  • 类型Functional
  • 前置条件:同一 cargo_hash 已存在 L23min 内)
  • 输入数据:相同货物,不同 request_id
  • 操作步骤POST /quotes新 request_id
  • 预期结果:
    • HTTP状态码200
    • 响应字段status=donesource_type=cacheis_realtime=true
    • 数据库变化quote_record 新增 1 条source_type=cacheidempotency_record 新增
    • 是否触发告警:否
    • 是否命中cacheL2
  • PASS标准source_type=cache耗时 <300ms无新 RPA 调用

TC-105 request_id 幂等24h 内重复提交)

  • 类型Functional
  • 前置条件request_id 已使用过
  • 输入数据:相同 request_id相同或不同货物
  • 操作步骤POST /quotes
  • 预期结果:
    • HTTP状态码200
    • 响应字段:返回首次 quote_idstatus=done
    • 数据库变化quote_record 不新增idempotency_record 不新增
    • 是否触发告警:否
    • 是否命中cacheL1
  • PASS标准返回原 quote_idDB 无新写入24h 内任意次数均返回同一 quote_id

TC-106 单位换算kg/cm

  • 类型Functional
  • 前置条件:无
  • 输入数据weight=227kgdims=122×102×122cm
  • 操作步骤POST /quotes → 轮询至 done
  • 预期结果:
    • HTTP状态码200
    • 响应字段:内部 weight_lb≈500.66dims 换算为 in 后正常询价
    • 数据库变化quote_record 记录换算后 in/lb 值
    • 是否触发告警:否
    • 是否命中cache
  • PASS标准200 doneDB weight_lb 与 dims 为换算后 in/lb 值

TC-107 不同 service_level 同货物共享缓存

  • 类型Functional
  • 前置条件L2 已存在(四档)
  • 输入数据相同货物service_level 分别为 standard / guaranteed 两次提交
  • 操作步骤:两次 POST /quotes不同 request_id
  • 预期结果:
    • HTTP状态码200
    • 响应字段:两次均返回四档 quotes第二次 source_type=cache
    • 数据库变化:第二次无新 RPA
    • 是否触发告警:否
    • 是否命中cache第二次命中 L2
  • PASS标准第二次 source_type=cachequotes 仍含四档

TC-108 L2 过期后触发新 RPA

  • 类型Functional
  • 前置条件:同 cargo_hash L2 已过期(>3min
  • 输入数据:相同货物
  • 操作步骤POST /quotes
  • 预期结果:
    • HTTP状态码200
    • 响应字段status=processing 后 donesource_type=rpa
    • 数据库变化:新 quote_record触发 RPA
    • 是否触发告警:否
    • 是否命中cache
  • PASS标准source_type=rpaRPA 被调用 1 次

12.2 异常场景验收(对齐第 11 章)

TC-201 输入必填缺失

  • 类型Exception
  • 前置条件:无
  • 输入数据weight 字段缺失
  • 操作步骤POST /quotes
  • 预期结果:
    • HTTP状态码400
    • 响应字段error_code=VALIDATION_FAILED
    • 数据库变化:无
    • 是否触发告警:否
    • 是否命中cache
  • PASS标准400 VALIDATION_FAILED不生成 quote_id

TC-202 数值超范围weight=0

  • 类型Exception
  • 前置条件:无
  • 输入数据weight.value=0
  • 操作步骤POST /quotes
  • 预期结果:
    • HTTP状态码400
    • 响应字段error_code=VALIDATION_FAILED
    • 数据库变化:无
    • 是否触发告警:否
    • 是否命中cache
  • PASS标准400提示超范围

TC-203 地址邮编非法

  • 类型Exception
  • 前置条件:无
  • 输入数据zip="9001"4 位)
  • 操作步骤POST /quotes
  • 预期结果:
    • HTTP状态码400
    • 响应字段error_code=VALIDATION_FAILED
    • 数据库变化:无
    • 是否触发告警:否
    • 是否命中cache
  • PASS标准400地址无效

TC-204 request_id 非 UUID

  • 类型Exception
  • 前置条件:无
  • 输入数据request_id="not-uuid"
  • 操作步骤POST /quotes
  • 预期结果:
    • HTTP状态码400
    • 响应字段error_code=VALIDATION_FAILED
    • 数据库变化:无
    • 是否触发告警:否
    • 是否命中cache
  • PASS标准400 VALIDATION_FAILED

TC-205 RPA 失败 + L3 stale 命中

  • 类型Exception
  • 前置条件cargo_hash 存在 L3≤30min
  • 输入数据:正常货物
  • 操作步骤RPA 触发失败(模拟验证码)
  • 预期结果:
    • HTTP状态码200
    • 响应字段status=donesource_type=staleis_realtime=false角标「非实时报价仅供参考」
    • 数据库变化quote_record 新增source_type=stale
    • 是否触发告警STALE_FALLBACK
    • 是否命中cacheL3
  • PASS标准200source_type=staleis_realtime=falsealert_log 新增 1 条 STALE_FALLBACK

TC-206 RPA 失败 + 无 L3

  • 类型Exception
  • 前置条件cargo_hash 无 L3
  • 输入数据:正常货物
  • 操作步骤RPA 触发失败
  • 预期结果:
    • HTTP状态码200
    • 响应字段status=failederror_code=QUOTE_UNAVAILABLE
    • 数据库变化quote_record 新增status=failed
    • 是否触发告警RPA_FAILED
    • 是否命中cache
  • PASS标准200status=failederror_code=QUOTE_UNAVAILABLEalert_log 新增 RPA_FAILED

TC-207 并发重复提交10 次相同 request_id

  • 类型Exception
  • 前置条件:无
  • 输入数据:相同 request_id10 并发
  • 操作步骤10 线程同时 POST /quotes
  • 预期结果:
    • HTTP状态码200
    • 响应字段:全部返回同一 quote_id
    • 数据库变化quote_record 仅 1 条idempotency_record 仅 1 条
    • 是否触发告警:否
    • 是否命中cacheL1
  • PASS标准10 次请求返回同一 quote_idDB 仅写入 1 条

TC-208 网络超时(>30s

  • 类型Exception
  • 前置条件:无
  • 输入数据:正常
  • 操作步骤RPA 模拟 >30s 未完成
  • 预期结果:
    • HTTP状态码200
    • 响应字段status=failederror_code=QUOTE_TIMEOUT
    • 数据库变化quote_record status=failed
    • 是否触发告警:是
    • 是否命中cache
  • PASS标准200status=failederror_code=QUOTE_TIMEOUT

TC-209 RPA 连续失败熔断

  • 类型Exception
  • 前置条件:无
  • 输入数据:连续 3 次 RPA 失败
  • 操作步骤:触发 3 次失败
  • 预期结果:
    • HTTP状态码200
    • 响应字段:第 4 次起 10 分钟内走 L3 或 failed
    • 数据库变化alert_log 记录熔断
    • 是否触发告警:是
    • 是否命中cache按 L3
  • PASS标准连续失败 ≥3 次后 10 分钟内无新 RPA 调用

TC-210 偏差预警≥5%

  • 类型Exception
  • 前置条件quote_cache_meta 存在上次报价 raw_total_standard=352
  • 输入数据:本次 RPA raw_total_standard=370偏差 5.1%
  • 操作步骤RPA 成功
  • 预期结果:
    • HTTP状态码200
    • 响应字段status=done正常返回报价
    • 数据库变化alert_log 新增 PRICE_DEVIATION
    • 是否触发告警:是
    • 是否命中cache
  • PASS标准200 donealert_log 新增 PRICE_DEVIATION

TC-211 偏差未达阈值4.9%

  • 类型Exception
  • 前置条件:上次 raw_total=352
  • 输入数据:本次 raw_total=369.25(偏差 4.9%
  • 操作步骤RPA 成功
  • 预期结果:
    • HTTP状态码200
    • 响应字段status=done
    • 数据库变化:无 PRICE_DEVIATION 告警
    • 是否触发告警:否
    • 是否命中cache
  • PASS标准200 donealert_log 无 PRICE_DEVIATION

TC-212 首次询价不触发偏差预警

  • 类型Exception
  • 前置条件cargo_hash 无历史基准
  • 输入数据:正常货物
  • 操作步骤:首次 RPA 成功
  • 预期结果:
    • HTTP状态码200
    • 响应字段status=done
    • 数据库变化quote_cache_meta 写入基准
    • 是否触发告警:否
    • 是否命中cache
  • PASS标准200 done无 PRICE_DEVIATION 告警

12.3 性能验收

TC-301 RPA 路径 P95 响应时间

  • 类型Performance
  • 前置条件RPA 正常
  • 输入数据:标准测试货物
  • 操作步骤100 次连续 POST + 轮询
  • 预期结果:
    • HTTP状态码200
    • 响应字段status=done
    • 数据库变化100 条 quote_record
    • 是否触发告警:否
    • 是否命中cache
  • PASS标准P95 完成时间 <30sP99 <45s

TC-302 L2 cache 命中 P95 响应时间

  • 类型Performance
  • 前置条件L2 已存在
  • 输入数据:相同货物
  • 操作步骤100 次 POST
  • 预期结果:
    • HTTP状态码200
    • 响应字段source_type=cache
    • 数据库变化100 条 quote_recordcache 来源)
    • 是否触发告警:否
    • 是否命中cache
  • PASS标准P95 响应 <300msP99 <500ms

TC-303 并发 QPS 限流验证

  • 类型Performance
  • 前置条件:单客户账号
  • 输入数据:不同 request_id
  • 操作步骤60s 内发送 70 次请求
  • 预期结果:
    • HTTP状态码前 60 次 200后 10 次 429
    • 响应字段429 响应 error_code=RATE_LIMITED
    • 数据库变化60 条 quote_record
    • 是否触发告警:是(限流)
    • 是否命中cache部分命中
  • PASS标准令牌桶 60 次/分钟生效,>60 次/分钟返回 429

TC-304 RPA 最大等待时间边界

  • 类型Performance
  • 前置条件:无
  • 输入数据:正常
  • 操作步骤RPA 模拟 28s 完成 vs 31s 未完成
  • 预期结果:
    • HTTP状态码200
    • 响应字段28s→done31s→failed QUOTE_TIMEOUT
    • 数据库变化:对应 status
    • 是否触发告警31s 时触发
    • 是否命中cache
  • PASS标准28s 内返回 done>30s 返回 QUOTE_TIMEOUT

12.4 数据一致性验收

TC-401 cache vs RPA 一致性

  • 类型Consistency
  • 前置条件L2 存在
  • 输入数据:相同货物
  • 操作步骤L2 命中 vs 新 RPA 结果比对
  • 预期结果:
    • HTTP状态码200
    • 响应字段3min 内 L2 raw_total 与生成时 RPA 一致
    • 数据库变化quote_cache_meta 记录最新 RPA
    • 是否触发告警:偏差 ≥5% 触发
    • 是否命中cacheL2 命中
  • PASS标准L2 内容与写入时 RPA 一致;新 RPA 偏差 ≥5% 触发 PRICE_DEVIATION

TC-402 quote_id 唯一性

  • 类型Consistency
  • 前置条件:无
  • 输入数据100 次不同 request_id
  • 操作步骤:并发提交
  • 预期结果:
    • HTTP状态码200
    • 响应字段100 个不同 quote_id
    • 数据库变化100 条 quote_recordquote_id 唯一
    • 是否触发告警:否
    • 是否命中cache
  • PASS标准DB quote_id 唯一键无冲突

TC-403 加价计算可复算

  • 类型Consistency
  • 前置条件DB 已存储 raw_freight 与 markup_percent
  • 输入数据quote_record 已存在
  • 操作步骤:从 DB 读取并重算 final_total
  • 预期结果:
    • HTTP状态码200
    • 响应字段:重算值与 quotes_json.final_total 一致
    • 数据库变化:无
    • 是否触发告警:否
    • 是否命中cache
  • PASS标准重算值 == 存储值,误差 ≤0.01

TC-404 request_id 幂等一致性(跨重启)

  • 类型Consistency
  • 前置条件Redis L1 已过期DB idempotency_record 仍存在
  • 输入数据:相同 request_id
  • 操作步骤:服务重启后再次 POST
  • 预期结果:
    • HTTP状态码200
    • 响应字段:返回原 quote_id
    • 数据库变化:无新写入
    • 是否触发告警:否
    • 是否命中cacheDB 兜底)
  • PASS标准重启后 24h 内仍返回原 quote_id

12.5 安全与权限验收

TC-501 越权访问customer_id 伪造)

  • 类型Security
  • 前置条件:客户 A JWT
  • 输入数据quote_id 属于客户 B
  • 操作步骤GET /quotes/{B的quote_id}(带 A 的 JWT
  • 预期结果:
    • HTTP状态码403
    • 响应字段error_code=FORBIDDEN
    • 数据库变化:无
    • 是否触发告警:是(安全类)
    • 是否命中cache
  • PASS标准403 FORBIDDENaudit log 记录越权尝试

TC-502 运营权限控制(加价配置)

  • 类型Security
  • 前置条件:客户角色 JWT无 pricing:markup:write
  • 输入数据PUT /markup-configs/CUST_001
  • 操作步骤:客户角色调用加价接口
  • 预期结果:
    • HTTP状态码403
    • 响应字段error_code=FORBIDDEN
    • 数据库变化markup_config 不更新
    • 是否触发告警:是(安全)
    • 是否命中cache
  • PASS标准403markup_config 不变

TC-503 批量刷接口限流

  • 类型Security
  • 前置条件:无
  • 输入数据:单 IP 120 次/分钟
  • 操作步骤:高频 POST /quotes
  • 预期结果:
    • HTTP状态码429
    • 响应字段error_code=RATE_LIMITED
    • 数据库变化:仅 60 条
    • 是否触发告警:是(安全)
    • 是否命中cache部分
  • PASS标准>60 次/分钟返回 429

TC-504 API 非法请求拦截

  • 类型Security
  • 前置条件:无
  • 输入数据payload 含 SQL 注入
  • 操作步骤POST /quotes恶意 payload
  • 预期结果:
    • HTTP状态码400
    • 响应字段error_code=VALIDATION_FAILED
    • 数据库变化:无
    • 是否触发告警:是(安全)
    • 是否命中cache
  • PASS标准400不执行 RPA不写入 DB

TC-505 加价上限边界

  • 类型Security
  • 前置条件:运营角色 JWT
  • 输入数据markup_percent=30.1
  • 操作步骤PUT /markup-configs/CUST_001
  • 预期结果:
    • HTTP状态码400
    • 响应字段:拒绝保存
    • 数据库变化:无
    • 是否触发告警:否
    • 是否命中cache
  • PASS标准400markup_config 不变

TC-506 历史接口强制分页

  • 类型Security
  • 前置条件:客户 JWT
  • 输入数据GET /quotes/history 不传 page/size
  • 操作步骤:调用历史接口
  • 预期结果:
    • HTTP状态码400
    • 响应字段VALIDATION_FAILED
    • 数据库变化:无
    • 是否触发告警:否
    • 是否命中cache
  • PASS标准400强制分页参数

12.6 RPA 与外部系统验收

TC-601 RPA 成功返回结构完整性

  • 类型Functional
  • 前置条件:MOTHERSHIP_QUOTE_URLS 已 headed 录制验证;RPA_MOCK_MODE=false
  • 输入数据标准货物LA 90001 → Dallas 752012 托500lb/托48×40×48in
  • 操作步骤:npx tsx scripts/probe-rpa.ts 连续执行 3 次
  • 预期结果:
    • exit code03 次均成功)
    • 响应字段:items.length === 4standard/guaranteed × lowest/fastest每条 raw_freight>0transit_description 非空
    • 数据库变化API 路径quote_record source_type=rpa
    • 是否触发告警:否
    • 是否命中cache
  • PASS标准3 次 probe 均 exit 0四档齐全且 raw_freight>0

TC-602 RPA 失败 fallback 触发

  • 类型Exception
  • 前置条件L3 存在
  • 输入数据:正常
  • 操作步骤:模拟验证码
  • 预期结果:
    • HTTP状态码200
    • 响应字段source_type=staleis_realtime=false
    • 数据库变化alert_log 新增 STALE_FALLBACK
    • 是否触发告警:是
    • 是否命中cacheL3
  • PASS标准source_type=stalealert_log 写入is_realtime=false

TC-603 验证码/封禁场景

  • 类型Exception
  • 前置条件Worker 可用
  • 输入数据:正常
  • 操作步骤RPA 触发验证码
  • 预期结果:
    • HTTP状态码200
    • 响应字段source_type=stale 或 failed
    • 数据库变化alert_log 新增 RPA_CAPTCHAWorker 暂停 10 分钟
    • 是否触发告警RPA_CAPTCHA
    • 是否命中cacheL3
  • PASS标准alert_log RPA_CAPTCHAWorker 10 分钟内不分配新 job

TC-604 页面结构变化容错

  • 类型Exception
  • 前置条件Mothership DOM 变更
  • 输入数据:正常
  • 操作步骤RPA preCheck 失败
  • 预期结果:
    • HTTP状态码200
    • 响应字段source_type=stale 或 failed
    • 数据库变化alert_log 新增 STRUCT_CHANGE
    • 是否触发告警:是
    • 是否命中cacheL3
  • PASS标准alert_log STRUCT_CHANGEWorker 停止,走 L3

TC-605 RPA 超时降级

  • 类型Exception
  • 前置条件:无
  • 输入数据:正常
  • 操作步骤RPA 执行 >30s
  • 预期结果:
    • HTTP状态码200
    • 响应字段status=failederror_code=QUOTE_TIMEOUT
    • 数据库变化quote_record status=failed
    • 是否触发告警:是
    • 是否命中cache
  • PASS标准status=failederror_code=QUOTE_TIMEOUTalert_log 写入

12.7 Go/No-Go 上线门禁

指标 目标值 测量方法 Go 条件
API success rate含 cache ≥99.5% 1000 次请求统计 1000 次中 ≥995 次 200 done
RPA success rate ≥85% 100 次真实 RPA 统计 100 次中 ≥85 次 source=rpa
RPA probe 门禁 3/3 成功 RPA_MOCK_MODE=false npx tsx scripts/probe-rpa.ts 连续 3 次 3 次均 exit 0TC-601
realtime rate ≥90% 1000 次 done 统计 1000 次中 ≥900 次 is_realtime=true
cache 命中率L1+L2 ≥90% 1000 次请求统计 1000 次中 ≥900 次命中 L1 或 L2
RPA vs 人工偏差 ≤5% 人工抽检 ≥30 单 30 单中偏差 ≤5% 通过率 ≥95%
P0 级别测试失败 0 全用例执行 §12.112.6 无 P0 失败
性能 P95 <30s Load Test 100 次TC-301 P95 <30s
无高危安全漏洞 0 渗透测试 TC-501~506 无高危漏洞

12.8 测试分层策略

层级 范围 工具/框架 覆盖率目标 执行频率
Unit Test PricingEngine、CacheModule、ValidationModule Jest / Vitest 80% 行覆盖 每次提交
Integration Test API + DB + Redis + BullMQ Supertest + Testcontainers 全部 API 契约 每次构建
E2E Test 客户端轮询 + RPA 模拟 Playwright 核心 5 条用户路径 每日
Load Test 60 QPS + 100 并发 k6 / JMeter P95 <30s成功率 ≥99% 每次发布前

12.9 测试数据设计

数据类型 模板/值 用途
标准测试货物 LA(90001)→Dallas(75201)500lb48×40×48in2件general_freight 功能/性能基线
极端托盘数 25 / 1 pallet 边界校验
极端尺寸 999×99×99 in上限 边界校验
超规尺寸 1000×100×100 in 边界校验 400
无效地址 zip=9001state=XX 异常输入 TC-203
高并发账号 100 个独立 customer_id Load Test / 并发
缓存命中货物 固定 3min 内重复使用 Cache 命中率 TC-104
RPA 失败模拟 故意触发验证码 / DOM 变更 降级 TC-602~604
加价配置 CUST_001=0%CUST_002=10.0%CUST_003=30.0% 加价计算 TC-103/505

12.10 用例与章节映射

PRD 章节 对应用例
§4 询价输入 TC-101~108TC-201~204
§6 加价规则 TC-103TC-505
§7 生命周期/缓存 TC-104~105TC-107~108TC-401~404
§11 异常场景 TC-205~212TC-501~506TC-602~605
§10 性能/安全 TC-301~304TC-503

第 13 章 3 周交付计划

Week 1 — 基础链路与数据层

任务 产出 验证
DB migration5 张表) 表结构 空库双跑通过
API 骨架 + 鉴权 + 统一响应 POST/GET/quotes TC-101TC-204
输入校验 + 单位换算 ValidationModule TC-201~203TC-106
Redis 三层缓存读写 CacheModule TC-104~105TC-107
幂等L1 + 表) IdempotencyModule TC-105TC-207TC-404

Week 2 — RPA 与加价

任务 产出 验证
MothershipRPAProvider四档 RPA Worker TC-601
BullMQ 队列 + Worker Pool 异步链路 TC-601TC-605
FallbackL3 stale+ 熔断 降级逻辑 TC-205~206TC-209
验证码处理 + 暂停 Worker 异常处理 TC-603
PricingEngine 加价 加价计算 TC-103TC-505
加价配置 API + 管理端页 配置功能 TC-502TC-505

Week 3 — 预警、前端、验收

任务 产出 验证
偏差预警 + alert_log AlertModule TC-210~212
预警中心管理端页 预警列表/处理 冒烟
宿主内嵌查价(轮询/倒计时/四档/非实时角标) 前端 TC-601TC-602 UI
历史记录页(分页) 前端 TC-506
限流 + 越权防护 安全 TC-501TC-503~506
人工抽检一致性 ≥30 单 报告 §12.7
指标采集(三成功率/命中率) 监控看板 §10.2§12.7
全量 TC 回归 + Go/No-Go 上线门禁 §12.7

里程碑

时点 交付物
W1 末 缓存+幂等可用,缓存命中路径端到端通
W2 末 RPA 四档 + 加价 + 降级全链路通
W3 末 §12 全用例通过 + §12.7 指标达标 + 可上线

第 14 章 Flock Freight 报价源GD-16Phase 扩展)

本章为 第二报价源 产品口径。MVPMothership不实现实现前须按技术设计 §7.6 录制 + probe 门禁。
实测入口:https://app.flockfreight.com/get-a-quote2026-07-13

14.1 目标与边界

规则
产品定位 Flock Freight 共享整车FlockDirect/ 标准 LTL 伙伴线路的公开查价
接入方式 仅 Playwright RPA不使用 Flock 官方 API
与 Mothership 关系 独立 Provider、独立硬限、独立 session同一编排层经 QuoteProvider 扩展
本期实现 已落地(FLOCK_RPA_ENABLED / Mock 联调;真实 RPA 须 probe 3/3
下单 / 支付 不在本期结果页「Complete your order」仅作 RPA 停止边界

14.2 官网流程实测摘要

1. 打开 get-a-quote可匿名填表无需先登录
2. 填写:取货日期、取件/投递 ZIP、托盘数、总重量(lb)、单托尺寸(in)
3. 「下一个」→ 结果页展示两档FlockDirect® / Standard
4. 「Complete your order」→ 引导「Create your free account」注册后继续
观察点 结论
报价表单 字段少:日期 + ZIP×2 + 托盘数 + 总重 + L/W/H无街道联想
结果页 两档对比卡;含 Reference #、Quotes generated
免费注册 First/Last name、Company、Phone、Work email、月均干货车次下拉实测随机邮箱/电话/名称可注册成功,站点无明显实名/邮箱域名校验
注册下拉选项 I haul or dispatch freight / 1-25 / 26-100 / 101-500 / 501-1000 / 1000+ / I'm a logistics provider

14.3 货物硬限(官网提示,独立于 GD-14

字段 规则(英制) 宿主提示文案(中文)
托盘数量 pallet_count 整数 420 托盘数量只能输入 4 到 20
总运输重量 total_weight_lb ≤ 45,000 lb整票总重非单托 总重量不能超过 45000 lb
单托长度 ≤ 636 in默认示例 48 托盘长度不能超过 636 in
单托宽度 ≤ 102 in默认示例 40 托盘宽度不能超过 102 in
单托高度 ≤ 108 in默认示例 48 托盘高度不能超过 108 in
取件/投递 ZIP 必填;美国 5 位 ZIP 需要提供取件/投递邮政编码
取货日期 必填;不得早于当日策略以录制为准 请选择取货日期

超限:400 VALIDATION_FAILEDmessage 用上表中文提示;禁止套用 Mothership §4.2.8 上限。

计量差异(相对 Mothership

维度 Mothership Flock Freight
重量 单托 weight_lb,总重 = 单托 × 托盘数 表单直接填整票总重lb
地址 街道 + 联想点选 place_id 仅 ZIP(取件/投递)
结果档位 4 档standard/guaranteed × lowest/fastest 2 档FlockDirect®、Standard

14.4 结果档位映射

官网卡片 映射 service_level rate_option 说明
FlockDirect® guaranteed语义premium / hubless fastest 价格从 $X含周末时效文案终端免中转卖点
Standard standard lowest 价格从 $X工作日时效走 LTL 伙伴

一次 RPA 须抓齐 2 档(价格 >0、时效文案非空。不足 2 档 → STRUCT_CHANGECARRIER_NO_CAPACITY(按页面文案分类)。
宿主展示:两张对比卡即可,不要求补齐 Mothership 四档空位。

14.5 账号与 SessionRPA

规则
匿名查价 get-a-quote → 结果页:优先走匿名路径(与实测一致)
注册页 仅当流程强制登录/下单时触发;可用预置测试账号或按需自动注册
注册字段强度 站点侧弱校验;中台仍须自有测试账号池,禁止把随机生成账号写入生产密钥库明文日志
storageState 独立路径,如 .rpa/flock-storage.json;与 Mothership 文件隔离

14.6 功能点与异常

ID 描述
F14.1 按 §14.3 校验 ZIP、托盘、总重、尺寸
F14.2 RPA 填写 get-a-quote 并抓取两档价格/时效/Reference #
F14.3 结果经 PricingEngine 加价后返回宿主(carrier=Flock Freight
F14.4 Selector / URL 改版 → STRUCT_CHANGE独立 probe
异常 处理
ZIP 缺失/非法 400中文提示
托盘/重量/尺寸超 §14.3 400中文提示
入口不可用 / 表单 DOM 变更 QUOTE_ENTRY_UNAVAILABLE / STRUCT_CHANGE
仅返回 1 档或价格为 0 失败,不写假档

14.7 验收门禁(实现阶段)

标准
Probe scripts/probe-flock-rpa.ts 标准样例(如 CA ZIP→TX ZIP连续 3 次 exit 0
档位 items 含 FlockDirect + Standard 映射各 1 条
校验 托盘 3 / 21、总重 45001、L/W/H 超限均 400
隔离 关闭 Flock 开关时不影响 Mothership 主路径

附录 AQuoteProvider 接口契约

interface Address {
  street: string; city: string; state: string; zip: string;
  placeId: string; formattedAddress: string; selectedFromSuggestions: boolean;
}
interface QuoteRequest {
  cargoHash: string;
  pickup: Address; delivery: Address;
  weightLb: number; dimsIn: { l: number; w: number; h: number };
  palletCount: number; cargoType: string;
}
interface QuoteItem {
  serviceLevel: 'standard' | 'guaranteed';
  rateOption: 'lowest' | 'fastest';
  carrier: string; transitDays: string; transitDescription: string;
  rawFreight: number; surcharges: number; rawTotal: number;
}
interface QuoteResult {
  items: QuoteItem[];          // 必含 4 档standard/guaranteed × lowest/fastest
  sourceType: 'rpa';
  confidenceScore: number;
}
interface QuoteProvider {
  getQuote(req: QuoteRequest): Promise<QuoteResult>; // 一次 RPA 返回四档
  healthCheck(): Promise<boolean>;
}

附录 B页面与状态清单

页面 状态 关键组件
宿主内嵌查价 idle / submitting / processing / done / stale / failed / expired 地址联想点选、kg/cm 录入、托盘数、pallet_count、四档报价卡、倒计时
管理端预警 list / detail 类型筛选、偏差%、quote_id、处理按钮
管理端 RPA/开发进度 dashboard Worker 状态、成功率、队列深度

附录 C状态/枚举字典

枚举 取值
quote.status processing / done / failed / expired
quote.source_type rpa / cache / stale
service_level standard / guaranteed
rate_option lowest / fastest
alert_type PRICE_DEVIATION / STALE_FALLBACK / RPA_FAILED / RPA_CAPTCHA / STRUCT_CHANGE / SESSION_EXPIRED / SECURITY
cargo_type 见 §4.2.59 项)
currency USD本期唯一

附录 Dv0.5 → v0.6 变更追踪

ID 章节 Before (v0.5) After (v0.6)
CHG-P01 版本 v0.5 v0.6
CHG-P02 GD 无入口 URL 规范 新增 GD-15MOTHERSHIP_QUOTE_URLS headed 录制;禁止 dashboard / /quote
CHG-P03 §2.5 仅「超时/验证码/封禁」 区分入口错误 vs 抓取错误;地址未锁定单独分类
CHG-P04 §5.6 QUOTE_ENTRY_UNAVAILABLE / ADDRESS_SUGGESTION_NOT_FOUND 新增两错误码
CHG-P05 §8.2 默认 dashboard.mothership.com 单 URL QuotePageAdapter 多 URL 探测 + storageState + login 无凭据→STRUCT_CHANGE
CHG-P06 §8.2 无 Selector 规范 新增 §8.2.1~§8.2.3env、禁止清单、codegen 维护)
CHG-P07 §11.5 login 一律 SESSION_EXPIRED login 无凭据→STRUCT_CHANGE新增 storageState 失效、地址联想失败
CHG-P08 TC-601 quotes 2 条 items 4 档 + probe-rpa 连续 3 次 exit 0
CHG-P09 §12.7 无 probe 门禁 新增 RPA probe 3/3 Go 条件

附录 Ev0.6 → v0.7 变更追踪

ID 章节 Before (v0.6) After (v0.7)
CHG-P10 版本 v0.6 v0.7
CHG-P11 GD GD-1~15 新增 GD-16 Flock Freight 第二报价源
CHG-P12 §3.1/3.2 仅 Priority1 扩展点 增加 Flock Freight 模块行与扩展点
CHG-P13 §14 新增 Flock Freight入口、硬限、两档映射、弱注册、验收