本服务由 proto 支持并已纳入已发布的 OpenAPI 包 — 参见
proto/worldmonitor/scenario/v1/service.proto 和 /api/ScenarioService.openapi.yaml。本页在生成的参考文档之上补充迁移说明和示例。旧版 v1 URL 别名 — sebuf 迁移 (#3207) 重命名了三个 v1 端点以与 proto RPC 名称对齐。旧 URL 作为轻量别名保留,以便现有集成继续工作:
在新代码中请优先使用规范 URL — 别名将在下一次 v1→v2 中断时废弃(追踪于 #3282)。
列出模板
GET /api/scenario/v1/list-scenario-templates
返回预定义场景模板的目录。缓存 public, max-age=3600。
响应 — 使用已上线的模板之一(server/worldmonitor/supply-chain/v1/scenario-templates.ts)的简略示例:
taiwan-strait-full-closure、suez-bab-simultaneous、panama-drought-50pct、russia-baltic-grain-suspension、us-tariff-escalation-electronics。请以实时的 /list-scenario-templates 响应作为权威来源 — 该集合会随时间增长。传输中的 affectedHs2: [] 表示该场景影响所有行业(即注册表中的 null 哨兵值,repeated string 无法直接承载)。
运行场景
POST /api/scenario/v1/run-scenario
将作业加入队列。返回调用方必须轮询的 jobId。
- 鉴权:需要 PRO 权限。可通过以下任一方式授予:(a) 有效的
X-WorldMonitor-Key(来自WORLDMONITOR_VALID_KEYS的环境密钥,或所有者拥有apiAccess权限的用户专属wm_前缀密钥),或 (b) 用户角色为pro或 Dodo 权限层级 ≥ 1 的 Clerk bearer token。仅凭受信任的浏览器 Origin 不足以通过鉴权 —server/_shared/premium-check.ts中的isCallerPremium()仅认可显式凭证。浏览器调用可用,是因为premiumFetch()(src/services/premium-fetch.ts)代表调用方注入了上述两种凭证形式之一。 - 速率限制:
- 每个 IP 每分钟 10 个作业(由网关通过
server/_shared/rate-limit.ts中的ENDPOINT_RATE_POLICIES强制执行) - 队列背压在入队前检查 pending Redis 列表;深度
> 100以429拒绝,因此深度100仍可再接受一个作业。
- 每个 IP 每分钟 10 个作业(由网关通过
-
scenarioId— 来自/list-scenario-templates的 id。必填。 -
iso2— 可选的 ISO-3166-1 alpha-2(大写)。将场景限定于某个国家。空字符串表示使用成功种子清单中的国家范围。 -
disruptionPct— 可选的 0–100 整数,仅适用于物理中断场景。省略时使用模板默认值;0 是有效值。关税场景不接受此覆盖参数。持续时间仅用于说明,不参与分数计算。
202 Accepted):
statusUrl— 服务端计算的便捷 URL。不想硬编码状态路径的调用方可直接跟随此 URL(它对jobId进行了 URL 编码)。Location响应头 — 携带与statusUrl相同的轮询 URL,遵循标准 REST 异步作业模式(202+Location→ 轮询直到终态)。
状态码历史 (v1 → v1 → v1) — sebuf 迁移前的端点在成功入队时返回
202 Accepted;sebuf 迁移将其改为 200 OK(sebuf 的 HTTP 注解不支持逐 RPC 状态码配置)。原始的 202 Accepted 契约此后已恢复 — 网关通过状态覆盖旁路通道升级生成的 200,并添加 Location 头。将任何 2xx 视为入队成功。此前关于基于响应体结构分支(response.body.status === "pending")而非状态码的临时指引仍然有效,statusUrl 完全保留原样。轮询作业状态
GET /api/scenario/v1/get-scenario-status?jobId=<jobId>
返回 worker 写入的作业当前状态,或在作业仍在队列中时返回合成的 pending 桩。
- 鉴权:同
/run-scenario - jobId 格式:
scenario:{unix-ms}:{32-hex}(128 位)。旧的scenario:{unix-ms}:{8-char}仍然有效。轮询 URL 不是能力凭证:任务绑定到入队的调用方,其他主体收到 404。
等待中响应 (
200):
200):
200) — result 携带 worker 计算的载荷:
template.name 是 worker 派生的键:物理场景用 + 连接受影响的咽喉点 id,而无物理咽喉点的关税冲击场景使用 tariff_shock。它不是目录标签。
totalImpact 是相对加权分数,而非货币金额或 USD 进口值。对于物理咽喉点场景,worker 为每个匹配的暴露条目计算 exposureScore * (disruptionPct / 100) * costShockMultiplier,然后按国家汇总。对于无受影响咽喉点 id 的关税冲击场景,它使用 vulnerabilityIndex * costShockMultiplier。impactPct 是每个返回国家占 max(maxReturnedTotalImpact, 1) 的份额,上限为 100。该分母下限意味着当每个返回的 totalImpact 都低于 1 时,返回的顶层国家可能低于 100。
失败响应 (200):
pending 和 processing 视为非终态;只有 done 和 failed 是终态。在负载下,pending 和 processing 都可能合理地持续数秒。
错误:
轮询策略
- 首次轮询:入队后约 1 秒。
- 后续轮询:指数退避(1s → 2s → 4s,上限 10s)。
- Worker 通常在 5-30 秒内完成,具体取决于场景复杂度。
- 如果 2 分钟后仍处于 pending,作业可能已失效 — 请重新入队。
结果覆盖证据
result 返回 scenarioId、scopedIso2、computedAt 和 coverage。coverage.status 为 complete(仅清单范围内)、partial 或 unknown。countryIds、hs2Codes、manifestFetchedAt 标明清单范围与缓存时间。records 按国家/HS2 区分 evaluated、missing、malformed、not_seeded;已计算记录包含 rawImpact 和 basis(flow_weighted 或 country_route_fallback)。缺失记录不是零;有效零影响仍保留。旧结果没有覆盖信息时按未知处理。
totalImpact 是模型分数,不是货币或贸易损失。impactPct 是相对分数。fetchedAt 是缓存日期,不能充当贸易观察日期;观察日期未知时明确标为未知。浏览器 JSON 导出使用已捕获的同一份结果。