# 报价提速与 API 稳定性 — 改动清单

> 版本：2026-06-30  
> 目标：端到端 **15s 内 P50**（Direct 成功路径）、**并发不互相挡死**、**失败快速返回**（禁 Widget 回退）。  
> 基准：当前 `probe:human-doc-system` 10/10 约 2.4min（~14s/单），候选 5～15s + 报价 5～20s + 队列/轮询开销。

---

## 总览（按优先级）

| 批次 | 项数 | 预期收益 | 风险 | 建议工期 |
|------|------|----------|------|----------|
| **P0 运维** | 5 | 稳定性 + 避免假慢 | 低 | 0.5 天 |
| **P1 小改** | 4 | 候选 -3～8s，报价 -2～5s | 低 | 1～2 天 |
| **P2 架构** | 2 | 整单 -3～8s（去队列+轮询） | 中 | 2～3 天 |
| **P3 隔离** | 2 | 并发稳定性 | 中 | 1～2 天 |

**推荐实施顺序：** P0 → P1-1 → P1-2 → P1-3 → P1-4 → P2-1 → P2-2 → P3（按需）

---

## P0 — 运维与配置（不改代码）

| ID | 改动 | 操作 | 验收 |
|----|------|------|------|
| **P0-1** | 生产 Direct + 禁 Widget | `.env`：`RPA_QUOTE_MODE=direct`、`RPA_DISABLE_WIDGET_QUOTE_FALLBACK=true` | worker 日志出现 `node-direct 成功`，**无** `Widget fallback` |
| **P0-2** | Nginx 超时对齐 | `proxy_read_timeout 420s`（方式 A submit） | submit 不在 60s 被 502，420s 内正常返回 |
| **P0-3** | storageState 预热的 | worker 启动日志无 bootstrap 失败；`.rpa/mothership-storage.json` 存在且 `rpa_state` 卷挂载 | 冷启动首单不额外 +30～60s |
| **P0-4** | orphan job 巡检 | 重启后看 `已回收 N 条 orphan`；Bull Board `/admin/queues` 无长期 active | 连续 2 单报价第二单不无故 +30s |
| **P0-5** | 宿主传完整 placeId | 第二步候选对象含 `option_id`（ChIJ…） | worker 日志无重复 `searchLocation` |

---

## P1 — 小改快赢（低风险代码）

### P1-1 去掉报价重复 `fetchPlace`

| 项 | 内容 |
|----|------|
| **根因** | `resolveSide` 已 `fetchPlace`，75～79 行又对同一 placeId 再 fetch 一次 |
| **文件** | `lib/axel/quote-from-request.ts` |
| **改法** | `resolveSide` 返回 `{ placeId, description, place?: AxelPlaceLocation }`；已知 placeId 路径复用 place 对象；search 路径只 fetch 一次 |
| **并行** | `pickupSide` / `deliverySide` 改 `Promise.all` |
| **测试** | 扩展 `__tests__/lib/axel/` 或 mock client，断言 `fetchPlace` 每侧最多 1 次 |
| **验收** | 同址报价 worker 日志 place 请求减少；`probe:human-doc-system` 仍 10/10 |
| **预期** | **-1～3s/单** |

### P1-2 候选 `verifyCandidates` 只校验 Top-K

| 项 | 内容 |
|----|------|
| **根因** | 每条联想都 `fetchPlace`，N 条候选 = N 次 HTTP |
| **文件** | `lib/axel/candidates.ts` |
| **改法** | 新增 `AXEL_CANDIDATE_VERIFY_TOP_K`（默认 **3**）；仅前 K 条 `verifyCandidate`，其余 `selectable: true`（或 `unknown` 由前端展示「未校验」— 推荐默认 true 保持 UX） |
| **测试** | `__tests__/lib/axel/candidates.test.ts`：5 条候选 mock 断言 fetchPlace ≤ K |
| **验收** | candidates API P50 下降；仍至少 1 条 `selectable:true` |
| **预期** | **候选 -3～8s**（候选多时明显） |

### P1-3 宿主同步接口轮询间隔缩短

| 项 | 内容 |
|----|------|
| **根因** | `waitForQuoteDetail` 用 `POLL_INTERVAL_MS=2000`，完成后再等平均 +1s |
| **文件** | `modules/host/wait-for-quote.ts`；可选 `lib/frontend/constants.ts` 拆 `HOST_POLL_INTERVAL_MS=500` |
| **改法** | 仅 host submit 路径用 500ms；embed 前端可保持 2s 减 DB 压力 |
| **测试** | `__tests__/modules/host/wait-for-quote.test.ts` mock 时间 |
| **验收** | submit 在 worker 完成后 **<1s** 内返回 |
| **预期** | **-0.5～1.5s/单**（方式 A） |

### P1-4 候选结果缓存（短 TTL）

| 项 | 内容 |
|----|------|
| **根因** | 同街道重复联想仍打 MotherShip |
| **文件** | `modules/address/mothership-candidates.ts` 或 `lib/axel/candidates.ts` + `modules/cache/redis-cache.ts` |
| **改法** | key `addr-cand:{hash(pickup+delivery)}`，TTL **120s**，仅缓存成功结果 |
| **测试** | 集成测试：第二次同址命中缓存 |
| **验收** | 重复候选 <500ms；TTL 后失效 |
| **预期** | **重复查询 -5～12s** |

---

## P2 — 架构提速（中风险，收益最大）

### P2-1 API 层 Direct 同步报价（跳过 BullMQ 快乐路径）

| 项 | 内容 |
|----|------|
| **根因** | L2 未命中必 `enqueueForRpa`，node-direct 本可在 API 进程完成 |
| **文件** | `modules/quote/orchestrator.ts`（主）、`workers/rpa/job-handler.ts`（复用 `completeQuoteSuccess` 逻辑抽共享函数）、`lib/rpa/env.ts`（`isInlineDirectQuoteEnabled()`） |
| **改法** | 1) 新增 `tryInlineDirectQuote(cargo)`：`fetchAxelQuoteItems` → 写 L2 + `quote_record.done`；2) 成功则直接返回 `status:done`；3) 失败且 `RPA_DISABLE_WIDGET_QUOTE_FALLBACK=true` 则 `failed`；4) 失败且允许 fallback 则入队 Worker |
| **开关** | `RPA_INLINE_DIRECT_QUOTE=true`（生产默认开，可关回滚） |
| **约束** | 仍计 1 次 MotherShip 报价；须写 L2/L3、metrics、幂等一致 |
| **测试** | 单测 mock axel；`probe:human-doc-system`；并发 2 单不串单 |
| **验收** | 日志 `inline-direct 成功`；无 queue 等待；10/10 PASS |
| **预期** | **整单 -3～8s**（去队列 + 部分轮询） |

### P2-2 方式 A submit 合并为「创建即等待」单事务感知

| 项 | 内容 |
|----|------|
| **根因** | P2-1 后 host submit 可 `submitQuote` 同步得到 `done`，无需 `waitForQuoteDetail` 长轮询 |
| **文件** | `modules/host/public-orchestrator.ts` |
| **改法** | `hostPublicSubmitAndWait`：若 `submitQuote` 返回 `done` 直接 `getQuoteDetail`；仅 `processing` 时轮询 |
| **测试** | host submit 单测 |
| **验收** | submit 在 inline 成功时 **0 次** DB 轮询 |
| **预期** | 叠加 P2-1，方式 A 再 **-1～2s** |

---

## P3 — 并发与稳定性隔离

### P3-1 Priority1 与 MotherShip 分队列

| 项 | 内容 |
|----|------|
| **根因** | `priority1-quote` 与 `quote-rpa` 同 Worker、`concurrency=1`，实验挡生产 |
| **文件** | `lib/constants/rpa.ts`、`workers/rpa/index.ts`、`workers/rpa/queue.ts` |
| **改法** | 新队列 `priority1-rpa` + 可选独立 worker 容器；或 Priority1 job 仅 dev 环境入队 |
| **测试** | 入队 priority1 时 MotherShip quote 仍可并发消费（若 P3-2） |
| **验收** | 生产 compose 仅 `rpa-worker` 消费 `quote-rpa` |
| **预期** | **消除实验性挡队列** |

### P3-2 node-direct 专用 Worker 并发（可选）

| 项 | 内容 |
|----|------|
| **根因** | `RPA_WORKER_CONCURRENCY=1` 为 Playwright 设计；HTTP-only 可并行 |
| **文件** | `workers/rpa/job-handler.ts`、`workers/rpa/mothership.ts` |
| **改法** | 仅当 job 走 `fetchAxelQuoteItems` 成功路径时无 session lock；浏览器 fallback 仍 concurrency=1；或拆 `rpa-http-worker` 进程 concurrency=3 |
| **风险** | MotherShip 限流；storageState 并发读 |
| **验收** | 3 并发 HTTP 报价无串扰；失败率不升 |
| **预期** | **高峰 P95 -10～30s** |

---

## 观测与门禁（每批完成后必跑）

```bash
# 功能回归
npm run test:unit
npm run probe:human-doc-system

# 耗时对比（改前改后各跑 1 次，记录总秒数）
npm run probe:human-doc-system

# 服务器
curl -w "candidates: %{time_total}s\n" -X POST .../api/host/quote/candidates ...
# worker 日志关键字
docker logs <rpa-worker> 2>&1 | grep -E "node-direct|inline-direct|Widget fallback"
```

| 指标 | 改前参考 | P1 目标 | P2 目标 |
|------|----------|---------|---------|
| 候选 API P50 | 5～15s | 3～8s | 3～8s |
| 报价（含队列）P50 | 10～25s | 8～18s | **5～12s** |
| 方式 A submit P50 | 15～30s | 12～22s | **8～15s** |
| `probe:human-doc-system` | 10/10 ~144s | 10/10 ≤120s | 10/10 ≤90s |

---

## 不建议做

| 项 | 原因 |
|----|------|
| 生产开启 Widget fallback | 6～12min/单，堵死 worker |
| 仅增大超时 | 不解决排队与冗余 HTTP |
| 盲目 `concurrency=4` 同进程 Playwright | session 争用、orphan job |
| 跳过 placeId 校验 | 报价 400 / 地址不可选返工更慢 |

---

## implementation-plan 对应 Step

| Step | 对应 ID | 状态 |
|------|---------|------|
| 12.1 | P1-1 去重 fetchPlace + 并行 resolve | 待执行 |
| 12.2 | P1-2 候选 Top-K verify | 待执行 |
| 12.3 | P1-3 host 轮询 500ms | 待执行 |
| 12.4 | P1-4 候选 Redis 短缓存 | 待执行 |
| 12.5 | P2-1 inline direct quote | 待执行 |
| 12.6 | P2-2 host submit 免轮询 | 待执行 |
| 12.7 | P3-1 Priority1 分队列 | 待执行 |
| 12.8 | P3-2 HTTP worker 并发（可选） | 待执行 |

详细执行记录在 `cursor_project_rules/implementation-plan.mdc` Phase 12。
