# 运营监控系统 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) ```prisma /// 运营日志表 (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(模块代码)- 完整列表 ```typescript type ModuleCode = | 'AIA' // AI智能问答 (12智能体 + Protocol Agent) | 'PKB' // 个人知识库 | 'ASL' // AI智能文献 | 'DC' // 数据清洗整理 | 'RVW' // 稿件审查系统 🆕 | 'IIT' // IIT Manager Agent 🆕 | 'SSA' // 智能统计分析 (预留) 🆕 | 'ST' // 统计分析工具 (预留) 🆕 | 'SYSTEM'; // 系统级行为 (登录/登出) ``` #### action(动作类型) ```typescript 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` ```json { "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` ```json { "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` ```json { "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` ```typescript 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 = {}; 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` ```typescript 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 执行 - 清理脚本: ```sql -- 清理180天前的日志 DELETE FROM admin_schema.simple_logs WHERE created_at < NOW() - INTERVAL '180 days'; ``` ### 8.2 定时任务配置 **文件路径**:`backend/src/common/jobs/cleanupWorker.ts` ```typescript 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 单元测试 ```bash # 埋点服务测试 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(基础设施),再逐步完成埋点集成和前端页面。