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.

225 lines
8.4 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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 KeyBearer | 代终端用户询价、查历史、配置加价;请求体携带 `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.019,999** lb
- 尺寸inlength 1999、width 199、height 199
- 整票总重:`weight_lb × pallet_count` ≤ 249,975 lb
### 4.3 托盘数pallet_count
- 正整数,范围 **125**
- 映射 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-119GD-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` | 否 |