# domain-rules > 业务领域规则。来源:查价系统 **PRD v0.7** + **技术设计 v1.3**。与 PRD 冲突时以 PRD 为准。 > 工程任务权威清单:`docs/查价系统-工程任务拆解-v1.2.md`。 ## 1. 系统定位与边界 | 项 | 规则 | |----|------| | 产品名 | 美美与共报价中台(查价系统) | | 报价源 | Mothership,**仅 RPA**,不使用 Mothership API | | 嵌入形态 | 查价 UI 为**宿主内嵌组件/API**;中台仅 **admin 管理端**需登录 | | 本期不含 | CC 对接、独立客户/运营登录、多币种、下单、对账;Priority1 实验中;**Flock Freight 已落地(默认关,见 PRD §14)** | | 交付周期 | 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` | | GD-16 | Flock Freight 第二报价源;硬限/两档映射见 PRD §14;与 Mothership 分源校验,禁止混用上限 | ## 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 - **MotherShip 登录态额外硬限(官网 Create shipment)**: - 可提货日 **禁止周六/周日**;若传入周末日期须拨到下一工作日 - **单件重量(Weight each)≤ 5000 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` | 否 |