Summary: - Fix methodology score display issue in task list (show score instead of 'warn') - Add methodology_score field to database schema - Fix report display when only methodology agent is selected - Implement Word document export using docx library - Update documentation to v3.0/v3.1 Backend changes: - Add methodologyScore to Prisma schema and TaskSummary type - Update reviewWorker to save methodologyScore - Update getTaskList to return methodologyScore Frontend changes: - Install docx and file-saver libraries - Implement handleExportReport with Word generation - Fix activeTab auto-selection based on available data - Add proper imports for docx components Documentation: - Update RVW module status to 90% (Phase 1-5 complete) - Update system status document to v3.0 Tested: All review workflows verified, Word export functional
295 lines
8.5 KiB
TypeScript
295 lines
8.5 KiB
TypeScript
/**
|
||
* 端到端真实测试 v2 - 简化版
|
||
*
|
||
* 使用真实数据测试完整流程:
|
||
* 1. 创建项目
|
||
* 2. 导入1篇文献(简化)
|
||
* 3. 创建全文复筛任务
|
||
* 4. 等待LLM处理
|
||
* 5. 查看结果
|
||
*/
|
||
|
||
import axios from 'axios';
|
||
import { PrismaClient } from '@prisma/client';
|
||
import fs from 'fs/promises';
|
||
import path from 'path';
|
||
|
||
const API_BASE = 'http://localhost:3000/api/v1/asl';
|
||
const prisma = new PrismaClient();
|
||
|
||
interface TestResult {
|
||
projectId?: string;
|
||
literatureIds?: string[];
|
||
taskId?: string;
|
||
success: boolean;
|
||
error?: string;
|
||
}
|
||
|
||
async function runTest(): Promise<TestResult> {
|
||
console.log('🚀 开始端到端真实测试 v2\n');
|
||
console.log('⏰ 测试时间:', new Date().toLocaleString('zh-CN'));
|
||
console.log('📍 API地址:', API_BASE);
|
||
console.log('=' .repeat(80) + '\n');
|
||
|
||
const result: TestResult = { success: false };
|
||
|
||
try {
|
||
// ========================================
|
||
// Step 1: 创建测试项目
|
||
// ========================================
|
||
console.log('📋 Step 1: 创建测试项目');
|
||
|
||
const picosPath = path.join(
|
||
process.cwd(),
|
||
'../docs/03-业务模块/ASL-AI智能文献/05-测试文档/03-测试数据/screening/测试案例的PICOS、纳入标准、排除标准.txt'
|
||
);
|
||
|
||
const picosContent = await fs.readFile(picosPath, 'utf-8');
|
||
|
||
// 解析PICOS
|
||
const populationMatch = picosContent.match(/P \(Population\)[::]\s*(.+)/);
|
||
const interventionMatch = picosContent.match(/I \(Intervention\)[::]\s*(.+)/);
|
||
const comparisonMatch = picosContent.match(/C \(Comparison\)[::]\s*(.+)/);
|
||
const outcomeMatch = picosContent.match(/O \(Outcome\)[::]\s*(.+)/);
|
||
const studyDesignMatch = picosContent.match(/S \(Study Design\)[::]\s*(.+)/);
|
||
|
||
const projectData = {
|
||
name: `E2E测试-${Date.now()}`,
|
||
description: '端到端真实测试项目',
|
||
picoCriteria: {
|
||
P: populationMatch?.[1]?.trim() || '缺血性卒中患者',
|
||
I: interventionMatch?.[1]?.trim() || '抗血小板治疗',
|
||
C: comparisonMatch?.[1]?.trim() || '对照组',
|
||
O: outcomeMatch?.[1]?.trim() || '卒中复发',
|
||
S: studyDesignMatch?.[1]?.trim() || 'RCT',
|
||
},
|
||
};
|
||
|
||
const projectResponse = await axios.post(`${API_BASE}/projects`, projectData);
|
||
result.projectId = projectResponse.data.data.id;
|
||
console.log(`✅ 项目创建成功: ${result.projectId}\n`);
|
||
|
||
// ========================================
|
||
// Step 2: 导入1篇简单测试文献
|
||
// ========================================
|
||
console.log('📚 Step 2: 导入测试文献(使用简化数据)');
|
||
|
||
const literatureData = {
|
||
projectId: result.projectId,
|
||
literatures: [
|
||
{
|
||
pmid: 'TEST001',
|
||
title: 'Antiplatelet Therapy for Secondary Stroke Prevention: A Randomized Controlled Trial',
|
||
abstract: 'Background: Stroke is a major cause of death worldwide. This study evaluates antiplatelet therapy effectiveness. Methods: We conducted an RCT with 500 patients randomized to aspirin vs clopidogrel groups. The study was double-blind. Results: Primary outcome (stroke recurrence) occurred in 12% of aspirin group vs 8% of clopidogrel group (p=0.03). Secondary outcomes showed similar trends. Conclusion: Clopidogrel demonstrates superior efficacy for secondary stroke prevention in Asian patients.',
|
||
authors: 'Zhang W, Li H, Wang Y',
|
||
journal: 'Stroke Research',
|
||
publicationYear: 2023,
|
||
hasPdf: false,
|
||
},
|
||
],
|
||
};
|
||
|
||
const importResponse = await axios.post(`${API_BASE}/literatures/import`, literatureData);
|
||
console.log(`✅ 文献导入成功: ${importResponse.data.data.importedCount}篇\n`);
|
||
|
||
// 获取文献ID
|
||
const literatures = await prisma.aslLiterature.findMany({
|
||
where: { projectId: result.projectId },
|
||
select: { id: true, title: true },
|
||
});
|
||
result.literatureIds = literatures.map(lit => lit.id);
|
||
console.log('📄 导入的文献:');
|
||
literatures.forEach(lit => {
|
||
console.log(` - ${lit.id.slice(0, 8)}: ${lit.title.slice(0, 60)}...`);
|
||
});
|
||
console.log('');
|
||
|
||
// ========================================
|
||
// Step 3: 创建全文复筛任务
|
||
// ========================================
|
||
console.log('🤖 Step 3: 创建全文复筛任务');
|
||
|
||
const taskData = {
|
||
projectId: result.projectId,
|
||
literatureIds: result.literatureIds,
|
||
config: {
|
||
modelA: 'deepseek-v3',
|
||
modelB: 'qwen-max',
|
||
concurrency: 1,
|
||
skipExtraction: true, // 跳过PDF提取,使用标题+摘要
|
||
},
|
||
};
|
||
|
||
const taskResponse = await axios.post(`${API_BASE}/fulltext-screening/tasks`, taskData);
|
||
result.taskId = taskResponse.data.data.taskId;
|
||
console.log(`✅ 任务创建成功: ${result.taskId}\n`);
|
||
|
||
// ========================================
|
||
// Step 4: 监控任务进度
|
||
// ========================================
|
||
console.log('⏳ Step 4: 监控任务进度(等待LLM处理)\n');
|
||
|
||
let maxAttempts = 30; // 最多等待5分钟
|
||
let attempt = 0;
|
||
let taskCompleted = false;
|
||
|
||
while (attempt < maxAttempts && !taskCompleted) {
|
||
await new Promise(resolve => setTimeout(resolve, 10000)); // 每10秒查询一次
|
||
attempt++;
|
||
|
||
try {
|
||
const progressResponse = await axios.get(
|
||
`${API_BASE}/fulltext-screening/tasks/${result.taskId}/progress`
|
||
);
|
||
const progress = progressResponse.data.data;
|
||
|
||
console.log(`[${attempt}/${maxAttempts}] 进度: ${progress.processedCount}/${progress.totalCount} | ` +
|
||
`成功: ${progress.successCount} | 失败: ${progress.failedCount} | ` +
|
||
`Token: ${progress.totalTokens} | 成本: ¥${progress.totalCost.toFixed(4)}`);
|
||
|
||
if (progress.status === 'completed' || progress.status === 'failed') {
|
||
taskCompleted = true;
|
||
console.log(`\n✅ 任务完成!状态: ${progress.status}\n`);
|
||
}
|
||
} catch (error: any) {
|
||
console.log(`⚠️ 查询进度失败: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
if (!taskCompleted) {
|
||
console.log('⚠️ 任务超时,但可能仍在后台处理\n');
|
||
}
|
||
|
||
// ========================================
|
||
// Step 5: 获取结果
|
||
// ========================================
|
||
console.log('📊 Step 5: 获取处理结果\n');
|
||
|
||
try {
|
||
const resultsResponse = await axios.get(
|
||
`${API_BASE}/fulltext-screening/tasks/${result.taskId}/results`
|
||
);
|
||
const results = resultsResponse.data.data;
|
||
|
||
console.log('=' .repeat(80));
|
||
console.log('📈 最终统计:');
|
||
console.log(` - 总文献数: ${results.results.length}`);
|
||
console.log(` - 总Token: ${results.summary.totalTokens}`);
|
||
console.log(` - 总成本: ¥${results.summary.totalCost.toFixed(4)}`);
|
||
console.log('');
|
||
|
||
if (results.results.length > 0) {
|
||
console.log('📄 文献结果详情:');
|
||
results.results.forEach((r: any, idx: number) => {
|
||
console.log(`\n[${idx + 1}] ${r.literatureTitle}`);
|
||
console.log(` Model A (${r.modelAName}): ${r.modelAStatus}`);
|
||
console.log(` Model B (${r.modelBName}): ${r.modelBStatus}`);
|
||
console.log(` Token: ${r.modelATokens + r.modelBTokens}`);
|
||
console.log(` 成本: ¥${(r.modelACost + r.modelBCost).toFixed(4)}`);
|
||
|
||
if (r.modelAStatus === 'success' && r.modelAOverall) {
|
||
console.log(` 决策: ${r.modelAOverall.overall_decision || 'N/A'}`);
|
||
}
|
||
});
|
||
}
|
||
|
||
result.success = results.results.length > 0;
|
||
|
||
} catch (error: any) {
|
||
console.log(`❌ 获取结果失败: ${error.message}`);
|
||
}
|
||
|
||
console.log('\n' + '=' .repeat(80));
|
||
console.log('🎉 测试完成!\n');
|
||
|
||
} catch (error: any) {
|
||
console.error('\n❌ 测试失败:', error.message);
|
||
if (error.response?.data) {
|
||
console.error('错误详情:', JSON.stringify(error.response.data, null, 2));
|
||
}
|
||
result.success = false;
|
||
result.error = error.message;
|
||
} finally {
|
||
await prisma.$disconnect();
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// 运行测试
|
||
runTest()
|
||
.then(result => {
|
||
if (result.success) {
|
||
console.log('✅ 端到端测试成功!');
|
||
process.exit(0);
|
||
} else {
|
||
console.log('❌ 端到端测试失败');
|
||
process.exit(1);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('💥 测试执行异常:', error);
|
||
process.exit(1);
|
||
});
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|