Files
Neo-ZQYY/apps/backend/app/schemas/admin_ai.py
Neo c43375734a feat(admin-web,backend): F1-5b Wave B UI-3 + UI-5 admin-web sandbox 透出补强 (W1)
UI-3 AIDashboard sandbox 提示 + today_calls 分组:
- 后端 schemas/admin_ai.py DashboardResponse 加 today_live_calls / today_sandbox_calls 字段(默认 0,向后兼容)
- 后端 services/ai/admin_service.py _get_range_stats SELECT 加 2 个 FILTER COUNT 表达式
- 前端 api/adminAI.ts DashboardResponse 类型补 2 字段
- 前端 pages/AIDashboard.tsx
  - 顶部加 sandbox Alert 提示条,选中 site sandbox 模式下显示业务日 + 实例 ID
  - today_calls 卡片下方加分组 Tag(实时 X / 沙箱 Y),feature flag 控制
  - import fetchRuntimeContext + useEffect 拉 RuntimeContext
- apps/admin-web/.env.example 新建,加 VITE_AI_RUNTIME_GROUPING=false 默认值说明

UI-5 AITriggerJobs runtime 列:
- 后端 schemas/admin_ai.py TriggerJobItem 加 runtime_mode / sandbox_instance_id 可选字段
- 后端 admin_service.py list_trigger_jobs / get_trigger_job 各加 SELECT 列
- 前端 adminAI.ts TriggerJobItem 类型补 2 字段
- 前端 pages/AITriggerJobs.tsx 列表 columns 加运行模式 + 沙箱实例(同 UI-1 模式),详情 Modal 加 2 项(同 UI-2 模式)

双口径验证(Playwright + DB 直查):
- UI-3 4a live: 选中默认门店,无 Alert,today_card 仅显示总数(flag off)
- UI-3 4b sandbox=4-20: Alert 显示"沙箱 + 业务日 + sbx_…",today_calls=93(sandbox 当日)
- UI-5 4a/4b: SQL INSERT 注入 walkthrough 测试行(id=9 live, id=10 sandbox),列表正确渲染 Tag + 短哈希

trend_7d 双线 / app_distribution 堆叠分布等更深入分组改造延后到 Wave C(§8.3 风险:破坏图表)。

审计:
- docs/audit/changes/2026-05-05__wave1_f1_5b_ui3_aidashboard_sandbox.md
- docs/audit/changes/2026-05-05__wave1_f1_5b_ui5_aitriggerjobs_runtime.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 19:16:47 +08:00

315 lines
9.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
管理端 — AI 监控后台 Pydantic Schema。
覆盖Dashboard 总览、调度任务、调用记录、缓存失效、Token 预算、批量执行、告警管理。
需求: A1.1, A2.1, A4.1, A5.1, A6.1, A7.1, A8.1
"""
from __future__ import annotations
from pydantic import BaseModel
# ── Dashboard ─────────────────────────────────────────────
class DailyTrend(BaseModel):
"""近 7 天按日聚合趋势项。"""
date: str # YYYY-MM-DD
calls: int
success_rate: float
class AppDistItem(BaseModel):
"""各 App 调用占比分布项。"""
app_type: str
count: int
percentage: float
class BudgetInfo(BaseModel):
"""日/月 Token 预算进度。"""
daily_used: int
daily_limit: int
daily_pct: float
monthly_used: int
monthly_limit: int
monthly_pct: float
class AlertItem(BaseModel):
"""告警事件项(失败/超时/熔断)。"""
id: int
app_type: str
status: str # failed / timeout / circuit_open
alert_status: str | None # pending / acknowledged / ignored
error_message: str | None
created_at: str
class AppHealthItem(BaseModel):
"""各 App 最近一次调用状态。"""
app_type: str
last_status: str | None
last_call_at: str | None
class DashboardResponse(BaseModel):
"""Dashboard 总览统计响应。"""
today_calls: int
today_success_rate: float # 0.0 ~ 1.0
today_tokens: int
today_avg_latency_ms: float
# F1-5b UI-3: today_calls 按 runtime_mode 分组
# live + sandbox = today_calls(总计),供前端 feature flag 控制是否展示
today_live_calls: int = 0
today_sandbox_calls: int = 0
trend_7d: list[DailyTrend]
app_distribution: list[AppDistItem]
budget: BudgetInfo
recent_alerts: list[AlertItem]
app_health: list[AppHealthItem]
# ── 调度任务 ──────────────────────────────────────────────
class TriggerJobItem(BaseModel):
"""调度任务列表项。"""
id: int
event_type: str
member_id: int | None
status: str
app_chain: str | None
is_forced: bool
site_id: int
started_at: str | None
finished_at: str | None
created_at: str
# F1-5b UI-5: runtime 透出(同 RunLogItem)
runtime_mode: str | None = None
sandbox_instance_id: str | None = None
class TriggerJobListResponse(BaseModel):
"""调度任务分页列表响应。"""
items: list[TriggerJobItem]
total: int
page: int
page_size: int
today_skipped_duplicates: int # 今日去重跳过数
class TriggerJobDetailResponse(TriggerJobItem):
"""调度任务详情响应(含 payload、error_message"""
payload: dict | None
error_message: str | None
connector_type: str
class RetryResponse(BaseModel):
"""手动重跑响应。"""
trigger_job_id: int
status: str # "pending"
# ── 调用记录 ──────────────────────────────────────────────
class RunLogItem(BaseModel):
"""调用记录列表项。F1-5b UI-1: 加 runtime_mode + sandbox_instance_id 透出 sandbox 状态。"""
id: int
app_type: str
trigger_type: str
member_id: int | None
tokens_used: int
latency_ms: int | None
status: str
site_id: int
created_at: str
runtime_mode: str | None = None
sandbox_instance_id: str | None = None
class RunLogListResponse(BaseModel):
"""调用记录分页列表响应。"""
items: list[RunLogItem]
total: int
page: int
page_size: int
class RunLogDetailResponse(RunLogItem):
"""调用记录详情响应(含完整 prompt/response不脱敏"""
request_prompt: str | None
response_text: str | None
error_message: str | None
session_id: str | None
finished_at: str | None
# ── 缓存失效 ─────────────────────────────────────────────
class CacheInvalidateRequest(BaseModel):
"""缓存失效请求site_id 必填)。"""
site_id: int
app_type: str | None = None
member_id: int | None = None
class CacheInvalidateResponse(BaseModel):
"""缓存失效响应。"""
affected_count: int
# ── Token 预算 ────────────────────────────────────────────
class BudgetResponse(BaseModel):
"""Token 预算使用情况响应。"""
daily_used: int
daily_limit: int
daily_pct: float
monthly_used: int
monthly_limit: int
monthly_pct: float
# ── 批量执行 ──────────────────────────────────────────────
class BatchRunRequest(BaseModel):
"""批量执行请求。"""
app_types: list[str]
member_ids: list[int]
site_id: int
class BatchRunEstimate(BaseModel):
"""批量执行预估响应(不立即执行)。"""
batch_id: str
estimated_calls: int
estimated_tokens: int
class BatchRunConfirm(BaseModel):
"""批量执行确认请求。"""
batch_id: str
class BatchRunConfirmResponse(BaseModel):
"""批量执行确认响应。"""
status: str # "started"
# ── 按需单 App 执行(/run/{app_type})──────────────────────
class RunAppRequest(BaseModel):
"""按需执行单个 App 请求体。
context 字段根据 app_type 不同有不同约束:
- app2_finance: site_id + time_dimension + areaarea 默认 all
- app3_clue / app7_customer: site_id + member_id
- app4_analysis / app5_tactics: site_id + member_id + assistant_id
- app6_note: site_id + member_id + note_content + noted_by_name
- app8_consolidation: site_id + member_id
"""
site_id: int
member_id: int | None = None
assistant_id: int | None = None
time_dimension: str | None = None
area: str | None = None # App2 专用,默认 all
note_content: str | None = None
noted_by_name: str | None = None
noted_by_created_at: str | None = None
class RunAppResponse(BaseModel):
"""按需执行单个 App 响应。"""
app_type: str
success: bool
result: dict | None = None # 百炼返回的 JSON成功时
error: str | None = None # 错误描述(失败时)
# ── 告警 ──────────────────────────────────────────────────
class AlertListResponse(BaseModel):
"""告警分页列表响应。"""
items: list[AlertItem]
total: int
page: int
page_size: int
class AlertActionResponse(BaseModel):
"""告警操作(确认/忽略)响应。"""
id: int
alert_status: str
# ── 触发器管理biz.trigger_jobs─────────────────────────
class TriggerItem(BaseModel):
"""触发器单条记录。"""
id: int
job_name: str
job_type: str
trigger_condition: str # event / cron / interval
trigger_config: dict # {"event_name": ...} 或 {"cron_expression": ...}
status: str # enabled / disabled
description: str | None = None
last_run_at: str | None = None
next_run_at: str | None = None
last_error: str | None = None
class TriggerUpdateRequest(BaseModel):
"""触发器更新请求3 个字段至少填一个)。"""
status: str | None = None # enabled / disabled
cron_expression: str | None = None # 标准 5 段 cron
description: str | None = None
# ── 预热进度app2_finance 72 组合)───────────────────────
class PrewarmMissingItem(BaseModel):
"""缺失的预热组合项。"""
target_id: str # this_month__all
time_dimension: str
area: str
class PrewarmProgressResponse(BaseModel):
"""app2_finance 预热进度响应。"""
total: int # 固定 72
done: int
missing: list[PrewarmMissingItem]
last_updated: str | None = None
# ── 手动事件触发(越过去重)───────────────────────────────
class ManualTriggerRequest(BaseModel):
"""手动触发 AI 事件请求。"""
event_type: str # consumption / dws_completed / note_created / task_assigned
site_id: int
member_id: int | None = None
assistant_id: int | None = None
payload: dict | None = None
is_forced: bool = True # 默认跳过去重
class ManualTriggerResponse(BaseModel):
"""手动事件触发响应。"""
trigger_job_id: int
status: str = "pending"