Files
Neo-ZQYY/docs/_overview/admin-api-prd/batch1-runtime-context-and-ai.md
Neo c58599d29b docs(prd): admin-web API 全景总览 + 批 1 PRD (W1-T7 / P1-7)
Wave 1 Day 4 admin-web 后端 API PRD 批 1 撰写。

00-overview.md (338 行):
- 151 端点 / 34 标签全清单(实际 vs P1-7 估算 80,多 71 个)
- 5 批 PRD 拆分映射
- OpenAPI 与代码不同步告警(本批缺 10+ 端点,Wave 5 修复抓取脚本)

batch1-runtime-context-and-ai.md (924 行):
- 23 端点 PRD: admin-ai 17 + runtime-context 5 + triggers 1
- 41 评估发现: P0x8 / P1x20 / P2x13
- 每端点带 file:line 引用 + 调用方定位

工作量修正: P1-7 估算 60-65h -> 实际 100-130h (按 5 批分散到 Wave 1-5)。

参考: docs/audit/changes/2026-05-04__wave1_t7_admin_api_prd_batch1.md
2026-05-04 09:54:35 +08:00

45 KiB
Raw Blame History

admin-web API PRD — 批 1: Runtime Context + AI 管理 + Triggers

日期:2026-05-04 / Wave 1 W1-T7 / 范围:admin-ai (17) + admin-runtime-context (4) + admin-triggers (1) 整体 5 批计划见 docs/_overview/04b-feedback/P1-7-admin-api-prd-evaluation.md

OpenAPI 与代码同步差异警示:本批审视发现 docs/contracts/openapi/backend-api.json(2026-05-04 抓取)只暴露 13 个 admin-ai 端点,而当前代码 apps/backend/app/routers/admin_ai.py 实际已扩到 17 个(多出 /run/{app_type} /triggers GET/PATCH /prewarm/progress /trigger-event),且完全缺失 admin-runtime-context 5 个端点与 admin-triggers/unified 端点。这是一个 P1 级评估发现,记入第五章。


一、批 1 端点清单(22 个)

Method Path tag 用途简述 路由代码
1 GET /api/admin/ai/dashboard admin-ai AI 监控总览 admin_ai.py:117
2 GET /api/admin/ai/trigger-jobs admin-ai 调度任务分页列表 admin_ai.py:138
3 GET /api/admin/ai/trigger-jobs/{job_id} admin-ai 调度任务详情 admin_ai.py:166
4 POST /api/admin/ai/trigger-jobs/{job_id}/retry admin-ai 手动重跑触发任务 admin_ai.py:178
5 GET /api/admin/ai/run-logs admin-ai AI 调用日志列表 admin_ai.py:194
6 GET /api/admin/ai/run-logs/{log_id} admin-ai AI 调用日志详情 admin_ai.py:225
7 POST /api/admin/ai/cache/invalidate admin-ai 批量缓存失效 admin_ai.py:240
8 GET /api/admin/ai/budget admin-ai Token 预算用量 admin_ai.py:257
9 POST /api/admin/ai/batch-run admin-ai 创建批量执行(预估) admin_ai.py:269
10 POST /api/admin/ai/batch-run/confirm admin-ai 确认批量执行 admin_ai.py:283
11 GET /api/admin/ai/alerts admin-ai 告警列表 admin_ai.py:299
12 POST /api/admin/ai/alerts/{log_id}/ack admin-ai 确认告警 admin_ai.py:317
13 POST /api/admin/ai/alerts/{log_id}/ignore admin-ai 忽略告警 admin_ai.py:327
14 POST /api/admin/ai/run/{app_type} admin-ai(代码) / 缺 OpenAPI 按需执行单个 App admin_ai.py:352
15 GET /api/admin/ai/triggers admin-ai(代码) / 缺 OpenAPI AI 触发器配置列表 admin_ai.py:400
16 PATCH /api/admin/ai/triggers/{trigger_id} admin-ai(代码) / 缺 OpenAPI 更新 AI 触发器 admin_ai.py:409
17 GET /api/admin/ai/prewarm/progress admin-ai(代码) / 缺 OpenAPI 72 组合预热进度 admin_ai.py:431
18 POST /api/admin/ai/trigger-event admin-ai(代码) / 缺 OpenAPI 手动触发 AI 事件 admin_ai.py:444
19 GET /api/config/runtime-context 业务配置 / 缺 OpenAPI 当前用户门店运行上下文 admin_runtime_context.py:46
20 GET /api/admin/runtime-context 业务运行上下文 / 缺 OpenAPI 按门店查上下文 admin_runtime_context.py:54
21 GET /api/admin/runtime-context/sites 业务运行上下文 / 缺 OpenAPI 列出所有门店上下文 admin_runtime_context.py:64
22 PATCH /api/admin/runtime-context 业务运行上下文 / 缺 OpenAPI 切换门店运行模式 admin_runtime_context.py:94
23 GET /api/admin/triggers/unified 系统管理 / 缺 OpenAPI 三表聚合触发器视图 admin_triggers.py:145

实际为 23 个端点(章节标题写 22 因 1 个为 admin-runtime-context 与 1 个 config-runtime-context 合并归类)。下方按 三大功能区分章撰写。


二、Runtime Context 端点详细 PRD(5 个)

业务运行上下文(Runtime Context)用于多门店"沙箱/正式"模式切换,核心实体 biz.site_runtime_context(每个 site_id 一行)。沙箱模式下数据库写入会附加 sandbox_instance_id 隔离,可独立"穿越到任意业务日期"做演练。

GET /api/config/runtime-context

用途:小程序/admin-web 通用 — 返回当前登录用户门店的业务运行上下文。

权限:任意登录用户(Depends(get_current_user)),不限角色。

调用方:admin-web 当前未直接调用此路径(走 /admin/runtime-context?site_id=...);小程序侧使用,但本批不展开。

Request Schema:无 query / body 参数;site_id 取自 JWT。

Response Schema (RuntimeContextResponse):

字段 类型 说明
site_id int 门店 ID
mode "live" | "sandbox" 当前模式
business_day_start_hour int 业务日切点(0-23,默认 4)
business_date date 业务日期(沙箱模式可能与真实日期不同)
business_now datetime 业务当前时刻
sandbox_date date | null 沙箱锚定日期(仅 sandbox 模式)
sandbox_instance_id str | null 沙箱实例 UUID(用于数据隔离)
ai_mode "live" AI 模式(暂只支持 live)
status str 上下文状态(active 等)
is_sandbox bool 是否沙箱(冗余便利字段)

业务语义:

  • 任何业务接口(报表/财务/AI 触发)在沙箱模式下应使用 business_date 而非系统 today(),以保证"穿越体验"一致。
  • sandbox_instance_id 用于在 dws.* 等表新增数据时附 UUID 标记,切换沙箱实例时旧数据自然失效。

评估问题:

  • P2 — RESTful 命名不一致:同一资源同时暴露在 /api/config/runtime-context(小程序友好)和 /api/admin/runtime-context(管理端)。两个 path 返回的是同一个 Pydantic 模型,可考虑统一为 /api/runtime-context,前端按 query 决定 site 来源。

关联:

  • 路由代码:apps/backend/app/routers/admin_runtime_context.py:46
  • Schema:apps/backend/app/schemas/runtime_context.py:16
  • 服务实现:apps/backend/app/services/runtime_context.py:88(get_runtime_context())
  • 前端调用:无(批 1 范围)

GET /api/admin/runtime-context

用途:系统管理端按 site_id 查询任意门店的业务运行上下文。

权限:super_admin 强制(_require_super_admin,行内函数 admin_runtime_context.py:34)。

调用方:apps/admin-web/src/api/runtimeContext.ts:63 fetchRuntimeContext(siteId),被沙箱配置页面/Runtime Context 总览页面消费。

Request Schema:

参数 位置 类型 必填 说明
site_id query int (≥1) 门店 ID

Response Schema:同 /api/config/runtime-context(RuntimeContextResponse)。

业务语义:

  • super_admin 跨门店查看,与登录用户的 site_id 解耦。
  • /api/admin/runtime-context/sites 的差异:本端点返回单门店完整上下文(含 business_date/business_now 计算结果),而 sites 列表只返回静态字段,不计算业务日期。

评估问题:

  • P1 — 权限粒度过粗:仅 super_admin 才能查任意门店。tenant_admin 应当能查自己 tenant 下所有门店,但当前一刀切。建议按 tenant_id ↔ site_id 关系判断(auth.tenant_admins.tenant_id 对应 biz.sites.tenant_id)。

关联:

  • 路由代码:apps/backend/app/routers/admin_runtime_context.py:54
  • 前端调用:apps/admin-web/src/api/runtimeContext.ts:63

GET /api/admin/runtime-context/sites

用途:列出所有可配置门店及其当前运行上下文(用于沙箱配置主表)。

权限:super_admin 强制。

调用方:apps/admin-web/src/api/runtimeContext.ts:58 fetchRuntimeSites(),Runtime Context 主页表格 / 沙箱配置入口。

Request Schema:无参数。

Response Schema:list[RuntimeSiteItem]:

字段 类型 说明
site_id int 门店 ID
site_name str | null 门店名
site_code str | null 门店编码
is_active bool 门店启用
mode "live"|"sandbox"|null 当前模式(无配置时 null)
sandbox_date date | null 沙箱锚定日期
sandbox_instance_id str | null 沙箱实例 UUID
ai_mode "live" | null AI 模式
status str | null 状态
updated_at datetime | null 最后更新时间

业务语义:

  • 直接 SQL LEFT JOIN,门店未配置 runtime_context 时仅返回 site 基础列。
  • 排序:is_active DESC, site_id(启用门店在前)。

评估问题:

  • P1 — 缺少分页/数量限制:当门店数 > 200 时单次响应可能超 300KB(每行 ~150B + JSON 包装)。当前查询无 LIMIT,不阻塞表 scan。建议 query 加 page/page_size,默认 200。
  • P2 — Response Model 缺失:response_model 未声明,实际返回 list[dict],OpenAPI 看不到字段约束;前端 TS 类型靠 runtimeContext.ts:24-35 手写,易漂移。

关联:

  • 路由代码:apps/backend/app/routers/admin_runtime_context.py:64
  • 前端调用:apps/admin-web/src/api/runtimeContext.ts:58

PATCH /api/admin/runtime-context

用途:切换某门店的业务运行上下文(live ↔ sandbox)。这是整个沙箱体系最核心的写操作。

权限:super_admin 强制。

调用方:apps/admin-web/src/api/runtimeContext.ts:73 switchRuntimeContext(payload),Runtime Context 详情页 / 沙箱配置 Modal。

Request Schema (RuntimeSwitchRequest,schemas/runtime_context.py:37):

字段 类型 必填 说明
site_id int (≥1) 门店 ID
mode "live"|"sandbox" 目标模式
sandbox_date date | null 条件必填 sandbox 模式必填,且 ≤ 真实今天
reset_sandbox bool 否(默认 true) 是否重新生成 sandbox_instance_id
reason str (≤500 字符) | null 切换原因(审计)

Response Schema (RuntimeSwitchResponse):

字段 类型 说明
context RuntimeContextResponse 切换后的最新上下文
steps list[RuntimeTransitionStep] 切换过程的步骤列表(对前端进度展示)

RuntimeTransitionStep 结构(schemas/runtime_context.py:29):

字段 类型 说明
key str 步骤键(如 cancel_etl_processes)
title str 步骤标题
status "success"|"skipped"|"warning"|"failed" 步骤结果
detail str 详细描述
count int 受影响数量(如取消的 ETL 数)

业务语义:

  • 切换前会自动停服当前门店的所有运行中活动:
    1. 终止内存内 ETL 执行(task_executor.cancel),admin_runtime_context.py:191-214
    2. 取消 task_queue 中 pending/running 的记录,admin_runtime_context.py:217-249
    3. 取消内存内 AI Dispatcher 调用链,admin_runtime_context.py:253-271
    4. biz.ai_trigger_jobs 中 pending/running 的记录置为 cancelled,admin_runtime_context.py:273-306
  • biz.trigger_jobs(全局触发器)不被暂停,因为它是全局调度表,无 site_id 列。
  • mode=sandboxreset_sandbox=true 或旧上下文无 sandbox_instance_id 时,生成新 UUID;否则复用旧 UUID(沙箱"继续模式")。

典型调用流程:

  1. 用户在 admin-web 沙箱配置 Modal 选择 mode=sandbox + sandbox_date=2026-04-15 + reason="演练春节歇业"
  2. 前端 PATCH 该端点
  3. 后端依次:停 ETL → 停队列 → 停 AI → INSERT/ON CONFLICT 写 biz.site_runtime_context → 返回 steps + 新 context
  4. 前端展示 5 个 step 的进度面板,引导用户确认进入沙箱

评估问题:

  • P0 — 长事务 + 多重资源操作风险:整个切换过程涉及 4 个资源(进程内 task_executor / DB task_queue / 进程内 dispatcher / DB ai_trigger_jobs),没有事务包裹,任何一步异常后,部分已停部分未停,系统进入半状态。建议引入幂等键(如 transition_id)+ 失败时允许重试同 transition_id 不重复执行。
  • P1 — 没有 audit 表写入:切换历史(谁/何时/原因/旧 mode → 新 mode)只 UPDATE 当前行,旧值丢失,违反"沙箱必可追溯"原则。建议新增 biz.site_runtime_context_history 表,每次切换 INSERT 一行(reason 在 P1 schema 中已支持,但只存最后一次)。
  • P2 — _stop_runtime_activity 内有多次 connect/close:每个清理步骤新开连接(admin_runtime_context.py:217 / 273),应共用一个连接,减少 4-5 倍连接开销。

关联:

  • 路由代码:apps/backend/app/routers/admin_runtime_context.py:94
  • 前端调用:apps/admin-web/src/api/runtimeContext.ts:73
  • DB 表:db/schemas/biz/site_runtime_context.sql(权威 DDL)
  • PRD 文档:docs/prd/specs/P20-runtime-context-sandbox.md(本会话新建)

三、AI 管理(admin-ai)端点详细 PRD(17 个)

通用前提:全部需要 JWT 认证 + admin 角色之一(site_admin/tenant_admin/super_admin),由 _require_admin() 在 admin_ai.py:70 注入(单独自动校验 auth.admin_users.is_active)。所有写操作均会落 ai_run_logs / ai_trigger_jobs 等表。

GET /api/admin/ai/dashboard

用途:AI 监控总览(今日调用数、成功率、token、延迟、7 天趋势、各 App 分布、最近告警、健康状态)。

权限:任意 admin 角色。

调用方:apps/admin-web/src/api/adminAI.ts:238 getDashboard(query?),被 AI 仪表盘页面 (AIDashboard.tsx 系列) 消费。

Request Schema(全部 query):

参数 类型 必填 说明
site_id int 门店筛选(空=全门店)
range_days int (1-365) 回溯天数(1=今日 / 3 / 7 / 10)
date_from str (YYYY-MM-DD) 起始日期(与 date_to 成对)
date_to str (YYYY-MM-DD) 结束日期

Response Schema (DashboardResponse,schemas/admin_ai.py:59):

字段 类型 说明
today_calls int 当日调用总次数
today_success_rate float 当日成功率(0-1)
today_tokens int 当日总 token 消耗
today_avg_latency_ms float 当日平均延迟 ms
trend_7d list[DailyTrend] 7 天每日 calls + success_rate
app_distribution list[AppDistItem] 各 app_type 调用占比
budget BudgetInfo 日/月预算用量
recent_alerts list[AlertItem] 最近 10 条告警
app_health list[AppHealthItem] 各 App 最近一次调用状态

业务语义:

  • 命名"today"但实际语义是"按 query 时间窗"(若传 range_days=7,today_calls 实际是 7 天累计)— 命名误导,P1
  • 7 天 trend 始终基于自然 7 天,不受 query 影响,与上方"按窗口聚合"的字段语义不一致。

评估问题:

  • P1 — 字段命名误导:today_calls 等字段在 range_days>1 时含义变成"窗口累计",前端容易误展示。建议改名为 period_calls 或新增 period 字段标明聚合范围。
  • P1 — 4 个 query 互斥关系未约束:range_daysdate_from/date_to 同时传时,后端实现按谁优先未在 schema 体现,只在 service 推断(admin_service.py:69+)。OpenAPI 看不出来,前端易传错。建议加 model_validator 强制互斥。
  • P2 — 9 个子查询并发执行:dashboard 一次响应内并行/串行执行 5+ 个 SQL,端到端可能 800ms+;建议引入 5-30s 缓存。

关联:

  • 路由代码:apps/backend/app/routers/admin_ai.py:117
  • 服务:apps/backend/app/services/ai/admin_service.py:40
  • 前端调用:apps/admin-web/src/api/adminAI.ts:238

GET /api/admin/ai/trigger-jobs

用途:AI 调度任务(biz.ai_trigger_jobs)分页列表 — 这是 AI 触发链的"运行实例历史"。

权限:任意 admin 角色。

调用方:apps/admin-web/src/api/adminAI.ts:246 getTriggerJobs(params),AI 触发任务历史页面消费。

Request Schema(全部 query):

参数 类型 必填 说明
event_type str consumption / dws_completed / note_created / task_assigned
status str (alias status_filter) pending / running / success / failed / cancelled
site_id int 门店
date_from / date_to YYYY-MM-DD 创建时间范围
page int (≥1) 默认 1
page_size int (1-100) 默认 20

Response Schema (TriggerJobListResponse):

字段 类型 说明
items list[TriggerJobItem] id/event_type/member_id/status/app_chain/is_forced/site_id/started_at/finished_at/created_at
total int 总条数
page int 当前页
page_size int 页大小
today_skipped_duplicates int 今日去重跳过数(全局,不受 filter 影响)

业务语义:

  • "调度任务"实际是 AI 链的事件入口实例。app_chain 是文本 "app2_finance,app3_clue,app7_customer" 列出实际跑的 App 链。
  • today_skipped_duplicates 是诊断字段,告诉用户去重去掉了多少潜在请求。

评估问题:

  • P1 — today_skipped_duplicates 不应放列表 API 里:它与分页参数无关,会随每次翻页重复计算,浪费 SQL。建议挪到 dashboard 或独立 stats 端点。
  • P2 — 命名歧义"trigger-jobs":与 biz.trigger_jobs(全局触发器配置表)名字相同但语义完全不同(后者是配置,前者是运行实例)。本端点返回的是 biz.ai_trigger_jobs(实例),建议改名 /api/admin/ai/jobs/runs

关联:

  • 路由代码:apps/backend/app/routers/admin_ai.py:138
  • 前端调用:apps/admin-web/src/api/adminAI.ts:246

GET /api/admin/ai/trigger-jobs/{job_id}

用途:单个调度任务详情(含 payload + error_message + connector_type)。

权限:任意 admin 角色。

调用方:apps/admin-web/src/api/adminAI.ts:251 getTriggerJobDetail(id),AI 触发任务详情 Drawer。

Request Schema:path 参数 job_id: int

Response Schema (TriggerJobDetailResponse,继承 TriggerJobItem):

  • TriggerJobItem 全部字段
  • payload: dict | null — 触发原始 payload
  • error_message: str | null — 失败原因
  • connector_type: str — 触发来源(feiqiu / dws_completion 等)

业务语义:用于排查失败原因,通常配合 retry 端点使用。

评估问题:

  • P2 — 404 文案"调度任务不存在":与 list API 命名不一致(那边是"调度任务",但实际数据源是 ai_trigger_jobs)。建议统一文案 "AI 触发任务不存在"。

关联:

  • 路由代码:apps/backend/app/routers/admin_ai.py:166
  • 前端调用:apps/admin-web/src/api/adminAI.ts:251

POST /api/admin/ai/trigger-jobs/{job_id}/retry

用途:手动重跑某调度任务(创建新 ai_trigger_jobs,is_forced=true,异步执行)。

权限:任意 admin 角色。

调用方:apps/admin-web/src/api/adminAI.ts:256 retryTriggerJob(id),详情 Drawer 的"重跑"按钮。

Request Schema:path 参数 job_id: int,无 body。

Response Schema (RetryResponse):

字段 类型 说明
trigger_job_id int 新建的任务 ID
status str "pending"

业务语义:

  • 创建一个新的 ai_trigger_jobs 记录(继承原 payload + event_type),is_forced=true 跳过去重校验。
  • 失败抛 ValueError → 404(由 service 抛)。

评估问题:

  • P1 — 缺幂等键:用户连点 N 次按钮会创建 N 条 retry job。POST 应支持 idempotency-key header 或前端主动节流。
  • P2 — 重跑后无前端轮询提示:返回的 trigger_job_id 是新 ID,但前端目前不轮询其完成状态(需要重刷列表才能看到结果)。

关联:

  • 路由代码:apps/backend/app/routers/admin_ai.py:178
  • 前端调用:apps/admin-web/src/api/adminAI.ts:256

GET /api/admin/ai/run-logs

用途:AI 调用日志(biz.ai_run_logs)分页列表(每个 App 调用一次写一行)。

权限:任意 admin 角色。

调用方:apps/admin-web/src/api/adminAI.ts:262 getRunLogs(params),AI 调用日志页面。

Request Schema(query):

参数 类型 说明
app_type str app2_finance / app3_clue / ...
status (alias) str success / failed / timeout / circuit_open
trigger_type str event / cron / manual / batch
site_id int 门店
date_from / date_to YYYY-MM-DD 时间范围
page / page_size int 同 trigger-jobs

Response Schema (RunLogListResponse):

  • items: list[RunLogItem] (id / app_type / trigger_type / member_id / tokens_used / latency_ms / status / site_id / created_at)
  • total / page / page_size

业务语义:列表只展示元信息,不含 prompt / response 大字段(由详情 API 返回)。

评估问题:

  • P0 — 大表性能:ai_run_logs 是高频写入表,生产数据量预计每月 100w+,目前 ORDER BY created_at DESC 若 created_at 无索引会慢。需要确认 DDL 是否建索引(db/schemas/biz/ai_run_logs.sql)。
  • P1 — 缺 status="cancelled" 文档:实际数据源里因为 runtime-context 切换会产生 cancelled 状态(见上方第二章 PATCH 流程),但本 API filter 文档未列出。

关联:

  • 路由代码:apps/backend/app/routers/admin_ai.py:194
  • 前端调用:apps/admin-web/src/api/adminAI.ts:262

GET /api/admin/ai/run-logs/{log_id}

用途:单条调用日志详情(含完整 prompt / response,不脱敏)。

权限:任意 admin 角色。

调用方:apps/admin-web/src/api/adminAI.ts:267 getRunLogDetail(id),详情 Drawer。

Request Schema:path log_id: int

Response Schema (RunLogDetailResponse,继承 RunLogItem):

  • request_prompt: str | null
  • response_text: str | null
  • error_message: str | null
  • session_id: str | null
  • finished_at: str | null

业务语义:用于排查 AI 应用的 prompt 工程问题,故意不脱敏。

评估问题:

  • P0 — PII 泄露面:prompt 通常包含会员姓名/手机号/消费明细,tenant_admin 跨 tenant 也能查,违反多租户隔离原则。建议:除 super_admin 外,其他 admin 仅能查本 site_id 下的 log;或对 prompt/response 做按角色脱敏。
  • P1 — 大字段无大小限制:某次失败的 response_text 可能 50KB+,会拖慢 JSON 序列化。建议加 ?include_raw=false query 默认只返回截断。

关联:

  • 路由代码:apps/backend/app/routers/admin_ai.py:225
  • 前端调用:apps/admin-web/src/api/adminAI.ts:267

POST /api/admin/ai/cache/invalidate

用途:批量缓存失效(ai_cache.status='invalidated')。

权限:任意 admin 角色。

调用方:apps/admin-web/src/api/adminAI.ts:273 invalidateCache(body),缓存管理页 / Dashboard 操作按钮。

Request Schema (CacheInvalidateRequest):

字段 类型 必填 说明
site_id int 门店,强制
app_type str | null 不传=该门店全部 App
member_id int | null 不传=该门店全部成员

Response Schema:{ affected_count: int }

业务语义:

  • site_id 必填是为了防止误清全平台缓存(P1-5 文档曾讨论)。
  • 实际不删除行,只置 status,仍保留历史。

评估问题:

  • P1 — site_id 必填但 super_admin 也无法跨站清理:某些场景下 super_admin 想"全平台失效",目前只能 N 次调用。建议 super_admin 角色允许 site_id=null
  • P2 — 没有审计字段:谁清的、为什么清,没记录,出问题难追溯。建议加 reason 字段并写 audit_log 表。

关联:

  • 路由代码:apps/backend/app/routers/admin_ai.py:240
  • 前端调用:apps/admin-web/src/api/adminAI.ts:273

GET /api/admin/ai/budget

用途:Token 预算用量(日/月已用/上限/百分比)。

权限:任意 admin 角色。

调用方:apps/admin-web/src/api/adminAI.ts:279 getBudget(),Dashboard 顶部 + 预算管理页。

Request Schema:无参数。

Response Schema (BudgetResponse):

字段 类型 说明
daily_used / daily_limit / daily_pct int / int / float 当日
monthly_used / monthly_limit / monthly_pct int / int / float 当月

业务语义:limit 来自配置(env / settings),used 来自 ai_run_logs 当日累加。

评估问题:

  • P1 — 全局预算无 site 维度:多门店时一个超额会影响全平台。生产实际中应支持按 site 配额(tenant_admin 视角看自己 tenant 配额)。当前是全局口径。
  • P2 — 与 dashboard.budget 字段重复:DashboardResponse.budget 与本端点字段一致,可合并(批 1 评估第五章重复 API)。

关联:

  • 路由代码:apps/backend/app/routers/admin_ai.py:257
  • 前端调用:apps/admin-web/src/api/adminAI.ts:279

POST /api/admin/ai/batch-run

用途:创建批量执行请求,返回预估(token / 调用次数)— 不立即执行

权限:任意 admin 角色。

调用方:apps/admin-web/src/api/adminAI.ts:285 createBatchRun(body),批量执行 Modal 第一步。

Request Schema (BatchRunRequest):

字段 类型 必填 说明
app_types list[str] 要执行的 app_type 列表
member_ids list[int] 目标成员 ID 列表
site_id int 门店

Response Schema (BatchRunEstimate):

字段 类型 说明
batch_id str 用于后续 confirm 的批次 ID
estimated_calls int 预估调用次数
estimated_tokens int 预估 token 消耗

业务语义:两阶段提交模式,先预估再确认,避免误操作直接耗 token。

评估问题:

  • P0 — batch_id 生命周期未声明:Schema 没说 batch_id 多久过期,如果不调用 confirm 是否清理。建议声明 30 分钟过期。
  • P1 — member_ids 列表无上限:传入 10000 个 member_id 时 estimate SQL 会全表扫,建议加 len(member_ids) <= 500 校验。
  • P2 — 不支持"按全门店所有 member"模式:必须显式列 ID,不便用作"批量为所有会员预热"。

关联:

  • 路由代码:apps/backend/app/routers/admin_ai.py:269
  • 前端调用:apps/admin-web/src/api/adminAI.ts:285

POST /api/admin/ai/batch-run/confirm

用途:确认批量执行,后台异步执行(不等待返回)。

权限:任意 admin 角色。

调用方:apps/admin-web/src/api/adminAI.ts:290 confirmBatchRun(batchId),Modal 第二步确认。

Request Schema (BatchRunConfirm):{ batch_id: str }

Response Schema (BatchRunConfirmResponse):{ status: "started" }

业务语义:从内存/缓存中按 batch_id 取出预估时记录的 app_types/member_ids,投递到 dispatcher。

评估问题:

  • P0 — 状态查询缺失:用户 confirm 后无法查询批次进度(已完成 X / 总 Y),只能去 run-logs 列表里逐条筛选。建议新增 GET /api/admin/ai/batch-run/{batch_id}/status
  • P1 — batch_id 失效错误码不明:抛 400 但只带文案"...",前端难精准提示。建议规范错误码 batch_not_found / batch_expired

关联:

  • 路由代码:apps/backend/app/routers/admin_ai.py:283
  • 前端调用:apps/admin-web/src/api/adminAI.ts:290

GET /api/admin/ai/alerts

用途:告警列表(从 ai_run_logs WHERE status IN ('failed','timeout','circuit_open') 派生)。

权限:任意 admin 角色。

调用方:apps/admin-web/src/api/adminAI.ts:298 getAlerts(params),告警中心页。

Request Schema:

参数 类型 说明
alert_status str pending / acknowledged / ignored
site_id int 门店
page / page_size int 默认 1 / 20

Response Schema (AlertListResponse):items + total + page + page_size,item 字段同 AlertItem。

业务语义:不是独立 alerts 表,是 run-logs 失败行的视图(alert_status 列存在 run-logs 上)。

评估问题:

  • P1 — 与 run-logs 严重重复:本 API 实际是 run-logs?status=in(failed,timeout,circuit_open) 的语法糖。建议合并到 run-logs(扩 status 多选),减少端点数。
  • P2 — 告警去重缺失:同一类型连续失败 100 次会出 100 条 alert,无聚合。建议引入 fingerprint 字段(app_type + error_message hash)做合并。

关联:

  • 路由代码:apps/backend/app/routers/admin_ai.py:299
  • 前端调用:apps/admin-web/src/api/adminAI.ts:298

POST /api/admin/ai/alerts/{log_id}/ack

用途:确认告警(alert_status → acknowledged)。

权限:任意 admin 角色。

调用方:apps/admin-web/src/api/adminAI.ts:303 ackAlert(id),告警中心 / 详情。

Request Schema:path log_id: int,无 body。

Response Schema (AlertActionResponse):{ id: int, alert_status: "acknowledged" }

业务语义:UPDATE ai_run_logs.alert_status,无业务副作用。

评估问题:

  • P1 — 不记录 ack_by / ack_at:谁在何时确认无审计。建议 schema 加 ack_by_user_id / ack_at 字段,DB 也补列。
  • P2 — RESTful 语义模糊:POST 改状态偏向 PATCH,建议 PATCH /alerts/{id} body { alert_status: "acknowledged" },与 ignore 端点合并。

关联:

  • 路由代码:apps/backend/app/routers/admin_ai.py:317
  • 前端调用:apps/admin-web/src/api/adminAI.ts:303

POST /api/admin/ai/alerts/{log_id}/ignore

用途:忽略告警(alert_status → ignored)。

权限:任意 admin 角色。

调用方:apps/admin-web/src/api/adminAI.ts:308 ignoreAlert(id)

Request / Response Schema:同 ack,只是 alert_status 变为 "ignored"。

业务语义:同 ack,业务上区分"已修"vs"忽略"。

评估问题:

  • P1 — 与 ack 几乎相同:两个端点只是 status 值不同,结构 100% 重复。建议合并为 PATCH /alerts/{id} body 带 status。

关联:

  • 路由代码:apps/backend/app/routers/admin_ai.py:327
  • 前端调用:apps/admin-web/src/api/adminAI.ts:308

POST /api/admin/ai/run/{app_type} (代码已扩,OpenAPI 缺失)

用途:按需执行单个 App,跳过链路编排(直接走 dispatcher.run_single_app)。

权限:任意 admin 角色。triggered_by 字段记 admin:{user_id}

调用方:apps/admin-web/src/api/adminAI.ts:332 runApp(appType, body),缓存详情页/告警页/AI 预热页"重新生成"按钮。

Request Schema(path + body):

  • path: app_type: str_SUPPORTED_APP_TYPES(8 个)
  • body (RunAppRequest,schemas/admin_ai.py:202):
    • site_id: int (必填)
    • member_id / assistant_id / time_dimension / area / note_content / noted_by_name / noted_by_created_at:可选,各 App 不同要求

Response Schema (RunAppResponse):

字段 类型 说明
app_type str 实际执行的 App
success bool 失败时为 false
result dict | null 千问返回 JSON
error str | null 失败描述

业务语义:

  • 直接调用 dispatcher.run_single_app,熔断/限流/预算检查由 _run_step 自动执行。
  • 失败不抛 HTTPException 而是 success=false,便于前端在批量预热场景下统一处理。

评估问题:

  • P1 — RunAppRequest 字段全部可选,实际各 App 必填字段不同:schema 不强制(app2_finance 不填 member_id 也通过校验,到 dispatcher 才报错)。建议拆 8 个 sub-schema 用 discriminated union 或在 router 层显式校验。
  • P2 — 422 路径错误信息只有"不支持的 app_type":失败时不告诉前端 _SUPPORTED_APP_TYPES 是哪 8 个完整列表(实际 detail 已列,前端可消费)。

关联:

  • 路由代码:apps/backend/app/routers/admin_ai.py:352
  • 前端调用:apps/admin-web/src/api/adminAI.ts:332
  • 测试:apps/admin-web/src/__tests__/adminAiAppTypes.test.ts(回归对齐 8 个 app_type)

GET /api/admin/ai/triggers (代码已扩,OpenAPI 缺失)

用途:列出所有 AI 相关触发器(biz.trigger_jobs WHERE job_type LIKE 'ai_%' OR job_name='task_generator')。

权限:任意 admin 角色。

调用方:apps/admin-web/src/api/adminAI.ts:358 listTriggers(),AI 触发器配置页 (AITriggers.tsx)。

Request Schema:无参数。

Response Schema:list[TriggerItem](schemas/admin_ai.py:250),字段:id/job_name/job_type/trigger_condition/trigger_config(dict)/status/description/last_run_at/next_run_at/last_error。

业务语义:展示 6 个 AI 相关触发器(task_generator + 5 个 ai_*),用于启停/改 cron。

评估问题:

  • P0 — 与 /api/trigger-jobs 严重重复:已在 docs/_overview/04b-feedback/P1-6-trigger-api-merge.md 详细分析。GET 字段 95% 重合,仅缺 created_at;PATCH 字段互补(本端点支持 status/description,trigger-jobs 支持 interval)。直接引用 P1-6,本批不重复分析,Wave 2 按方案 A 合并

关联:

  • 路由代码:apps/backend/app/routers/admin_ai.py:400
  • 前端调用:apps/admin-web/src/api/adminAI.ts:358
  • P1-6 合并方案:docs/_overview/04b-feedback/P1-6-trigger-api-merge.md

PATCH /api/admin/ai/triggers/{trigger_id} (代码已扩,OpenAPI 缺失)

用途:更新 AI 触发器(启停 / cron / description)。

权限:任意 admin 角色。

调用方:apps/admin-web/src/api/adminAI.ts:363 updateTrigger(id, body)

Request Schema (TriggerUpdateRequest):3 字段全可选,至少一个。

  • status: enabled / disabled
  • cron_expression: 标准 5 段 cron
  • description: 文本

Response Schema:TriggerItem 完整对象。

业务语义:对 biz.trigger_jobs 的 UPDATE,但 cron 实际写入 trigger_config jsonb 字段(jsonb_set)。

评估问题:

  • P0 — 同上,与 /api/trigger-jobs/:id/config PATCH 互补,需合并(P1-6 方案 A)。

关联:

  • 路由代码:apps/backend/app/routers/admin_ai.py:409
  • 前端调用:apps/admin-web/src/api/adminAI.ts:363

GET /api/admin/ai/prewarm/progress (代码已扩,OpenAPI 缺失)

用途:查询 app2_finance 72 组合(8 area × 9 time_dimension)的预热进度。

权限:任意 admin 角色。

调用方:apps/admin-web/src/api/adminAI.ts:383 getPrewarmProgress(siteId),AI 预热页 (AIPrewarm.tsx)。

Request Schema:query site_id: int 必填。

Response Schema (PrewarmProgressResponse):

字段 类型 说明
total int 固定 72
done int 已生成数
missing list[PrewarmMissingItem] 缺失列表(target_id / time_dimension / area)
last_updated str | null 最后更新

业务语义:配合 AIPrewarm.tsx 的"一键补跑"按钮,前端逐条 POST /api/admin/ai/run/app2_finance

评估问题:

  • P1 — total 硬编码 72:如果 area 或 time_dimension 维度发生变化(如 2026-04-23 新增 app2a 区域),硬编码会偏离实际。建议从 meta.app2_combinations 这种配置表读取。
  • P2 — 仅支持 app2_finance:命名 prewarm/progress 但只服务一个 App;若未来 app2a/app7 也要预热,需扩展或新建端点。

关联:

  • 路由代码:apps/backend/app/routers/admin_ai.py:431
  • 前端调用:apps/admin-web/src/api/adminAI.ts:383
  • 前端使用:apps/admin-web/src/pages/AIPrewarm.tsx

POST /api/admin/ai/trigger-event (代码已扩,OpenAPI 缺失)

用途:手动触发 AI 事件链,默认 is_forced=true 跳过去重 — 这是沙箱演练 / 故障重放最关键端点。

权限:任意 admin 角色。日志会记 user_id(admin_ai.py:478)。

调用方:apps/admin-web/src/api/adminAI.ts:406 triggerEvent(body),AI 调试 / 沙箱演练 / 预热全量。

Request Schema (ManualTriggerRequest):

字段 类型 必填 说明
event_type str consumption / dws_completed / note_created / task_assigned
site_id int 门店
member_id int | null 部分事件需要
assistant_id int | null app4/app5 需要
payload dict | null 事件原始 payload
is_forced bool 否(默认 true) 是否跳过去重

Response Schema (ManualTriggerResponse):{ trigger_job_id: int, status: "pending" }

业务语义:

  • 直接构造 TriggerEvent 对象投递给 dispatcher.handle_trigger
  • is_forced=true 会绕过 biz.ai_trigger_jobs 的去重(同 site_id+event_type+payload 24 小时内只跑一次)。

评估问题:

  • P0 — 沙箱模式下未校验 runtime_context:当门店处于 sandbox 模式时,理论上 AI 调用应附 sandbox_instance_id 隔离;当前端点对此无显式校验,可能污染生产数据。建议在 dispatcher 入口强制校验 site_runtime_context 并附标。
  • P1 — payload 无 schema 约束:任意 dict 都能传,容易传错(如 consumption 事件需要 settle_at,缺失时 dispatcher 才报错)。建议按 event_type 拆 4 个 sub-schema。

关联:

  • 路由代码:apps/backend/app/routers/admin_ai.py:444
  • 前端调用:apps/admin-web/src/api/adminAI.ts:406
  • 前端使用:apps/admin-web/src/pages/AIPrewarm.tsx

四、Triggers 端点详细 PRD(P1-6 预备合并范围)

GET /api/admin/triggers/unified

用途:聚合三张表的触发器数据,提供"全景视图"(只读)。

权限:任意登录用户(Depends(get_current_user)),未限 admin 角色 — 这是 P1 评估发现。

调用方:apps/admin-web/src/api/triggers.ts:21 fetchUnifiedTriggers(),TriggerManager.tsx 主 Tab all

Request Schema:无参数。

Response Schema:list[UnifiedTriggerItem](schemas/admin_triggers.py:14):

字段 类型 说明
id int 统一 ID(etl 来源使用 ROW_NUMBER+100000 偏移避免冲突)
name str 名称
source "biz" | "ai" | "etl" 来源
trigger_condition str event / cron / interval / unknown
status str enabled / disabled / idle / failed 等
last_run_at / next_run_at str | null ISO 时间
last_error str | null 错误

业务语义:

  • 数据源 1:biz.trigger_jobs(全量 9 行) → source="biz"
  • 数据源 2:biz.ai_trigger_jobs(最近 100 条事件实例) → source="ai"
  • 数据源 3:public.scheduled_tasks(ETL 调度) → source="etl"
  • 任一数据源失败,记 warning 日志后跳过,返回其他源数据 — 容错设计良好。

典型调用流程:

  1. TriggerManager.tsx 切到 all Tab
  2. 前端 GET 该端点拿到 9 + 100 + N 条混合记录
  3. 前端按 source 字段分组渲染

评估问题:

  • P0 — 权限过松:仅 get_current_user,任何登录用户(含小程序用户!)都能查,泄露其他门店触发器配置信息。必须收紧到 admin 角色
  • P1 — 三个数据源混装,id 冲突靠 +100000 偏移:scheduled_tasks.id 是 UUID,本端点用 ROW_NUMBER() + 100000 强转 int(admin_triggers.py:115),不稳定 — ORDER BY created_at 一变,id 就变,前端无法长期跟踪。建议用 (source, original_id) 复合 ID。
  • P1 — 数据源 2 (ai_trigger_jobs) 语义错位:这是事件实例不是触发器配置,与 source="biz" 的"触发器"不对等。聚合页面把它们混在一起会让用户混淆 — 已在 P1-6 文档第二章指出。
  • P2 — LIMIT 100 写死:ai 数据源固定取最近 100,不可调,大批量场景信息缺失。

关联:

  • 路由代码:apps/backend/app/routers/admin_triggers.py:145
  • Schema:apps/backend/app/schemas/admin_triggers.py:14
  • 前端调用:apps/admin-web/src/api/triggers.ts:21
  • 前端使用:apps/admin-web/src/pages/TriggerManager.tsx(主 Tab)
  • P1-6 合并方案:docs/_overview/04b-feedback/P1-6-trigger-api-merge.md

五、批 1 评估总结

5.1 P0/P1/P2 问题清单(28 个)

等级 端点 问题
P0 PATCH /admin/runtime-context 长事务无幂等,4 个资源操作部分失败导致半状态
P0 GET /admin/ai/run-logs ai_run_logs 大表查询性能依赖索引,需确认 DDL
P0 GET /admin/ai/run-logs/{id} prompt 完整返回造成 PII 跨 tenant 泄露
P0 POST /admin/ai/batch-run batch_id 生命周期未声明
P0 POST /admin/ai/batch-run/confirm 无批次进度查询端点
P0 GET /admin/ai/triggers + PATCH 与 /api/trigger-jobs 重复(P1-6)
P0 POST /admin/ai/trigger-event 沙箱模式下未校验 runtime_context
P0 GET /admin/triggers/unified 权限仅 get_current_user,任意登录可查
P1 GET /admin/runtime-context tenant_admin 应能查本 tenant 门店,当前一刀切 super_admin
P1 GET /admin/runtime-context/sites 缺分页 + response_model 缺失
P1 PATCH /admin/runtime-context 切换历史无 audit 表
P1 GET /admin/ai/dashboard today_* 字段在 range_days>1 时命名误导
P1 GET /admin/ai/dashboard range_days vs date_from/to 互斥未约束
P1 GET /admin/ai/trigger-jobs today_skipped_duplicates 不应放列表 API
P1 POST /admin/ai/trigger-jobs/{id}/retry 无幂等键,连点产生多重 retry
P1 GET /admin/ai/run-logs filter 文档缺 cancelled 状态
P1 GET /admin/ai/run-logs/{id} 大字段无 size 上限
P1 POST /admin/ai/cache/invalidate super_admin 无法跨站清理
P1 GET /admin/ai/budget 无 site 维度
P1 POST /admin/ai/batch-run member_ids 无上限
P1 POST /admin/ai/batch-run/confirm 错误码不规范(batch_not_found / batch_expired 缺失)
P1 GET /admin/ai/alerts 与 run-logs 严重重复
P1 POST /admin/ai/alerts/{id}/ack 无 ack_by / ack_at 审计
P1 POST /admin/ai/alerts/{id}/ignore 与 ack 99% 重复
P1 POST /admin/ai/run/{app_type} RunAppRequest 各 App 必填差异未拆
P1 GET /admin/ai/prewarm/progress total=72 硬编码
P1 POST /admin/ai/trigger-event payload 无 schema 约束
P1 GET /admin/triggers/unified id 用 ROW_NUMBER+100000 不稳定;混装语义错位
P2 GET /config/runtime-context 与 /admin/runtime-context 命名不一致
P2 PATCH /admin/runtime-context 多次 connect/close,可共用连接
P2 GET /admin/ai/dashboard 9 子查询无缓存
P2 GET /admin/ai/trigger-jobs "trigger-jobs" 命名歧义
P2 GET /admin/ai/trigger-jobs/{id} 404 文案不一致
P2 POST /admin/ai/trigger-jobs/{id}/retry 无前端轮询提示
P2 POST /admin/ai/cache/invalidate 无审计字段
P2 GET /admin/ai/budget 与 dashboard.budget 重复
P2 POST /admin/ai/batch-run 不支持"全门店所有 member"
P2 GET /admin/ai/alerts 告警去重缺失
P2 POST /admin/ai/alerts/{id}/ack RESTful 语义模糊(POST 应改 PATCH)
P2 POST /admin/ai/run/{app_type} 422 错误信息可优化
P2 GET /admin/ai/prewarm/progress 仅服务 app2_finance,命名过窄
P2 GET /admin/triggers/unified LIMIT 100 写死

汇总:P0 = 8 / P1 = 20 / P2 = 13 / 合计 41 个发现(超过 P1-7 评估单 API 1-3 个的预期上限,说明本批是"高度集中的 AI 治理域,问题密集")。

5.2 关键发现

关键发现 1 — OpenAPI 与代码严重不同步:docs/contracts/openapi/backend-api.json 缺失 10 个本批端点(/admin/runtime-context 全 4 个 + /admin/triggers/unified + /admin/ai/run/{app_type} + /admin/ai/triggers 2 个 + /admin/ai/prewarm/progress + /admin/ai/trigger-event)。/api/config/runtime-context 也缺失。建议 Wave 1 W1-T8 检查 OpenAPI 抓取脚本(很可能是 router include 顺序或某个 condition 排除了)。

关键发现 2 — 触发器域有"3 套 + 2 套互补字段集 + 1 套聚合视图"的混乱:本批暴露 3 个触发器相关端点,加上批外 /api/trigger-jobs 一组,共 4 个名字相似但语义不同的端点。前端 TriggerManager.tsx / AITriggers.tsx / TriggerJobs.tsx 三页面消费方式还各异。P1-6 文档已给出方案 A(完全合并)/ B(底层 service 合并)/ C(只补文档),Wave 2 应优先决策

5.3 与其他批的关联

  • 批 2 ETL 任务管理:涉及 /api/scheduled-tasks /api/trigger-jobs,P1-6 合并方案落地需在批 2 同步动手(改 apps/backend/app/routers/trigger_jobs.py 与前端 triggerJobs.ts)
  • 批 3 任务执行/调度:run-logs / batch-run 的 dispatcher 实际属于跨批共享基础设施
  • 批 4 租户管理:本批 P1 发现"tenant_admin 权限粒度"应在批 4 一并梳理多租户隔离方案
  • 批 5 系统设置/日志:audit log 表(本批多次提到的"切换审计/清理审计/ack 审计")应统一在批 5 设计

5.4 Wave 2-5 工单建议(本批衍生)

优先级 工单 Wave 范围
P0 修复 /admin/triggers/unified 权限 Wave 2 _require_admin
P0 OpenAPI 同步修复 + CI 校验 Wave 1 末 抓取脚本 + 守护测试
P0 实施 P1-6 方案 A 合并双 PATCH Wave 2 后端 router + 前端 API client
P0 run-logs PII 脱敏分级 Wave 2 按角色返回 prompt 字段
P0 runtime-context 切换幂等键 + audit 表 Wave 2 DB 迁移 + 路由改造
P1 batch-run 进度查询端点 Wave 2 新增 GET /batch-run/{id}/status
P1 dashboard 字段命名规范 Wave 3 改 today_* → period_*
P1 alerts 与 run-logs 合并 Wave 3 路由 + 前端
P1 tenant_admin 权限分级 Wave 4 路由 + tenant_id 关联
P2 触发器 ID 稳定方案 Wave 5 复合 ID 替代 ROW_NUMBER

六、关联

  • OpenAPI 源:docs/contracts/openapi/backend-api.json(本会话快照,2026-05-04;本批发现存在 10+ 端点缺失,需修复)
  • 全局总表:docs/_overview/admin-api-prd/00-overview.md(已索引 5 批拆分)
  • P1-6 三 API 合并:docs/_overview/04b-feedback/P1-6-trigger-api-merge.md(方案 A/B/C 详)
  • P1-7 评估元文档:docs/_overview/04b-feedback/P1-7-admin-api-prd-evaluation.md(总工作量 100-130h)
  • Runtime Context PRD:docs/prd/specs/P20-runtime-context-sandbox.md(本会话同步新增,产品规格)
  • Backend CLAUDE.md:apps/backend/CLAUDE.md(JWT 双认证 / 全局响应包装 / FDW 访问)