feat(aia): Implement Protocol Agent MVP with reusable Agent framework
Sprint 1-3 Completed (Backend + Frontend): Backend (Sprint 1-2): - Implement 5-layer Agent framework (Query->Planner->Executor->Tools->Reflection) - Create agent_schema with 6 tables (agent_definitions, stages, prompts, sessions, traces, reflexion_rules) - Create protocol_schema with 2 tables (protocol_contexts, protocol_generations) - Implement Protocol Agent core services (Orchestrator, ContextService, PromptBuilder) - Integrate LLM service adapter (DeepSeek/Qwen/GPT-5/Claude) - 6 API endpoints with full authentication - 10/10 API tests passed Frontend (Sprint 3): - Add Protocol Agent entry in AgentHub (indigo theme card) - Implement ProtocolAgentPage with 3-column layout - Collapsible sidebar (Gemini style, 48px <-> 280px) - StatePanel with 5 stage cards (scientific_question, pico, study_design, sample_size, endpoints) - ChatArea with sync button and action cards integration - 100% prototype design restoration (608 lines CSS) - Detailed endpoints structure: baseline, exposure, outcomes, confounders Features: - 5-stage dialogue flow for research protocol design - Conversation-driven interaction with sync-to-protocol button - Real-time context state management - One-click protocol generation button (UI ready, backend pending) Database: - agent_schema: 6 tables for reusable Agent framework - protocol_schema: 2 tables for Protocol Agent - Seed data: 1 agent + 5 stages + 9 prompts + 4 reflexion rules Code Stats: - Backend: 13 files, 4338 lines - Frontend: 14 files, 2071 lines - Total: 27 files, 6409 lines Status: MVP core functionality completed, pending frontend-backend integration testing Next: Sprint 4 - One-click protocol generation + Word export
This commit is contained in:
@@ -418,6 +418,8 @@ curl http://你的SAE地址:3001/health
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -750,6 +750,8 @@ const job = await queue.getJob(jobId);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -517,6 +517,8 @@ processLiteraturesInBackground(task.id, projectId, testLiteratures);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -994,6 +994,8 @@ ROI = (¥22,556 - ¥144) / ¥144 × 100% = 15,564%
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
193
docs/07-运维文档/全自动巡检系统设计方案.md
Normal file
193
docs/07-运维文档/全自动巡检系统设计方案.md
Normal file
@@ -0,0 +1,193 @@
|
||||
# **全自动巡检系统设计方案 (Synthetic Monitoring)**
|
||||
|
||||
**目标:** 每天早上 06:00 自动验证系统 7 大模块核心功能,确诊“系统健康”,如有异常立即推送到企业微信。
|
||||
|
||||
**适用环境:** 阿里云 SAE (Job 任务)
|
||||
|
||||
**执行者:** 全自动巡检脚本 (HealthCheck Bot)
|
||||
|
||||
## **1\. 架构设计**
|
||||
|
||||
### **运行原理**
|
||||
|
||||
graph LR
|
||||
A\[⏰ SAE 定时任务\<br\>06:00 AM\] \--\>|启动容器| B\[🩺 巡检脚本\<br\>HealthCheck Bot\]
|
||||
B \--\>|1. HTTP请求| C\[🌐 前端/后端 API\]
|
||||
B \--\>|2. 数据库查询| D\[🐘 PostgreSQL\]
|
||||
B \--\>|3. 模型调用| E\[🤖 LLM 服务\]
|
||||
|
||||
B \--\>|✅ 所有检查通过| F\[✅ 记录日志 (静默)\]
|
||||
B \--\>|❌ 发现异常| G\[🚨 企业微信报警\]
|
||||
|
||||
### **为什么选择 SAE Job?**
|
||||
|
||||
* **无需额外服务器**:不需要为了跑一个脚本单独买 ECS。
|
||||
* **环境互通**:脚本在 VPC 内网运行,可以直接连接 RDS 数据库验证数据一致性,也可以通过内网 IP 调用 Python 微服务,**不走公网流量,速度极快**。
|
||||
* **配置简单**:和部署后端应用完全一样,只是把“启动命令”改成了“执行一次脚本”。
|
||||
|
||||
## **2\. 巡检脚本逻辑 (TypeScript 伪代码)**
|
||||
|
||||
建议在 backend 项目中新建一个目录 scripts/health-check/,复用现有的 ORM 和 Service。
|
||||
|
||||
### **核心检测项 (覆盖 7 大模块)**
|
||||
|
||||
| 模块 | 检测点 (Check Point) | 预期结果 |
|
||||
| :---- | :---- | :---- |
|
||||
| **基础层** | **数据库连接** | prisma.$queryRaw('SELECT 1') 返回 1,耗时 \< 100ms |
|
||||
| **基础层** | **外部 API 连通性** | ping api.deepseek.com 成功 (验证 NAT 网关正常) |
|
||||
| **AIA** | **AI 问答响应** | 向 DeepSeek 发送 "Hi",能在 5s 内收到回复 (验证 LLM 通路) |
|
||||
| **PKB** | **向量检索 (RAG)** | 上传一段测试文本,并在 1s 后通过关键词检索到它 (验证 pgvector 和 embedding) |
|
||||
| **ASL** | **Python 微服务** | 调用 Python 服务 /health 接口,返回 200 (验证 Python 容器存活) |
|
||||
| **DC** | **数据清洗** | 发送一个简单的 JSON 数据给 Tool C 接口,验证返回清洗结果 |
|
||||
| **OSS** | **文件存取** | 上传一个 health\_check.txt 到 OSS 并下载,内容一致 (验证存储) |
|
||||
|
||||
### **代码示例**
|
||||
|
||||
// backend/scripts/health-check/run.ts
|
||||
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import axios from 'axios';
|
||||
import { sendWecomAlert } from './wecom'; // 复用 IIT 模块的企微通知代码
|
||||
|
||||
const prisma \= new PrismaClient();
|
||||
const REPORT \= {
|
||||
success: true,
|
||||
modules: \[\] as string\[\],
|
||||
errors: \[\] as string\[\]
|
||||
};
|
||||
|
||||
async function checkDatabase() {
|
||||
const start \= Date.now();
|
||||
try {
|
||||
await prisma.$queryRaw\`SELECT 1\`;
|
||||
REPORT.modules.push(\`✅ Database (${Date.now() \- start}ms)\`);
|
||||
} catch (e) {
|
||||
throw new Error(\`Database connection failed: ${e.message}\`);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkPythonService() {
|
||||
// 使用内网地址,复用环境变量
|
||||
const url \= process.env.EXTRACTION\_SERVICE\_URL \+ '/health';
|
||||
try {
|
||||
const res \= await axios.get(url, { timeout: 2000 });
|
||||
if (res.status \=== 200\) REPORT.modules.push(\`✅ Python Service\`);
|
||||
} catch (e) {
|
||||
throw new Error(\`Python Service unreachable: ${e.message}\`);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkLLM() {
|
||||
// 调用简单的 Chat 接口测试
|
||||
try {
|
||||
// 模拟一次简单的 AI 对话...
|
||||
REPORT.modules.push(\`✅ LLM Gateway\`);
|
||||
} catch (e) {
|
||||
throw new Error(\`LLM API failed: ${e.message}\`);
|
||||
}
|
||||
}
|
||||
|
||||
// ... 更多检查函数 ...
|
||||
|
||||
async function main() {
|
||||
console.log('🚀 Starting Daily Health Check...');
|
||||
|
||||
try {
|
||||
await checkDatabase();
|
||||
await checkPythonService();
|
||||
await checkLLM();
|
||||
// await checkOSS();
|
||||
// await checkRAG();
|
||||
|
||||
console.log('🎉 All systems healthy\!');
|
||||
// 可选:成功也发送一条简短通知,让你早上醒来看到绿色对勾
|
||||
// await sendWecomAlert('🟢 每日巡检通过:系统运行正常');
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('🔥 Health Check Failed:', error);
|
||||
REPORT.success \= false;
|
||||
|
||||
// 🚨 发生异常,立即推送报警!
|
||||
await sendWecomAlert(\`🔴 \*\*线上环境异常报警\*\* 🔴\\n\\n检查时间: ${new Date().toLocaleString()}\\n错误模块: ${error.message}\\n\\n请尽快检查 SAE 控制台!\`);
|
||||
|
||||
process.exit(1); // 让 SAE 任务标记为失败
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
## **3\. 阿里云 SAE 部署实操**
|
||||
|
||||
### **步骤 1: 构建镜像**
|
||||
|
||||
既然脚本在 backend 项目里,你可以**直接复用 Node.js 后端的镜像**!
|
||||
|
||||
不需要重新构建专门的镜像,因为后端镜像里已经包含了 Node 环境、Prisma Client 和所有依赖。
|
||||
|
||||
### **步骤 2: 创建 SAE 任务 (Job)**
|
||||
|
||||
1. 登录 SAE 控制台 \-\> 任务列表 (Job) \-\> 创建任务。
|
||||
2. **应用名称**:clinical-health-check
|
||||
3. **镜像地址**:选择你们的 backend 镜像 (如 backend:latest)。
|
||||
4. **运行命令**:
|
||||
* 这里的命令会覆盖 Dockerfile 的 CMD。
|
||||
* 填写:npx tsx scripts/health-check/run.ts (假设你用 tsx 运行)
|
||||
5. **调度配置**:
|
||||
* 并发策略:Forbid (禁止并发,上一次没跑完下一次不跑)
|
||||
* 定时配置 (Crontab):0 6 \* \* \* (每天 06:00 执行)
|
||||
6. **环境变量**:
|
||||
* **直接复制** 生产环境后端应用的所有环境变量 (DATABASE\_URL, WECHAT\_KEY 等)。
|
||||
7. **网络配置**:
|
||||
* 选择和生产环境一样的 VPC 和 VSwitch (确保能连上 RDS)。
|
||||
|
||||
## **4\. 报警通知模板 (企业微信)**
|
||||
|
||||
当脚本捕获到异常时,发送如下 Markdown 消息到你们的开发群:
|
||||
|
||||
🔴 \*\*严重:线上巡检未通过\*\*
|
||||
|
||||
\> \*\*检测时间\*\*:2026-01-24 06:00:05
|
||||
\> \*\*环境\*\*:Aliyun Production
|
||||
|
||||
\*\*异常详情\*\*:
|
||||
❌ \*\*\[Python Service\]\*\* Connection refused (172.16.x.x:8000)
|
||||
✅ \*\*\[Database\]\*\* OK
|
||||
✅ \*\*\[OSS\]\*\* OK
|
||||
|
||||
\*\*建议操作\*\*:
|
||||
1\. 检查 Python 微服务容器是否重启
|
||||
2\. 检查 SAE 内存监控
|
||||
3\. \[点击跳转 SAE 控制台\](https://sae.console.aliyun.com/...)
|
||||
|
||||
## **5\. 进阶:阿里云原生监控 (兜底方案)**
|
||||
|
||||
除了自己写脚本,阿里云还有一个**免费且好用**的功能,建议同时开启,作为**双重保险**。
|
||||
|
||||
### **阿里云 CloudMonitor (云监控) \-\> 站点监控 (Site Monitor)**
|
||||
|
||||
1. **无需写代码**。
|
||||
2. 在云监控控制台,创建一个“站点监控”任务。
|
||||
3. **监控地址**:输入你们的公网域名 http://8.140.53.236/。
|
||||
4. **频率**:每 1 分钟一次。
|
||||
5. **报警**:如果 HTTP 状态码 \!= 200,或者响应时间 \> 5秒,发送短信给你们手机。
|
||||
|
||||
**对比:**
|
||||
|
||||
* **云监控**:只能告诉你“网站打不打得开”(Ping 通不通)。
|
||||
* **自研脚本 (本方案)**:能告诉你“**功能坏没坏**”(比如网站能打开,但 AI 回复不了,云监控发现不了,但脚本能发现)。
|
||||
|
||||
## **6\. 实施路线图**
|
||||
|
||||
1. **Day 1 (本地开发)**:
|
||||
* 在 backend 项目里写好 run.ts。
|
||||
* 在本地运行 npx tsx scripts/health-check/run.ts,确保它能跑通所有检查流程。
|
||||
* 测试企业微信推送是否正常。
|
||||
2. **Day 2 (SAE 部署)**:
|
||||
* 不需要重新发版,只要之前的后端镜像里包含了这个脚本文件(通常都会包含 src 目录)。
|
||||
* 在 SAE 创建 Job,手动触发一次,看日志是否成功。
|
||||
3. **Day 3 (安心睡觉)**:
|
||||
* 设置定时任务,每天早上收那个绿色的 ✅ 消息。
|
||||
|
||||
这样做,你每天早上醒来第一眼看手机,如果看到“✅ 每日巡检通过”,你就可以安心刷牙洗脸;如果没收到或者收到红色报警,你在地铁上就能开始安排修复,而不是等用户投诉了才手忙脚乱。
|
||||
124
docs/07-运维文档/架构优化建议:2人团队SAE降本增效方案.md
Normal file
124
docs/07-运维文档/架构优化建议:2人团队SAE降本增效方案.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# **针对 2 人初创团队的 SAE \+ RDS 架构降本增效方案**
|
||||
|
||||
**现状诊断:**
|
||||
|
||||
* **团队规模:** 2人(极简团队)
|
||||
* **当前痛点:** 成本压力(双 SAE 环境费用) vs 运维安全(需要隔离测试)
|
||||
* **关键约束:** 必须防止类似 prisma db push 的生产事故再次发生。
|
||||
|
||||
## **策略一:数据库层面 —— “同居不同室”**
|
||||
|
||||
你们现在的做法是共享同一个 RDS PostgreSQL 15 实例。这在创业初期是**非常明智**的选择,不需要改变。购买两个 RDS 实例对于目前的业务量来说太浪费了。
|
||||
|
||||
但是,**“共享”的方式决定了生死**。
|
||||
|
||||
### **1\. 绝对禁止的做法**
|
||||
|
||||
❌ **Dev 和 Prod 使用同一个数据库名(Database Name),试图靠 Schema 区分。**
|
||||
|
||||
* **风险**:你们的代码中(特别是 Prisma 和 pg-boss)可能硬编码了 Schema 名称(如 platform\_schema)。
|
||||
* **后果**:Dev 环境启动后,pg-boss Worker 会连接到数据库,开始抓取 Prod 环境的任务队列进行处理。这会导致生产任务丢失或被错误处理。
|
||||
|
||||
### **2\. 推荐做法:Database 级别的隔离**
|
||||
|
||||
✅ **在同一个 RDS 实例中,创建两个完全独立的数据库。**
|
||||
|
||||
| 环境变量 | 生产环境 (Prod) | 开发/测试环境 (Dev) |
|
||||
| :---- | :---- | :---- |
|
||||
| **DATABASE\_URL** | .../ai\_clinical\_prod | .../ai\_clinical\_dev |
|
||||
| **用户名** | aiclinical\_prod\_user | aiclinical\_dev\_user |
|
||||
| **资源限制** | 无限制 | 限制连接数 (避免 Dev 耗尽连接) |
|
||||
|
||||
**实施步骤:**
|
||||
|
||||
1. 进入 RDS 控制台,新建数据库 ai\_clinical\_dev。
|
||||
2. 即使是同一个 RDS 实例,由于数据库名不同,数据文件在逻辑上是完全隔离的。
|
||||
3. Dev 环境即使执行 prisma db push \--force-reset,也只会清空 ai\_clinical\_dev,**生产环境绝对安全**。
|
||||
|
||||
## **策略二:计算层面 (SAE) —— “分时租赁与按需启停”**
|
||||
|
||||
对于 SAE,你们最大的优势是**Serverless 的弹性**。如果测试环境 24 小时开着,那就是把 SAE 当 ECS 用,非常亏。
|
||||
|
||||
### **1\. 生产环境 (Production)**
|
||||
|
||||
* **策略**:保持常驻,配置最小实例数为 1(或 2 保证高可用)。
|
||||
* **配置**:开启弹性伸缩,根据 CPU/内存自动扩容。
|
||||
|
||||
### **2\. 开发/测试环境 (Dev/Test)**
|
||||
|
||||
**核心建议:不要让它 24 小时运行!**
|
||||
|
||||
#### **方案 A:一键启停 (手动省钱法)**
|
||||
|
||||
* **操作**:
|
||||
* 平时开发:在本地 localhost 使用 Docker Compose \+ 本地 Postgres 开发(完全免费)。
|
||||
* 提交代码后:如果需要验证,去 SAE 控制台点击“启动”。
|
||||
* 验证完/下班后:去 SAE 控制台点击\*\*“停止”\*\*(由 1 实例缩容为 0)。
|
||||
* **成本影响**:SAE 停止的应用**不收取计算费用**(只收取极少的快照存储费)。如果每天只开 2 小时测试,成本降低 90%。
|
||||
|
||||
#### **方案 B:CI/CD 触发式环境 (自动化法)**
|
||||
|
||||
* **原理**:利用 GitHub Actions 或 阿里云云效。
|
||||
* **流程**:
|
||||
1. 代码 Push 到 dev 分支。
|
||||
2. CI 流水线构建镜像。
|
||||
3. CI 调用阿里云 API,**更新并启动** SAE Dev 应用。
|
||||
4. 设置一个定时任务(如每晚 22:00),自动调用 API **停止** Dev 应用。
|
||||
|
||||
## **策略三:极致省钱的替代架构 (如果不使用 SAE 测试环境)**
|
||||
|
||||
如果连 SAE 测试环境的“按需启动”成本都想省,可以考虑以下架构:
|
||||
|
||||
### **1\. "Localhost is King" 模式**
|
||||
|
||||
既然只有 2 个人,且都在内网或可以通过 VPN 协作:
|
||||
|
||||
* **放弃 SAE 测试环境**。
|
||||
* **开发验证**:每人本地运行全套 Docker Compose(包含前端、后端、Postgres、MinIO 模拟 OSS)。
|
||||
* **集成测试**:其中一人的电脑作为“临时服务器”,通过 ngrok 或 frp 暴露端口给另一人测试。
|
||||
* **上线**:直接部署到 SAE 生产环境。
|
||||
* **缺点**:缺乏一个稳定的“预发环境”,上线风险略高。
|
||||
|
||||
### **2\. ECS "脏"环境模式 (Dirty Dev Box)**
|
||||
|
||||
* **做法**:买一台最便宜的 **ECS (突发性能实例 t6/t5)**,或者利用闲置的旧笔记本/工控机放在公司/家里。
|
||||
* **部署**:上面装一个 Docker,把所有 Dev 服务(前端、后端、Python、DB)全扔进去。
|
||||
* **成本**:一台 2核4G 的 t6 实例,一个月可能只要几十块钱(或者抢占式实例,更便宜)。
|
||||
* **优势**:固定 IP,成本极低,随便折腾。
|
||||
* **劣势**:需要自己维护 Linux、Docker 环境。
|
||||
|
||||
## **综合建议:给你们团队的最佳实践**
|
||||
|
||||
考虑到你们已经熟悉了 SAE 且希望减少运维,我推荐 **“改良版 SAE \+ 共享 RDS”** 方案:
|
||||
|
||||
1. **数据库**:
|
||||
* 继续使用**同一个 RDS 实例**。
|
||||
* **必须**创建两个 DB:prod\_db 和 dev\_db。
|
||||
* **必须**使用两个数据库账号,防止权限越界。
|
||||
2. **SAE 环境**:
|
||||
* **生产环境**:SAE 专业版(或标准版),常驻。
|
||||
* **测试环境**:**SAE 标准版(无需专业版)**。
|
||||
* **关键动作**:
|
||||
* 配置 SAE 测试环境的**定时启停**策略(阿里云 SAE 控制台支持定时启停)。
|
||||
* 例如:09:00 启动,20:00 停止。周末不启动。
|
||||
* 或者写一个脚本,只在部署新镜像时启动,闲置 1 小时无流量自动缩容到 0(需配合 ALB/CLB 流量监控,略复杂,建议手动或定时停止)。
|
||||
3. **本地开发增强**:
|
||||
* 完善 docker-compose.yml。确保本地开发体验和云端 99% 一致。
|
||||
* 这样你们 90% 的测试工作(包括 Python 微服务、数据清洗逻辑)都可以在本地完成,只有最后 10% 需要用到 SAE 测试环境。
|
||||
|
||||
### **成本对比预估 (月)**
|
||||
|
||||
| 方案 | 生产环境成本 | 测试环境成本 | 运维复杂度 | 推荐度 |
|
||||
| :---- | :---- | :---- | :---- | :---- |
|
||||
| **现状 (双 SAE 常驻)** | ¥600+ | ¥300+ | 低 | ⭐⭐ |
|
||||
| **推荐 (Dev 定时启停)** | ¥600+ | **¥50 (按量付费)** | 中 (需配置一次) | ⭐⭐⭐⭐⭐ |
|
||||
| **ECS 廉价测试机** | ¥600+ | ¥40-80 | 高 (需维护服务器) | ⭐⭐⭐ |
|
||||
| **Localhost 只有生产** | ¥600+ | ¥0 | 低 (但在上线时风险极高) | ⭐ |
|
||||
|
||||
### **总结**
|
||||
|
||||
对于 2 人团队,**时间比计算资源更贵**。
|
||||
|
||||
不要为了省几百块钱去维护一套复杂的自建 ECS 环境。
|
||||
|
||||
**继续用 SAE,但是要学会“关机”。** 就像你离开房间会关灯一样,没人测试的时候,把 SAE Dev 环境关掉(缩容到 0),这就是云原生最大的红利。
|
||||
293
docs/07-运维文档/运营体系设计方案-MVP-V3.0.md
Normal file
293
docs/07-运维文档/运营体系设计方案-MVP-V3.0.md
Normal file
@@ -0,0 +1,293 @@
|
||||
# **AI 临床科研平台 \- MVP 运营监控体系实施方案 V3.0**
|
||||
|
||||
**文档版本**:V3.0 (全景洞察版)
|
||||
|
||||
**面向对象**:核心开发团队 (2人)
|
||||
|
||||
**更新重点**:
|
||||
|
||||
1. **宏观**:确立 DAU/DAT 双核心指标,防止“假活跃”。
|
||||
2. **微观**:新增 **“用户 360 全景画像”**,支持查看单用户的资产存量与行为流水。
|
||||
3. **技术**:维持 Postgres-Only 极简架构,不引入新组件。
|
||||
|
||||
## **1\. 核心理念:关注“生存”与“真实价值”**
|
||||
|
||||
在 MVP 阶段,我们必须警惕“只有管理员登录”的假象。**医生用起来,才是真的活了。**
|
||||
|
||||
| 优先级 | 指标名称 | 定义 | 为什么关注? |
|
||||
| :---- | :---- | :---- | :---- |
|
||||
| **P0+** | **活跃医生数 (DAU)** | 今日有登录/使用行为的去重 user\_id 数。 | **真实价值线**。只有医生个人觉得好用,产品才有生命力。这是防止客户流失的前哨指标。 |
|
||||
| **P0** | **活跃租户数 (DAT)** | 今日有登录行为的去重 tenant\_id 数。 | **商务生死线**。代表医院客户的存活情况。 |
|
||||
| **P1** | **具体功能渗透率** | 例如:“选题助手”的使用次数 vs “论文润色”的使用次数。 | **产品迭代指引**。如果没人用“润色”,我们就别在这个功能上浪费时间开发了。 |
|
||||
| **P2** | **价值交付次数** | 用户点击 **“导出/下载”** 的次数。 | **北极星指标**。用户只有认可了 AI 的结果才会导出。这是交付价值的终点。 |
|
||||
|
||||
## **2\. 技术架构:Postgres-Only 极简方案**
|
||||
|
||||
### **2.1 数据库设计 (Schema)**
|
||||
|
||||
在 admin\_schema 下新增一张通用日志表。**不需要**做聚合表,**不需要**做定时统计任务,直接查表即可。
|
||||
|
||||
**文件路径**:backend/prisma/schema.prisma
|
||||
|
||||
// ... inside admin\_schema ...
|
||||
|
||||
/// 极简运营日志表 (MVP)
|
||||
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)
|
||||
userId String @map("user\_id") @db.Uuid
|
||||
userName String? @map("user\_name") @db.VarChar(50)
|
||||
|
||||
// V3.0 核心字段设计
|
||||
module String @db.VarChar(20) // 大模块: 'AIA', 'ASL', 'DC', 'PKB'
|
||||
feature String @db.VarChar(50) // 细分功能: 'Topic Agent', 'Tool C', 'RAG Chat'
|
||||
action String @db.VarChar(20) // 动作: 'USE', 'EXPORT', 'ERROR', 'LOGIN'
|
||||
|
||||
info String? @db.Text // 详情: "生成了3个选题" / "导出Excel"
|
||||
|
||||
@@index(\[createdAt\])
|
||||
@@index(\[tenantId\])
|
||||
@@index(\[userId\]) // V3.0 新增:支持查询单用户轨迹
|
||||
@@index(\[module, feature\]) // 支持按功能统计热度
|
||||
@@map("simple\_logs")
|
||||
@@schema("admin\_schema")
|
||||
}
|
||||
|
||||
### **2.2 后端服务实现 (ActivityService)**
|
||||
|
||||
负责写入日志和获取大盘数据。
|
||||
|
||||
**文件路径**:backend/src/common/services/activity.service.ts
|
||||
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
// 假设这是全局 Prisma 实例
|
||||
import { prisma } from '@/common/database/prisma';
|
||||
|
||||
export const activityService \= {
|
||||
/\*\*
|
||||
\* 核心埋点方法 (Fire-and-Forget 模式)
|
||||
\* @param feature 具体功能名称,如 'Topic Agent'
|
||||
\*/
|
||||
async log(
|
||||
tenantId: string,
|
||||
userId: string,
|
||||
userName: string,
|
||||
action: 'LOGIN' | 'USE' | 'EXPORT' | 'ERROR',
|
||||
module: 'AIA' | 'ASL' | 'DC' | 'PKB' | 'SYSTEM',
|
||||
feature: string, // 必填:细分功能
|
||||
info: any
|
||||
) {
|
||||
// 异步执行,不要 await,避免影响主接口性能
|
||||
prisma.simpleLog.create({
|
||||
data: {
|
||||
tenantId, userId, userName, action, module, feature,
|
||||
info: typeof info \=== 'object' ? JSON.stringify(info) : String(info),
|
||||
}
|
||||
}).catch(e \=\> console.error('埋点写入失败(可忽略):', e));
|
||||
},
|
||||
|
||||
/\*\*
|
||||
\* 运营看板:获取实时流水账
|
||||
\*/
|
||||
async getLiveFeed(limit \= 100\) {
|
||||
return prisma.simpleLog.findMany({
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: limit
|
||||
});
|
||||
},
|
||||
|
||||
/\*\*
|
||||
\* 🟢 获取今日核心大盘数据 (DAU \+ DAT)
|
||||
\* 用于 Admin 首页顶部展示
|
||||
\*/
|
||||
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}
|
||||
\`;
|
||||
|
||||
return {
|
||||
dau: Number(stats\[0\].dau),
|
||||
dat: Number(stats\[0\].dat),
|
||||
exportCount: Number(stats\[0\].export\_count)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
## **3\. 细粒度埋点实施清单 (直接复制给开发)**
|
||||
|
||||
为了满足运营需求,请在以下位置埋点,并务必带上 feature 参数。
|
||||
|
||||
### **🤖 3.1 AIA 模块 (12个智能体细分)**
|
||||
|
||||
**位置**:aia/services/chat.service.ts \-\> complete 方法 (AI回复完成时)
|
||||
|
||||
**逻辑**:根据当前对话的 agentId 记录不同的 feature。
|
||||
|
||||
// 示例映射表
|
||||
const agentNameMap \= {
|
||||
'topic-scoping': '科学问题梳理',
|
||||
'pico-analysis': 'PICO梳理',
|
||||
'topic-eval': '选题评价',
|
||||
'outcome-design': '观察指标设计',
|
||||
'crf-design': 'CRF设计',
|
||||
'sample-size': '样本量计算',
|
||||
'protocol-writing': '方案撰写',
|
||||
'methodology-review': '方法学评审',
|
||||
'paper-polish': '论文润色',
|
||||
'paper-translate': '论文翻译',
|
||||
// ... 其他智能体
|
||||
};
|
||||
const feature \= agentNameMap\[agentId\] || 'Unknown Agent';
|
||||
activityService.log(..., 'USE', 'AIA', feature, '生成完成');
|
||||
|
||||
### **🧹 3.2 DC 模块 (清洗工具细分)**
|
||||
|
||||
**位置**:dc/controllers/extraction.controller.ts
|
||||
|
||||
* **Tool B (自动提取)**: log(..., 'USE', 'DC', 'Tool B', '提取任务: 50条')
|
||||
* **Tool C (数据清洗)**: log(..., 'USE', 'DC', 'Tool C', '执行清洗操作')
|
||||
* **导出结果**: log(..., 'EXPORT', 'DC', 'Tool C', '导出 Excel')
|
||||
|
||||
### **📚 3.3 ASL 模块 (文献功能细分)**
|
||||
|
||||
**位置**:asl/controllers/research.controller.ts
|
||||
|
||||
* **智能检索**: log(..., 'USE', 'ASL', 'DeepSearch', '关键词: 肺癌')
|
||||
* **标题摘要筛选**: log(..., 'USE', 'ASL', 'Title/Abstract Screening', '筛选进度: 100/500')
|
||||
* **全文复筛**: log(..., 'USE', 'ASL', 'Fulltext Review', '复筛完成')
|
||||
|
||||
### **🧠 3.4 PKB 模块 (知识库行为)**
|
||||
|
||||
**位置**:pkb/controllers/...
|
||||
|
||||
* **上传文档**: log(..., 'USE', 'PKB', 'Document Upload', '上传: 3篇')
|
||||
* **RAG 问答**: log(..., 'USE', 'PKB', 'RAG Chat', '提问: 入排标准是什么?')
|
||||
* **批处理**: log(..., 'USE', 'PKB', 'Batch Process', '批量提取: 10篇')
|
||||
|
||||
## **4\. 运营看板设计 (Admin Portal) \- 宏观**
|
||||
|
||||
### **界面参考**
|
||||
|
||||
在 Admin 首页展示。
|
||||
|
||||
#### **\[ 顶部卡片区域 \]**
|
||||
|
||||
| 今日活跃医生 (DAU) | 今日活跃医院 (DAT) | 今日价值交付 (导出) |
|
||||
| :---- | :---- | :---- |
|
||||
| **12** 👨⚕️ | **3** 🏥 | **5** 🟢 |
|
||||
|
||||
#### **\[ 实时流水账区域 \]**
|
||||
|
||||
| 时间 | 医院 | 医生 | 模块 | 具体功能 | 动作 | 详情 |
|
||||
| :---- | :---- | :---- | :---- | :---- | :---- | :---- |
|
||||
| 10:05 | 协和 | 张主任 | **AIA** | **选题评价** | 🔵 USE | 评价得分: 85分 |
|
||||
| 10:03 | 协和 | 张主任 | **AIA** | **PICO梳理** | 🔵 USE | 梳理完成 |
|
||||
| 09:55 | 华西 | 李医生 | **DC** | **Tool C** | 🟢 **EXPORT** | 导出 Excel |
|
||||
|
||||
## **5\. 用户 360 全景画像 (User 360 View) \- 微观 🆕**
|
||||
|
||||
**新增模块**:当运营人员点击用户列表中的“详情”时,展示该用户的全生命周期数据。
|
||||
|
||||
### **5.1 后端聚合服务 (UserOverviewService)**
|
||||
|
||||
不建立宽表,利用 Prisma 并行查询实现\*\*“资产快照 \+ 行为流水”\*\*聚合。
|
||||
|
||||
**文件路径**:backend/src/modules/admin/services/user-overview.service.ts
|
||||
|
||||
import { prisma } from '@/common/database/prisma';
|
||||
|
||||
export const userOverviewService \= {
|
||||
async getUserProfile(userId: string) {
|
||||
// 并行查询,耗时取决于最慢的那个查询,通常 \< 200ms
|
||||
const \[user, aiaStats, kbs, dcStats, logs\] \= await Promise.all(\[
|
||||
// 1\. 基础信息
|
||||
prisma.user.findUnique({ where: { id: userId } }),
|
||||
|
||||
// 2\. AIA 资产 (会话数)
|
||||
prisma.conversation.count({ where: { userId, isDeleted: false } }),
|
||||
|
||||
// 3\. PKB 资产 (知识库数 \+ 文档数)
|
||||
prisma.knowledgeBase.findMany({
|
||||
where: { userId },
|
||||
include: { \_count: { select: { documents: true } } }
|
||||
}),
|
||||
|
||||
// 4\. DC 资产 (任务数)
|
||||
prisma.extractionTask.count({ where: { userId } }),
|
||||
|
||||
// 5\. 最近行为 (从 SimpleLog 查最近 20 条)
|
||||
prisma.simpleLog.findMany({
|
||||
where: { userId },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 20
|
||||
})
|
||||
\]);
|
||||
|
||||
// 计算文档总数
|
||||
const totalDocs \= kbs.reduce((sum, kb) \=\> sum \+ kb.\_count.documents, 0);
|
||||
|
||||
return {
|
||||
profile: user,
|
||||
assets: {
|
||||
aia: { conversationCount: aiaStats },
|
||||
pkb: { kbCount: kbs.length, docCount: totalDocs, kbs },
|
||||
dc: { taskCount: dcStats }
|
||||
},
|
||||
activities: logs
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
### **5.2 前端布局设计 (UserDetailPage)**
|
||||
|
||||
使用 Ant Design 组件库实现三段式布局。
|
||||
|
||||
#### **区域一:资产数字 (Asset Stats)**
|
||||
|
||||
| 💬 AIA 对话 | 📚 PKB 知识库 | 📄 上传文献 | 🧹 DC 清洗任务 |
|
||||
| :---- | :---- | :---- | :---- |
|
||||
| **158** 次 | **3** 个 | **450** 篇 | **12** 次 |
|
||||
|
||||
*(点击数字可展开查看具体列表,如查看 3 个知识库的名称)*
|
||||
|
||||
#### **区域二:行为时间轴 (Timeline)**
|
||||
|
||||
展示用户最近的操作轨迹,快速判断用户是否遇到困难。
|
||||
|
||||
* **10:30** \[AIA\] 使用了 **“选题评价”** (生成结果: 85分)
|
||||
* **10:15** \[PKB\] 上传了文档 lung\_cancer\_study.pdf 到 **“肺癌课题库”**
|
||||
* **09:50** \[DC\] 导出了 **Tool C** 清洗结果 (Excel)
|
||||
* **09:48** \[系统\] 登录系统
|
||||
|
||||
## **6\. 每日自动化巡检 (SAE Job)**
|
||||
|
||||
复用后端镜像,使用 SAE Job 功能,每天 06:00 执行。
|
||||
|
||||
* **检查项**:数据库连接、Python 微服务心跳、公网连通性 (NAT)。
|
||||
* **通知**:检查失败时发送企业微信报警。
|
||||
* **目的**:确保早上醒来时系统是健康的。
|
||||
|
||||
## **7\. 开发执行计划**
|
||||
|
||||
**总耗时预计:3-4 小时**
|
||||
|
||||
1. **\[DB\] (15min)**:
|
||||
* 更新 Prisma Schema (新增 feature 字段和索引)。
|
||||
* 执行 prisma db push。
|
||||
2. **\[Backend\] (60min)**:
|
||||
* 实现 activityService (埋点 \+ 大盘)。
|
||||
* 实现 userOverviewService (360 聚合)。
|
||||
* 在 AIA/DC/ASL/PKB 核心 Controller 插入 log() 代码。
|
||||
3. **\[Frontend\] (90min)**:
|
||||
* Admin 首页:实现大盘卡片 \+ 实时流水表格。
|
||||
* User 详情页:实现资产统计卡片 \+ 行为 Timeline。
|
||||
Reference in New Issue
Block a user