Files
Neo-ZQYY/docs/prd/specs/P18-admin-task-engine-dashboard.md
Neo 6f8f12314f feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本
包含多个会话的累积代码变更:
- backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔
- admin-web: ETL 状态页、任务管理、调度配置、登录优化
- miniprogram: 看板页面、聊天集成、UI 组件、导航更新
- etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强
- tenant-admin: 项目初始化
- db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8)
- packages/shared: 枚举和工具函数更新
- tools: 数据库工具、报表生成、健康检查
- docs: PRD/架构/部署/合约文档更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 00:03:48 +08:00

30 KiB
Raw Blame History

P18管理后台 — 任务引擎运营看板与参数管理

版本v2.1-reviewed | 日期2026-03-24 | 作者AI待评审 依赖P17助教客户归属与任务生成引擎、P10租户管理后台部分复用 状态:评审通过,可进入实施


0. 文档背景与目标

0.1 问题来源

P17 实现了助教客户归属、任务生成、客户转移三大引擎能力,但所有数据仅存在于数据库中,运营团队无法:

  • 查看客户转移日志(谁被转给了谁、为什么、三重保护检查结果)
  • 审核 pending_review 状态的任务(转移次数超限后需人工介入)
  • 按门店调整任务生成参数(召回阈值、转移保护参数等)
  • 监控任务生成器的运行健康度(生成/替换/跳过/转移统计)

同时,现有 http://localhost:5173/trigger-jobs 定时任务页面功能较基础(仅展示 + 手动执行),需要评估是否扩展为更完整的调度监控面板。

0.2 本文档目标

  1. 定义 admin-web 中 P17 相关的三个新页面(转移日志、待审核任务、参数管理)
  2. 评估现有 trigger-jobs 页面的扩展需求
  3. 明确后端 API 需求(含 Pydantic schema 定义)
  4. 给出优先级排序和实施建议
  5. 明确技术实现方案(前端组件结构、路由注册、权限控制)

0.3 技术栈概要

技术
前端 React 19 + Vite 6 + Ant Design 5 + axios + Zustand + react-router-dom 7
API 客户端 apps/admin-web/src/api/client.tsbaseURL = /apiJWT 自动附加 + 401 自动刷新
后端 FastAPI + Pydantic v2 + asyncpg
认证 JWTCurrentUser 含 user_id / site_id / rolesDepends(get_current_user)
响应包装 全局中间件自动 { code: 0, data: <body> },前端 axios 拦截器自动解包

1. 现有 trigger-jobs 页面评估

1.1 当前能力

功能 状态
列出所有定时任务(名称、触发方式、配置、状态) 已实现
展示上次/下次执行时间 已实现
展示最近错误信息 已实现
手动执行按钮(需确认) 已实现
刷新按钮 已实现

1.2 缺失能力(待评估)

功能 优先级 说明
执行历史记录 当前只有 last_run_at无法查看历史执行记录和每次的统计结果
任务启用/禁用开关 当前只能查看状态,无法在界面上切换
执行结果统计 task_generator 的 created/replaced/skipped/transferred 统计应可视化
Cron 表达式可编辑 当前触发配置只读,修改需直接改数据库
执行耗时监控 无执行耗时记录

1.3 建议

trigger-jobs 页面的核心定位是"调度监控",不宜过度膨胀。建议:

  • 短期:在现有页面增加"最近执行结果"列(展示 task_generator 的 stats JSON
  • 中期:新增"执行历史"抽屉(点击任务名展开,展示最近 N 次执行记录)
  • 长期:考虑是否需要独立的"调度中心"页面(类似 Airflow UI

决策O6:暂不新增 biz.trigger_job_execution_log 表。短期通过在 trigger_jobs 表新增 last_stats JSONB 字段记录最近一次执行统计即可满足需求。中期再评估是否需要完整执行历史表。


2. 新增页面设计

2.1 页面一客户转移日志Transfer Log

路由/task-engine/transfer-log

目标用户:运营管理员(超级管理员看全部,门店管理员看本店)

数据源biz.coach_task_transfer_log

展示内容

数据来源 说明
转移时间 created_at 降序排列
门店 site_idbiz.sites.site_name JOIN 门店表
客户 member_id → 会员昵称/手机号 FDW 关联 ETL 库会员维度表
原助教 from_assistant_id → 助教姓名 FDW 关联 ETL 库助教维度表
新助教 to_assistant_id → 助教姓名 同上
转移原因 transfer_reason 文本展示
转移得分 transfer_score 数值,保留 2 位小数
保护检查 guard_checks JSON → 三项检查结果(/

筛选条件

  • 门店(下拉选择,门店管理员自动锁定本店)
  • 时间范围(日期选择器,默认最近 7 天)
  • 助教(搜索框,支持原助教/新助教)

操作

  • 无写操作,纯查看
  • 支持导出 CSV后续迭代

决策O3:转移日志不关联 WBI/NCI/RS 快照。原因:(1) 转移时刻的指数值可通过 transfer_reason 文本和 transfer_score 间接推断;(2) 存储快照会增加 coach_task_transfer_log 表宽度,且历史指数可从 DWS 层按日期回溯。如后续运营反馈需要,可在 guard_checks JSONB 中追加指数快照字段,无需 DDL 变更。


2.2 页面二待审核任务Pending Review

路由/task-engine/pending-review

目标用户:运营管理员(超级管理员看全部,门店管理员看本店)

数据源biz.coach_tasks WHERE status = 'pending_review'

展示内容

数据来源 说明
创建时间 created_at 降序排列
门店 site_id → 门店名称
客户 member_id → 会员昵称 FDW 关联
当前助教 assistant_id → 助教姓名 FDW 关联
任务类型 task_type 中文映射(见下方枚举表)
累计转移次数 transfer_count
优先级分 priority_score WBI/NCI 分值

任务类型中文映射

task_type 中文
high_priority_recall 高优先召回
priority_recall 优先召回
follow_up_visit 客户回访
relationship_building 关系构建

操作

操作 说明
重新分配 弹窗选择目标助教 → 调用 reassign API → 原任务标记 transferred,新任务 active
关闭任务 弹窗填写关闭原因 → 调用 close API → 原任务标记 inactive
查看转移历史 抽屉展示该客户的所有转移记录(复用转移日志 API

决策O1:重新分配的候选助教列表获取方式 — 复用 P17 的转移候选逻辑。后端新增 GET /api/admin/task-engine/pending-review/{task_id}/candidates 端点,内部调用 fdw_queries.get_pool_assistants(member_id) 获取 POOL 助教列表,按转移得分排序返回。前端展示为下拉选择框,显示助教姓名 + 转移得分。若 POOL 为空,候选列表返回空数组,界面提示"该客户暂无符合转移条件的助教,请联系 ETL 团队确认数据覆盖情况"不提供降级到全店助教的选项P17 明确规定 UNASSIGNED 助教永远不分配)。若确实有紧急人工干预需求,可提供"强制指定"按钮,需额外二次确认弹窗,且操作记录中标注 source: "manual_override",写入 transfer_log.transfer_reason


2.3 页面三任务引擎参数管理Task Engine Config

路由/task-engine/config

目标用户:超级管理员(门店管理员只读)

数据源biz.cfg_task_generator_params

展示内容

表格形式,按参数分组,每行展示全局默认值 + 各门店覆盖值:

说明
参数名 param_key,中文描述
全局默认值 site_id IS NULL 的记录
门店覆盖值 特定 site_id 的记录(无覆盖时显示"使用默认"灰色标签)
说明 description 字段
操作 编辑 / 删除覆盖

参数列表(来自 P17 第 5 节):

参数 默认值 中文说明 校验规则
high_priority_recall_threshold 7.0 高优先召回阈值 0-10numeric
priority_recall_threshold 5.0 优先召回阈值 0-10numeric
rs_min_for_relationship 1.0 关系构建 RS 下限 0-10numeric
rs_max_for_relationship 6.0 关系构建 RS 上限 0-10> rs_min
consecutive_recall_fail_cycles 3 连续失败触发转移的轮数 1-10integer
min_wbi_for_transfer 5.0 触发转移的最低 WBI 0-10numeric
guard_assistant_coverage_ratio 0.5 门店助教绑定率保护阈值 0-1numeric
guard_new_assistant_days 10 新助教入驻保护天数 1-90integer
transfer_score_w_rs 0.5 转移排序 RS 权重 0-1三项之和 = 1
transfer_score_w_ms 0.3 转移排序 MS 权重 0-1三项之和 = 1
transfer_score_w_ml 0.2 转移排序 ML 权重 0-1三项之和 = 1
max_transfer_count 2 单客户最大转移次数 1-5integer
follow_up_visit_retention_hours 48 回访任务保留时长(小时) 1-168integer

操作

操作 说明 权限
编辑参数值 行内编辑,保存后立即生效 超级管理员
新增门店覆盖 选择门店 + 参数,设置覆盖值 超级管理员
删除门店覆盖 恢复使用全局默认值 超级管理员
重置为默认 将全局默认值恢复为 P17 定义的初始值 超级管理员

前端校验规则

  • 权重参数(w_rs + w_ms + w_ml)之和必须 = 1.0(容差 0.001)。前端将三个权重参数合并为一个"权重配置"卡片,同时展示三个输入框,整体保存。后端 PUT 接口对权重类参数做联合校验:收到任一权重参数修改时,自动读取当前另外两个权重的值做求和校验
  • 阈值参数范围 0-10与指数分值范围一致
  • rs_max_for_relationship 必须 > rs_min_for_relationship
  • 修改后弹出确认对话框,展示变更前后对比

决策O2:参数修改直接生效,不需要审批流程。原因:(1) 参数修改频率极低(预计每月 1-2 次);(2) 修改记录通过 updated_at 字段和后端日志可追溯;(3) 引入审批流程会增加不必要的复杂度。后续如需审批,可在 cfg_task_generator_params 表新增 updated_by 字段记录操作人。


3. 后端 API 需求

3.1 转移日志 API

路由文件apps/backend/app/routers/admin_task_engine.py(新建)

GET /api/admin/task-engine/transfer-log
  Query: site_id?, from_date?, to_date?, assistant_id?, page=1, page_size=20
  Response: { items: TransferLogItem[], total: int }
  权限: get_current_user门店管理员自动按 site_id 过滤)

GET /api/admin/task-engine/transfer-log/{member_id}/history
  Response: { items: TransferLogItem[] }
  权限: get_current_user

SQL 示例(转移日志列表)

SELECT
    tl.id, tl.site_id, tl.member_id,
    tl.from_assistant_id, tl.to_assistant_id,
    tl.transfer_reason, tl.transfer_score,
    tl.guard_checks, tl.created_at,
    s.site_name
FROM biz.coach_task_transfer_log tl
JOIN biz.sites s ON s.site_id = tl.site_id
WHERE ($1::bigint IS NULL OR tl.site_id = $1)
  AND ($2::date IS NULL OR tl.created_at >= $2)
  AND ($3::date IS NULL OR tl.created_at < $3 + INTERVAL '1 day')
  AND ($4::bigint IS NULL OR tl.from_assistant_id = $4 OR tl.to_assistant_id = $4)
ORDER BY tl.created_at DESC
LIMIT $5 OFFSET $6;

姓名关联实现方式:由于 postgres_fdw 不传递 GUC 参数,不能在 admin-web 后端直接用 FDW JOIN。实现上采用 Python 层批量合并:先从 biz.coach_task_transfer_log 取分页数据,然后收集所有 member_idassistant_id,批量调用 fdw_queries.get_member_names(member_ids) 和新增的 fdw_queries.get_assistant_names(assistant_ids) 工具函数(通过 _fdw_context() 创建独立 ETL 连接),最后在 Python 层做字典合并。待审核任务列表同理。避免在 SQL 里做跨库 JOIN。

3.2 待审核任务 API

GET /api/admin/task-engine/pending-review
  Query: site_id?, page=1, page_size=20
  Response: { items: PendingReviewItem[], total: int }
  权限: get_current_user

GET /api/admin/task-engine/pending-review/{task_id}/candidates
  Response: { candidates: CandidateAssistant[] }
  权限: get_current_user
  说明: 返回可接收转移的候选助教列表POOL 助教 + 降级全店助教)

POST /api/admin/task-engine/pending-review/{task_id}/reassign
  Body: { to_assistant_id: int }
  Response: { success: bool, new_task_id: int }
  权限: get_current_user + roles 包含 'super_admin'
  副作用: 原任务 status → 'transferred',新建 active 任务,写入 transfer_log

POST /api/admin/task-engine/pending-review/{task_id}/close
  Body: { reason: str }
  Response: { success: bool }
  权限: get_current_user + roles 包含 'super_admin'
  副作用: 任务 status → 'inactive'abandon_reason = reason

3.3 参数管理 API

GET /api/admin/task-engine/config
  Query: site_id?(不传返回全局默认 + 所有门店覆盖)
  Response: { params: ConfigParam[] }
  权限: get_current_user

PUT /api/admin/task-engine/config/{param_id}
  Body: { param_value: float }
  Response: { success: bool }
  权限: get_current_user + roles 包含 'super_admin'

POST /api/admin/task-engine/config
  Body: { site_id: int, param_key: str, param_value: float }
  Response: { success: bool, id: int }
  权限: get_current_user + roles 包含 'super_admin'

DELETE /api/admin/task-engine/config/{param_id}
  Response: { success: bool }
  权限: get_current_user + roles 包含 'super_admin'
  约束: 不允许删除全局默认值site_id IS NULL仅允许删除门店覆盖

4. Pydantic Schema 定义

文件位置apps/backend/app/schemas/admin_task_engine.py(新建)

from __future__ import annotations
from datetime import datetime
from pydantic import BaseModel, Field

# ── 转移日志 ──

class TransferLogItem(BaseModel):
    id: int
    site_id: int
    site_name: str = ""
    member_id: int
    member_name: str = ""          # FDW 关联
    from_assistant_id: int
    from_assistant_name: str = ""  # FDW 关联
    to_assistant_id: int
    to_assistant_name: str = ""    # FDW 关联
    transfer_reason: str | None = None
    transfer_score: float | None = None
    guard_checks: dict | None = None
    created_at: datetime

class TransferLogPage(BaseModel):
    items: list[TransferLogItem]
    total: int

# ── 待审核任务 ──

class PendingReviewItem(BaseModel):
    id: int
    site_id: int
    site_name: str = ""
    member_id: int
    member_name: str = ""
    assistant_id: int
    assistant_name: str = ""
    task_type: str
    task_type_label: str = ""      # 中文映射
    transfer_count: int = 0
    priority_score: float | None = None
    created_at: datetime

class PendingReviewPage(BaseModel):
    items: list[PendingReviewItem]
    total: int

class CandidateAssistant(BaseModel):
    assistant_id: int
    assistant_name: str = ""
    rs_display: float = 0
    ms_display: float = 0
    ml_display: float = 0
    transfer_score: float = 0      # w_rs*rs + w_ms*ms + w_ml*ml
    source: str = "pool"           # "pool" | "manual_override"(强制指定标记)

class CandidateListResponse(BaseModel):
    candidates: list[CandidateAssistant]

class ReassignRequest(BaseModel):
    to_assistant_id: int

class ReassignResponse(BaseModel):
    success: bool
    new_task_id: int | None = None

class CloseRequest(BaseModel):
    reason: str = Field(..., min_length=1, max_length=500)

class CloseResponse(BaseModel):
    success: bool

# ── 参数管理 ──

class ConfigParam(BaseModel):
    id: int
    site_id: int | None = None
    site_name: str | None = None   # site_id 非空时关联
    param_key: str
    param_value: float
    description: str | None = None
    updated_at: datetime

class ConfigParamList(BaseModel):
    params: list[ConfigParam]

class ConfigParamUpdate(BaseModel):
    param_value: float

class ConfigParamCreate(BaseModel):
    site_id: int
    param_key: str = Field(..., max_length=64)
    param_value: float

class ConfigParamResponse(BaseModel):
    success: bool
    id: int | None = None

5. 权限控制方案

决策O5:采用双层权限模型 — 超级管理员(super_admin)全局访问 + 门店管理员(site_admin)本店只读。

5.1 权限矩阵

页面/操作 super_admin site_admin
转移日志 — 查看全部
转移日志 — 查看本店
待审核任务 — 查看全部
待审核任务 — 查看本店
待审核任务 — 重新分配
待审核任务 — 关闭任务
参数管理 — 查看 (只读)
参数管理 — 编辑/新增/删除

5.2 后端实现

与 P10 账号体系的关系P10 租户管理后台(/tenant-admins)的门店管理员账号与本页面的 site_admin 角色使用同一套 JWT 体系,通过 roles 字段区分权限范围。site_admin 在 P18 页面为只读访问,在 P10 页面可能有写操作权限。两者面向的用户群不同P18 面向平台运营P10 面向门店管理员),但共享认证基础设施。

# apps/backend/app/auth/dependencies.py 中已有 CurrentUser(roles=[...])
# 新增权限检查辅助函数:

from fastapi import HTTPException, status

def require_super_admin(user: CurrentUser) -> None:
    """写操作权限检查:仅超级管理员可执行。"""
    if "super_admin" not in user.roles:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="仅超级管理员可执行此操作",
        )

def filter_by_site(user: CurrentUser, query_site_id: int | None) -> int | None:
    """读操作门店过滤:门店管理员强制锁定本店。"""
    if "super_admin" in user.roles:
        return query_site_id  # 超级管理员可查看任意门店
    return user.site_id       # 门店管理员强制本店

5.3 前端实现

// 在页面组件中检查权限,控制操作按钮显示
const isSuperAdmin = user.roles.includes('super_admin');

// 转移日志:门店筛选器
// super_admin → 显示全部门店下拉
// site_admin → 隐藏门店筛选器API 自动按 site_id 过滤

// 待审核任务:操作列
// super_admin → 显示"重新分配"和"关闭"按钮
// site_admin → 不显示操作列

// 参数管理:编辑按钮
// super_admin → 显示编辑/新增/删除按钮
// site_admin → 隐藏所有写操作按钮

6. 前端实现方案

6.1 导航结构

apps/admin-web/src/App.tsxNAV_ITEMS 数组中新增"任务引擎"菜单组:

// 新增 import
import { ApartmentOutlined } from "@ant-design/icons";
import TransferLog from "./pages/TransferLog";
import PendingReview from "./pages/PendingReview";
import TaskEngineConfig from "./pages/TaskEngineConfig";

// NAV_ITEMS 中新增(插入在"定时任务"之后)
{
  key: "task-engine-group",
  icon: <ApartmentOutlined />,
  label: "任务引擎",
  children: [
    { key: "/task-engine/transfer-log", label: "转移日志" },
    { key: "/task-engine/pending-review", label: "待审核任务" },
    { key: "/task-engine/config", label: "参数管理" },
  ],
},

最终侧边栏结构

📋 任务配置          /
📋 任务管理          /task-manager
📊 ETL 状态          /etl-status
⏰ 定时任务          /trigger-jobs
🔀 任务引擎(新增)
  ├── 转移日志        /task-engine/transfer-log
  ├── 待审核任务      /task-engine/pending-review
  └── 参数管理        /task-engine/config
💾 数据库            /db-viewer
📄 日志              /log-viewer
⚙️ 环境配置          /env-config
🖥️ 运维面板          /ops-panel
👥 租户管理员        /tenant-admins
🤖 AI 监控
  ├── 运行总览        /ai/dashboard
  ├── 调度状态        /ai/trigger-jobs
  ├── 调用明细        /ai/run-logs
  └── 手动操作        /ai/operations
🐛 开发调试日志      /dev-trace

6.2 路由注册

AppLayout<Routes> 中新增:

<Route path="/task-engine/transfer-log" element={<TransferLog />} />
<Route path="/task-engine/pending-review" element={<PendingReview />} />
<Route path="/task-engine/config" element={<TaskEngineConfig />} />

同时修改 <Menu>defaultOpenKeys,补充 task-engine-group 前缀:

defaultOpenKeys={[
  ...(location.pathname.startsWith("/ai/") ? ["ai-group"] : []),
  ...(location.pathname.startsWith("/task-engine/") ? ["task-engine-group"] : []),
]}

6.3 API 模块

新建 apps/admin-web/src/api/taskEngine.ts

import { apiClient } from "./client";

// ── 转移日志 ──
export interface TransferLogItem { /* 同 Pydantic schema */ }
export interface TransferLogPage { items: TransferLogItem[]; total: number; }

export async function fetchTransferLog(params: {
  site_id?: number; from_date?: string; to_date?: string;
  assistant_id?: number; page?: number; page_size?: number;
}): Promise<TransferLogPage> {
  const { data } = await apiClient.get("/admin/task-engine/transfer-log", { params });
  return data;
}

export async function fetchMemberTransferHistory(memberId: number): Promise<TransferLogItem[]> {
  const { data } = await apiClient.get(`/admin/task-engine/transfer-log/${memberId}/history`);
  return data.items;
}

// ── 待审核任务 ──
export interface PendingReviewItem { /* 同 Pydantic schema */ }

export async function fetchPendingReview(params: {
  site_id?: number; page?: number; page_size?: number;
}): Promise<{ items: PendingReviewItem[]; total: number }> {
  const { data } = await apiClient.get("/admin/task-engine/pending-review", { params });
  return data;
}

export async function fetchCandidates(taskId: number) {
  const { data } = await apiClient.get(`/admin/task-engine/pending-review/${taskId}/candidates`);
  return data.candidates;
}

export async function reassignTask(taskId: number, toAssistantId: number) {
  const { data } = await apiClient.post(`/admin/task-engine/pending-review/${taskId}/reassign`, {
    to_assistant_id: toAssistantId,
  });
  return data;
}

export async function closeTask(taskId: number, reason: string) {
  const { data } = await apiClient.post(`/admin/task-engine/pending-review/${taskId}/close`, {
    reason,
  });
  return data;
}

// ── 参数管理 ──
export interface ConfigParam { /* 同 Pydantic schema */ }

export async function fetchConfig(siteId?: number): Promise<ConfigParam[]> {
  const { data } = await apiClient.get("/admin/task-engine/config", {
    params: siteId ? { site_id: siteId } : {},
  });
  return data.params;
}

export async function updateConfig(paramId: number, value: number) {
  const { data } = await apiClient.put(`/admin/task-engine/config/${paramId}`, {
    param_value: value,
  });
  return data;
}

export async function createConfig(siteId: number, paramKey: string, value: number) {
  const { data } = await apiClient.post("/admin/task-engine/config", {
    site_id: siteId, param_key: paramKey, param_value: value,
  });
  return data;
}

export async function deleteConfig(paramId: number) {
  const { data } = await apiClient.delete(`/admin/task-engine/config/${paramId}`);
  return data;
}

6.4 页面组件结构

每个页面遵循现有 TriggerJobs.tsx 的模式:useState + useCallback + useEffect + Ant Design Table。

文件 说明
apps/admin-web/src/pages/TransferLog.tsx 转移日志页面
apps/admin-web/src/pages/PendingReview.tsx 待审核任务页面
apps/admin-web/src/pages/TaskEngineConfig.tsx 参数管理页面
apps/admin-web/src/api/taskEngine.ts API 调用模块
apps/backend/app/routers/admin_task_engine.py 后端路由
apps/backend/app/schemas/admin_task_engine.py Pydantic schema

7. 数据库变更需求

7.1 trigger_jobs 表扩展P1 优先级)

last_errordescription 字段已存在于数据库中(已通过 \d biz.trigger_jobs 确认),无需重复添加。

-- 仅 last_stats 是真正新增的
ALTER TABLE biz.trigger_jobs
  ADD COLUMN IF NOT EXISTS last_stats JSONB;

COMMENT ON COLUMN biz.trigger_jobs.last_stats
  IS '最近一次执行的统计结果 JSON如 {"created":5,"replaced":2,"skipped":10,"transferred":1}';

last_statstask_generator.run() 在执行完成后写入,是运营监控任务引擎健康度的最直接手段。

7.2 cfg_task_generator_params 表扩展

-- 记录修改人(审计追溯)
ALTER TABLE biz.cfg_task_generator_params
  ADD COLUMN IF NOT EXISTS updated_by BIGINT;

COMMENT ON COLUMN biz.cfg_task_generator_params.updated_by IS '最近修改人 user_id用于审计追溯';

7.3 无需新建表

P18 不新增业务表。所有数据来源于 P17 已创建的:

  • biz.coach_task_transfer_log(转移日志)
  • biz.cfg_task_generator_params(参数配置)
  • biz.coach_tasks(任务表,含 pending_review 状态)

8. 优先级排序

优先级 功能 理由 预估工时
P0 待审核任务页面 pending_review 任务需要人工介入,无此页面则转移超限的任务无法处理 后端 4h + 前端 4h
P1 参数管理页面 门店级参数调整是日常运营需求,否则每次改参数都要直接改数据库 后端 3h + 前端 4h
P1 转移日志页面 运营需要追踪转移效果,但短期可通过数据库查询替代 后端 3h + 前端 3h
P1 trigger-jobs last_stats 展示 任务引擎上线后运营最直接的监控手段,能看到 created/replaced/skipped/transferred 数字 后端 1h + 前端 1h
P2 trigger-jobs 启用/禁用开关 低频操作,可通过数据库修改 后端 0.5h + 前端 0.5h

建议实施顺序

  1. 后端:先建 router + schema 骨架(admin_task_engine.py),再逐个实现端点
  2. 前端:先注册路由和导航,再逐个实现页面组件
  3. P0 → P1参数管理→ P1转移日志→ P2 → P3

9. 开放问题决策汇总

# 问题 决策 理由
O1 重新分配候选助教列表获取方式 复用 P17 转移候选逻辑(仅 POOL 助教POOL 为空时返回空列表 + 提示,紧急情况提供"强制指定"(需二次确认 + manual_override 标记) 严格遵守 P17 的 UNASSIGNED 永不分配约束
O2 参数修改是否需要审批流程 直接生效,不需要审批 修改频率极低,updated_at + updated_by 可追溯
O3 转移日志是否关联 WBI/NCI/RS 快照 不关联,通过 guard_checks JSONB 间接推断 避免表膨胀,历史指数可从 DWS 层回溯
O4 是否需要运行总览 Dashboard 暂不需要,后续根据运营反馈评估 当前数据量不足以支撑有意义的趋势图
O5 权限模型 双层super_admin 全局 + site_admin 本店只读 与现有 JWT roles 体系一致,实现成本最低
O6 trigger-jobs 执行历史是否需要新表 暂不新增,用 last_stats JSONB 字段过渡 短期够用,中期再评估完整历史表

决策O4:暂不建设运行总览 Dashboard。原因(1) P17 刚上线,历史数据不足以生成有意义的趋势图;(2) 三个功能页面已覆盖核心运营需求;(3) 后续积累 1-2 个月数据后,可作为 P19 或 P20 独立评估。


10. 验收标准

# 验收项 判定方式
AC1 转移日志页面可按门店/时间/助教筛选 手动验证筛选条件组合
AC2 门店管理员只能看到本店转移日志 用 site_admin 角色登录验证
AC3 待审核任务页面展示所有 pending_review 任务 数据库插入测试数据后验证
AC4 重新分配操作正确创建新任务并标记原任务 执行后检查 coach_tasks + transfer_log
AC5 关闭任务操作正确更新状态和原因 执行后检查 coach_tasks.status + abandon_reason
AC6 参数管理页面展示全局默认 + 门店覆盖 插入门店覆盖数据后验证
AC7 权重参数之和校验生效 尝试设置不等于 1.0 的权重组合
AC8 超级管理员可执行所有写操作 用 super_admin 角色验证
AC9 门店管理员无法执行写操作 用 site_admin 角色验证 403
AC10 不允许删除全局默认参数 尝试删除 site_id IS NULL 的记录

11. 与现有 PRD/模块的关系

文档/模块 关系说明
P17 本 PRD 是 P17 的管理后台配套P17 提供数据P18 提供可视化和操作入口
P10租户管理后台 P10 是面向门店管理员的独立应用P18 是面向平台运营的 admin-web 扩展。两者共享 JWT 认证体系(同一套 roles 字段),site_admin 在 P18 为只读、在 P10 可能有写权限。详见第 5.2 节说明
trigger-jobs 现有页面 本 PRD 评估其扩展需求,但不强制改动,保持现有功能稳定
AI 监控模块 参考其 API 设计模式JWT + admin 角色、分页列表 + 详情)

附录 A后端路由注册

apps/backend/app/main.py 中注册新路由:

from app.routers import admin_task_engine
app.include_router(admin_task_engine.router)

附录 B变更历史

版本 日期 变更内容
v1.0-draft 2026-03-24 初始草稿,定义 3 个页面 + 9 个 API + 6 个开放问题
v2.0-ready-for-review 2026-03-24 关闭全部 6 个开放问题;补充 Pydantic schema、权限矩阵、前端实现方案、SQL 示例、校验规则、验收标准;升级为待评审
v2.1-reviewed 2026-03-24 评审修正:(1) DDL 移除已存在的 last_error/description仅新增 last_stats(2) defaultOpenKeys 补充 task-engine-group(3) last_stats 优先级 P2→P1(4) 候选助教降级方案改为空结果+提示+强制指定,不放开到全店助教;(5) 权重参数改为卡片整体编辑+联合校验;(6) 明确姓名关联走 Python 层批量合并;(7) 补充 site_admin 与 P10 账号体系关系说明