|
|
# domain-rules
|
|
|
|
|
|
> 业务领域规则。来源:查价系统 **PRD v0.6** + **技术设计 v1.2**。与 PRD 冲突时以 PRD 为准。
|
|
|
> 工程任务权威清单:`docs/查价系统-工程任务拆解-v1.2.md`。
|
|
|
|
|
|
## 1. 系统定位与边界
|
|
|
|
|
|
| 项 | 规则 |
|
|
|
|----|------|
|
|
|
| 产品名 | 美美与共报价中台(查价系统) |
|
|
|
| 报价源 | Mothership,**仅 RPA**,不使用 Mothership API |
|
|
|
| 嵌入形态 | 查价 UI 为**宿主内嵌组件/API**;中台仅 **admin 管理端**需登录 |
|
|
|
| 本期不含 | CC 对接、独立客户/运营登录、多币种、下单、对账、Priority1 |
|
|
|
| 交付周期 | 3 周;MVP |
|
|
|
|
|
|
## 2. 角色与鉴权
|
|
|
|
|
|
| 角色 | 鉴权方式 | 权限 |
|
|
|
|------|----------|------|
|
|
|
| 宿主系统 | Service Token / API Key(Bearer) | 代终端用户询价、查历史、配置加价;请求体携带 `customer_id` |
|
|
|
| 管理员 | JWT + RBAC | `alert:read`、`rpa:operate`;预警/RPA/开发进度 |
|
|
|
|
|
|
- 查价 API:`Authorization: Bearer <宿主 token>`;中台校验 token 与租户绑定,越权 403
|
|
|
- 管理端 API:管理员 JWT,与宿主 token 隔离
|
|
|
- **不提供** `customer_demo` / `operator_demo` 独立业务账号
|
|
|
- Demo:宿主模拟 `customer_id` + 管理端 `admin_demo`(Demo@123)
|
|
|
|
|
|
## 3. 全局收敛决策(GD-1 ~ GD-15)
|
|
|
|
|
|
| 编号 | 规则 |
|
|
|
|------|------|
|
|
|
| GD-1 | 一次询价 = 1 次 RPA;单次返回 **4 档**(standard/guaranteed × lowest/fastest) |
|
|
|
| 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 / RPA success / realtime rate |
|
|
|
| GD-5 | 异步模型:`POST /quotes` 返回 `quote_id`;客户端轮询 `GET /quotes/{quote_id}`(2s,最长 30s) |
|
|
|
| GD-6 | 加价仅对 `raw_freight` 按百分比;上限 30%;未配置客户 0% |
|
|
|
| GD-7 | 本期仅 USD |
|
|
|
| GD-8 | 货物类型固定 9 项枚举(见 §4) |
|
|
|
| GD-9 | 地址须从联想列表点选;须 `place_id` + `selected_from_suggestions=true` |
|
|
|
| GD-10 | 金额 `DECIMAL(12,2)`,`ROUND_HALF_UP` |
|
|
|
| GD-11 | 查价嵌入宿主;中台无运营加价 UI(宿主调 API) |
|
|
|
| GD-12 | 使用 `pallet_count`(托盘数),禁止件数/箱数 |
|
|
|
| GD-13 | API 可收 kg/cm;落库与 RPA 前强制换算 lb/in |
|
|
|
| GD-14 | 尺寸重量上限以 Mothership Help Center 公布值为硬校验 |
|
|
|
| GD-15 | `MOTHERSHIP_QUOTE_URLS` 须 headed 录制验证的匿名报价 URL 列表;**禁止**默认 dashboard / `/quote` |
|
|
|
|
|
|
## 4. 询价输入口径
|
|
|
|
|
|
### 4.1 地址(pickup_address / delivery_address)
|
|
|
|
|
|
- 必填:`street`、`city`、`state`、`zip`、`place_id`、`formatted_address`
|
|
|
- `selected_from_suggestions` 必须为 `true`
|
|
|
- RPA 须点击联想项,禁止仅填街道后 Enter
|
|
|
|
|
|
### 4.2 重量与尺寸(单托)
|
|
|
|
|
|
- 重量:换算后 **0.01–9,999** lb
|
|
|
- 尺寸(in):length 1–999、width 1–99、height 1–99
|
|
|
- 整票总重:`weight_lb × pallet_count` ≤ 249,975 lb
|
|
|
|
|
|
### 4.3 托盘数(pallet_count)
|
|
|
|
|
|
- 正整数,范围 **1–25**
|
|
|
- 映射 Mothership `freight[].quantity`(type=Pallet)
|
|
|
- **禁止** `quantity` 件数字段
|
|
|
|
|
|
### 4.4 货物类型(cargo_type)
|
|
|
|
|
|
```
|
|
|
general_freight | machinery | furniture | electronics | building_materials
|
|
|
auto_parts | food_nonperishable | apparel | other
|
|
|
```
|
|
|
|
|
|
默认 `general_freight`。
|
|
|
|
|
|
### 4.5 cargo_hash
|
|
|
|
|
|
```
|
|
|
MD5(pickup + delivery + weight_lb + dims_in + pallet_count + cargo_type)
|
|
|
```
|
|
|
|
|
|
不含 `service_level`、`rate_option`、`customer_id`。
|
|
|
|
|
|
## 5. 询价输出口径
|
|
|
|
|
|
- 一次返回 **4 档**报价
|
|
|
- 默认展示:`service_level=standard` + `rate_option=lowest`(仅 UI 默认 Tab)
|
|
|
- `status`:`processing` | `done` | `failed` | `expired`
|
|
|
- `source`:`cache` | `rpa` | `stale`
|
|
|
- `is_realtime`:L3 降级时为 `false`
|
|
|
- 报价有效期:3 分钟(`valid_until`)
|
|
|
|
|
|
## 6. 加价规则
|
|
|
|
|
|
```
|
|
|
markup_amount = ROUND_HALF_UP(raw_freight × markup_percent / 100, 2)
|
|
|
final_total = ROUND_HALF_UP(raw_total + markup_amount, 2)
|
|
|
```
|
|
|
|
|
|
| 规则 | 说明 |
|
|
|
|------|------|
|
|
|
| BR-6.1 | 写操作需 `pricing:markup:write` |
|
|
|
| BR-6.2 | `markup_percent ∈ [0, 30.0]`,步长 0.1 |
|
|
|
| BR-6.3 | 未配置客户按 0% |
|
|
|
| BR-6.4 | 配置变更仅影响新询价,历史不重算 |
|
|
|
|
|
|
## 7. 报价状态机
|
|
|
|
|
|
```
|
|
|
[processing]
|
|
|
→ L1 命中 → [done]
|
|
|
→ L2 命中 → [done, source=cache]
|
|
|
→ 未命中 → 入 RPA 队列
|
|
|
→ RPA 成功 → [done, source=rpa]
|
|
|
→ RPA 失败 → L3 命中 → [done, source=stale, is_realtime=false]
|
|
|
→ L3 未命中 → [failed, QUOTE_UNAVAILABLE]
|
|
|
→ 30s 未完成 → [failed, QUOTE_TIMEOUT]
|
|
|
[done] --3min--> [expired]
|
|
|
```
|
|
|
|
|
|
## 8. 缓存规则
|
|
|
|
|
|
| 层 | Key | TTL | 内容 |
|
|
|
|----|-----|-----|------|
|
|
|
| L1 | `idem:{request_id}` | 24h | 完整响应快照(含加价后结果) |
|
|
|
| L2 | `quote:{cargo_hash}` | 3min + jitter(0~30s) | 四档原始报价(未加价) |
|
|
|
| L3 | `stale:{cargo_hash}` | 30min | 同 L2,仅 RPA 失败降级只读 |
|
|
|
|
|
|
读取顺序:L1 → L2 → RPA →(失败)L3。L3 永不用于正常路径。
|
|
|
|
|
|
## 9. 预警类型
|
|
|
|
|
|
| type | 触发 |
|
|
|
|------|------|
|
|
|
| PRICE_DEVIATION | RPA 结果与 L3 历史偏差 ≥ 5% |
|
|
|
| STALE_FALLBACK | 使用 L3 降级返回 |
|
|
|
| RPA_FAILED | RPA 失败且无 stale |
|
|
|
| RPA_CAPTCHA | 遇验证码;暂停该 Worker |
|
|
|
|
|
|
## 10. 错误码
|
|
|
|
|
|
| error_code | 含义 |
|
|
|
|------------|------|
|
|
|
| VALIDATION_FAILED | 入参校验失败 |
|
|
|
| QUOTE_UNAVAILABLE | RPA 失败且无 stale |
|
|
|
| QUOTE_TIMEOUT | 30s 内未完成 |
|
|
|
| QUOTE_NOT_FOUND | quote_id 不存在 |
|
|
|
| FORBIDDEN | 越权/无权限 |
|
|
|
| RATE_LIMITED | 触发限流 |
|
|
|
| CARRIER_NO_CAPACITY | Mothership 无运力 |
|
|
|
| ADDRESS_NOT_SUPPORTED | 邮编不支持 |
|
|
|
| ADDRESS_SUGGESTION_NOT_FOUND | RPA 地址联想点选失败(v0.6) |
|
|
|
| QUOTE_ENTRY_UNAVAILABLE | 所有报价入口 URL preCheck 失败(v0.6) |
|
|
|
| INTERNAL_ERROR | 系统异常 |
|
|
|
|
|
|
## 11. API 契约
|
|
|
|
|
|
统一响应:`{ "code": 0, "message": "ok", "data": {...} }`
|
|
|
|
|
|
| 方法 | 路径 | 权限 |
|
|
|
|------|------|------|
|
|
|
| POST | /api/quotes | 宿主 Service Token |
|
|
|
| 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 | 管理员 |
|
|
|
|
|
|
## 12. 数据表(Prisma 实体)
|
|
|
|
|
|
`SysUser` · `QuoteRecord` · `IdempotencyRecord` · `QuoteCacheMeta` · `MarkupConfig` · `AlertLog` · `AuditLog`
|
|
|
|
|
|
- 金额字段:`DECIMAL(12,2)`
|
|
|
- 删除:逻辑删除(`is_deleted`)
|
|
|
- DB 变更须 migration,禁止代码先行引用未存在字段
|
|
|
|
|
|
## 13. 样例数据(联调/测试)
|
|
|
|
|
|
| 项 | 值 |
|
|
|
|----|-----|
|
|
|
| customer_id | `CUST_001` |
|
|
|
| request_id | UUID v4,如 `550e8400-e29b-41d4-a716-446655440000` |
|
|
|
| 路线 | LA → Dallas |
|
|
|
| 单托重量 | 500 lb |
|
|
|
| 单托尺寸 | 48×40×48 in |
|
|
|
| 托盘数 | 2 |
|
|
|
| 货物类型 | `general_freight` |
|
|
|
| 加价示例 | 10%:raw_freight=320 → final_total=352(无附加费) |
|
|
|
| 管理端账号 | `admin_demo` / `Demo@123` |
|
|
|
|
|
|
## 14. 废止口径(v1.1 冻结)
|
|
|
|
|
|
- task-075~078 **废止**:独立客户查价页、运营登录、运营加价 UI
|
|
|
- task-106 **替代** task-075:宿主内嵌查价组件
|
|
|
- 四档报价以 GD-1/TC-102 为准(非 2 档)
|
|
|
|
|
|
## 15. v1.2 增量任务(task-119~135)
|
|
|
|
|
|
> v1.1 基线 task-001~118(除废止)**已完成**;以下按 `工程任务拆解-v1.2.md` 顺序交付。
|
|
|
|
|
|
| Phase | Task | 目标 |
|
|
|
|-------|------|------|
|
|
|
| 0(增量) | **119→120** | RPA env 模板(GD-15)+ docker `rpa_state` volume |
|
|
|
| 5.0 | 121→122 | headed 录制 SOP + selector 提取 |
|
|
|
| 5 | 123→131 | QuotePageAdapter、storageState、selector env 化、错误分类、probe 3/3 |
|
|
|
| 8 | 132 | `/admin/queues` Bull Board |
|
|
|
| 9 | 133→135 | TC-601 修订 + Go/No-Go probe 门禁 + CI |
|
|
|
|
|
|
### RPA 环境变量(task-119,GD-15)
|
|
|
|
|
|
| 变量 | 必填 | 说明 |
|
|
|
|------|------|------|
|
|
|
| `MOTHERSHIP_QUOTE_URLS` | **是** | 逗号分隔匿名报价入口 URL,按序探测 |
|
|
|
| `RPA_STORAGE_STATE_PATH` | 否 | 默认 `.rpa/mothership-storage.json` |
|
|
|
| `RPA_SELECTOR_*` | **是**(8 项) | 录制产出,禁止硬编码 selector |
|
|
|
| `RPA_USE_PATCHRIGHT` | 否 | 可选 Patchright 驱动 |
|
|
|
|
|
|
### RPA 错误分类(v0.6)
|
|
|
|
|
|
| 类型 | 典型场景 | 错误码 | 重试 |
|
|
|
|------|----------|--------|------|
|
|
|
| 入口错误 | URL 404、login 无凭据、preCheck 无表单 | `QUOTE_ENTRY_UNAVAILABLE` / `STRUCT_CHANGE` | 否 |
|
|
|
| 地址未锁定 | 联想点选失败 | `ADDRESS_SUGGESTION_NOT_FOUND` | 否 |
|