Files
AIclinicalresearch/docs/03-业务模块/ADMIN-运营管理端/04-开发计划/03-运营监控系统MVP开发计划.md
HaHafeng bbf98c4d5c fix(backend): Resolve PgBoss infinite loop issue and cleanup unused files
Backend fixes:
- Fix PgBoss task infinite loop on SAE (root cause: missing queue table constraints)
- Add singletonKey to prevent duplicate job enqueueing
- Add idempotency check in reviewWorker (skip completed tasks)
- Add optimistic locking in reviewService (atomic status update)

Frontend fixes:
- Add isSubmitting state to prevent duplicate submissions in RVW Dashboard
- Fix API baseURL in knowledgeBaseApi (relative path)

Cleanup (removed):
- Old frontend/ directory (migrated to frontend-v2)
- python-microservice/ (unused, replaced by extraction_service)
- Root package.json and node_modules (accidentally created)
- redcap-docker-dev/ (external dependency)
- Various temporary files and outdated docs in root

New documentation:
- docs/07-运维文档/01-PgBoss队列监控与维护.md
- docs/07-运维文档/02-故障预防检查清单.md
- docs/07-运维文档/03-数据库迁移注意事项.md

Database fix applied to RDS:
- Added PRIMARY KEY to platform_schema.queue
- Added 3 missing foreign key constraints

Tested: Local build passed, RDS constraints verified
2026-01-27 18:16:22 +08:00

22 KiB
Raw Blame History

运营监控系统 MVP 开发计划

文档版本V3.1 (完整版)
创建日期2026-01-25
基于文档:运营体系设计方案-MVP-V3.0.md
预计工时4-5 小时


📋 修订说明

本计划基于 V3.0 方案进行审查修订,主要解决以下 8 个问题

# 问题 严重程度 修订内容
1 模块覆盖不完整 🔴 严重 补充 RVW、IIT、Protocol Agent、SSA/ST 预留
2 缺少 tenantName 字段 🔴 严重 添加冗余字段避免 JOIN
3 RVW 埋点清单缺失 🔴 严重 新增 RVW 模块埋点清单
4 用户360画像缺少 RVW 🔴 严重 补充 RVW 资产统计
5 action 类型不够全面 🟡 中等 扩展 CREATE/DELETE 类型
6 缺少 API 路由设计 🟡 中等 新增完整 API 端点设计
7 数据保留策略缺失 🟡 中等 补充 180 天数据清理
8 权限控制未说明 🟡 中等 明确角色权限矩阵

1. 核心指标定义(保持 V3.0

优先级 指标名称 定义 价值
P0+ 活跃医生数 (DAU) 今日有行为的去重 user_id 数 真实价值线
P0 活跃租户数 (DAT) 今日有行为的去重 tenant_id 数 商务生死线
P1 功能渗透率 各模块/功能使用次数分布 产品迭代指引
P2 价值交付次数 导出/下载次数 北极星指标

2. 数据库设计V3.1 修订版)

2.1 SimpleLog 表admin_schema

/// 运营日志表 (MVP V3.1)
model SimpleLog {
  id         String   @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
  createdAt  DateTime @default(now()) @map("created_at")
  
  // === 租户和用户信息 ===
  tenantId   String   @map("tenant_id") @db.VarChar(50)
  tenantName String?  @map("tenant_name") @db.VarChar(100)  // 🆕 冗余字段避免JOIN
  userId     String   @map("user_id") @db.Uuid
  userName   String?  @map("user_name") @db.VarChar(50)
  
  // === 行为记录 ===
  module     String   @db.VarChar(20)   // 模块代码
  feature    String   @db.VarChar(50)   // 细分功能
  action     String   @db.VarChar(20)   // 动作类型
  
  // === 详情信息 ===
  info       String?  @db.Text          // JSON或文本详情
  
  // === 索引 ===
  @@index([createdAt])
  @@index([tenantId])
  @@index([userId])
  @@index([module, feature])
  @@index([action])              // 🆕 支持按动作筛选
  @@map("simple_logs")
  @@schema("admin_schema")
}

2.2 字段说明

module模块代码- 完整列表

type ModuleCode = 
  | 'AIA'     // AI智能问答 (12智能体 + Protocol Agent)
  | 'PKB'     // 个人知识库
  | 'ASL'     // AI智能文献
  | 'DC'      // 数据清洗整理
  | 'RVW'     // 稿件审查系统 🆕
  | 'IIT'     // IIT Manager Agent 🆕
  | 'SSA'     // 智能统计分析 (预留) 🆕
  | 'ST'      // 统计分析工具 (预留) 🆕
  | 'SYSTEM'; // 系统级行为 (登录/登出)

action动作类型

type ActionType = 
  | 'LOGIN'   // 登录系统
  | 'USE'     // 使用功能
  | 'EXPORT'  // 导出/下载
  | 'CREATE'  // 创建资源 🆕
  | 'DELETE'  // 删除资源 🆕
  | 'ERROR';  // 错误记录

3. 完整埋点清单(按模块)

3.1 🤖 AIA 模块AI智能问答

12 个智能体

agentId feature (中文) 埋点位置
topic-scoping 科学问题梳理 conversationService.complete()
pico-analysis PICO梳理 conversationService.complete()
topic-eval 选题评价 conversationService.complete()
outcome-design 观察指标设计 conversationService.complete()
crf-design CRF设计 conversationService.complete()
sample-size 样本量计算 conversationService.complete()
protocol-writing 方案撰写 conversationService.complete()
methodology-review 方法学评审 conversationService.complete()
paper-polish 论文润色 conversationService.complete()
paper-translate 论文翻译 conversationService.complete()
data-preprocess 数据预处理 跳转DC记录来源
stat-analysis 统计分析 跳转DC记录来源

Protocol Agent🆕 2026-01-25 新功能)

feature action 埋点位置 info 示例
Protocol要素收集 USE ProtocolOrchestrator.collectPhase() "阶段1完成"
Protocol方案生成 USE ProtocolOrchestrator.generateProtocol() "生成12章节方案"
Protocol Word导出 EXPORT ProtocolAgentController.exportWord() "导出Word文档"

埋点代码位置

  • backend/src/modules/agent/protocol/services/ProtocolOrchestrator.ts
  • backend/src/modules/agent/protocol/controllers/ProtocolAgentController.ts

3.2 📚 PKB 模块(个人知识库)

feature action 埋点位置 info 示例
知识库创建 CREATE knowledgeBaseController.create() "创建: 肺癌研究库"
文档上传 USE documentController.upload() "上传: 5篇PDF"
RAG问答 USE ragController.chat() "提问: 入排标准是什么?"
批处理提取 USE batchController.process() "批量提取: 10篇"
结果导出 EXPORT batchController.export() "导出CSV"

埋点代码位置

  • backend/src/modules/pkb/controllers/

3.3 📖 ASL 模块AI智能文献

feature action 埋点位置 info 示例
DeepSearch检索 USE researchController.stream() "关键词: 肺癌治疗"
标题摘要筛选 USE screeningController.start() "筛选: 500篇"
全文复筛 USE fullTextController.start() "复筛: 100篇"
筛选结果导出 EXPORT screeningController.export() "导出Excel"

埋点代码位置

  • backend/src/modules/asl/controllers/

3.4 🧹 DC 模块(数据清洗整理)

feature action 埋点位置 info 示例
Tool B 健康检查 USE toolBController.healthCheck() "检查: 1000行数据"
Tool B 自动提取 USE toolBController.extract() "提取任务: 50条"
Tool C 数据清洗 USE toolCController.process() "执行: 筛选操作"
Tool C Pivot USE toolCController.pivot() "Pivot转换"
结果导出 EXPORT toolCController.export() "导出Excel"

埋点代码位置

  • backend/src/modules/dc/controllers/

3.5 📝 RVW 模块(稿件审查系统)🆕

feature action 埋点位置 info 示例
稿件上传 USE reviewController.upload() "上传: xxx.pdf"
稿约规范性审查 USE reviewWorker (editorial) "审查开始"
方法学审查 USE reviewWorker (methodology) "方法学审查开始"
审查完成 USE reviewWorker.complete() "评分: 规范85/方法78"
报告导出 EXPORT TaskDetail.exportWord() "导出Word报告"

埋点代码位置

  • backend/src/modules/rvw/services/reviewWorker.ts
  • backend/src/modules/rvw/controllers/reviewController.ts

3.6 🏥 IIT 模块IIT Manager Agent🆕

feature action 埋点位置 info 示例
REDCap数据同步 USE redcapAdapter.sync() "同步: 10条记录"
AI质控检查 USE qualityCheckService.check() "检查患者ID 7"
企微通知推送 USE wechatService.notify() "推送预警通知"
对话查询 USE chatService.query() "查询患者统计"
人工确权 USE actionController.approve() "确权: 排除患者"

埋点代码位置

  • backend/src/modules/iit-manager/services/
  • backend/src/modules/iit-manager/controllers/

3.7 🔐 SYSTEM系统级

feature action 埋点位置 info 示例
用户登录 LOGIN authController.login() "密码登录"
用户登出 USE authController.logout() -

埋点代码位置

  • backend/src/common/auth/auth.controller.ts

4. 后端 API 设计

4.1 运营统计 API

方法 路径 说明 权限
GET /api/admin/stats/overview 今日大盘DAU/DAT/导出数) SUPER_ADMIN
GET /api/admin/stats/live-feed 实时流水账最近100条 SUPER_ADMIN
GET /api/admin/stats/module/:code 模块使用统计 SUPER_ADMIN
GET /api/admin/users/:id/overview 用户360画像 SUPER_ADMIN

4.2 API 响应示例

今日大盘 /api/admin/stats/overview

{
  "success": true,
  "data": {
    "dau": 12,           // 今日活跃医生数
    "dat": 3,            // 今日活跃租户数
    "exportCount": 5,    // 今日导出次数
    "moduleStats": {
      "AIA": 45,
      "PKB": 23,
      "DC": 12,
      "RVW": 8,
      "ASL": 5,
      "IIT": 3
    }
  }
}

实时流水账 /api/admin/stats/live-feed

{
  "success": true,
  "data": [
    {
      "id": "uuid",
      "createdAt": "2026-01-25T10:05:00Z",
      "tenantName": "协和医院",
      "userName": "张主任",
      "module": "AIA",
      "feature": "选题评价",
      "action": "USE",
      "info": "评价得分: 85分"
    }
  ]
}

用户360画像 /api/admin/users/:id/overview

{
  "success": true,
  "data": {
    "profile": {
      "id": "uuid",
      "name": "张主任",
      "phone": "138****1234",
      "tenantName": "协和医院"
    },
    "assets": {
      "aia": { "conversationCount": 158 },
      "pkb": { "kbCount": 3, "docCount": 450 },
      "dc": { "taskCount": 12 },
      "rvw": { "reviewTaskCount": 25, "completedCount": 20 }  // 🆕
    },
    "activities": [
      {
        "createdAt": "2026-01-25T10:30:00Z",
        "module": "AIA",
        "feature": "选题评价",
        "action": "USE",
        "info": "生成结果: 85分"
      }
    ]
  }
}

5. 后端服务实现

5.1 ActivityService埋点服务

文件路径backend/src/common/services/activity.service.ts

import { prisma } from '../../config/database.js';
import { logger } from '../logging/index.js';

type ModuleCode = 'AIA' | 'PKB' | 'ASL' | 'DC' | 'RVW' | 'IIT' | 'SSA' | 'ST' | 'SYSTEM';
type ActionType = 'LOGIN' | 'USE' | 'EXPORT' | 'CREATE' | 'DELETE' | 'ERROR';

export const activityService = {
  /**
   * 核心埋点方法 (Fire-and-Forget 模式)
   * 异步执行,不阻塞主业务
   */
  log(
    tenantId: string,
    tenantName: string,    // 🆕 新增
    userId: string,
    userName: string,
    module: ModuleCode,
    feature: string,
    action: ActionType,
    info?: any
  ) {
    // 异步执行,不要 await
    prisma.simpleLog.create({
      data: {
        tenantId,
        tenantName,      // 🆕
        userId,
        userName,
        module,
        feature,
        action,
        info: typeof info === 'object' ? JSON.stringify(info) : String(info || ''),
      }
    }).catch(e => {
      logger.warn('埋点写入失败(可忽略)', { error: e.message });
    });
  },

  /**
   * 获取今日核心大盘数据
   */
  async getTodayOverview() {
    const todayStart = new Date();
    todayStart.setHours(0, 0, 0, 0);

    const stats = await prisma.$queryRaw`
      SELECT 
        COUNT(DISTINCT user_id) as dau,
        COUNT(DISTINCT tenant_id) as dat,
        COUNT(CASE WHEN action = 'EXPORT' THEN 1 END) as export_count
      FROM admin_schema.simple_logs
      WHERE created_at >= ${todayStart}
    ` as any[];

    // 模块使用统计
    const moduleStats = await prisma.$queryRaw`
      SELECT module, COUNT(*) as count
      FROM admin_schema.simple_logs
      WHERE created_at >= ${todayStart}
      GROUP BY module
    ` as any[];

    const moduleMap: Record<string, number> = {};
    moduleStats.forEach((m: any) => {
      moduleMap[m.module] = Number(m.count);
    });

    return {
      dau: Number(stats[0]?.dau || 0),
      dat: Number(stats[0]?.dat || 0),
      exportCount: Number(stats[0]?.export_count || 0),
      moduleStats: moduleMap,
    };
  },

  /**
   * 获取实时流水账
   */
  async getLiveFeed(limit = 100) {
    return prisma.simpleLog.findMany({
      orderBy: { createdAt: 'desc' },
      take: limit,
      select: {
        id: true,
        createdAt: true,
        tenantName: true,
        userName: true,
        module: true,
        feature: true,
        action: true,
        info: true,
      }
    });
  },

  /**
   * 获取用户360画像
   */
  async getUserOverview(userId: string) {
    const [user, aiaStats, kbs, dcStats, rvwStats, logs] = await Promise.all([
      // 基础信息
      prisma.user.findUnique({ 
        where: { id: userId },
        include: { tenants: true }
      }),
      
      // AIA 资产 (会话数)
      prisma.conversation.count({ 
        where: { userId, deletedAt: null } 
      }),
      
      // PKB 资产 (知识库数 + 文档数)
      prisma.knowledgeBase.findMany({
        where: { userId, deletedAt: null },
        include: { _count: { select: { documents: true } } }
      }),
      
      // DC 资产 (任务数)
      prisma.extractionTask.count({ where: { userId } }),
      
      // RVW 资产 (审稿任务数) 🆕
      prisma.reviewTask.groupBy({
        by: ['status'],
        where: { userId },
        _count: true,
      }),
      
      // 最近行为 (从 SimpleLog 查最近 20 条)
      prisma.simpleLog.findMany({
        where: { userId },
        orderBy: { createdAt: 'desc' },
        take: 20,
        select: {
          createdAt: true,
          module: true,
          feature: true,
          action: true,
          info: true,
        }
      })
    ]);

    const totalDocs = kbs.reduce((sum, kb) => sum + kb._count.documents, 0);
    
    // 计算 RVW 统计
    const rvwTotal = rvwStats.reduce((sum, s) => sum + s._count, 0);
    const rvwCompleted = rvwStats.find(s => s.status === 'completed')?._count || 0;

    return {
      profile: user ? {
        id: user.id,
        name: user.name,
        phone: user.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'),
        tenantName: user.tenants?.name,
      } : null,
      assets: {
        aia: { conversationCount: aiaStats },
        pkb: { kbCount: kbs.length, docCount: totalDocs },
        dc: { taskCount: dcStats },
        rvw: { reviewTaskCount: rvwTotal, completedCount: rvwCompleted },  // 🆕
      },
      activities: logs,
    };
  }
};

5.2 StatsController统计控制器

文件路径backend/src/modules/admin/controllers/statsController.ts

import type { FastifyRequest, FastifyReply } from 'fastify';
import { activityService } from '../../../common/services/activity.service.js';

/**
 * 获取今日大盘
 * GET /api/admin/stats/overview
 */
export async function getOverview(request: FastifyRequest, reply: FastifyReply) {
  const data = await activityService.getTodayOverview();
  return reply.send({ success: true, data });
}

/**
 * 获取实时流水账
 * GET /api/admin/stats/live-feed
 */
export async function getLiveFeed(
  request: FastifyRequest<{ Querystring: { limit?: number } }>,
  reply: FastifyReply
) {
  const limit = request.query.limit || 100;
  const data = await activityService.getLiveFeed(limit);
  return reply.send({ success: true, data });
}

/**
 * 获取用户360画像
 * GET /api/admin/users/:id/overview
 */
export async function getUserOverview(
  request: FastifyRequest<{ Params: { id: string } }>,
  reply: FastifyReply
) {
  const { id } = request.params;
  const data = await activityService.getUserOverview(id);
  return reply.send({ success: true, data });
}

6. 前端页面设计

6.1 Admin 首页改造

位置frontend-v2/src/pages/admin/AdminDashboard.tsx

顶部卡片区域

┌─────────────────┬─────────────────┬─────────────────┐
│  今日活跃医生   │  今日活跃医院   │  今日价值交付   │
│      12 👨‍⚕️     │       3 🏥      │       5 🟢      │
│   (DAU)        │   (DAT)        │  (导出次数)     │
└─────────────────┴─────────────────┴─────────────────┘

实时流水账区域

┌──────┬──────┬──────┬──────┬────────────┬──────┬────────────┐
│ 时间 │ 医院 │ 医生 │ 模块 │  具体功能  │ 动作 │    详情    │
├──────┼──────┼──────┼──────┼────────────┼──────┼────────────┤
│10:05 │ 协和 │张主任│ AIA  │  选题评价  │🔵USE │评分: 85分  │
│10:03 │ 协和 │张主任│ RVW  │  稿约规范  │🔵USE │审查开始    │
│09:55 │ 华西 │李医生│ DC   │  Tool C   │🟢EXP │导出 Excel  │
└──────┴──────┴──────┴──────┴────────────┴──────┴────────────┘

6.2 用户详情页增强

位置frontend-v2/src/modules/admin/pages/UserDetailPage.tsx

资产统计区域

┌─────────────┬─────────────┬─────────────┬─────────────┬─────────────┐
│  💬 AIA对话  │ 📚 PKB知识库 │  📄 上传文献 │ 🧹 DC清洗  │  📝 RVW审稿 │
│    158 次   │     3 个    │   450 篇    │   12 次    │    25 篇   │
└─────────────┴─────────────┴─────────────┴─────────────┴─────────────┘

行为时间轴

• 10:30 [AIA] 使用了 "选题评价" (生成结果: 85分)
• 10:15 [RVW] 完成了 "稿约规范性审查" (评分: 82分)  🆕
• 09:50 [DC] 导出了 Tool C 清洗结果 (Excel)
• 09:48 [SYSTEM] 登录系统

7. 权限控制

7.1 角色权限矩阵

功能 SUPER_ADMIN PROMPT_ENGINEER HOSPITAL_ADMIN
查看全局大盘 (只读)
查看实时流水 (只读)
查看用户画像 本租户用户
数据导出

7.2 数据隔离规则

  • SUPER_ADMIN:可查看全部租户数据
  • HOSPITAL_ADMIN:只能查看本租户的用户活动
  • 普通用户:无运营数据访问权限

8. 数据保留策略

8.1 清理规则

  • 保留期限:180 天
  • 清理方式pg-boss 定时任务,每日 03:00 执行
  • 清理脚本:
-- 清理180天前的日志
DELETE FROM admin_schema.simple_logs 
WHERE created_at < NOW() - INTERVAL '180 days';

8.2 定时任务配置

文件路径backend/src/common/jobs/cleanupWorker.ts

import { jobQueue } from './jobQueue.js';
import { prisma } from '../../config/database.js';
import { logger } from '../logging/index.js';

// 注册清理任务
export async function registerCleanupJobs() {
  await jobQueue.schedule('cleanup-simple-logs', '0 3 * * *', async () => {
    const result = await prisma.$executeRaw`
      DELETE FROM admin_schema.simple_logs 
      WHERE created_at < NOW() - INTERVAL '180 days'
    `;
    logger.info('运营日志清理完成', { deletedCount: result });
  });
}

9. 开发任务清单

Phase 1: 数据库15分钟

  • 更新 prisma/schema.prisma,添加 SimpleLog 模型
  • 执行 npx prisma db push 同步数据库
  • 验证表结构和索引

Phase 2: 后端服务60分钟

  • 创建 common/services/activity.service.ts
  • 创建 modules/admin/controllers/statsController.ts
  • 创建 modules/admin/routes/statsRoutes.ts
  • index.ts 注册路由

Phase 3: 埋点集成90分钟

系统级

  • auth.controller.ts - 登录埋点

AIA 模块

  • conversationService.ts - 12个智能体埋点
  • ProtocolOrchestrator.ts - Protocol Agent 埋点

PKB 模块

  • knowledgeBaseController.ts - 知识库创建埋点
  • documentController.ts - 文档上传埋点
  • ragController.ts - RAG问答埋点

DC 模块

  • toolBController.ts - Tool B 埋点
  • toolCController.ts - Tool C 埋点

RVW 模块 🆕

  • reviewController.ts - 上传埋点
  • reviewWorker.ts - 审查完成埋点

IIT 模块 🆕

  • chatService.ts - 对话查询埋点
  • redcapAdapter.ts - 同步埋点

Phase 4: 前端页面90分钟

  • 改造 AdminDashboard.tsx - 添加大盘卡片和流水账
  • 改造 UserDetailPage.tsx - 添加资产统计和时间轴
  • 创建 StatsCard.tsx 组件
  • 创建 LiveFeed.tsx 组件
  • 创建 ActivityTimeline.tsx 组件

Phase 5: 数据清理15分钟

  • 创建 cleanupWorker.ts
  • 注册定时任务
  • 测试清理逻辑

10. 测试验证

10.1 单元测试

# 埋点服务测试
npm test -- --grep "ActivityService"

# API 测试
npm test -- --grep "Stats API"

10.2 端到端验证

  1. 登录系统,检查是否记录 LOGIN
  2. 使用 AIA 智能体,检查是否记录 USE
  3. 导出文件,检查是否记录 EXPORT
  4. 访问 Admin 首页,验证大盘数据
  5. 访问用户详情,验证 360 画像

📊 总结

项目 V3.0 原方案 V3.1 修订版
模块覆盖 4 个 8 个
字段设计 缺少 tenantName 完整
API 设计 缺失 4 个端点
数据保留 缺失 180 天
权限控制 缺失 角色矩阵
预计工时 3-4 小时 4-5 小时

下一步:按照任务清单执行开发,优先完成 Phase 1-2基础设施再逐步完成埋点集成和前端页面。