Summary: - Implement PKB Dashboard and Workspace pages based on V3 prototype - Add single-layer header with integrated Tab navigation - Implement 3 work modes: Full Text, Deep Read, Batch Processing - Integrate Ant Design X Chat component for AI conversations - Create BatchModeComplete with template selection and document processing - Add compact work mode selector with dropdown design Backend: - Migrate PKB controllers and services to /modules/pkb structure - Register v2 API routes at /api/v2/pkb/knowledge - Maintain dual API routes for backward compatibility Technical details: - Use Zustand for state management - Handle SSE streaming responses for AI chat - Support document selection for Deep Read mode - Implement batch processing with progress tracking Known issues: - Batch processing API integration pending - Knowledge assets page navigation needs optimization Status: Frontend functional, pending refinement
383 lines
11 KiB
JavaScript
383 lines
11 KiB
JavaScript
/**
|
||
* Tool C Day 3 测试脚本
|
||
*
|
||
* 测试内容:
|
||
* 1. 10个Few-shot示例场景测试
|
||
* 2. AI自我修正机制测试(重试)
|
||
* 3. 端到端测试
|
||
*
|
||
* 前提:
|
||
* - 需要先创建一个Session(使用Day 2的upload接口)
|
||
* - 需要Python服务运行(端口8000)
|
||
* - 需要后端服务运行(端口3000)
|
||
*
|
||
* 执行方式:node test-tool-c-day3.mjs
|
||
*/
|
||
|
||
import axios from 'axios';
|
||
import FormData from 'form-data';
|
||
import * as XLSX from 'xlsx';
|
||
|
||
const BASE_URL = 'http://localhost:3000';
|
||
const API_PREFIX = '/api/v1/dc/tool-c';
|
||
|
||
let testSessionId = null;
|
||
|
||
// ==================== 辅助函数 ====================
|
||
|
||
function printSection(title) {
|
||
console.log('\n' + '='.repeat(70));
|
||
console.log(` ${title}`);
|
||
console.log('='.repeat(70) + '\n');
|
||
}
|
||
|
||
function printSuccess(message) {
|
||
console.log('✅ ' + message);
|
||
}
|
||
|
||
function printError(message) {
|
||
console.log('❌ ' + message);
|
||
}
|
||
|
||
function printInfo(message) {
|
||
console.log('ℹ️ ' + message);
|
||
}
|
||
|
||
// ==================== 准备测试Session ====================
|
||
|
||
async function createTestSession() {
|
||
printSection('准备:创建测试Session');
|
||
|
||
try {
|
||
// 创建测试Excel数据
|
||
const testData = [
|
||
{ patient_id: 'P001', name: '张三', age: 25, gender: '男', diagnosis: '感冒', sbp: 120, dbp: 80, weight: 70, height: 175, BMI: '', creatinine: '>100', check_date: '2024-01-01' },
|
||
{ patient_id: 'P002', name: '李四', age: 65, gender: '女', diagnosis: '高血压', sbp: 150, dbp: 95, weight: 65, height: 160, BMI: '', creatinine: '<0.1', check_date: '2024-01-05' },
|
||
{ patient_id: 'P003', name: '王五', age: 45, gender: '男', diagnosis: '糖尿病', sbp: 135, dbp: 85, weight: 80, height: 170, BMI: '', creatinine: '85', check_date: '2024-01-03' },
|
||
{ patient_id: 'P004', name: '赵六', age: 70, gender: '女', diagnosis: '冠心病', sbp: 160, dbp: 100, weight: 60, height: 155, BMI: '', creatinine: '120', check_date: '2024-01-10' },
|
||
{ patient_id: 'P005', name: '钱七', age: 35, gender: '男', diagnosis: '胃炎', sbp: 110, dbp: 70, weight: 75, height: 180, BMI: '', creatinine: '-', check_date: '2024-01-08' },
|
||
{ patient_id: 'P003', name: '王五', age: 45, gender: '男', diagnosis: '糖尿病', sbp: 138, dbp: 88, weight: 82, height: 170, BMI: '', creatinine: '88', check_date: '2024-01-12' }, // 重复ID,日期更新
|
||
];
|
||
|
||
// 生成Excel
|
||
const ws = XLSX.utils.json_to_sheet(testData);
|
||
const wb = XLSX.utils.book_new();
|
||
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
|
||
const excelBuffer = XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' });
|
||
|
||
// 上传创建Session
|
||
const form = new FormData();
|
||
form.append('file', excelBuffer, {
|
||
filename: 'test-medical-data-day3.xlsx',
|
||
contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||
});
|
||
|
||
const response = await axios.post(
|
||
`${BASE_URL}${API_PREFIX}/sessions/upload`,
|
||
form,
|
||
{ headers: form.getHeaders(), timeout: 10000 }
|
||
);
|
||
|
||
if (response.data.success) {
|
||
testSessionId = response.data.data.sessionId;
|
||
printSuccess(`Session创建成功: ${testSessionId}`);
|
||
printInfo(`数据: ${testData.length}行 x ${Object.keys(testData[0]).length}列`);
|
||
return true;
|
||
} else {
|
||
printError('Session创建失败');
|
||
return false;
|
||
}
|
||
} catch (error) {
|
||
printError('Session创建异常: ' + error.message);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// ==================== AI测试函数 ====================
|
||
|
||
async function testAIGenerate(testName, userMessage, shouldSucceed = true) {
|
||
printSection(`测试: ${testName}`);
|
||
|
||
try {
|
||
printInfo(`用户需求: ${userMessage}`);
|
||
|
||
const response = await axios.post(
|
||
`${BASE_URL}${API_PREFIX}/ai/generate`,
|
||
{
|
||
sessionId: testSessionId,
|
||
message: userMessage
|
||
},
|
||
{ timeout: 30000 } // AI调用可能需要较长时间
|
||
);
|
||
|
||
if (response.data.success) {
|
||
printSuccess('AI生成代码成功');
|
||
console.log('\n生成的代码:');
|
||
console.log('```python');
|
||
console.log(response.data.data.code);
|
||
console.log('```\n');
|
||
console.log('解释:', response.data.data.explanation);
|
||
console.log('MessageID:', response.data.data.messageId);
|
||
return { success: true, data: response.data.data };
|
||
} else {
|
||
if (shouldSucceed) {
|
||
printError('AI生成失败: ' + response.data.error);
|
||
} else {
|
||
printInfo('预期失败: ' + response.data.error);
|
||
}
|
||
return { success: false, error: response.data.error };
|
||
}
|
||
} catch (error) {
|
||
printError('AI生成异常: ' + (error.response?.data?.error || error.message));
|
||
return { success: false, error: error.message };
|
||
}
|
||
}
|
||
|
||
async function testAIProcess(testName, userMessage) {
|
||
printSection(`测试(一步到位): ${testName}`);
|
||
|
||
try {
|
||
printInfo(`用户需求: ${userMessage}`);
|
||
|
||
const response = await axios.post(
|
||
`${BASE_URL}${API_PREFIX}/ai/process`,
|
||
{
|
||
sessionId: testSessionId,
|
||
message: userMessage,
|
||
maxRetries: 3
|
||
},
|
||
{ timeout: 60000 } // 带重试可能需要更长时间
|
||
);
|
||
|
||
if (response.data.success) {
|
||
printSuccess(`执行成功${response.data.data.retryCount > 0 ? `(重试${response.data.data.retryCount}次)` : ''}`);
|
||
console.log('\n生成的代码:');
|
||
console.log('```python');
|
||
console.log(response.data.data.code);
|
||
console.log('```\n');
|
||
console.log('解释:', response.data.data.explanation);
|
||
|
||
if (response.data.data.executeResult.success) {
|
||
printSuccess('代码执行成功');
|
||
console.log('数据预览(前5行):');
|
||
console.log(JSON.stringify(response.data.data.executeResult.newDataPreview?.slice(0, 5), null, 2));
|
||
}
|
||
|
||
return { success: true, data: response.data.data };
|
||
} else {
|
||
printError('处理失败: ' + response.data.error);
|
||
return { success: false, error: response.data.error };
|
||
}
|
||
} catch (error) {
|
||
printError('处理异常: ' + (error.response?.data?.error || error.message));
|
||
return { success: false, error: error.message };
|
||
}
|
||
}
|
||
|
||
// ==================== 主测试函数 ====================
|
||
|
||
async function runAllTests() {
|
||
console.log('\n' + '🚀'.repeat(35));
|
||
console.log(' Tool C Day 3 测试 - AI代码生成');
|
||
console.log('🚀'.repeat(35));
|
||
|
||
const results = {};
|
||
|
||
try {
|
||
// 0. 准备测试Session
|
||
const sessionCreated = await createTestSession();
|
||
if (!sessionCreated) {
|
||
printError('测试Session创建失败,无法继续');
|
||
return;
|
||
}
|
||
|
||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||
|
||
// ==================== 10个Few-shot示例测试 ====================
|
||
|
||
// 测试1: 统一缺失值标记
|
||
let result = await testAIProcess(
|
||
'示例1: 统一缺失值标记',
|
||
'把所有代表缺失的符号(-、不详、NA、N/A)统一替换为标准空值'
|
||
);
|
||
results['示例1-缺失值'] = result.success;
|
||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||
|
||
// 测试2: 数值列清洗
|
||
result = await testAIProcess(
|
||
'示例2: 数值列清洗',
|
||
'把creatinine列里的非数字符号去掉,<0.1按0.05处理,转为数值类型'
|
||
);
|
||
results['示例2-数值清洗'] = result.success;
|
||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||
|
||
// 测试3: 分类变量编码
|
||
result = await testAIProcess(
|
||
'示例3: 分类变量编码',
|
||
'把gender列转为数字,男=1,女=0'
|
||
);
|
||
results['示例3-编码'] = result.success;
|
||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||
|
||
// 测试4: 连续变量分箱
|
||
result = await testAIProcess(
|
||
'示例4: 连续变量分箱',
|
||
'把age列按18岁、60岁分为未成年、成年、老年三组'
|
||
);
|
||
results['示例4-分箱'] = result.success;
|
||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||
|
||
// 测试5: BMI计算
|
||
result = await testAIProcess(
|
||
'示例5: BMI计算',
|
||
'根据weight和height计算BMI,并标记BMI≥28为肥胖'
|
||
);
|
||
results['示例5-BMI'] = result.success;
|
||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||
|
||
// 测试6: 条件筛选
|
||
result = await testAIProcess(
|
||
'示例6: 条件筛选',
|
||
'筛选出年龄≥60岁、且sbp≥140的患者'
|
||
);
|
||
results['示例6-筛选'] = result.success;
|
||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||
|
||
// 测试7: 智能去重
|
||
result = await testAIProcess(
|
||
'示例7: 智能去重',
|
||
'按patient_id去重,保留check_date最新的记录'
|
||
);
|
||
results['示例7-去重'] = result.success;
|
||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||
|
||
// 测试8: 中位数填补(简化版,跳过多重插补)
|
||
result = await testAIProcess(
|
||
'示例8: 缺失值填补',
|
||
'用age列的中位数填补age列的缺失值'
|
||
);
|
||
results['示例8-填补'] = result.success;
|
||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||
|
||
// 测试9: 统计汇总
|
||
result = await testAIProcess(
|
||
'示例9: 统计汇总',
|
||
'按diagnosis分组,统计每个诊断的平均年龄和患者数量'
|
||
);
|
||
results['示例9-统计'] = result.success;
|
||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||
|
||
// 测试10: 复杂计算
|
||
result = await testAIProcess(
|
||
'示例10: 复杂计算',
|
||
'根据sbp判断血压分类:正常(<140)、高血压I级(140-159)、高血压II级(≥160)'
|
||
);
|
||
results['示例10-分类'] = result.success;
|
||
|
||
// ==================== 对话历史测试 ====================
|
||
|
||
printSection('测试: 获取对话历史');
|
||
try {
|
||
const historyResponse = await axios.get(
|
||
`${BASE_URL}${API_PREFIX}/ai/history/${testSessionId}?limit=5`
|
||
);
|
||
|
||
if (historyResponse.data.success) {
|
||
printSuccess(`获取历史成功: ${historyResponse.data.data.count}条`);
|
||
results['对话历史'] = true;
|
||
} else {
|
||
printError('获取历史失败');
|
||
results['对话历史'] = false;
|
||
}
|
||
} catch (error) {
|
||
printError('获取历史异常: ' + error.message);
|
||
results['对话历史'] = false;
|
||
}
|
||
|
||
} catch (error) {
|
||
printError('测试过程中发生异常: ' + error.message);
|
||
console.error(error);
|
||
}
|
||
|
||
// 汇总结果
|
||
printSection('测试结果汇总');
|
||
|
||
let passed = 0;
|
||
let total = 0;
|
||
|
||
for (const [testName, result] of Object.entries(results)) {
|
||
total++;
|
||
if (result) {
|
||
passed++;
|
||
console.log(`${testName.padEnd(20)}: ✅ 通过`);
|
||
} else {
|
||
console.log(`${testName.padEnd(20)}: ❌ 失败`);
|
||
}
|
||
}
|
||
|
||
console.log('\n' + '-'.repeat(70));
|
||
console.log(`总计: ${passed}/${total} 通过 (${((passed/total)*100).toFixed(1)}%)`);
|
||
console.log('-'.repeat(70));
|
||
|
||
if (passed === total) {
|
||
console.log('\n🎉 所有测试通过!Day 3 AI功能完成!\n');
|
||
} else if (passed >= total * 0.7) {
|
||
console.log(`\n⚠️ 有 ${total - passed} 个测试失败,但通过率≥70%,基本可用\n`);
|
||
} else {
|
||
console.log(`\n❌ 通过率过低,需要调试\n`);
|
||
}
|
||
}
|
||
|
||
// 执行测试
|
||
runAllTests()
|
||
.then(() => {
|
||
console.log('测试完成');
|
||
process.exit(0);
|
||
})
|
||
.catch((error) => {
|
||
console.error('测试失败:', error);
|
||
process.exit(1);
|
||
});
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|