feat(iit): Initialize IIT Manager Agent MVP - Day 1 complete
- Add iit_schema with 5 tables - Create module structure and types (223 lines) - WeChat integration verified (Access Token success) - Update system docs to v2.4 - Add REDCap source folders to .gitignore - Day 1/14 complete (11/11 tasks)
This commit is contained in:
@@ -38,6 +38,12 @@ WHERE table_schema = 'dc_schema'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -81,5 +81,11 @@ ORDER BY ordinal_position;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -95,4 +95,10 @@ runMigration()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -23,6 +23,12 @@ COMMENT ON COLUMN "dc_schema"."dc_tool_c_sessions"."column_mapping" IS '列名
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -50,6 +50,12 @@ COMMENT ON COLUMN dc_schema.dc_tool_c_sessions.expires_at IS '过期时间(创
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ generator client {
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
schemas = ["admin_schema", "aia_schema", "asl_schema", "common_schema", "dc_schema", "pkb_schema", "platform_schema", "public", "rvw_schema", "ssa_schema", "st_schema"]
|
||||
schemas = ["admin_schema", "aia_schema", "asl_schema", "common_schema", "dc_schema", "iit_schema", "pkb_schema", "platform_schema", "public", "rvw_schema", "ssa_schema", "st_schema"]
|
||||
}
|
||||
|
||||
/// 应用缓存表 - Postgres-Only架构
|
||||
@@ -825,3 +825,195 @@ enum job_state {
|
||||
|
||||
@@schema("platform_schema")
|
||||
}
|
||||
|
||||
// ==============================
|
||||
// IIT Manager Schema (V1.1)
|
||||
// ==============================
|
||||
|
||||
/// IIT项目表
|
||||
model IitProject {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
description String? @db.Text
|
||||
|
||||
// Protocol知识库
|
||||
difyDatasetId String? @unique @map("dify_dataset_id")
|
||||
protocolFileKey String? @map("protocol_file_key")
|
||||
|
||||
// V1.1 新增:Dify性能优化 - 缓存关键规则
|
||||
cachedRules Json? @map("cached_rules")
|
||||
|
||||
// 字段映射配置(JSON)
|
||||
fieldMappings Json @map("field_mappings")
|
||||
|
||||
// REDCap配置
|
||||
redcapProjectId String @map("redcap_project_id")
|
||||
redcapApiToken String @db.Text @map("redcap_api_token")
|
||||
redcapUrl String @map("redcap_url")
|
||||
|
||||
// V1.1 新增:同步管理 - 记录上次同步时间
|
||||
lastSyncAt DateTime? @map("last_sync_at")
|
||||
|
||||
// 项目状态
|
||||
status String @default("active")
|
||||
|
||||
// 时间戳
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
|
||||
// 关系
|
||||
pendingActions IitPendingAction[]
|
||||
taskRuns IitTaskRun[]
|
||||
userMappings IitUserMapping[]
|
||||
auditLogs IitAuditLog[]
|
||||
|
||||
@@index([status, deletedAt])
|
||||
@@map("projects")
|
||||
@@schema("iit_schema")
|
||||
}
|
||||
|
||||
/// 影子状态表(核心)
|
||||
model IitPendingAction {
|
||||
id String @id @default(uuid())
|
||||
projectId String @map("project_id")
|
||||
recordId String @map("record_id")
|
||||
fieldName String @map("field_name")
|
||||
|
||||
// 数据对比
|
||||
currentValue Json? @map("current_value")
|
||||
suggestedValue Json? @map("suggested_value")
|
||||
|
||||
// 状态流转
|
||||
status String // PROPOSED/APPROVED/REJECTED/EXECUTED/FAILED
|
||||
agentType String @map("agent_type") // DATA_QUALITY/TASK_DRIVEN/COUNSELING/REPORTING
|
||||
|
||||
// AI推理信息
|
||||
reasoning String @db.Text
|
||||
evidence Json
|
||||
|
||||
// 人类确认信息
|
||||
approvedBy String? @map("approved_by")
|
||||
approvedAt DateTime? @map("approved_at")
|
||||
rejectionReason String? @db.Text @map("rejection_reason")
|
||||
|
||||
// 执行信息
|
||||
executedAt DateTime? @map("executed_at")
|
||||
errorMessage String? @db.Text @map("error_message")
|
||||
|
||||
// 时间戳
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
// 关系
|
||||
project IitProject @relation(fields: [projectId], references: [id])
|
||||
|
||||
@@index([projectId, status])
|
||||
@@index([projectId, recordId])
|
||||
@@index([status, createdAt])
|
||||
@@map("pending_actions")
|
||||
@@schema("iit_schema")
|
||||
}
|
||||
|
||||
/// 任务运行记录(与 pg-boss 关联)
|
||||
model IitTaskRun {
|
||||
id String @id @default(uuid())
|
||||
projectId String @map("project_id")
|
||||
taskType String @map("task_type")
|
||||
|
||||
// 关联 pg-boss job
|
||||
jobId String? @unique @map("job_id")
|
||||
|
||||
// 任务状态
|
||||
status String
|
||||
|
||||
// 业务结果
|
||||
totalItems Int @map("total_items")
|
||||
processedItems Int @default(0) @map("processed_items")
|
||||
successItems Int @default(0) @map("success_items")
|
||||
failedItems Int @default(0) @map("failed_items")
|
||||
|
||||
// 时间信息
|
||||
startedAt DateTime? @map("started_at")
|
||||
completedAt DateTime? @map("completed_at")
|
||||
duration Int? // 秒
|
||||
|
||||
// 时间戳
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
// 关系
|
||||
project IitProject @relation(fields: [projectId], references: [id])
|
||||
|
||||
@@index([projectId, taskType, status])
|
||||
@@index([jobId])
|
||||
@@map("task_runs")
|
||||
@@schema("iit_schema")
|
||||
}
|
||||
|
||||
/// 用户映射表(异构系统身份关联)
|
||||
model IitUserMapping {
|
||||
id String @id @default(uuid())
|
||||
projectId String @map("project_id")
|
||||
|
||||
// 系统用户ID(本系统)
|
||||
systemUserId String @map("system_user_id")
|
||||
|
||||
// REDCap用户名
|
||||
redcapUsername String @map("redcap_username")
|
||||
|
||||
// 企微OpenID
|
||||
wecomUserId String? @map("wecom_user_id")
|
||||
|
||||
// V1.1 新增:小程序支持
|
||||
miniProgramOpenId String? @unique @map("mini_program_open_id")
|
||||
sessionKey String? @map("session_key")
|
||||
|
||||
// 角色
|
||||
role String
|
||||
|
||||
// 时间戳
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
// 关系
|
||||
project IitProject @relation(fields: [projectId], references: [id])
|
||||
|
||||
@@unique([projectId, systemUserId])
|
||||
@@unique([projectId, redcapUsername])
|
||||
@@index([wecomUserId])
|
||||
@@index([miniProgramOpenId])
|
||||
@@map("user_mappings")
|
||||
@@schema("iit_schema")
|
||||
}
|
||||
|
||||
/// 审计日志表
|
||||
model IitAuditLog {
|
||||
id String @id @default(uuid())
|
||||
projectId String @map("project_id")
|
||||
|
||||
// 操作信息
|
||||
userId String @map("user_id")
|
||||
actionType String @map("action_type")
|
||||
entityType String @map("entity_type")
|
||||
entityId String @map("entity_id")
|
||||
|
||||
// 详细信息
|
||||
details Json?
|
||||
|
||||
// 追踪链
|
||||
traceId String @map("trace_id")
|
||||
|
||||
// 时间戳
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
// 关系
|
||||
project IitProject @relation(fields: [projectId], references: [id])
|
||||
|
||||
@@index([projectId, createdAt])
|
||||
@@index([userId, createdAt])
|
||||
@@index([actionType, createdAt])
|
||||
@@index([traceId])
|
||||
@@map("audit_logs")
|
||||
@@schema("iit_schema")
|
||||
}
|
||||
|
||||
@@ -97,3 +97,9 @@ Write-Host " 5. 保存并重新部署" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -200,6 +200,12 @@ function extractCodeBlocks(obj, blocks = []) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -219,6 +219,12 @@ checkDCTables();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -171,6 +171,12 @@ createAiHistoryTable()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -158,6 +158,12 @@ createToolCTable()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -155,6 +155,12 @@ createToolCTable()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -292,5 +292,11 @@ export function getBatchItems<T>(
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -145,6 +145,17 @@ export const config = {
|
||||
/** Dify API URL */
|
||||
difyApiUrl: process.env.DIFY_API_URL || 'http://localhost/v1',
|
||||
|
||||
// ==================== 企业微信配置(IIT Manager Agent)====================
|
||||
|
||||
/** 企业微信企业ID */
|
||||
wechatCorpId: process.env.WECHAT_CORP_ID || '',
|
||||
|
||||
/** 企业微信应用Secret */
|
||||
wechatCorpSecret: process.env.WECHAT_CORP_SECRET || '',
|
||||
|
||||
/** 企业微信应用AgentID */
|
||||
wechatAgentId: process.env.WECHAT_AGENT_ID || '',
|
||||
|
||||
// ==================== 文件上传配置(Legacy兼容)====================
|
||||
|
||||
/** 文件上传大小限制 */
|
||||
|
||||
@@ -123,6 +123,13 @@ logger.info('✅ ASL智能文献筛选路由已注册: /api/v1/asl');
|
||||
await registerDCRoutes(fastify);
|
||||
logger.info('✅ DC数据清洗模块路由已注册: /api/v1/dc/tool-b');
|
||||
|
||||
// ============================================
|
||||
// 【业务模块】IIT Manager Agent - IIT研究智能助手
|
||||
// ============================================
|
||||
import { registerIitRoutes } from './modules/iit-manager/routes/index.js';
|
||||
await registerIitRoutes(fastify);
|
||||
logger.info('✅ IIT Manager Agent路由已注册: /api/v1/iit');
|
||||
|
||||
// 启动服务器
|
||||
const start = async () => {
|
||||
try {
|
||||
|
||||
@@ -323,6 +323,12 @@ runTests().catch((error) => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -264,6 +264,12 @@ runTest()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -302,6 +302,12 @@ Content-Type: application/json
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -238,6 +238,12 @@ export const conflictDetectionService = new ConflictDetectionService();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -188,6 +188,12 @@ curl -X POST http://localhost:3000/api/v1/dc/tool-c/test/execute \
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -242,6 +242,12 @@ export const streamAIController = new StreamAIController();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
16
backend/src/modules/iit-manager/index.ts
Normal file
16
backend/src/modules/iit-manager/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* IIT Manager Agent 模块
|
||||
*
|
||||
* 核心功能:
|
||||
* - REDCap数据同步(混合模式:Webhook + 轮询)
|
||||
* - AI智能质控(Dify RAG)
|
||||
* - 影子状态管理
|
||||
* - 企微卡片通知
|
||||
*
|
||||
* @module iit-manager
|
||||
* @version 1.1.0
|
||||
*/
|
||||
|
||||
export * from './routes';
|
||||
export * from './types';
|
||||
|
||||
24
backend/src/modules/iit-manager/routes/index.ts
Normal file
24
backend/src/modules/iit-manager/routes/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* IIT Manager 路由注册
|
||||
*/
|
||||
|
||||
import { FastifyInstance } from 'fastify';
|
||||
|
||||
export async function registerIitRoutes(fastify: FastifyInstance) {
|
||||
// 健康检查
|
||||
fastify.get('/api/v1/iit/health', async (request, reply) => {
|
||||
return {
|
||||
status: 'ok',
|
||||
module: 'iit-manager',
|
||||
version: '1.1.0',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
});
|
||||
|
||||
// TODO: 注册其他路由
|
||||
// - 项目管理路由
|
||||
// - Webhook路由
|
||||
// - 影子状态路由
|
||||
// - 任务管理路由
|
||||
}
|
||||
|
||||
152
backend/src/modules/iit-manager/test-iit-database.ts
Normal file
152
backend/src/modules/iit-manager/test-iit-database.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* IIT Manager 数据库测试脚本
|
||||
* 验证Schema和CRUD操作
|
||||
*/
|
||||
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function testIitDatabase() {
|
||||
console.log('🔍 Testing IIT Manager Database...\n');
|
||||
|
||||
try {
|
||||
// ==================== 测试1:创建项目 ====================
|
||||
console.log('✅ Test 1: 创建IIT项目');
|
||||
const testProject = await prisma.iitProject.create({
|
||||
data: {
|
||||
name: 'Test IIT Project',
|
||||
description: '这是一个测试项目',
|
||||
fieldMappings: {
|
||||
age: 'patient_age',
|
||||
gender: 'sex',
|
||||
enrollmentDate: 'consent_date'
|
||||
},
|
||||
redcapProjectId: '123',
|
||||
redcapApiToken: 'test_token_12345',
|
||||
redcapUrl: 'https://redcap.example.com',
|
||||
status: 'active'
|
||||
}
|
||||
});
|
||||
console.log(` ✓ 项目ID: ${testProject.id}`);
|
||||
console.log(` ✓ 项目名称: ${testProject.name}\n`);
|
||||
|
||||
// ==================== 测试2:创建影子状态记录 ====================
|
||||
console.log('✅ Test 2: 创建影子状态记录');
|
||||
const testAction = await prisma.iitPendingAction.create({
|
||||
data: {
|
||||
projectId: testProject.id,
|
||||
recordId: 'RECORD_001',
|
||||
fieldName: 'age',
|
||||
currentValue: 65,
|
||||
suggestedValue: null,
|
||||
status: 'PROPOSED',
|
||||
agentType: 'DATA_QUALITY',
|
||||
reasoning: 'AI detected: 年龄65岁超出入排标准(18-60岁)',
|
||||
evidence: {
|
||||
protocolPage: 12,
|
||||
protocolSection: '入排标准',
|
||||
confidence: 0.95,
|
||||
ruleType: 'inclusion'
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log(` ✓ 影子状态ID: ${testAction.id}`);
|
||||
console.log(` ✓ 状态: ${testAction.status}\n`);
|
||||
|
||||
// ==================== 测试3:创建任务记录 ====================
|
||||
console.log('✅ Test 3: 创建任务运行记录');
|
||||
const testTaskRun = await prisma.iitTaskRun.create({
|
||||
data: {
|
||||
projectId: testProject.id,
|
||||
taskType: 'bulk-scan',
|
||||
status: 'pending',
|
||||
totalItems: 100,
|
||||
processedItems: 0,
|
||||
successItems: 0,
|
||||
failedItems: 0
|
||||
}
|
||||
});
|
||||
console.log(` ✓ 任务ID: ${testTaskRun.id}`);
|
||||
console.log(` ✓ 任务类型: ${testTaskRun.taskType}\n`);
|
||||
|
||||
// ==================== 测试4:创建用户映射 ====================
|
||||
console.log('✅ Test 4: 创建用户映射');
|
||||
const testUserMapping = await prisma.iitUserMapping.create({
|
||||
data: {
|
||||
projectId: testProject.id,
|
||||
systemUserId: 'user_123',
|
||||
redcapUsername: 'test_crc',
|
||||
wecomUserId: 'wecom_123',
|
||||
role: 'CRC'
|
||||
}
|
||||
});
|
||||
console.log(` ✓ 用户映射ID: ${testUserMapping.id}`);
|
||||
console.log(` ✓ 角色: ${testUserMapping.role}\n`);
|
||||
|
||||
// ==================== 测试5:创建审计日志 ====================
|
||||
console.log('✅ Test 5: 创建审计日志');
|
||||
const testAuditLog = await prisma.iitAuditLog.create({
|
||||
data: {
|
||||
projectId: testProject.id,
|
||||
userId: 'user_123',
|
||||
actionType: 'APPROVE_ACTION',
|
||||
entityType: 'PENDING_ACTION',
|
||||
entityId: testAction.id,
|
||||
details: {
|
||||
before: { status: 'PROPOSED' },
|
||||
after: { status: 'APPROVED' }
|
||||
},
|
||||
traceId: 'trace_' + Date.now()
|
||||
}
|
||||
});
|
||||
console.log(` ✓ 审计日志ID: ${testAuditLog.id}`);
|
||||
console.log(` ✓ 操作类型: ${testAuditLog.actionType}\n`);
|
||||
|
||||
// ==================== 测试6:查询和关联 ====================
|
||||
console.log('✅ Test 6: 查询项目及关联数据');
|
||||
const projectWithRelations = await prisma.iitProject.findUnique({
|
||||
where: { id: testProject.id },
|
||||
include: {
|
||||
pendingActions: true,
|
||||
taskRuns: true,
|
||||
userMappings: true,
|
||||
auditLogs: true
|
||||
}
|
||||
});
|
||||
console.log(` ✓ 项目名称: ${projectWithRelations?.name}`);
|
||||
console.log(` ✓ 影子状态记录数: ${projectWithRelations?.pendingActions.length}`);
|
||||
console.log(` ✓ 任务记录数: ${projectWithRelations?.taskRuns.length}`);
|
||||
console.log(` ✓ 用户映射数: ${projectWithRelations?.userMappings.length}`);
|
||||
console.log(` ✓ 审计日志数: ${projectWithRelations?.auditLogs.length}\n`);
|
||||
|
||||
// ==================== 清理测试数据 ====================
|
||||
console.log('🧹 清理测试数据...');
|
||||
await prisma.iitAuditLog.delete({ where: { id: testAuditLog.id } });
|
||||
await prisma.iitUserMapping.delete({ where: { id: testUserMapping.id } });
|
||||
await prisma.iitTaskRun.delete({ where: { id: testTaskRun.id } });
|
||||
await prisma.iitPendingAction.delete({ where: { id: testAction.id } });
|
||||
await prisma.iitProject.delete({ where: { id: testProject.id } });
|
||||
console.log(' ✓ 测试数据已清理\n');
|
||||
|
||||
console.log('🎉 所有测试通过!IIT Schema工作正常!\n');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 测试失败:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testIitDatabase()
|
||||
.then(() => {
|
||||
console.log('✅ 数据库验证完成');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('❌ 数据库验证失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
137
backend/src/modules/iit-manager/test-wechat-push.ts
Normal file
137
backend/src/modules/iit-manager/test-wechat-push.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* 企业微信推送测试脚本
|
||||
* 验证企微API配置是否正确
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import { config } from '../../config/env.js';
|
||||
|
||||
// 从配置文件读取配置
|
||||
const CORP_ID = config.wechatCorpId;
|
||||
const CORP_SECRET = config.wechatCorpSecret;
|
||||
const AGENT_ID = config.wechatAgentId;
|
||||
|
||||
async function testWeChatPush() {
|
||||
console.log('🔍 测试企业微信推送功能...\n');
|
||||
|
||||
try {
|
||||
// ==================== 步骤1:验证环境变量 ====================
|
||||
console.log('✅ 步骤1: 验证环境变量');
|
||||
if (!CORP_ID || !CORP_SECRET || !AGENT_ID) {
|
||||
console.error('❌ 缺少企业微信配置!');
|
||||
console.log(' 请检查 .env 文件中的配置:');
|
||||
console.log(' - WECHAT_CORP_ID');
|
||||
console.log(' - WECHAT_CORP_SECRET');
|
||||
console.log(' - WECHAT_AGENT_ID');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(` ✓ CORP_ID: ${CORP_ID}`);
|
||||
console.log(` ✓ AGENT_ID: ${AGENT_ID}`);
|
||||
console.log(` ✓ SECRET: ${CORP_SECRET.substring(0, 10)}...(已隐藏)\n`);
|
||||
|
||||
// ==================== 步骤2:获取Access Token ====================
|
||||
console.log('✅ 步骤2: 获取Access Token');
|
||||
const tokenUrl = `https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${CORP_ID}&corpsecret=${CORP_SECRET}`;
|
||||
|
||||
const tokenResponse = await axios.get(tokenUrl);
|
||||
|
||||
if (tokenResponse.data.errcode !== undefined && tokenResponse.data.errcode !== 0) {
|
||||
console.error('❌ 获取Access Token失败:');
|
||||
console.error(` 错误码: ${tokenResponse.data.errcode}`);
|
||||
console.error(` 错误信息: ${tokenResponse.data.errmsg}`);
|
||||
console.log('\n常见错误码:');
|
||||
console.log(' 40013: invalid corpid - 企业ID错误');
|
||||
console.log(' 40001: invalid secret - Secret错误');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const accessToken = tokenResponse.data.access_token;
|
||||
console.log(` ✓ Access Token: ${accessToken.substring(0, 20)}...`);
|
||||
console.log(` ✓ 有效期: ${tokenResponse.data.expires_in}秒 (约${Math.floor(tokenResponse.data.expires_in / 60)}分钟)\n`);
|
||||
|
||||
// ==================== 步骤3:发送测试消息 ====================
|
||||
console.log('✅ 步骤3: 发送测试文本消息');
|
||||
|
||||
// 注意:touser 使用 "@all" 发送给所有人(测试阶段)
|
||||
// 生产环境需要指定具体的UserID
|
||||
const message = {
|
||||
touser: '@all', // 发送给所有人
|
||||
msgtype: 'text',
|
||||
agentid: parseInt(AGENT_ID!),
|
||||
text: {
|
||||
content: '🎉 IIT Manager Agent 测试消息\n\n这是一条来自后端服务的测试推送。\n\n如果您看到这条消息,说明企业微信配置成功!✅'
|
||||
},
|
||||
safe: 0
|
||||
};
|
||||
|
||||
const sendUrl = `https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${accessToken}`;
|
||||
const sendResponse = await axios.post(sendUrl, message);
|
||||
|
||||
if (sendResponse.data.errcode !== 0) {
|
||||
console.error('❌ 发送消息失败:');
|
||||
console.error(` 错误码: ${sendResponse.data.errcode}`);
|
||||
console.error(` 错误信息: ${sendResponse.data.errmsg}`);
|
||||
console.log('\n常见错误码:');
|
||||
console.log(' 40014: invalid access_token - Token无效或过期');
|
||||
console.log(' 40003: invalid openid - UserID无效');
|
||||
console.log(' 60020: not allow to access from your ip - IP白名单限制');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(' ✓ 消息发送成功!');
|
||||
console.log(` ✓ 消息ID: ${sendResponse.data.msgid}`);
|
||||
console.log(` ✓ 无效用户: ${sendResponse.data.invaliduser || '无'}\n`);
|
||||
|
||||
// ==================== 步骤4:发送测试卡片消息 ====================
|
||||
console.log('✅ 步骤4: 发送测试卡片消息(质控预警样式)');
|
||||
|
||||
const cardMessage = {
|
||||
touser: '@all',
|
||||
msgtype: 'textcard',
|
||||
agentid: parseInt(AGENT_ID!),
|
||||
textcard: {
|
||||
title: '🚨 数据质控预警(测试)',
|
||||
description: '<div class="gray">项目:骨科IIT研究</div><div class="gray">患者:RECORD_001</div><div class="normal">AI检测到1个问题</div><div class="highlight">置信度:高(95%)</div><div class="normal">请尽快处理</div>',
|
||||
url: 'http://localhost:5173',
|
||||
btntxt: '查看详情'
|
||||
}
|
||||
};
|
||||
|
||||
const cardResponse = await axios.post(sendUrl, cardMessage);
|
||||
|
||||
if (cardResponse.data.errcode !== 0) {
|
||||
console.error('❌ 发送卡片消息失败:');
|
||||
console.error(` 错误码: ${cardResponse.data.errcode}`);
|
||||
console.error(` 错误信息: ${cardResponse.data.errmsg}`);
|
||||
} else {
|
||||
console.log(' ✓ 卡片消息发送成功!');
|
||||
console.log(` ✓ 消息ID: ${cardResponse.data.msgid}\n`);
|
||||
}
|
||||
|
||||
// ==================== 完成 ====================
|
||||
console.log('🎉 所有测试通过!\n');
|
||||
console.log('📱 请在企业微信APP中查看收到的消息:');
|
||||
console.log(' 1. 文本消息:确认配置成功');
|
||||
console.log(' 2. 卡片消息:预览质控预警样式\n');
|
||||
console.log('✅ 企业微信集成就绪,可以进入Day 2开发!\n');
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ 测试失败:', error.message);
|
||||
if (error.response) {
|
||||
console.error(' 响应数据:', error.response.data);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
testWeChatPush()
|
||||
.then(() => {
|
||||
console.log('✅ 企业微信测试完成');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('❌ 企业微信测试失败:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
222
backend/src/modules/iit-manager/types/index.ts
Normal file
222
backend/src/modules/iit-manager/types/index.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* IIT Manager 类型定义
|
||||
*/
|
||||
|
||||
// ==================== REDCap相关 ====================
|
||||
|
||||
export interface RedcapRecord {
|
||||
record_id: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface RedcapExportOptions {
|
||||
records?: string[];
|
||||
fields?: string[];
|
||||
dateRangeBegin?: string;
|
||||
dateRangeEnd?: string;
|
||||
rawOrLabel?: 'raw' | 'label';
|
||||
}
|
||||
|
||||
export interface RedcapMetadataField {
|
||||
field_name: string;
|
||||
form_name: string;
|
||||
section_header?: string;
|
||||
field_type: string;
|
||||
field_label: string;
|
||||
select_choices_or_calculations?: string;
|
||||
field_note?: string;
|
||||
text_validation_type_or_show_slider_number?: string;
|
||||
text_validation_min?: string;
|
||||
text_validation_max?: string;
|
||||
identifier?: string;
|
||||
branching_logic?: string;
|
||||
required_field?: string;
|
||||
custom_alignment?: string;
|
||||
question_number?: string;
|
||||
matrix_group_name?: string;
|
||||
matrix_ranking?: string;
|
||||
field_annotation?: string;
|
||||
}
|
||||
|
||||
// ==================== Webhook相关 ====================
|
||||
|
||||
export interface WebhookPayload {
|
||||
project_id: string;
|
||||
record_id: string;
|
||||
instrument: string;
|
||||
event_id?: string;
|
||||
repeat_instance?: number;
|
||||
data: Record<string, any>;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
// ==================== 质控相关 ====================
|
||||
|
||||
export interface QualityCheckParams {
|
||||
projectId: string;
|
||||
recordId: string;
|
||||
data: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface QualityIssue {
|
||||
fieldName: string;
|
||||
logicalField: string;
|
||||
currentValue: any;
|
||||
suggestedValue?: any;
|
||||
reason: string;
|
||||
evidence: {
|
||||
protocolPage?: number;
|
||||
protocolSection?: string;
|
||||
confidence: number;
|
||||
ruleType: 'inclusion' | 'exclusion' | 'validation' | 'logic';
|
||||
};
|
||||
severity: 'critical' | 'major' | 'minor';
|
||||
}
|
||||
|
||||
export interface ProtocolComplianceResult {
|
||||
compliant: boolean;
|
||||
issues: QualityIssue[];
|
||||
reasoning: string;
|
||||
}
|
||||
|
||||
// ==================== 影子状态相关 ====================
|
||||
|
||||
export type PendingActionStatus =
|
||||
| 'PROPOSED' // AI建议已创建
|
||||
| 'APPROVED' // 人类已确认
|
||||
| 'REJECTED' // 人类已拒绝
|
||||
| 'EXECUTED' // 已回写REDCap
|
||||
| 'FAILED'; // 回写失败
|
||||
|
||||
export type AgentType =
|
||||
| 'DATA_QUALITY' // 数据质控
|
||||
| 'TASK_DRIVEN' // 任务驱动
|
||||
| 'COUNSELING' // 智能咨询
|
||||
| 'REPORTING'; // 智能汇报
|
||||
|
||||
export interface PendingActionDetail {
|
||||
id: string;
|
||||
projectId: string;
|
||||
recordId: string;
|
||||
fieldName: string;
|
||||
currentValue: any;
|
||||
suggestedValue: any;
|
||||
status: PendingActionStatus;
|
||||
agentType: AgentType;
|
||||
reasoning: string;
|
||||
evidence: QualityIssue['evidence'];
|
||||
approvedBy?: string;
|
||||
approvedAt?: Date;
|
||||
rejectionReason?: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
// ==================== 任务相关 ====================
|
||||
|
||||
export type TaskType =
|
||||
| 'quality-check' // 单条质控
|
||||
| 'bulk-scan' // 全量扫描
|
||||
| 'bulk-scan:batch' // 批次扫描
|
||||
| 'follow-up' // 随访提醒
|
||||
| 'report-generation'; // 报告生成
|
||||
|
||||
export interface TaskRunStatus {
|
||||
id: string;
|
||||
projectId: string;
|
||||
taskType: TaskType;
|
||||
status: 'pending' | 'processing' | 'completed' | 'failed';
|
||||
totalItems: number;
|
||||
processedItems: number;
|
||||
successItems: number;
|
||||
failedItems: number;
|
||||
startedAt?: Date;
|
||||
completedAt?: Date;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
// ==================== 企微相关 ====================
|
||||
|
||||
export interface WeChatMessageCard {
|
||||
toUser: string;
|
||||
title: string;
|
||||
description: string;
|
||||
url: string;
|
||||
btntxt?: string;
|
||||
}
|
||||
|
||||
// ==================== 同步相关 ====================
|
||||
|
||||
export interface SyncMode {
|
||||
webhook: boolean; // Webhook是否可用
|
||||
polling: boolean; // 是否启用轮询
|
||||
pollingInterval: number; // 轮询间隔(分钟)
|
||||
}
|
||||
|
||||
export interface SyncCheckpoint {
|
||||
projectId: string;
|
||||
lastSyncAt: Date;
|
||||
lastRecordId?: string;
|
||||
recordsProcessed: number;
|
||||
}
|
||||
|
||||
// ==================== Dify RAG相关 ====================
|
||||
|
||||
export interface DifyQueryParams {
|
||||
datasetId: string;
|
||||
query: string;
|
||||
retrieval_model?: {
|
||||
search_method: 'keyword_search' | 'semantic_search' | 'full_text_search' | 'hybrid_search';
|
||||
reranking_enable?: boolean;
|
||||
reranking_model?: {
|
||||
reranking_provider_name: string;
|
||||
reranking_model_name: string;
|
||||
};
|
||||
top_k?: number;
|
||||
score_threshold_enabled?: boolean;
|
||||
score_threshold?: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DifyQueryResult {
|
||||
records: Array<{
|
||||
segment: {
|
||||
content: string;
|
||||
answer: string;
|
||||
id: string;
|
||||
position: number;
|
||||
document_id: string;
|
||||
score: number;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
// ==================== Protocol缓存规则 ====================
|
||||
|
||||
export interface CachedProtocolRules {
|
||||
// 入排标准
|
||||
inclusionCriteria: Array<{
|
||||
field: string;
|
||||
rule: string;
|
||||
type: 'range' | 'enum' | 'boolean' | 'expression';
|
||||
value: any;
|
||||
}>;
|
||||
exclusionCriteria: Array<{
|
||||
field: string;
|
||||
rule: string;
|
||||
type: 'range' | 'enum' | 'boolean' | 'expression';
|
||||
value: any;
|
||||
}>;
|
||||
// 字段验证规则
|
||||
fields: Record<string, {
|
||||
required?: boolean;
|
||||
type?: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
enum?: string[];
|
||||
pattern?: string;
|
||||
dependencies?: string[];
|
||||
}>;
|
||||
// 提取时间
|
||||
extractedAt: string;
|
||||
}
|
||||
|
||||
@@ -394,4 +394,10 @@ SET session_replication_role = 'origin';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -96,4 +96,10 @@ WHERE key = 'verify_test';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -239,4 +239,10 @@ verifyDatabase()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
6
backend/src/types/global.d.ts
vendored
6
backend/src/types/global.d.ts
vendored
@@ -28,5 +28,11 @@ export {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -46,6 +46,12 @@ Write-Host "✅ 完成!" -ForegroundColor Green
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -333,6 +333,12 @@ runAdvancedTests().catch(error => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -399,6 +399,12 @@ runAllTests()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -357,6 +357,12 @@ runAllTests()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user