feat(rvw): Complete Phase 4-5 - Bug fixes and Word export
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
This commit is contained in:
@@ -39,3 +39,5 @@ Status: Day 1 complete (11/11 tasks), ready for Day 2
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -266,6 +266,8 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -215,3 +215,5 @@ https://iit.xunzhengyixue.com/api/v1/iit/health
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -144,3 +144,5 @@ https://iit.xunzhengyixue.com/api/v1/iit/health
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -45,3 +45,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -305,3 +305,5 @@ npx tsx src/modules/iit-manager/test-patient-wechat-url-verify.ts
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -167,3 +167,5 @@ npm run dev
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -61,6 +61,8 @@ WHERE table_schema = 'dc_schema'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -99,6 +99,8 @@ ORDER BY ordinal_position;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -112,6 +112,8 @@ runMigration()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ COMMENT ON COLUMN "dc_schema"."dc_tool_c_sessions"."column_mapping" IS '列名
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -73,6 +73,8 @@ COMMENT ON COLUMN dc_schema.dc_tool_c_sessions.expires_at IS '过期时间(创
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -314,6 +314,7 @@ model ReviewTask {
|
||||
|
||||
// 🆕 结果摘要(Phase 2新增,用于列表展示)
|
||||
editorialScore Float? @map("editorial_score")
|
||||
methodologyScore Float? @map("methodology_score")
|
||||
methodologyStatus String? @map("methodology_status") // pass/warn/fail
|
||||
|
||||
// 🆕 预留字段(暂不使用)
|
||||
|
||||
@@ -113,6 +113,8 @@ Write-Host ""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -223,6 +223,8 @@ function extractCodeBlocks(obj, blocks = []) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -242,6 +242,8 @@ checkDCTables();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -194,6 +194,8 @@ createAiHistoryTable()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -181,6 +181,8 @@ createToolCTable()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -178,6 +178,8 @@ createToolCTable()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -328,3 +328,5 @@ runTests().catch(error => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -293,3 +293,5 @@ verifySchemas()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -310,6 +310,8 @@ export function getBatchItems<T>(
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import { registerTestRoutes } from './test-platform-api.js';
|
||||
import { registerScreeningWorkers } from './modules/asl/services/screeningWorker.js';
|
||||
import { registerExtractionWorkers } from './modules/dc/tool-b/workers/extractionWorker.js';
|
||||
import { registerParseExcelWorker } from './modules/dc/tool-c/workers/parseExcelWorker.js';
|
||||
import { registerReviewWorker } from './modules/rvw/workers/reviewWorker.js';
|
||||
import { jobQueue } from './common/jobs/index.js';
|
||||
|
||||
|
||||
@@ -183,6 +184,10 @@ const start = async () => {
|
||||
registerParseExcelWorker();
|
||||
logger.info('✅ DC Tool C parse excel worker registered');
|
||||
|
||||
// 注册RVW审稿Worker
|
||||
registerReviewWorker();
|
||||
logger.info('✅ RVW review worker registered');
|
||||
|
||||
// 注册IIT Manager Workers
|
||||
await initIitManager();
|
||||
logger.info('✅ IIT Manager workers registered');
|
||||
@@ -201,6 +206,7 @@ const start = async () => {
|
||||
console.log(' - asl_screening_batch (文献筛选批次处理)');
|
||||
console.log(' - dc_extraction_batch (数据提取批次处理)');
|
||||
console.log(' - dc_toolc_parse_excel (Tool C Excel解析)');
|
||||
console.log(' - rvw_review_task (稿件审查任务)');
|
||||
console.log(' - iit_quality_check (IIT质控+企微推送)');
|
||||
console.log(' - iit_redcap_poll (IIT REDCap轮询)');
|
||||
console.log('='.repeat(60) + '\n');
|
||||
|
||||
@@ -346,6 +346,8 @@ runTests().catch((error) => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -287,6 +287,8 @@ runTest()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -325,6 +325,8 @@ Content-Type: application/json
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -261,6 +261,8 @@ export const conflictDetectionService = new ConflictDetectionService();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -211,6 +211,8 @@ curl -X POST http://localhost:3000/api/v1/dc/tool-c/test/execute \
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -265,6 +265,8 @@ export const streamAIController = new StreamAIController();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -177,3 +177,5 @@ logger.info('[SessionMemory] 会话记忆管理器已启动', {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -111,3 +111,5 @@ checkTableStructure();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -98,3 +98,5 @@ checkProjectConfig().catch(console.error);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -80,3 +80,5 @@ main();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -537,3 +537,5 @@ URL: https://iit.xunzhengyixue.com/api/v1/iit/patient-wechat/callback
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -172,3 +172,5 @@ console.log('');
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -489,3 +489,5 @@ export const patientWechatService = new PatientWechatService();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -134,3 +134,5 @@ testDifyIntegration().catch(error => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -163,3 +163,5 @@ testIitDatabase()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -149,3 +149,5 @@ if (hasError) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -175,3 +175,5 @@ async function testUrlVerification() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -256,3 +256,5 @@ main().catch((error) => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -140,3 +140,5 @@ Write-Host ""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -233,3 +233,5 @@ export interface CachedProtocolRules {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -46,3 +46,5 @@ export default async function healthRoutes(fastify: FastifyInstance) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -124,3 +124,5 @@ Content-Type: application/json
|
||||
GET {{baseUrl}}/api/v1/review/tasks/{{taskId}}/report
|
||||
Content-Type: application/json
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -109,3 +109,5 @@ Write-Host " - 查看报告: GET $BaseUrl/api/v2/rvw/tasks/{taskId}/report" -Fo
|
||||
Write-Host " - 批量运行: POST $BaseUrl/api/v2/rvw/tasks/batch/run" -ForegroundColor Gray
|
||||
Write-Host " - 删除任务: DELETE $BaseUrl/api/v2/rvw/tasks/{taskId}" -ForegroundColor Gray
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -136,6 +136,8 @@ export async function createTask(
|
||||
/**
|
||||
* 运行审查(选择智能体)
|
||||
* POST /api/v2/rvw/tasks/:taskId/run
|
||||
*
|
||||
* ✅ Platform-Only架构:返回 jobId 供前端轮询
|
||||
*/
|
||||
export async function runReview(
|
||||
request: FastifyRequest<{
|
||||
@@ -151,11 +153,16 @@ export async function runReview(
|
||||
|
||||
logger.info('[RVW:Controller] 运行审查', { taskId, agents });
|
||||
|
||||
await reviewService.runReview({ taskId, agents, userId });
|
||||
// ✅ 返回 jobId(Platform-Only架构)
|
||||
const { jobId } = await reviewService.runReview({ taskId, agents, userId });
|
||||
|
||||
return reply.send({
|
||||
success: true,
|
||||
message: '审查任务已启动',
|
||||
data: {
|
||||
taskId,
|
||||
jobId, // ✅ 供前端轮询状态
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[RVW:Controller] 运行审查失败', {
|
||||
|
||||
@@ -23,3 +23,5 @@ export * from './types/index.js';
|
||||
// 导出工具函数
|
||||
export * from './services/utils.js';
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -44,3 +44,5 @@ export default async function rvwRoutes(fastify: FastifyInstance) {
|
||||
fastify.post('/tasks/batch/run', reviewController.batchRunReview);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -68,3 +68,5 @@ export async function reviewEditorialStandards(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -68,3 +68,5 @@ export async function reviewMethodology(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { prisma } from '../../../config/database.js';
|
||||
import { extractionClient } from '../../../common/document/ExtractionClient.js';
|
||||
import { ModelType } from '../../../common/llm/adapters/types.js';
|
||||
import { logger } from '../../../common/logging/index.js';
|
||||
import { jobQueue } from '../../../common/jobs/index.js';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import {
|
||||
AgentType,
|
||||
@@ -124,9 +125,15 @@ async function extractDocumentAsync(taskId: string, file: Buffer, filename: stri
|
||||
|
||||
/**
|
||||
* 运行审查(核心功能)
|
||||
* 支持选择1个或2个智能体
|
||||
*
|
||||
* ✅ Platform-Only架构:
|
||||
* - 使用 pg-boss 队列处理审查任务
|
||||
* - API 立即返回 jobId
|
||||
* - 后台 Worker 执行实际审查
|
||||
*
|
||||
* @returns jobId 供前端轮询状态
|
||||
*/
|
||||
export async function runReview(params: RunReviewParams): Promise<void> {
|
||||
export async function runReview(params: RunReviewParams): Promise<{ jobId: string }> {
|
||||
const { taskId, agents, userId } = params;
|
||||
|
||||
// 验证智能体选择
|
||||
@@ -149,91 +156,38 @@ export async function runReview(params: RunReviewParams): Promise<void> {
|
||||
throw new Error('文档尚未提取完成,请稍后再试');
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
// 更新任务状态为reviewing
|
||||
await prisma.reviewTask.update({
|
||||
where: { id: taskId },
|
||||
data: {
|
||||
status: 'reviewing',
|
||||
selectedAgents: agents,
|
||||
startedAt: new Date(),
|
||||
// 清除之前的结果(如果重新运行)
|
||||
editorialReview: Prisma.JsonNull,
|
||||
methodologyReview: Prisma.JsonNull,
|
||||
overallScore: null,
|
||||
errorMessage: null,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
// 更新任务状态
|
||||
await prisma.reviewTask.update({
|
||||
where: { id: taskId },
|
||||
data: {
|
||||
status: 'reviewing',
|
||||
startedAt: new Date(),
|
||||
// 清除之前的结果(如果重新运行)
|
||||
editorialReview: Prisma.JsonNull,
|
||||
methodologyReview: Prisma.JsonNull,
|
||||
overallScore: null,
|
||||
errorMessage: null,
|
||||
},
|
||||
});
|
||||
// ✅ 推送任务到 pg-boss 队列(Platform-Only架构)
|
||||
const job = await jobQueue.push('rvw_review_task', {
|
||||
taskId,
|
||||
userId,
|
||||
agents,
|
||||
extractedText: task.extractedText,
|
||||
modelType: (task.modelUsed || 'deepseek-v3') as ModelType,
|
||||
});
|
||||
|
||||
logger.info('[RVW] 开始审查', { taskId, agents });
|
||||
logger.info('[RVW] 审查任务已推送到队列', {
|
||||
taskId,
|
||||
jobId: job.id,
|
||||
agents,
|
||||
});
|
||||
|
||||
// 获取模型类型
|
||||
const modelType = (task.modelUsed || 'deepseek-v3') as ModelType;
|
||||
|
||||
// 运行选中的智能体
|
||||
let editorialResult: EditorialReview | null = null;
|
||||
let methodologyResult: MethodologyReview | null = null;
|
||||
|
||||
if (agents.includes('editorial')) {
|
||||
logger.info('[RVW] 运行稿约规范性智能体', { taskId });
|
||||
editorialResult = await reviewEditorialStandards(task.extractedText, modelType);
|
||||
}
|
||||
|
||||
if (agents.includes('methodology')) {
|
||||
logger.info('[RVW] 运行方法学智能体', { taskId });
|
||||
methodologyResult = await reviewMethodology(task.extractedText, modelType);
|
||||
}
|
||||
|
||||
// 计算综合分数
|
||||
const editorialScore = editorialResult?.overall_score ?? null;
|
||||
const methodologyScore = methodologyResult?.overall_score ?? null;
|
||||
const overallScore = calculateOverallScore(editorialScore, methodologyScore, agents);
|
||||
|
||||
// 计算耗时
|
||||
const endTime = Date.now();
|
||||
const durationSeconds = Math.floor((endTime - startTime) / 1000);
|
||||
|
||||
// 更新任务结果(使用新字段)
|
||||
await prisma.reviewTask.update({
|
||||
where: { id: taskId },
|
||||
data: {
|
||||
status: 'completed',
|
||||
editorialReview: editorialResult as unknown as Prisma.InputJsonValue ?? Prisma.JsonNull,
|
||||
methodologyReview: methodologyResult as unknown as Prisma.InputJsonValue ?? Prisma.JsonNull,
|
||||
overallScore,
|
||||
// 🆕 使用新字段存储摘要信息
|
||||
editorialScore: editorialScore,
|
||||
methodologyStatus: getMethodologyStatus(methodologyResult),
|
||||
completedAt: new Date(),
|
||||
durationSeconds,
|
||||
},
|
||||
});
|
||||
|
||||
logger.info('[RVW] 审查完成', {
|
||||
taskId,
|
||||
agents,
|
||||
editorialScore,
|
||||
methodologyScore,
|
||||
overallScore,
|
||||
durationSeconds,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[RVW] 审查失败', {
|
||||
taskId,
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
});
|
||||
|
||||
await prisma.reviewTask.update({
|
||||
where: { id: taskId },
|
||||
data: {
|
||||
status: 'failed',
|
||||
errorMessage: error instanceof Error ? error.message : 'Review failed',
|
||||
},
|
||||
});
|
||||
|
||||
throw error;
|
||||
}
|
||||
// ✅ 立即返回 jobId(不阻塞请求)
|
||||
return { jobId: job.id };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -324,6 +278,7 @@ export async function getTaskList(params: TaskListParams): Promise<TaskListRespo
|
||||
// 🆕 使用新字段
|
||||
selectedAgents: true,
|
||||
editorialScore: true,
|
||||
methodologyScore: true,
|
||||
methodologyStatus: true,
|
||||
overallScore: true,
|
||||
modelUsed: true,
|
||||
@@ -344,6 +299,7 @@ export async function getTaskList(params: TaskListParams): Promise<TaskListRespo
|
||||
status: task.status as TaskStatus,
|
||||
selectedAgents: (task.selectedAgents || ['editorial', 'methodology']) as AgentType[],
|
||||
editorialScore: task.editorialScore ?? undefined,
|
||||
methodologyScore: task.methodologyScore ?? undefined,
|
||||
methodologyStatus: task.methodologyStatus as any,
|
||||
overallScore: task.overallScore ?? undefined,
|
||||
modelUsed: task.modelUsed ?? undefined,
|
||||
|
||||
@@ -114,3 +114,5 @@ export function validateAgentSelection(agents: string[]): void {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ export interface TaskSummary {
|
||||
status: TaskStatus;
|
||||
selectedAgents: AgentType[];
|
||||
editorialScore?: number;
|
||||
methodologyScore?: number;
|
||||
methodologyStatus?: MethodologyStatus;
|
||||
overallScore?: number;
|
||||
modelUsed?: string;
|
||||
@@ -155,3 +156,5 @@ export interface LLMMessage {
|
||||
content: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
190
backend/src/modules/rvw/workers/reviewWorker.ts
Normal file
190
backend/src/modules/rvw/workers/reviewWorker.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* RVW稿件审查 Worker(Platform-Only架构)
|
||||
*
|
||||
* ✅ Platform-Only架构:
|
||||
* - 使用 pg-boss 队列处理审查任务
|
||||
* - 任务状态存储在 job.state (pg-boss管理)
|
||||
* - 审查结果更新到 ReviewTask表(业务信息)
|
||||
*
|
||||
* 任务流程:
|
||||
* 1. 获取任务信息和提取的文本
|
||||
* 2. 根据选择的智能体执行审查
|
||||
* 3. 更新任务状态和结果
|
||||
*/
|
||||
|
||||
import { prisma } from '../../../config/database.js';
|
||||
import { logger } from '../../../common/logging/index.js';
|
||||
import { jobQueue } from '../../../common/jobs/index.js';
|
||||
import type { Job } from '../../../common/jobs/types.js';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { ModelType } from '../../../common/llm/adapters/types.js';
|
||||
import { reviewEditorialStandards } from '../services/editorialService.js';
|
||||
import { reviewMethodology } from '../services/methodologyService.js';
|
||||
import { calculateOverallScore, getMethodologyStatus } from '../services/utils.js';
|
||||
import type { AgentType, EditorialReview, MethodologyReview } from '../types/index.js';
|
||||
|
||||
/**
|
||||
* 审查任务数据结构
|
||||
*/
|
||||
interface ReviewJob {
|
||||
taskId: string;
|
||||
userId: string;
|
||||
agents: AgentType[];
|
||||
extractedText: string;
|
||||
modelType: ModelType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册审查 Worker 到队列
|
||||
*
|
||||
* 此函数应在应用启动时调用(index.ts)
|
||||
*/
|
||||
export function registerReviewWorker() {
|
||||
logger.info('[reviewWorker] Registering reviewWorker');
|
||||
|
||||
// 注册审查Worker(队列名使用下划线,不用冒号)
|
||||
jobQueue.process<ReviewJob>('rvw_review_task', async (job: Job<ReviewJob>) => {
|
||||
const { taskId, userId, agents, extractedText, modelType } = job.data;
|
||||
const startTime = Date.now();
|
||||
|
||||
logger.info('[reviewWorker] Processing review job', {
|
||||
jobId: job.id,
|
||||
taskId,
|
||||
userId,
|
||||
agents,
|
||||
textLength: extractedText.length,
|
||||
});
|
||||
|
||||
console.log(`\n📝 处理审查任务`);
|
||||
console.log(` Job ID: ${job.id}`);
|
||||
console.log(` Task ID: ${taskId}`);
|
||||
console.log(` 智能体: ${agents.join(', ')}`);
|
||||
console.log(` 文本长度: ${extractedText.length} 字符`);
|
||||
|
||||
try {
|
||||
// ========================================
|
||||
// 1. 运行选中的智能体
|
||||
// ========================================
|
||||
let editorialResult: EditorialReview | null = null;
|
||||
let methodologyResult: MethodologyReview | null = null;
|
||||
|
||||
if (agents.includes('editorial')) {
|
||||
// 更新进度状态
|
||||
await prisma.reviewTask.update({
|
||||
where: { id: taskId },
|
||||
data: { status: 'reviewing_editorial' },
|
||||
});
|
||||
|
||||
logger.info('[reviewWorker] Running editorial review', { taskId });
|
||||
console.log(' 🔍 运行稿约规范性智能体...');
|
||||
|
||||
editorialResult = await reviewEditorialStandards(extractedText, modelType);
|
||||
|
||||
logger.info('[reviewWorker] Editorial review completed', {
|
||||
taskId,
|
||||
score: editorialResult?.overall_score,
|
||||
});
|
||||
console.log(` ✅ 稿约规范性完成,得分: ${editorialResult?.overall_score}`);
|
||||
}
|
||||
|
||||
if (agents.includes('methodology')) {
|
||||
// 更新进度状态
|
||||
await prisma.reviewTask.update({
|
||||
where: { id: taskId },
|
||||
data: { status: 'reviewing_methodology' },
|
||||
});
|
||||
|
||||
logger.info('[reviewWorker] Running methodology review', { taskId });
|
||||
console.log(' 🔬 运行方法学智能体...');
|
||||
|
||||
methodologyResult = await reviewMethodology(extractedText, modelType);
|
||||
|
||||
logger.info('[reviewWorker] Methodology review completed', {
|
||||
taskId,
|
||||
score: methodologyResult?.overall_score,
|
||||
});
|
||||
console.log(` ✅ 方法学评估完成,得分: ${methodologyResult?.overall_score}`);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 2. 计算综合分数
|
||||
// ========================================
|
||||
const editorialScore = editorialResult?.overall_score ?? null;
|
||||
const methodologyScore = methodologyResult?.overall_score ?? null;
|
||||
const overallScore = calculateOverallScore(editorialScore, methodologyScore, agents);
|
||||
|
||||
// 计算耗时
|
||||
const endTime = Date.now();
|
||||
const durationSeconds = Math.floor((endTime - startTime) / 1000);
|
||||
|
||||
// ========================================
|
||||
// 3. 更新任务结果
|
||||
// ========================================
|
||||
logger.info('[reviewWorker] Updating task result', { taskId });
|
||||
|
||||
await prisma.reviewTask.update({
|
||||
where: { id: taskId },
|
||||
data: {
|
||||
status: 'completed',
|
||||
editorialReview: editorialResult as unknown as Prisma.InputJsonValue ?? Prisma.JsonNull,
|
||||
methodologyReview: methodologyResult as unknown as Prisma.InputJsonValue ?? Prisma.JsonNull,
|
||||
overallScore,
|
||||
editorialScore: editorialScore,
|
||||
methodologyScore: methodologyScore,
|
||||
methodologyStatus: getMethodologyStatus(methodologyResult),
|
||||
completedAt: new Date(),
|
||||
durationSeconds,
|
||||
},
|
||||
});
|
||||
|
||||
logger.info('[reviewWorker] ✅ Review completed', {
|
||||
jobId: job.id,
|
||||
taskId,
|
||||
agents,
|
||||
editorialScore,
|
||||
methodologyScore,
|
||||
overallScore,
|
||||
durationSeconds,
|
||||
});
|
||||
|
||||
console.log('\n✅ 审查完成:');
|
||||
console.log(` Task ID: ${taskId}`);
|
||||
console.log(` 综合得分: ${overallScore}`);
|
||||
console.log(` 耗时: ${durationSeconds}秒`);
|
||||
|
||||
return {
|
||||
taskId,
|
||||
overallScore,
|
||||
editorialScore,
|
||||
methodologyScore,
|
||||
durationSeconds,
|
||||
success: true,
|
||||
};
|
||||
} catch (error: any) {
|
||||
logger.error('[reviewWorker] ❌ Review failed', {
|
||||
jobId: job.id,
|
||||
taskId,
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
|
||||
console.error(`\n❌ 审查失败: ${error.message}`);
|
||||
|
||||
// 更新任务状态为失败
|
||||
await prisma.reviewTask.update({
|
||||
where: { id: taskId },
|
||||
data: {
|
||||
status: 'failed',
|
||||
errorMessage: error.message || 'Review failed',
|
||||
},
|
||||
});
|
||||
|
||||
// 抛出错误,让 pg-boss 处理重试
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
logger.info('[reviewWorker] ✅ Worker registered: rvw_review_task');
|
||||
}
|
||||
|
||||
|
||||
@@ -411,6 +411,8 @@ SET session_replication_role = 'origin';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -113,6 +113,8 @@ WHERE key = 'verify_test';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -256,6 +256,8 @@ verifyDatabase()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
2
backend/src/types/global.d.ts
vendored
2
backend/src/types/global.d.ts
vendored
@@ -46,6 +46,8 @@ export {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -69,6 +69,8 @@ Write-Host "✅ 完成!" -ForegroundColor Green
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -160,3 +160,5 @@ DELETE {{baseUrl}}/api/v2/pkb/knowledge/knowledge-bases/{{testKbId}}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -356,6 +356,8 @@ runAdvancedTests().catch(error => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -422,6 +422,8 @@ runAllTests()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -380,6 +380,8 @@ runAllTests()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -164,6 +164,8 @@ Set-Location ..
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# AIclinicalresearch 系统当前状态与开发指南
|
||||
|
||||
> **文档版本:** v2.9
|
||||
> **文档版本:** v3.0
|
||||
> **创建日期:** 2025-11-28
|
||||
> **维护者:** 开发团队
|
||||
> **最后更新:** 2026-01-07
|
||||
> **重大进展:** 🎉 **RVW稿件审查模块开发完成(85%)!** - 后端迁移+数据库扩展+前端重构全部完成
|
||||
> **最后更新:** 2026-01-10
|
||||
> **重大进展:** 🎉 **RVW稿件审查模块开发完成(90%)!** - 后端迁移+数据库扩展+前端重构+Word导出全部完成
|
||||
> **部署状态:** ✅ 生产环境运行中 | 公网地址:http://8.140.53.236/
|
||||
> **文档目的:** 快速了解系统当前状态,为新AI助手提供上下文
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
| **IIT** | IIT Manager Agent | AI驱动IIT研究助手 - 智能质控+REDCap集成 | ⭐⭐⭐⭐⭐ | 🎉 **Phase 1.5完成(60%)- AI对话+REDCap数据集成** | **P0** |
|
||||
| **SSA** | 智能统计分析 | 队列/预测模型/RCT分析 | ⭐⭐⭐⭐⭐ | 📋 规划中 | P2 |
|
||||
| **ST** | 统计分析工具 | 100+轻量化统计工具 | ⭐⭐⭐⭐ | 📋 规划中 | P2 |
|
||||
| **RVW** | 稿件审查系统 | 方法学评估、审稿流程 | ⭐⭐⭐⭐ | ✅ **开发完成(85%)** | P3 |
|
||||
| **RVW** | 稿件审查系统 | 方法学评估、审稿流程、Word导出 | ⭐⭐⭐⭐ | ✅ **开发完成(90%)** | P3 |
|
||||
|
||||
---
|
||||
|
||||
@@ -815,7 +815,7 @@ npm run dev # http://localhost:3000
|
||||
- **总计**:约 85,000 行
|
||||
|
||||
### 模块完成度
|
||||
- ✅ **已完成**:AIA(100%)、平台基础层(100%)、RVW(85%,Phase 1-3完成)
|
||||
- ✅ **已完成**:AIA(100%)、平台基础层(100%)、RVW(90%,Phase 1-5完成,支持Word导出)
|
||||
- 🚧 **开发中**:PKB(90%,核心功能完成)、ASL(80%)、DC(Tool C 98%,Tool B后端100%,Tool B前端0%)、IIT(60%,Phase 1.5完成)
|
||||
- 📋 **未开始**:SSA、ST
|
||||
|
||||
@@ -953,8 +953,8 @@ if (items.length >= 50) {
|
||||
|
||||
---
|
||||
|
||||
**文档版本**:v2.9
|
||||
**最后更新**:2026-01-07
|
||||
**文档版本**:v3.0
|
||||
**最后更新**:2026-01-10
|
||||
**下次更新**:RVW生产环境部署 或 ASL智能文献筛选模块启动
|
||||
|
||||
---
|
||||
@@ -1020,7 +1020,7 @@ if (items.length >= 50) {
|
||||
|
||||
---
|
||||
|
||||
**RVW稿件审查模块开发完成(2026-01-07)**:
|
||||
**RVW稿件审查模块开发完成(2026-01-07 ~ 2026-01-10)**:
|
||||
|
||||
### Phase 1:后端模块迁移与扩展
|
||||
- ✅ 创建 `backend/src/modules/rvw/` 模块结构
|
||||
@@ -1029,24 +1029,36 @@ if (items.length >= 50) {
|
||||
- ✅ 实现批量运行API(batchRunReviewTasks)
|
||||
- ✅ 替换 console.log 为 logger 服务
|
||||
- ✅ 注册 v2 API路由(/api/v2/rvw)
|
||||
- ✅ 实现 pg-boss 异步任务处理(reviewWorker)
|
||||
|
||||
### Phase 2:数据库字段扩展
|
||||
- ✅ 添加 selectedAgents、editorialScore、methodologyStatus 字段
|
||||
- ✅ 添加 selectedAgents、editorialScore、methodologyScore、methodologyStatus 字段
|
||||
- ✅ 添加 picoExtract、isArchived、archivedAt 字段
|
||||
- ✅ 使用 prisma db push 同步到数据库
|
||||
|
||||
### Phase 3:前端重构(frontend-v2)
|
||||
- ✅ 创建 `frontend-v2/src/modules/rvw/index.tsx`(~503行)
|
||||
- ✅ 实现 Dashboard 组件(任务列表、筛选、批量操作)
|
||||
- ✅ 实现 ReportDetail 组件(双标签页切换)
|
||||
- ✅ 创建 `frontend-v2/src/modules/rvw/` 完整模块目录结构
|
||||
- ✅ 实现 Dashboard 页面(任务列表、筛选、批量操作)
|
||||
- ✅ 实现 TaskDetail 组件(审稿进度条、实时状态轮询)
|
||||
- ✅ 实现 EditorialReport/MethodologyReport 组件
|
||||
- ✅ 实现 AgentModal 组件(智能体选择弹窗)
|
||||
- ✅ 注册到 moduleRegistry.ts
|
||||
- ✅ 添加顶部导航"预审稿"入口
|
||||
|
||||
### Phase 4:集成测试与Bug修复(2026-01-10)
|
||||
- ✅ 修复方法学分数不显示问题
|
||||
- ✅ 修复只选方法学时详情页不显示报告问题
|
||||
- ✅ 完整测试单智能体和双智能体审稿流程
|
||||
|
||||
### Phase 5:报告导出(2026-01-10)
|
||||
- ✅ 安装 docx 和 file-saver 库
|
||||
- ✅ 实现 Word 文档导出功能
|
||||
- ✅ 支持结构化报告(标题、基本信息、稿约规范性、方法学评估)
|
||||
|
||||
**技术亮点**:
|
||||
- 🔥 **新旧API兼容**:v1 + v2 API同时运行
|
||||
- 🔥 **智能体可选**:用户可选择运行稿约规范性/方法学/两者
|
||||
- 🔥 **批量操作**:支持多选任务批量运行
|
||||
- 🔥 **异步任务处理**:使用 pg-boss 队列处理长时间审稿任务
|
||||
- 🔥 **Word导出**:使用 docx 库生成专业格式的审稿报告
|
||||
- 🔥 **云原生改造**:使用 logger 服务,遵循开发规范
|
||||
|
||||
**模块进度**:85%完成(Phase 1-3)
|
||||
**模块进度**:90%完成(Phase 1-5)
|
||||
|
||||
@@ -606,6 +606,8 @@ async saveProcessedData(recordId, newData) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -793,6 +793,8 @@ export const AsyncProgressBar: React.FC<AsyncProgressBarProps> = ({
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1286,6 +1286,8 @@ interface FulltextScreeningResult {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -400,6 +400,8 @@ GET /api/v1/asl/fulltext-screening/tasks/:taskId/export
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -343,6 +343,8 @@ Linter错误:0个
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -502,6 +502,8 @@ Failed to open file '\\tmp\\extraction_service\\temp_10000_test.pdf'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -568,6 +568,8 @@ df['creatinine'] = pd.to_numeric(df['creatinine'], errors='coerce')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -406,6 +406,8 @@ npm run dev
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -983,6 +983,8 @@ export const aiController = new AIController();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1317,6 +1317,8 @@ npm install react-markdown
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -225,6 +225,8 @@ FMA___基线 | FMA___1个月 | FMA___2个月
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -383,6 +383,8 @@ formula = "FMA总分(0-100) / 100"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -217,6 +217,8 @@ async handleFillnaMice(request, reply) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -189,6 +189,8 @@ method: 'mean' | 'median' | 'mode' | 'constant' | 'ffill' | 'bfill'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -339,6 +339,8 @@ Changes:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -411,6 +411,8 @@ cd path; command
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -640,6 +640,8 @@ import { logger } from '../../../../common/logging/index.js';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -644,6 +644,8 @@ Content-Length: 45234
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -296,6 +296,8 @@ Response:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -449,6 +449,8 @@ Response:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -443,6 +443,8 @@ import { ChatContainer } from '@/shared/components/Chat';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -353,6 +353,8 @@ const initialMessages = defaultMessages.length > 0 ? defaultMessages : [{
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -393,6 +393,8 @@ python main.py
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -641,6 +641,8 @@ http://localhost:5173/data-cleaning/tool-c
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -251,6 +251,8 @@ Day 5 (6-8小时):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -429,6 +429,8 @@ Docs: docs/03-业务模块/DC-数据清洗整理/06-开发记录/DC模块重建
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -404,6 +404,8 @@ const mockAssets: Asset[] = [
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -388,6 +388,8 @@ frontend-v2/src/modules/dc/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -348,6 +348,8 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -302,6 +302,8 @@ ConflictDetectionService // 冲突检测(字段级对比)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -351,6 +351,8 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -314,6 +314,8 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -378,6 +378,8 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -466,6 +466,8 @@ Tool B后端代码**100%复用**了平台通用能力层,无任何重复开发
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user