跳转到主要内容
The scenarios API 是建立在 WorldMonitor 咽喉点 + 贸易数据集之上的 PRO 专属、作业排队接口。调用方将命名的场景模板加入队列(可选国家),然后轮询 job-id 直到 worker 完成。若未提供国家,v1 仅在种子报告集上计算:USCNRUIRINTW
本服务由 proto 支持并已纳入已发布的 OpenAPI 包 — 参见 proto/worldmonitor/scenario/v1/service.proto/api/ScenarioService.openapi.yaml。本页在生成的参考文档之上补充迁移说明和示例。
旧版 v1 URL 别名 — sebuf 迁移 (#3207) 重命名了三个 v1 端点以与 proto RPC 名称对齐。旧 URL 作为轻量别名保留,以便现有集成继续工作:
旧 URL规范 URL
POST /api/scenario/v1/runPOST /api/scenario/v1/run-scenario
GET /api/scenario/v1/statusGET /api/scenario/v1/get-scenario-status
GET /api/scenario/v1/templatesGET /api/scenario/v1/list-scenario-templates
在新代码中请优先使用规范 URL — 别名将在下一次 v1→v2 中断时废弃(追踪于 #3282)。

列出模板

GET /api/scenario/v1/list-scenario-templates

返回预定义场景模板的目录。缓存 public, max-age=3600 响应 — 使用已上线的模板之一(server/worldmonitor/supply-chain/v1/scenario-templates.ts)的简略示例:
{
  "templates": [
    {
      "id": "hormuz-tanker-blockade",
      "name": "Hormuz Strait Tanker Blockade",
      "affectedChokepointIds": ["hormuz_strait"],
      "disruptionPct": 100,
      "durationDays": 14,
      "affectedHs2": ["27", "29"],
      "costShockMultiplier": 2.10
    }
  ]
}
撰写本文时其他已上线的模板包括:taiwan-strait-full-closuresuez-bab-simultaneouspanama-drought-50pctrussia-baltic-grain-suspensionus-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 列表;深度 > 100429 拒绝,因此深度 100 仍可再接受一个作业。
请求
{
  "scenarioId": "hormuz-tanker-blockade",
  "iso2": "US"
}
  • scenarioId — 来自 /list-scenario-templates 的 id。必填。
  • iso2 — 可选的 ISO-3166-1 alpha-2(大写)。将场景限定于某个国家。空字符串表示 worker 使用 v1 种子报告集:USCNRUIRINTW
响应(202 Accepted
{
  "jobId": "scenario:1713456789012:a1b2c3d4",
  "status": "pending",
  "statusUrl": "/api/scenario/v1/get-scenario-status?jobId=scenario%3A1713456789012%3Aa1b2c3d4"
}
  • 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 完全保留原样。
错误
状态码message原因
400Validation failed (包含 scenarioId)缺失或未知的 scenarioId
400Validation failed (包含 iso2)格式错误的 iso2
403PRO subscription required非 PRO
405POST 方法(由 sebuf service-config 强制)
429Too many requests每 IP 10 次/分钟的网关速率限制
429Scenario queue is at capacity, please try again later入队前 pending 队列深度大于 100
502Failed to enqueue scenario jobRedis 入队失败

轮询作业状态

GET /api/scenario/v1/get-scenario-status?jobId=<jobId>

返回 worker 写入的作业当前状态,或在作业仍在队列中时返回合成的 pending 桩。
  • 鉴权:同 /run-scenario
  • jobId 格式scenario:{unix-ms}:{8-char-suffix} — 严格校验以防路径遍历
状态生命周期
status何时发生
pending作业已入队但 worker 尚未处理。当无 Redis 记录时由状态处理器合成。
processingWorker 已取出作业并开始计算。
doneWorker 成功完成;result 已填充。
failedWorker 遇到计算错误;error 已填充。
等待中响应 (200)
{ "status": "pending", "error": "" }
处理中响应 (200)
{ "status": "processing", "error": "" }
完成响应 (200)result 携带 worker 计算的载荷:
{
  "status": "done",
  "error": "",
  "result": {
    "affectedChokepointIds": ["hormuz_strait"],
    "topImpactCountries": [
      { "iso2": "US", "totalImpact": 150.0, "impactPct": 100 }
    ],
    "template": {
      "name": "hormuz_strait",
      "disruptionPct": 100,
      "durationDays": 14,
      "costShockMultiplier": 2.10
    }
  }
}
在状态载荷中,template.name 是 worker 派生的键:物理场景用 + 连接受影响的咽喉点 id,而无物理咽喉点的关税冲击场景使用 tariff_shock。它不是目录标签。 totalImpact 是相对加权分数,而非货币金额或 USD 进口值。对于物理咽喉点场景,worker 为每个匹配的暴露条目计算 exposureScore * (disruptionPct / 100) * costShockMultiplier,然后按国家汇总。对于无受影响咽喉点 id 的关税冲击场景,它使用 vulnerabilityIndex * costShockMultiplierimpactPct 是每个返回国家占 max(maxReturnedTotalImpact, 1) 的份额,上限为 100。该分母下限意味着当每个返回的 totalImpact 都低于 1 时,返回的顶层国家可能低于 100。 失败响应 (200)
{ "status": "failed", "error": "computation_error" }
轮询循环:将 pendingprocessing 视为非终态;只有 donefailed 是终态。在负载下,pending 和 processing 都可能合理地持续数秒。 错误
状态码message原因
400Validation failed (包含 jobId)缺失或格式错误的 jobId
403PRO subscription required非 PRO
405GET 方法(由 sebuf service-config 强制)
502Failed to fetch job statusRedis 读取失败

轮询策略

  • 首次轮询:入队后约 1 秒。
  • 后续轮询:指数退避(1s → 2s → 4s,上限 10s)。
  • Worker 通常在 5-30 秒内完成,具体取决于场景复杂度。
  • 如果 2 分钟后仍处于 pending,作业可能已失效 — 请重新入队。