feat(rvw): Complete RVW module development Phase 1-3
Summary: - Migrate backend to modules/rvw with v2 API routes (/api/v2/rvw) - Add new database fields: selectedAgents, editorialScore, methodologyStatus, picoExtract, isArchived - Create frontend module in frontend-v2/src/modules/rvw - Implement Dashboard with task list, filtering, batch operations - Implement ReportDetail with dual tabs (editorial/methodology) - Implement AgentModal for intelligent agent selection - Register RVW module in moduleRegistry.ts - Add navigation entry in TopNavigation - Update documentation for RVW module status (v3.0) - Update system status document (v2.9) Features: - User can select agents: editorial, methodology, or both - Support batch task execution - Task status filtering - Replace console.log with logger service - Maintain v1 API backward compatibility Tested: Frontend and backend verified locally Status: 85% complete (Phase 1-3 done)
This commit is contained in:
@@ -38,3 +38,4 @@ Status: Day 1 complete (11/11 tasks), ready for Day 2
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -266,5 +266,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -214,3 +214,4 @@ https://iit.xunzhengyixue.com/api/v1/iit/health
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -143,3 +143,4 @@ https://iit.xunzhengyixue.com/api/v1/iit/health
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -44,3 +44,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -304,3 +304,4 @@ npx tsx src/modules/iit-manager/test-patient-wechat-url-verify.ts
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -166,3 +166,4 @@ npm run dev
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -61,5 +61,6 @@ WHERE table_schema = 'dc_schema'
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -99,5 +99,6 @@ ORDER BY ordinal_position;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -112,5 +112,6 @@ runMigration()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -46,5 +46,6 @@ COMMENT ON COLUMN "dc_schema"."dc_tool_c_sessions"."column_mapping" IS '列名
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -73,5 +73,6 @@ COMMENT ON COLUMN dc_schema.dc_tool_c_sessions.expires_at IS '过期时间(创
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -303,9 +303,25 @@ model ReviewTask {
|
|||||||
extractedText String @map("extracted_text")
|
extractedText String @map("extracted_text")
|
||||||
wordCount Int? @map("word_count")
|
wordCount Int? @map("word_count")
|
||||||
status String @default("pending")
|
status String @default("pending")
|
||||||
|
|
||||||
|
// 🆕 智能体选择(Phase 2新增)
|
||||||
|
selectedAgents String[] @default(["editorial", "methodology"]) @map("selected_agents")
|
||||||
|
|
||||||
|
// 评估结果
|
||||||
editorialReview Json? @map("editorial_review")
|
editorialReview Json? @map("editorial_review")
|
||||||
methodologyReview Json? @map("methodology_review")
|
methodologyReview Json? @map("methodology_review")
|
||||||
overallScore Float? @map("overall_score")
|
overallScore Float? @map("overall_score")
|
||||||
|
|
||||||
|
// 🆕 结果摘要(Phase 2新增,用于列表展示)
|
||||||
|
editorialScore Float? @map("editorial_score")
|
||||||
|
methodologyStatus String? @map("methodology_status") // pass/warn/fail
|
||||||
|
|
||||||
|
// 🆕 预留字段(暂不使用)
|
||||||
|
picoExtract Json? @map("pico_extract")
|
||||||
|
isArchived Boolean @default(false) @map("is_archived")
|
||||||
|
archivedAt DateTime? @map("archived_at")
|
||||||
|
|
||||||
|
// 元数据
|
||||||
modelUsed String? @map("model_used")
|
modelUsed String? @map("model_used")
|
||||||
startedAt DateTime? @map("started_at")
|
startedAt DateTime? @map("started_at")
|
||||||
completedAt DateTime? @map("completed_at")
|
completedAt DateTime? @map("completed_at")
|
||||||
@@ -318,6 +334,7 @@ model ReviewTask {
|
|||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([status])
|
@@index([status])
|
||||||
@@index([createdAt])
|
@@index([createdAt])
|
||||||
|
@@index([isArchived])
|
||||||
@@map("review_tasks")
|
@@map("review_tasks")
|
||||||
@@schema("public")
|
@@schema("public")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,4 +114,5 @@ Write-Host ""
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -223,5 +223,6 @@ function extractCodeBlocks(obj, blocks = []) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -242,5 +242,6 @@ checkDCTables();
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -194,5 +194,6 @@ createAiHistoryTable()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -181,5 +181,6 @@ createToolCTable()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -178,5 +178,6 @@ createToolCTable()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -327,3 +327,4 @@ runTests().catch(error => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -292,3 +292,4 @@ verifySchemas()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -310,5 +310,6 @@ export function getBatchItems<T>(
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import knowledgeBaseRoutes from './legacy/routes/knowledgeBases.js';
|
|||||||
import { chatRoutes } from './legacy/routes/chatRoutes.js';
|
import { chatRoutes } from './legacy/routes/chatRoutes.js';
|
||||||
import { batchRoutes } from './legacy/routes/batchRoutes.js';
|
import { batchRoutes } from './legacy/routes/batchRoutes.js';
|
||||||
import reviewRoutes from './legacy/routes/reviewRoutes.js';
|
import reviewRoutes from './legacy/routes/reviewRoutes.js';
|
||||||
|
import { rvwRoutes } from './modules/rvw/index.js';
|
||||||
import { aslRoutes } from './modules/asl/routes/index.js';
|
import { aslRoutes } from './modules/asl/routes/index.js';
|
||||||
import { registerDCRoutes, initDCModule } from './modules/dc/index.js';
|
import { registerDCRoutes, initDCModule } from './modules/dc/index.js';
|
||||||
import pkbRoutes from './modules/pkb/routes/index.js';
|
import pkbRoutes from './modules/pkb/routes/index.js';
|
||||||
@@ -109,9 +110,16 @@ await fastify.register(chatRoutes, { prefix: '/api/v1' });
|
|||||||
// Phase 3: 注册批处理路由
|
// Phase 3: 注册批处理路由
|
||||||
await fastify.register(batchRoutes, { prefix: '/api/v1' });
|
await fastify.register(batchRoutes, { prefix: '/api/v1' });
|
||||||
|
|
||||||
// 注册稿件审查路由
|
// 注册稿件审查路由(旧版,保留兼容)
|
||||||
await fastify.register(reviewRoutes, { prefix: '/api/v1' });
|
await fastify.register(reviewRoutes, { prefix: '/api/v1' });
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// 【业务模块】RVW - 稿件审查系统(新架构 v2)
|
||||||
|
// ============================================
|
||||||
|
await fastify.register(rvwRoutes, { prefix: '/api/v2/rvw' });
|
||||||
|
logger.info('✅ RVW稿件审查路由已注册(v2新架构): /api/v2/rvw');
|
||||||
|
logger.info(' ⚠️ 旧版路由仍可用: /api/v1/review');
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// 【业务模块】PKB - 个人知识库(新架构 v2)
|
// 【业务模块】PKB - 个人知识库(新架构 v2)
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|||||||
@@ -346,5 +346,6 @@ runTests().catch((error) => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -287,5 +287,6 @@ runTest()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -325,5 +325,6 @@ Content-Type: application/json
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -261,5 +261,6 @@ export const conflictDetectionService = new ConflictDetectionService();
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -211,5 +211,6 @@ curl -X POST http://localhost:3000/api/v1/dc/tool-c/test/execute \
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -265,5 +265,6 @@ export const streamAIController = new StreamAIController();
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -176,3 +176,4 @@ logger.info('[SessionMemory] 会话记忆管理器已启动', {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -110,3 +110,4 @@ checkTableStructure();
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -97,3 +97,4 @@ checkProjectConfig().catch(console.error);
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -79,3 +79,4 @@ main();
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -536,3 +536,4 @@ URL: https://iit.xunzhengyixue.com/api/v1/iit/patient-wechat/callback
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -171,3 +171,4 @@ console.log('');
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -488,3 +488,4 @@ export const patientWechatService = new PatientWechatService();
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -133,3 +133,4 @@ testDifyIntegration().catch(error => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -162,3 +162,4 @@ testIitDatabase()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -148,3 +148,4 @@ if (hasError) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -174,3 +174,4 @@ async function testUrlVerification() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -255,3 +255,4 @@ main().catch((error) => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -139,3 +139,4 @@ Write-Host ""
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -232,3 +232,4 @@ export interface CachedProtocolRules {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -45,3 +45,4 @@ export default async function healthRoutes(fastify: FastifyInstance) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
126
backend/src/modules/rvw/__tests__/api.http
Normal file
126
backend/src/modules/rvw/__tests__/api.http
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
### RVW稿件审查模块 - API测试
|
||||||
|
### Phase 1 验证用例
|
||||||
|
|
||||||
|
@baseUrl = http://localhost:3001
|
||||||
|
@taskId = {{$uuid}}
|
||||||
|
|
||||||
|
### ========================================
|
||||||
|
### 1. 创建任务(上传稿件)
|
||||||
|
### POST /api/v2/rvw/tasks
|
||||||
|
### ========================================
|
||||||
|
|
||||||
|
# 注意:需要使用工具(如Postman)上传文件
|
||||||
|
# curl -X POST http://localhost:3001/api/v2/rvw/tasks \
|
||||||
|
# -F "file=@test.docx" \
|
||||||
|
# -F "modelType=deepseek-v3"
|
||||||
|
|
||||||
|
|
||||||
|
### ========================================
|
||||||
|
### 2. 获取任务列表
|
||||||
|
### GET /api/v2/rvw/tasks
|
||||||
|
### ========================================
|
||||||
|
|
||||||
|
### 获取全部任务
|
||||||
|
GET {{baseUrl}}/api/v2/rvw/tasks
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
### 获取待处理任务
|
||||||
|
GET {{baseUrl}}/api/v2/rvw/tasks?status=pending
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
### 获取已完成任务
|
||||||
|
GET {{baseUrl}}/api/v2/rvw/tasks?status=completed
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
### 分页获取
|
||||||
|
GET {{baseUrl}}/api/v2/rvw/tasks?page=1&limit=10
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
|
||||||
|
### ========================================
|
||||||
|
### 3. 运行审查(选择智能体)
|
||||||
|
### POST /api/v2/rvw/tasks/:taskId/run
|
||||||
|
### ========================================
|
||||||
|
|
||||||
|
### 只选择规范性智能体
|
||||||
|
POST {{baseUrl}}/api/v2/rvw/tasks/{{taskId}}/run
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"agents": ["editorial"]
|
||||||
|
}
|
||||||
|
|
||||||
|
### 只选择方法学智能体
|
||||||
|
POST {{baseUrl}}/api/v2/rvw/tasks/{{taskId}}/run
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"agents": ["methodology"]
|
||||||
|
}
|
||||||
|
|
||||||
|
### 同时选择两个智能体(默认)
|
||||||
|
POST {{baseUrl}}/api/v2/rvw/tasks/{{taskId}}/run
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"agents": ["editorial", "methodology"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
### ========================================
|
||||||
|
### 4. 批量运行审查
|
||||||
|
### POST /api/v2/rvw/tasks/batch/run
|
||||||
|
### ========================================
|
||||||
|
|
||||||
|
POST {{baseUrl}}/api/v2/rvw/tasks/batch/run
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"taskIds": ["task-id-1", "task-id-2", "task-id-3"],
|
||||||
|
"agents": ["editorial", "methodology"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
### ========================================
|
||||||
|
### 5. 获取任务详情
|
||||||
|
### GET /api/v2/rvw/tasks/:taskId
|
||||||
|
### ========================================
|
||||||
|
|
||||||
|
GET {{baseUrl}}/api/v2/rvw/tasks/{{taskId}}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
|
||||||
|
### ========================================
|
||||||
|
### 6. 获取审查报告
|
||||||
|
### GET /api/v2/rvw/tasks/:taskId/report
|
||||||
|
### ========================================
|
||||||
|
|
||||||
|
GET {{baseUrl}}/api/v2/rvw/tasks/{{taskId}}/report
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
|
||||||
|
### ========================================
|
||||||
|
### 7. 删除任务
|
||||||
|
### DELETE /api/v2/rvw/tasks/:taskId
|
||||||
|
### ========================================
|
||||||
|
|
||||||
|
DELETE {{baseUrl}}/api/v2/rvw/tasks/{{taskId}}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
|
||||||
|
### ========================================
|
||||||
|
### 旧版API(兼容性测试)
|
||||||
|
### ========================================
|
||||||
|
|
||||||
|
### 旧版:获取任务列表
|
||||||
|
GET {{baseUrl}}/api/v1/review/tasks
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
### 旧版:获取任务状态
|
||||||
|
GET {{baseUrl}}/api/v1/review/tasks/{{taskId}}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
### 旧版:获取报告
|
||||||
|
GET {{baseUrl}}/api/v1/review/tasks/{{taskId}}/report
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
111
backend/src/modules/rvw/__tests__/test-api.ps1
Normal file
111
backend/src/modules/rvw/__tests__/test-api.ps1
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# RVW稿件审查模块 - API测试脚本
|
||||||
|
# Phase 1 验证用例
|
||||||
|
|
||||||
|
$BaseUrl = "http://localhost:3001"
|
||||||
|
$TestFile = "D:\MyCursor\AIclinicalresearch\docs\03-业务模块\ASL-AI智能文献\05-测试文档\03-测试数据\pdf-extraction\rayyan-256859669.pdf"
|
||||||
|
|
||||||
|
Write-Host "`n========================================" -ForegroundColor Cyan
|
||||||
|
Write-Host "RVW模块 Phase 1 API测试" -ForegroundColor Cyan
|
||||||
|
Write-Host "========================================`n" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# 检查服务器是否运行
|
||||||
|
Write-Host "1. 检查服务器状态..." -ForegroundColor Yellow
|
||||||
|
try {
|
||||||
|
$health = Invoke-RestMethod -Uri "$BaseUrl/health" -Method Get
|
||||||
|
Write-Host " ✅ 服务器运行中" -ForegroundColor Green
|
||||||
|
} catch {
|
||||||
|
Write-Host " ❌ 服务器未运行,请先启动后端: cd backend && npm run dev" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 测试1: 获取任务列表(不需要上传)
|
||||||
|
Write-Host "`n2. 测试获取任务列表 (GET /api/v2/rvw/tasks)..." -ForegroundColor Yellow
|
||||||
|
try {
|
||||||
|
$response = Invoke-RestMethod -Uri "$BaseUrl/api/v2/rvw/tasks" -Method Get
|
||||||
|
Write-Host " ✅ 成功! 当前任务数: $($response.pagination.total)" -ForegroundColor Green
|
||||||
|
if ($response.data.Count -gt 0) {
|
||||||
|
Write-Host " 最近任务: $($response.data[0].fileName) - $($response.data[0].status)" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Host " ❌ 失败: $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
# 测试2: 按状态筛选
|
||||||
|
Write-Host "`n3. 测试筛选待处理任务 (GET /api/v2/rvw/tasks?status=pending)..." -ForegroundColor Yellow
|
||||||
|
try {
|
||||||
|
$response = Invoke-RestMethod -Uri "$BaseUrl/api/v2/rvw/tasks?status=pending" -Method Get
|
||||||
|
Write-Host " ✅ 成功! 待处理任务数: $($response.pagination.total)" -ForegroundColor Green
|
||||||
|
} catch {
|
||||||
|
Write-Host " ❌ 失败: $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
# 测试3: 上传文件创建任务
|
||||||
|
Write-Host "`n4. 测试上传文件 (POST /api/v2/rvw/tasks)..." -ForegroundColor Yellow
|
||||||
|
if (Test-Path $TestFile) {
|
||||||
|
try {
|
||||||
|
# 使用curl上传(PowerShell的Invoke-RestMethod对multipart支持不好)
|
||||||
|
$curlResult = & curl.exe -s -X POST "$BaseUrl/api/v2/rvw/tasks" `
|
||||||
|
-F "file=@$TestFile" `
|
||||||
|
-F "modelType=deepseek-v3"
|
||||||
|
|
||||||
|
$uploadResponse = $curlResult | ConvertFrom-Json
|
||||||
|
if ($uploadResponse.success) {
|
||||||
|
$taskId = $uploadResponse.data.taskId
|
||||||
|
Write-Host " ✅ 上传成功! TaskId: $taskId" -ForegroundColor Green
|
||||||
|
Write-Host " 文件名: $($uploadResponse.data.fileName)" -ForegroundColor Gray
|
||||||
|
|
||||||
|
# 等待文档提取
|
||||||
|
Write-Host "`n5. 等待文档提取(3秒)..." -ForegroundColor Yellow
|
||||||
|
Start-Sleep -Seconds 3
|
||||||
|
|
||||||
|
# 测试4: 获取任务详情
|
||||||
|
Write-Host "`n6. 测试获取任务详情 (GET /api/v2/rvw/tasks/$taskId)..." -ForegroundColor Yellow
|
||||||
|
try {
|
||||||
|
$detail = Invoke-RestMethod -Uri "$BaseUrl/api/v2/rvw/tasks/$taskId" -Method Get
|
||||||
|
Write-Host " ✅ 成功! 状态: $($detail.data.status)" -ForegroundColor Green
|
||||||
|
} catch {
|
||||||
|
Write-Host " ❌ 失败: $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
# 测试5: 运行审查(只选规范性)
|
||||||
|
Write-Host "`n7. 测试运行审查-只选规范性 (POST /api/v2/rvw/tasks/$taskId/run)..." -ForegroundColor Yellow
|
||||||
|
try {
|
||||||
|
$body = @{ agents = @("editorial") } | ConvertTo-Json
|
||||||
|
$runResult = Invoke-RestMethod -Uri "$BaseUrl/api/v2/rvw/tasks/$taskId/run" `
|
||||||
|
-Method Post -Body $body -ContentType "application/json"
|
||||||
|
Write-Host " ✅ 审查任务已启动!" -ForegroundColor Green
|
||||||
|
Write-Host " ⏳ 注意:AI评估需要1-2分钟,可稍后查看报告" -ForegroundColor Yellow
|
||||||
|
} catch {
|
||||||
|
$errorBody = $_.ErrorDetails.Message | ConvertFrom-Json
|
||||||
|
Write-Host " ⚠️ $($errorBody.message)" -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Write-Host " ❌ 上传失败: $($uploadResponse.message)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Host " ❌ 失败: $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host " ⚠️ 测试文件不存在: $TestFile" -ForegroundColor Yellow
|
||||||
|
Write-Host " 跳过上传测试,请手动测试" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
|
||||||
|
# 测试6: 旧版API兼容性
|
||||||
|
Write-Host "`n8. 测试旧版API兼容性 (GET /api/v1/review/tasks)..." -ForegroundColor Yellow
|
||||||
|
try {
|
||||||
|
$response = Invoke-RestMethod -Uri "$BaseUrl/api/v1/review/tasks" -Method Get
|
||||||
|
Write-Host " ✅ 旧版API正常! 任务数: $($response.pagination.total)" -ForegroundColor Green
|
||||||
|
} catch {
|
||||||
|
Write-Host " ❌ 旧版API异常: $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n========================================" -ForegroundColor Cyan
|
||||||
|
Write-Host "测试完成!" -ForegroundColor Cyan
|
||||||
|
Write-Host "========================================`n" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
Write-Host "后续操作:" -ForegroundColor Yellow
|
||||||
|
Write-Host " - 查看报告: GET $BaseUrl/api/v2/rvw/tasks/{taskId}/report" -ForegroundColor Gray
|
||||||
|
Write-Host " - 批量运行: POST $BaseUrl/api/v2/rvw/tasks/batch/run" -ForegroundColor Gray
|
||||||
|
Write-Host " - 删除任务: DELETE $BaseUrl/api/v2/rvw/tasks/{taskId}" -ForegroundColor Gray
|
||||||
|
|
||||||
374
backend/src/modules/rvw/controllers/reviewController.ts
Normal file
374
backend/src/modules/rvw/controllers/reviewController.ts
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
/**
|
||||||
|
* RVW稿件审查模块 - 控制器
|
||||||
|
* @module rvw/controllers/reviewController
|
||||||
|
*
|
||||||
|
* 基于旧代码迁移:backend/src/legacy/controllers/reviewController.ts
|
||||||
|
* 改造内容:
|
||||||
|
* 1. console.log → logger
|
||||||
|
* 2. MOCK_USER_ID → 从请求中获取(暂时保留Mock,待JWT集成)
|
||||||
|
* 3. 新增智能体选择、批量运行接口
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { FastifyRequest, FastifyReply } from 'fastify';
|
||||||
|
import { logger } from '../../../common/logging/index.js';
|
||||||
|
import { ModelType } from '../../../common/llm/adapters/types.js';
|
||||||
|
import * as reviewService from '../services/reviewService.js';
|
||||||
|
import { AgentType } from '../types/index.js';
|
||||||
|
|
||||||
|
// TODO: 集成JWT认证后移除
|
||||||
|
const MOCK_USER_ID = 'user-mock-001';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户ID(暂时使用Mock,待JWT集成)
|
||||||
|
*/
|
||||||
|
function getUserId(request: FastifyRequest): string {
|
||||||
|
// TODO: 从JWT token中获取
|
||||||
|
// return request.user?.id;
|
||||||
|
return MOCK_USER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 任务创建 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传稿件创建任务
|
||||||
|
* POST /api/v2/rvw/tasks
|
||||||
|
*/
|
||||||
|
export async function createTask(
|
||||||
|
request: FastifyRequest<{
|
||||||
|
Body: {
|
||||||
|
modelType?: string;
|
||||||
|
};
|
||||||
|
}>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const userId = getUserId(request);
|
||||||
|
logger.info('[RVW:Controller] 上传稿件', { userId });
|
||||||
|
|
||||||
|
// 获取上传的文件
|
||||||
|
const data = await request.file();
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
logger.warn('[RVW:Controller] 没有接收到文件');
|
||||||
|
return reply.status(400).send({
|
||||||
|
success: false,
|
||||||
|
message: '请上传文件',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = await data.toBuffer();
|
||||||
|
const filename = data.filename;
|
||||||
|
const fileType = data.mimetype;
|
||||||
|
const fileSizeBytes = file.length;
|
||||||
|
|
||||||
|
logger.info('[RVW:Controller] 接收文件', {
|
||||||
|
filename,
|
||||||
|
fileType,
|
||||||
|
sizeMB: (fileSizeBytes / 1024 / 1024).toFixed(2)
|
||||||
|
});
|
||||||
|
|
||||||
|
// 文件大小限制(50MB,根据MVP需求)
|
||||||
|
const maxSize = 50 * 1024 * 1024;
|
||||||
|
if (fileSizeBytes > maxSize) {
|
||||||
|
logger.warn('[RVW:Controller] 文件太大', { sizeMB: (fileSizeBytes / 1024 / 1024).toFixed(2) });
|
||||||
|
return reply.status(400).send({
|
||||||
|
success: false,
|
||||||
|
message: '文件大小不能超过50MB',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件类型限制
|
||||||
|
const allowedTypes = [
|
||||||
|
'application/msword', // .doc
|
||||||
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // .docx
|
||||||
|
'application/pdf', // .pdf
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!allowedTypes.includes(fileType)) {
|
||||||
|
logger.warn('[RVW:Controller] 不支持的文件类型', { fileType });
|
||||||
|
return reply.status(400).send({
|
||||||
|
success: false,
|
||||||
|
message: '仅支持 Word (.doc, .docx) 和 PDF 文件',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取模型类型
|
||||||
|
const modelType = ((data.fields.modelType as any)?.value || 'deepseek-v3') as ModelType;
|
||||||
|
|
||||||
|
// 验证模型类型
|
||||||
|
const validModels: ModelType[] = ['deepseek-v3', 'qwen3-72b', 'qwen-long'];
|
||||||
|
if (!validModels.includes(modelType)) {
|
||||||
|
return reply.status(400).send({
|
||||||
|
success: false,
|
||||||
|
message: `无效的模型类型,可选: ${validModels.join(', ')}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建任务
|
||||||
|
const task = await reviewService.createTask(file, filename, userId, modelType);
|
||||||
|
|
||||||
|
logger.info('[RVW:Controller] 任务已创建', { taskId: task.id });
|
||||||
|
|
||||||
|
return reply.send({
|
||||||
|
success: true,
|
||||||
|
message: '稿件上传成功,任务已创建',
|
||||||
|
data: {
|
||||||
|
taskId: task.id,
|
||||||
|
fileName: task.fileName,
|
||||||
|
fileSize: task.fileSize,
|
||||||
|
status: task.status,
|
||||||
|
createdAt: task.createdAt,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[RVW:Controller] 上传失败', {
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
});
|
||||||
|
return reply.status(500).send({
|
||||||
|
success: false,
|
||||||
|
message: error instanceof Error ? error.message : '上传失败',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 运行审查 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行审查(选择智能体)
|
||||||
|
* POST /api/v2/rvw/tasks/:taskId/run
|
||||||
|
*/
|
||||||
|
export async function runReview(
|
||||||
|
request: FastifyRequest<{
|
||||||
|
Params: { taskId: string };
|
||||||
|
Body: { agents: AgentType[] };
|
||||||
|
}>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const userId = getUserId(request);
|
||||||
|
const { taskId } = request.params;
|
||||||
|
const { agents } = request.body;
|
||||||
|
|
||||||
|
logger.info('[RVW:Controller] 运行审查', { taskId, agents });
|
||||||
|
|
||||||
|
await reviewService.runReview({ taskId, agents, userId });
|
||||||
|
|
||||||
|
return reply.send({
|
||||||
|
success: true,
|
||||||
|
message: '审查任务已启动',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[RVW:Controller] 运行审查失败', {
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
});
|
||||||
|
return reply.status(400).send({
|
||||||
|
success: false,
|
||||||
|
message: error instanceof Error ? error.message : '运行审查失败',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量运行审查
|
||||||
|
* POST /api/v2/rvw/tasks/batch/run
|
||||||
|
*/
|
||||||
|
export async function batchRunReview(
|
||||||
|
request: FastifyRequest<{
|
||||||
|
Body: {
|
||||||
|
taskIds: string[];
|
||||||
|
agents: AgentType[];
|
||||||
|
};
|
||||||
|
}>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const userId = getUserId(request);
|
||||||
|
const { taskIds, agents } = request.body;
|
||||||
|
|
||||||
|
logger.info('[RVW:Controller] 批量运行审查', { taskCount: taskIds.length, agents });
|
||||||
|
|
||||||
|
const result = await reviewService.batchRunReview({ taskIds, agents, userId });
|
||||||
|
|
||||||
|
return reply.send({
|
||||||
|
success: true,
|
||||||
|
message: `批量审查完成: ${result.success.length} 成功, ${result.failed.length} 失败`,
|
||||||
|
data: result,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[RVW:Controller] 批量运行失败', {
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
});
|
||||||
|
return reply.status(400).send({
|
||||||
|
success: false,
|
||||||
|
message: error instanceof Error ? error.message : '批量运行失败',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 任务查询 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取任务列表
|
||||||
|
* GET /api/v2/rvw/tasks
|
||||||
|
*/
|
||||||
|
export async function getTaskList(
|
||||||
|
request: FastifyRequest<{
|
||||||
|
Querystring: {
|
||||||
|
status?: 'all' | 'pending' | 'completed';
|
||||||
|
page?: string;
|
||||||
|
limit?: string;
|
||||||
|
};
|
||||||
|
}>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const userId = getUserId(request);
|
||||||
|
const status = request.query.status || 'all';
|
||||||
|
const page = parseInt(request.query.page || '1', 10);
|
||||||
|
const limit = parseInt(request.query.limit || '20', 10);
|
||||||
|
|
||||||
|
logger.info('[RVW:Controller] 获取任务列表', { status, page, limit });
|
||||||
|
|
||||||
|
const result = await reviewService.getTaskList({ userId, status, page, limit });
|
||||||
|
|
||||||
|
return reply.send({
|
||||||
|
success: true,
|
||||||
|
data: result.tasks,
|
||||||
|
pagination: result.pagination,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[RVW:Controller] 获取列表失败', {
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
});
|
||||||
|
return reply.status(500).send({
|
||||||
|
success: false,
|
||||||
|
message: error instanceof Error ? error.message : '获取列表失败',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取任务详情
|
||||||
|
* GET /api/v2/rvw/tasks/:taskId
|
||||||
|
*/
|
||||||
|
export async function getTaskDetail(
|
||||||
|
request: FastifyRequest<{
|
||||||
|
Params: { taskId: string };
|
||||||
|
}>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const userId = getUserId(request);
|
||||||
|
const { taskId } = request.params;
|
||||||
|
|
||||||
|
logger.info('[RVW:Controller] 获取任务详情', { taskId });
|
||||||
|
|
||||||
|
const task = await reviewService.getTaskDetail(userId, taskId);
|
||||||
|
|
||||||
|
// 🆕 直接使用新字段
|
||||||
|
return reply.send({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
id: task.id,
|
||||||
|
fileName: task.fileName,
|
||||||
|
fileSize: task.fileSize,
|
||||||
|
status: task.status,
|
||||||
|
selectedAgents: task.selectedAgents || ['editorial', 'methodology'],
|
||||||
|
wordCount: task.wordCount,
|
||||||
|
editorialScore: task.editorialScore,
|
||||||
|
methodologyStatus: task.methodologyStatus,
|
||||||
|
overallScore: task.overallScore,
|
||||||
|
modelUsed: task.modelUsed,
|
||||||
|
createdAt: task.createdAt,
|
||||||
|
startedAt: task.startedAt,
|
||||||
|
completedAt: task.completedAt,
|
||||||
|
durationSeconds: task.durationSeconds,
|
||||||
|
errorMessage: task.errorMessage,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[RVW:Controller] 获取详情失败', {
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
});
|
||||||
|
return reply.status(404).send({
|
||||||
|
success: false,
|
||||||
|
message: error instanceof Error ? error.message : '任务不存在',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取审查报告
|
||||||
|
* GET /api/v2/rvw/tasks/:taskId/report
|
||||||
|
*/
|
||||||
|
export async function getTaskReport(
|
||||||
|
request: FastifyRequest<{
|
||||||
|
Params: { taskId: string };
|
||||||
|
}>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const userId = getUserId(request);
|
||||||
|
const { taskId } = request.params;
|
||||||
|
|
||||||
|
logger.info('[RVW:Controller] 获取审查报告', { taskId });
|
||||||
|
|
||||||
|
const report = await reviewService.getTaskReport(userId, taskId);
|
||||||
|
|
||||||
|
return reply.send({
|
||||||
|
success: true,
|
||||||
|
data: report,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[RVW:Controller] 获取报告失败', {
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果报告尚未完成,返回202
|
||||||
|
if (error instanceof Error && error.message.includes('尚未完成')) {
|
||||||
|
return reply.status(202).send({
|
||||||
|
success: false,
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply.status(404).send({
|
||||||
|
success: false,
|
||||||
|
message: error instanceof Error ? error.message : '报告不存在',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除任务
|
||||||
|
* DELETE /api/v2/rvw/tasks/:taskId
|
||||||
|
*/
|
||||||
|
export async function deleteTask(
|
||||||
|
request: FastifyRequest<{
|
||||||
|
Params: { taskId: string };
|
||||||
|
}>,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const userId = getUserId(request);
|
||||||
|
const { taskId } = request.params;
|
||||||
|
|
||||||
|
logger.info('[RVW:Controller] 删除任务', { taskId });
|
||||||
|
|
||||||
|
await reviewService.deleteTask(userId, taskId);
|
||||||
|
|
||||||
|
return reply.send({
|
||||||
|
success: true,
|
||||||
|
message: '任务已删除',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[RVW:Controller] 删除失败', {
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
});
|
||||||
|
return reply.status(404).send({
|
||||||
|
success: false,
|
||||||
|
message: error instanceof Error ? error.message : '删除失败',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
25
backend/src/modules/rvw/index.ts
Normal file
25
backend/src/modules/rvw/index.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* RVW稿件审查模块 - 模块入口
|
||||||
|
* @module rvw
|
||||||
|
*
|
||||||
|
* 功能:
|
||||||
|
* - 稿约规范性评估(11项标准)
|
||||||
|
* - 方法学评估(3部分,20个检查点)
|
||||||
|
* - 智能体选择(可选1个或2个)
|
||||||
|
* - 批量审查支持
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 导出路由
|
||||||
|
export { default as rvwRoutes } from './routes/index.js';
|
||||||
|
|
||||||
|
// 导出服务(供其他模块使用)
|
||||||
|
export * as reviewService from './services/reviewService.js';
|
||||||
|
export * as editorialService from './services/editorialService.js';
|
||||||
|
export * as methodologyService from './services/methodologyService.js';
|
||||||
|
|
||||||
|
// 导出类型
|
||||||
|
export * from './types/index.js';
|
||||||
|
|
||||||
|
// 导出工具函数
|
||||||
|
export * from './services/utils.js';
|
||||||
|
|
||||||
46
backend/src/modules/rvw/routes/index.ts
Normal file
46
backend/src/modules/rvw/routes/index.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* RVW稿件审查模块 - 路由定义
|
||||||
|
* @module rvw/routes
|
||||||
|
*
|
||||||
|
* API前缀: /api/v2/rvw
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { FastifyInstance } from 'fastify';
|
||||||
|
import * as reviewController from '../controllers/reviewController.js';
|
||||||
|
|
||||||
|
export default async function rvwRoutes(fastify: FastifyInstance) {
|
||||||
|
// ==================== 任务管理 ====================
|
||||||
|
|
||||||
|
// 创建任务(上传稿件)
|
||||||
|
// POST /api/v2/rvw/tasks
|
||||||
|
fastify.post('/tasks', reviewController.createTask);
|
||||||
|
|
||||||
|
// 获取任务列表
|
||||||
|
// GET /api/v2/rvw/tasks?status=all|pending|completed&page=1&limit=20
|
||||||
|
fastify.get('/tasks', reviewController.getTaskList);
|
||||||
|
|
||||||
|
// 获取任务详情
|
||||||
|
// GET /api/v2/rvw/tasks/:taskId
|
||||||
|
fastify.get('/tasks/:taskId', reviewController.getTaskDetail);
|
||||||
|
|
||||||
|
// 获取审查报告
|
||||||
|
// GET /api/v2/rvw/tasks/:taskId/report
|
||||||
|
fastify.get('/tasks/:taskId/report', reviewController.getTaskReport);
|
||||||
|
|
||||||
|
// 删除任务
|
||||||
|
// DELETE /api/v2/rvw/tasks/:taskId
|
||||||
|
fastify.delete('/tasks/:taskId', reviewController.deleteTask);
|
||||||
|
|
||||||
|
// ==================== 运行审查 ====================
|
||||||
|
|
||||||
|
// 运行审查(选择智能体)
|
||||||
|
// POST /api/v2/rvw/tasks/:taskId/run
|
||||||
|
// Body: { agents: ['editorial', 'methodology'] }
|
||||||
|
fastify.post('/tasks/:taskId/run', reviewController.runReview);
|
||||||
|
|
||||||
|
// 批量运行审查
|
||||||
|
// POST /api/v2/rvw/tasks/batch/run
|
||||||
|
// Body: { taskIds: [...], agents: ['editorial', 'methodology'] }
|
||||||
|
fastify.post('/tasks/batch/run', reviewController.batchRunReview);
|
||||||
|
}
|
||||||
|
|
||||||
70
backend/src/modules/rvw/services/editorialService.ts
Normal file
70
backend/src/modules/rvw/services/editorialService.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* RVW稿件审查模块 - 稿约规范性评估服务
|
||||||
|
* @module rvw/services/editorialService
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { LLMFactory } from '../../../common/llm/adapters/LLMFactory.js';
|
||||||
|
import { ModelType } from '../../../common/llm/adapters/types.js';
|
||||||
|
import { logger } from '../../../common/logging/index.js';
|
||||||
|
import { EditorialReview } from '../types/index.js';
|
||||||
|
import { parseJSONFromLLMResponse } from './utils.js';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
// Prompt文件路径
|
||||||
|
const PROMPT_PATH = path.join(__dirname, '../../../../prompts/review_editorial_system.txt');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 稿约规范性评估
|
||||||
|
* @param text 稿件文本
|
||||||
|
* @param modelType 模型类型
|
||||||
|
* @returns 评估结果
|
||||||
|
*/
|
||||||
|
export async function reviewEditorialStandards(
|
||||||
|
text: string,
|
||||||
|
modelType: ModelType = 'deepseek-v3'
|
||||||
|
): Promise<EditorialReview> {
|
||||||
|
try {
|
||||||
|
// 1. 读取系统Prompt
|
||||||
|
const systemPrompt = await fs.readFile(PROMPT_PATH, 'utf-8');
|
||||||
|
|
||||||
|
// 2. 构建消息
|
||||||
|
const messages = [
|
||||||
|
{ role: 'system' as const, content: systemPrompt },
|
||||||
|
{ role: 'user' as const, content: `请对以下稿件进行稿约规范性评估:\n\n${text}` },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 3. 调用LLM
|
||||||
|
logger.info('[RVW:Editorial] 开始稿约规范性评估', { modelType });
|
||||||
|
const llmAdapter = LLMFactory.getAdapter(modelType);
|
||||||
|
const response = await llmAdapter.chat(messages, {
|
||||||
|
temperature: 0.3, // 较低温度以获得更稳定的评估
|
||||||
|
maxTokens: 8000, // 确保完整输出
|
||||||
|
});
|
||||||
|
logger.info('[RVW:Editorial] 评估完成', {
|
||||||
|
modelType,
|
||||||
|
responseLength: response.content.length
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. 解析JSON响应
|
||||||
|
const result = parseJSONFromLLMResponse<EditorialReview>(response.content);
|
||||||
|
|
||||||
|
// 5. 验证响应格式
|
||||||
|
if (!result || typeof result.overall_score !== 'number' || !Array.isArray(result.items)) {
|
||||||
|
throw new Error('LLM返回的数据格式不正确');
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[RVW:Editorial] 稿约规范性评估失败', {
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
stack: error instanceof Error ? error.stack : undefined,
|
||||||
|
});
|
||||||
|
throw new Error(`稿约规范性评估失败: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
70
backend/src/modules/rvw/services/methodologyService.ts
Normal file
70
backend/src/modules/rvw/services/methodologyService.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* RVW稿件审查模块 - 方法学评估服务
|
||||||
|
* @module rvw/services/methodologyService
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { LLMFactory } from '../../../common/llm/adapters/LLMFactory.js';
|
||||||
|
import { ModelType } from '../../../common/llm/adapters/types.js';
|
||||||
|
import { logger } from '../../../common/logging/index.js';
|
||||||
|
import { MethodologyReview } from '../types/index.js';
|
||||||
|
import { parseJSONFromLLMResponse } from './utils.js';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
// Prompt文件路径
|
||||||
|
const PROMPT_PATH = path.join(__dirname, '../../../../prompts/review_methodology_system.txt');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 方法学评估
|
||||||
|
* @param text 稿件文本
|
||||||
|
* @param modelType 模型类型
|
||||||
|
* @returns 评估结果
|
||||||
|
*/
|
||||||
|
export async function reviewMethodology(
|
||||||
|
text: string,
|
||||||
|
modelType: ModelType = 'deepseek-v3'
|
||||||
|
): Promise<MethodologyReview> {
|
||||||
|
try {
|
||||||
|
// 1. 读取系统Prompt
|
||||||
|
const systemPrompt = await fs.readFile(PROMPT_PATH, 'utf-8');
|
||||||
|
|
||||||
|
// 2. 构建消息
|
||||||
|
const messages = [
|
||||||
|
{ role: 'system' as const, content: systemPrompt },
|
||||||
|
{ role: 'user' as const, content: `请对以下稿件进行方法学评估:\n\n${text}` },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 3. 调用LLM
|
||||||
|
logger.info('[RVW:Methodology] 开始方法学评估', { modelType });
|
||||||
|
const llmAdapter = LLMFactory.getAdapter(modelType);
|
||||||
|
const response = await llmAdapter.chat(messages, {
|
||||||
|
temperature: 0.3,
|
||||||
|
maxTokens: 8000,
|
||||||
|
});
|
||||||
|
logger.info('[RVW:Methodology] 评估完成', {
|
||||||
|
modelType,
|
||||||
|
responseLength: response.content.length
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. 解析JSON响应
|
||||||
|
const result = parseJSONFromLLMResponse<MethodologyReview>(response.content);
|
||||||
|
|
||||||
|
// 5. 验证响应格式
|
||||||
|
if (!result || typeof result.overall_score !== 'number' || !Array.isArray(result.parts)) {
|
||||||
|
throw new Error('LLM返回的数据格式不正确');
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[RVW:Methodology] 方法学评估失败', {
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
stack: error instanceof Error ? error.stack : undefined,
|
||||||
|
});
|
||||||
|
throw new Error(`方法学评估失败: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
430
backend/src/modules/rvw/services/reviewService.ts
Normal file
430
backend/src/modules/rvw/services/reviewService.ts
Normal file
@@ -0,0 +1,430 @@
|
|||||||
|
/**
|
||||||
|
* RVW稿件审查模块 - 主服务
|
||||||
|
* @module rvw/services/reviewService
|
||||||
|
*
|
||||||
|
* 基于旧代码迁移:backend/src/legacy/services/reviewService.ts
|
||||||
|
* 改造内容:
|
||||||
|
* 1. console.log → logger
|
||||||
|
* 2. 新增智能体选择逻辑
|
||||||
|
* 3. 新增批量运行接口
|
||||||
|
*
|
||||||
|
* 注意:当前适配现有schema,Phase 2数据库迁移后可启用新字段
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 { Prisma } from '@prisma/client';
|
||||||
|
import {
|
||||||
|
AgentType,
|
||||||
|
TaskStatus,
|
||||||
|
TaskListParams,
|
||||||
|
TaskListResponse,
|
||||||
|
TaskSummary,
|
||||||
|
ReviewReport,
|
||||||
|
RunReviewParams,
|
||||||
|
BatchRunParams,
|
||||||
|
EditorialReview,
|
||||||
|
MethodologyReview,
|
||||||
|
} from '../types/index.js';
|
||||||
|
import { reviewEditorialStandards } from './editorialService.js';
|
||||||
|
import { reviewMethodology } from './methodologyService.js';
|
||||||
|
import {
|
||||||
|
calculateOverallScore,
|
||||||
|
getMethodologyStatus,
|
||||||
|
validateAgentSelection,
|
||||||
|
} from './utils.js';
|
||||||
|
|
||||||
|
// ==================== 任务创建 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建审查任务(上传稿件)
|
||||||
|
* @param file 文件Buffer
|
||||||
|
* @param filename 文件名
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param modelType 模型类型
|
||||||
|
* @returns 创建的任务
|
||||||
|
*/
|
||||||
|
export async function createTask(
|
||||||
|
file: Buffer,
|
||||||
|
filename: string,
|
||||||
|
userId: string,
|
||||||
|
modelType: ModelType = 'deepseek-v3'
|
||||||
|
) {
|
||||||
|
logger.info('[RVW] 创建审查任务', { filename, userId, modelType });
|
||||||
|
|
||||||
|
// 创建任务记录(状态为pending,等待用户选择智能体后运行)
|
||||||
|
const task = await prisma.reviewTask.create({
|
||||||
|
data: {
|
||||||
|
userId,
|
||||||
|
fileName: filename,
|
||||||
|
fileSize: file.length,
|
||||||
|
extractedText: '', // 初始为空,运行时提取
|
||||||
|
status: 'pending',
|
||||||
|
modelUsed: modelType,
|
||||||
|
selectedAgents: ['editorial', 'methodology'], // 默认选择两个智能体
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info('[RVW] 任务已创建', { taskId: task.id, status: task.status });
|
||||||
|
|
||||||
|
// 异步提取文档文本(预处理,不运行评估)
|
||||||
|
extractDocumentAsync(task.id, file, filename).catch(error => {
|
||||||
|
logger.error('[RVW] 文档提取失败', { taskId: task.id, error: error.message });
|
||||||
|
});
|
||||||
|
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步提取文档文本(预处理)
|
||||||
|
*/
|
||||||
|
async function extractDocumentAsync(taskId: string, file: Buffer, filename: string) {
|
||||||
|
try {
|
||||||
|
logger.info('[RVW] 开始提取文档', { taskId, filename });
|
||||||
|
|
||||||
|
const extractionResult = await extractionClient.extractDocument(file, filename);
|
||||||
|
|
||||||
|
if (!extractionResult.success || !extractionResult.text) {
|
||||||
|
throw new Error('文档提取失败或内容为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractedText = extractionResult.text;
|
||||||
|
const wordCount = extractionResult.metadata?.char_count || extractedText.length;
|
||||||
|
|
||||||
|
// 更新提取的文本(保持pending状态)
|
||||||
|
await prisma.reviewTask.update({
|
||||||
|
where: { id: taskId },
|
||||||
|
data: {
|
||||||
|
extractedText,
|
||||||
|
wordCount,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info('[RVW] 文档提取成功', { taskId, wordCount });
|
||||||
|
} 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 : 'Document extraction failed',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 运行审查 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行审查(核心功能)
|
||||||
|
* 支持选择1个或2个智能体
|
||||||
|
*/
|
||||||
|
export async function runReview(params: RunReviewParams): Promise<void> {
|
||||||
|
const { taskId, agents, userId } = params;
|
||||||
|
|
||||||
|
// 验证智能体选择
|
||||||
|
validateAgentSelection(agents);
|
||||||
|
|
||||||
|
// 获取任务
|
||||||
|
const task = await prisma.reviewTask.findFirst({
|
||||||
|
where: { id: taskId, userId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!task) {
|
||||||
|
throw new Error('任务不存在或无权限访问');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task.status === 'reviewing' || task.status === 'reviewing_editorial' || task.status === 'reviewing_methodology') {
|
||||||
|
throw new Error('任务正在审查中,请稍后');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!task.extractedText) {
|
||||||
|
throw new Error('文档尚未提取完成,请稍后再试');
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 更新任务状态
|
||||||
|
await prisma.reviewTask.update({
|
||||||
|
where: { id: taskId },
|
||||||
|
data: {
|
||||||
|
status: 'reviewing',
|
||||||
|
startedAt: new Date(),
|
||||||
|
// 清除之前的结果(如果重新运行)
|
||||||
|
editorialReview: Prisma.JsonNull,
|
||||||
|
methodologyReview: Prisma.JsonNull,
|
||||||
|
overallScore: null,
|
||||||
|
errorMessage: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info('[RVW] 开始审查', { taskId, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量运行审查
|
||||||
|
* 最多并发5个任务
|
||||||
|
*/
|
||||||
|
export async function batchRunReview(params: BatchRunParams): Promise<{
|
||||||
|
success: string[];
|
||||||
|
failed: { taskId: string; error: string }[]
|
||||||
|
}> {
|
||||||
|
const { taskIds, agents, userId } = params;
|
||||||
|
|
||||||
|
// 验证智能体选择
|
||||||
|
validateAgentSelection(agents);
|
||||||
|
|
||||||
|
if (taskIds.length === 0) {
|
||||||
|
throw new Error('请选择至少一个任务');
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('[RVW] 开始批量审查', {
|
||||||
|
taskCount: taskIds.length,
|
||||||
|
agents
|
||||||
|
});
|
||||||
|
|
||||||
|
const success: string[] = [];
|
||||||
|
const failed: { taskId: string; error: string }[] = [];
|
||||||
|
|
||||||
|
// 并发控制:最多5个
|
||||||
|
const MAX_CONCURRENT = 5;
|
||||||
|
|
||||||
|
for (let i = 0; i < taskIds.length; i += MAX_CONCURRENT) {
|
||||||
|
const batch = taskIds.slice(i, i + MAX_CONCURRENT);
|
||||||
|
|
||||||
|
const results = await Promise.allSettled(
|
||||||
|
batch.map(taskId => runReview({ taskId, agents, userId }))
|
||||||
|
);
|
||||||
|
|
||||||
|
results.forEach((result, index) => {
|
||||||
|
const taskId = batch[index];
|
||||||
|
if (result.status === 'fulfilled') {
|
||||||
|
success.push(taskId);
|
||||||
|
} else {
|
||||||
|
failed.push({
|
||||||
|
taskId,
|
||||||
|
error: result.reason?.message || 'Unknown error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('[RVW] 批量审查完成', {
|
||||||
|
successCount: success.length,
|
||||||
|
failedCount: failed.length
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success, failed };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 任务查询 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取任务列表
|
||||||
|
*/
|
||||||
|
export async function getTaskList(params: TaskListParams): Promise<TaskListResponse> {
|
||||||
|
const { userId, status = 'all', page = 1, limit = 20 } = params;
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
// 构建查询条件
|
||||||
|
const where: Prisma.ReviewTaskWhereInput = { userId };
|
||||||
|
|
||||||
|
if (status === 'pending') {
|
||||||
|
where.status = { in: ['pending', 'extracting', 'reviewing', 'reviewing_editorial', 'reviewing_methodology'] };
|
||||||
|
} else if (status === 'completed') {
|
||||||
|
where.status = { in: ['completed', 'failed'] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const [tasks, total] = await Promise.all([
|
||||||
|
prisma.reviewTask.findMany({
|
||||||
|
where,
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
skip,
|
||||||
|
take: limit,
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
fileName: true,
|
||||||
|
fileSize: true,
|
||||||
|
status: true,
|
||||||
|
// 🆕 使用新字段
|
||||||
|
selectedAgents: true,
|
||||||
|
editorialScore: true,
|
||||||
|
methodologyStatus: true,
|
||||||
|
overallScore: true,
|
||||||
|
modelUsed: true,
|
||||||
|
createdAt: true,
|
||||||
|
completedAt: true,
|
||||||
|
durationSeconds: true,
|
||||||
|
wordCount: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
prisma.reviewTask.count({ where }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 转换为TaskSummary格式(直接使用新字段)
|
||||||
|
const taskSummaries: TaskSummary[] = tasks.map(task => ({
|
||||||
|
id: task.id,
|
||||||
|
fileName: task.fileName,
|
||||||
|
fileSize: task.fileSize,
|
||||||
|
status: task.status as TaskStatus,
|
||||||
|
selectedAgents: (task.selectedAgents || ['editorial', 'methodology']) as AgentType[],
|
||||||
|
editorialScore: task.editorialScore ?? undefined,
|
||||||
|
methodologyStatus: task.methodologyStatus as any,
|
||||||
|
overallScore: task.overallScore ?? undefined,
|
||||||
|
modelUsed: task.modelUsed ?? undefined,
|
||||||
|
createdAt: task.createdAt,
|
||||||
|
completedAt: task.completedAt ?? undefined,
|
||||||
|
durationSeconds: task.durationSeconds ?? undefined,
|
||||||
|
wordCount: task.wordCount ?? undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
tasks: taskSummaries,
|
||||||
|
pagination: {
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
total,
|
||||||
|
totalPages: Math.ceil(total / limit),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取任务详情
|
||||||
|
*/
|
||||||
|
export async function getTaskDetail(userId: string, taskId: string) {
|
||||||
|
const task = await prisma.reviewTask.findFirst({
|
||||||
|
where: { id: taskId, userId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!task) {
|
||||||
|
throw new Error('任务不存在或无权限访问');
|
||||||
|
}
|
||||||
|
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取任务报告
|
||||||
|
*/
|
||||||
|
export async function getTaskReport(userId: string, taskId: string): Promise<ReviewReport> {
|
||||||
|
const task = await prisma.reviewTask.findFirst({
|
||||||
|
where: { id: taskId, userId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!task) {
|
||||||
|
throw new Error('任务不存在或无权限访问');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task.status !== 'completed') {
|
||||||
|
throw new Error(`报告尚未完成,当前状态: ${task.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
taskId: task.id,
|
||||||
|
fileName: task.fileName,
|
||||||
|
wordCount: task.wordCount ?? undefined,
|
||||||
|
modelUsed: task.modelUsed ?? undefined,
|
||||||
|
// 🆕 直接使用新字段
|
||||||
|
selectedAgents: (task.selectedAgents || ['editorial', 'methodology']) as AgentType[],
|
||||||
|
overallScore: task.overallScore ?? undefined,
|
||||||
|
editorialReview: task.editorialReview as unknown as EditorialReview | undefined,
|
||||||
|
methodologyReview: task.methodologyReview as unknown as MethodologyReview | undefined,
|
||||||
|
completedAt: task.completedAt ?? undefined,
|
||||||
|
durationSeconds: task.durationSeconds ?? undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除任务
|
||||||
|
*/
|
||||||
|
export async function deleteTask(userId: string, taskId: string): Promise<void> {
|
||||||
|
const task = await prisma.reviewTask.findFirst({
|
||||||
|
where: { id: taskId, userId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!task) {
|
||||||
|
throw new Error('任务不存在或无权限访问');
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.reviewTask.delete({
|
||||||
|
where: { id: taskId },
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info('[RVW] 任务已删除', { taskId });
|
||||||
|
}
|
||||||
116
backend/src/modules/rvw/services/utils.ts
Normal file
116
backend/src/modules/rvw/services/utils.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/**
|
||||||
|
* RVW稿件审查模块 - 工具函数
|
||||||
|
* @module rvw/services/utils
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { MethodologyReview, MethodologyStatus } from '../types/index.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从LLM响应中解析JSON
|
||||||
|
* 支持多种格式:纯JSON、```json代码块、混合文本
|
||||||
|
*/
|
||||||
|
export function parseJSONFromLLMResponse<T>(content: string): T {
|
||||||
|
try {
|
||||||
|
// 1. 尝试直接解析
|
||||||
|
return JSON.parse(content) as T;
|
||||||
|
} catch {
|
||||||
|
// 2. 尝试提取```json代码块
|
||||||
|
const jsonMatch = content.match(/```json\s*\n?([\s\S]*?)\n?```/);
|
||||||
|
if (jsonMatch) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(jsonMatch[1].trim()) as T;
|
||||||
|
} catch {
|
||||||
|
// 继续尝试其他方法
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 尝试提取{}或[]包裹的内容
|
||||||
|
const objectMatch = content.match(/(\{[\s\S]*\})/);
|
||||||
|
if (objectMatch) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(objectMatch[1]) as T;
|
||||||
|
} catch {
|
||||||
|
// 继续尝试其他方法
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrayMatch = content.match(/(\[[\s\S]*\])/);
|
||||||
|
if (arrayMatch) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(arrayMatch[1]) as T;
|
||||||
|
} catch {
|
||||||
|
// 失败
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 所有尝试都失败
|
||||||
|
throw new Error('无法从LLM响应中解析JSON');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据方法学评估结果判断状态
|
||||||
|
* @param review 方法学评估结果
|
||||||
|
* @returns pass | warn | fail
|
||||||
|
*/
|
||||||
|
export function getMethodologyStatus(review: MethodologyReview | null | undefined): MethodologyStatus | undefined {
|
||||||
|
if (!review) return undefined;
|
||||||
|
|
||||||
|
const score = review.overall_score;
|
||||||
|
|
||||||
|
if (score >= 80) return 'pass';
|
||||||
|
if (score >= 60) return 'warn';
|
||||||
|
return 'fail';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据选择的智能体计算综合分数
|
||||||
|
* @param editorialScore 稿约规范性分数
|
||||||
|
* @param methodologyScore 方法学分数
|
||||||
|
* @param agents 选择的智能体
|
||||||
|
* @returns 综合分数
|
||||||
|
*/
|
||||||
|
export function calculateOverallScore(
|
||||||
|
editorialScore: number | null | undefined,
|
||||||
|
methodologyScore: number | null | undefined,
|
||||||
|
agents: string[]
|
||||||
|
): number | null {
|
||||||
|
const hasEditorial = agents.includes('editorial') && editorialScore != null;
|
||||||
|
const hasMethodology = agents.includes('methodology') && methodologyScore != null;
|
||||||
|
|
||||||
|
if (hasEditorial && hasMethodology) {
|
||||||
|
// 两个都选:稿约40% + 方法学60%
|
||||||
|
return editorialScore! * 0.4 + methodologyScore! * 0.6;
|
||||||
|
} else if (hasEditorial) {
|
||||||
|
// 只选规范性
|
||||||
|
return editorialScore!;
|
||||||
|
} else if (hasMethodology) {
|
||||||
|
// 只选方法学
|
||||||
|
return methodologyScore!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证智能体选择
|
||||||
|
* @param agents 选择的智能体列表
|
||||||
|
* @throws 如果选择无效
|
||||||
|
*/
|
||||||
|
export function validateAgentSelection(agents: string[]): void {
|
||||||
|
if (!agents || agents.length === 0) {
|
||||||
|
throw new Error('请至少选择一个智能体');
|
||||||
|
}
|
||||||
|
|
||||||
|
const validAgents = ['editorial', 'methodology'];
|
||||||
|
for (const agent of agents) {
|
||||||
|
if (!validAgents.includes(agent)) {
|
||||||
|
throw new Error(`无效的智能体类型: ${agent}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (agents.length > 2) {
|
||||||
|
throw new Error('最多只能选择2个智能体');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
157
backend/src/modules/rvw/types/index.ts
Normal file
157
backend/src/modules/rvw/types/index.ts
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
/**
|
||||||
|
* RVW稿件审查模块 - 类型定义
|
||||||
|
* @module rvw/types
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ==================== 智能体类型 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能体类型
|
||||||
|
* - editorial: 稿约规范性智能体
|
||||||
|
* - methodology: 方法学统计智能体
|
||||||
|
*/
|
||||||
|
export type AgentType = 'editorial' | 'methodology';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务状态
|
||||||
|
*/
|
||||||
|
export type TaskStatus =
|
||||||
|
| 'pending' // 待处理
|
||||||
|
| 'extracting' // 正在提取文档
|
||||||
|
| 'reviewing' // 正在评估
|
||||||
|
| 'completed' // 已完成
|
||||||
|
| 'failed'; // 失败
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 方法学评估状态
|
||||||
|
*/
|
||||||
|
export type MethodologyStatus = 'pass' | 'warn' | 'fail';
|
||||||
|
|
||||||
|
// ==================== 稿约规范性评估 ====================
|
||||||
|
|
||||||
|
export interface EditorialItem {
|
||||||
|
criterion: string;
|
||||||
|
status: 'pass' | 'warning' | 'fail';
|
||||||
|
score: number;
|
||||||
|
issues: string[];
|
||||||
|
suggestions: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EditorialReview {
|
||||||
|
overall_score: number;
|
||||||
|
summary: string;
|
||||||
|
items: EditorialItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 方法学评估 ====================
|
||||||
|
|
||||||
|
export interface MethodologyIssue {
|
||||||
|
type: string;
|
||||||
|
severity: 'major' | 'minor';
|
||||||
|
description: string;
|
||||||
|
location: string;
|
||||||
|
suggestion: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MethodologyPart {
|
||||||
|
part: string;
|
||||||
|
score: number;
|
||||||
|
issues: MethodologyIssue[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MethodologyReview {
|
||||||
|
overall_score: number;
|
||||||
|
summary: string;
|
||||||
|
parts: MethodologyPart[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 请求参数 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运行审查的参数
|
||||||
|
*/
|
||||||
|
export interface RunReviewParams {
|
||||||
|
taskId: string;
|
||||||
|
agents: AgentType[]; // 可选1个或2个
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量运行审查的参数
|
||||||
|
*/
|
||||||
|
export interface BatchRunParams {
|
||||||
|
taskIds: string[];
|
||||||
|
agents: AgentType[];
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务列表查询参数
|
||||||
|
*/
|
||||||
|
export interface TaskListParams {
|
||||||
|
userId: string;
|
||||||
|
status?: 'all' | 'pending' | 'completed';
|
||||||
|
page?: number;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 响应类型 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务摘要(用于列表展示)
|
||||||
|
*/
|
||||||
|
export interface TaskSummary {
|
||||||
|
id: string;
|
||||||
|
fileName: string;
|
||||||
|
fileSize: number;
|
||||||
|
status: TaskStatus;
|
||||||
|
selectedAgents: AgentType[];
|
||||||
|
editorialScore?: number;
|
||||||
|
methodologyStatus?: MethodologyStatus;
|
||||||
|
overallScore?: number;
|
||||||
|
modelUsed?: string;
|
||||||
|
createdAt: Date;
|
||||||
|
completedAt?: Date;
|
||||||
|
durationSeconds?: number;
|
||||||
|
wordCount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务列表响应
|
||||||
|
*/
|
||||||
|
export interface TaskListResponse {
|
||||||
|
tasks: TaskSummary[];
|
||||||
|
pagination: {
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
total: number;
|
||||||
|
totalPages: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完整报告
|
||||||
|
*/
|
||||||
|
export interface ReviewReport {
|
||||||
|
taskId: string;
|
||||||
|
fileName: string;
|
||||||
|
wordCount?: number;
|
||||||
|
modelUsed?: string;
|
||||||
|
selectedAgents: AgentType[];
|
||||||
|
overallScore?: number;
|
||||||
|
editorialReview?: EditorialReview;
|
||||||
|
methodologyReview?: MethodologyReview;
|
||||||
|
completedAt?: Date;
|
||||||
|
durationSeconds?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 工具函数类型 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LLM消息格式
|
||||||
|
*/
|
||||||
|
export interface LLMMessage {
|
||||||
|
role: 'system' | 'user' | 'assistant';
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -411,5 +411,6 @@ SET session_replication_role = 'origin';
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -113,5 +113,6 @@ WHERE key = 'verify_test';
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -256,5 +256,6 @@ verifyDatabase()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1
backend/src/types/global.d.ts
vendored
1
backend/src/types/global.d.ts
vendored
@@ -46,5 +46,6 @@ export {}
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -69,5 +69,6 @@ Write-Host "✅ 完成!" -ForegroundColor Green
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -159,3 +159,4 @@ DELETE {{baseUrl}}/api/v2/pkb/knowledge/knowledge-bases/{{testKbId}}
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -356,5 +356,6 @@ runAdvancedTests().catch(error => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -422,5 +422,6 @@ runAllTests()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -380,5 +380,6 @@ runAllTests()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -164,5 +164,6 @@ Set-Location ..
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# AIclinicalresearch 系统当前状态与开发指南
|
# AIclinicalresearch 系统当前状态与开发指南
|
||||||
|
|
||||||
> **文档版本:** v2.8
|
> **文档版本:** v2.9
|
||||||
> **创建日期:** 2025-11-28
|
> **创建日期:** 2025-11-28
|
||||||
> **维护者:** 开发团队
|
> **维护者:** 开发团队
|
||||||
> **最后更新:** 2026-01-07
|
> **最后更新:** 2026-01-07
|
||||||
> **重大进展:** 🎉 **PKB模块核心功能全部实现,具备生产可用性!** - 批处理完整流程验证通过
|
> **重大进展:** 🎉 **RVW稿件审查模块开发完成(85%)!** - 后端迁移+数据库扩展+前端重构全部完成
|
||||||
> **部署状态:** ✅ 生产环境运行中 | 公网地址:http://8.140.53.236/
|
> **部署状态:** ✅ 生产环境运行中 | 公网地址:http://8.140.53.236/
|
||||||
> **文档目的:** 快速了解系统当前状态,为新AI助手提供上下文
|
> **文档目的:** 快速了解系统当前状态,为新AI助手提供上下文
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
| **IIT** | IIT Manager Agent | AI驱动IIT研究助手 - 智能质控+REDCap集成 | ⭐⭐⭐⭐⭐ | 🎉 **Phase 1.5完成(60%)- AI对话+REDCap数据集成** | **P0** |
|
| **IIT** | IIT Manager Agent | AI驱动IIT研究助手 - 智能质控+REDCap集成 | ⭐⭐⭐⭐⭐ | 🎉 **Phase 1.5完成(60%)- AI对话+REDCap数据集成** | **P0** |
|
||||||
| **SSA** | 智能统计分析 | 队列/预测模型/RCT分析 | ⭐⭐⭐⭐⭐ | 📋 规划中 | P2 |
|
| **SSA** | 智能统计分析 | 队列/预测模型/RCT分析 | ⭐⭐⭐⭐⭐ | 📋 规划中 | P2 |
|
||||||
| **ST** | 统计分析工具 | 100+轻量化统计工具 | ⭐⭐⭐⭐ | 📋 规划中 | P2 |
|
| **ST** | 统计分析工具 | 100+轻量化统计工具 | ⭐⭐⭐⭐ | 📋 规划中 | P2 |
|
||||||
| **RVW** | 稿件审查系统 | 方法学评估、审稿流程 | ⭐⭐⭐⭐ | 📋 规划中 | P3 |
|
| **RVW** | 稿件审查系统 | 方法学评估、审稿流程 | ⭐⭐⭐⭐ | ✅ **开发完成(85%)** | P3 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -661,6 +661,7 @@ AIclinicalresearch/
|
|||||||
| **2026-01-07 上午** | **PKB前端V3** 🎉 | ✅ PKB模块前端V3设计实现完成(Dashboard+Workspace+3种工作模式) |
|
| **2026-01-07 上午** | **PKB前端V3** 🎉 | ✅ PKB模块前端V3设计实现完成(Dashboard+Workspace+3种工作模式) |
|
||||||
| **2026-01-07 下午** | **PKB批处理完善** 🏆 | ✅ 批处理完整流程调试通过(执行+进度+结果导出)+ 文档上传功能 + UI优化 |
|
| **2026-01-07 下午** | **PKB批处理完善** 🏆 | ✅ 批处理完整流程调试通过(执行+进度+结果导出)+ 文档上传功能 + UI优化 |
|
||||||
| **当前** | **PKB模块生产可用** | ✅ 核心功能全部实现(90%),具备生产环境部署条件 |
|
| **当前** | **PKB模块生产可用** | ✅ 核心功能全部实现(90%),具备生产环境部署条件 |
|
||||||
|
| **2026-01-07 晚** | **RVW模块开发完成** 🎉 | ✅ Phase 1-3完成(后端迁移+数据库扩展+前端重构) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -814,9 +815,9 @@ npm run dev # http://localhost:3000
|
|||||||
- **总计**:约 85,000 行
|
- **总计**:约 85,000 行
|
||||||
|
|
||||||
### 模块完成度
|
### 模块完成度
|
||||||
- ✅ **已完成**:AIA(100%)、平台基础层(100%)
|
- ✅ **已完成**:AIA(100%)、平台基础层(100%)、RVW(85%,Phase 1-3完成)
|
||||||
- 🚧 **开发中**:PKB(75%,前端V3设计完成)、ASL(80%)、DC(Tool C 98%,Tool B后端100%,Tool B前端0%)、IIT(60%,Phase 1.5完成)
|
- 🚧 **开发中**:PKB(90%,核心功能完成)、ASL(80%)、DC(Tool C 98%,Tool B后端100%,Tool B前端0%)、IIT(60%,Phase 1.5完成)
|
||||||
- 📋 **未开始**:SSA、ST、RVW
|
- 📋 **未开始**:SSA、ST
|
||||||
|
|
||||||
### 部署完成度
|
### 部署完成度
|
||||||
- ✅ **基础设施**:VPC(100%)、NAT网关(100%)、安全组(100%)
|
- ✅ **基础设施**:VPC(100%)、NAT网关(100%)、安全组(100%)
|
||||||
@@ -952,9 +953,9 @@ if (items.length >= 50) {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**文档版本**:v2.8
|
**文档版本**:v2.9
|
||||||
**最后更新**:2026-01-07
|
**最后更新**:2026-01-07
|
||||||
**下次更新**:ASL智能文献筛选模块启动 或 IIT Manager Agent Phase 2
|
**下次更新**:RVW生产环境部署 或 ASL智能文献筛选模块启动
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1016,3 +1017,36 @@ if (items.length >= 50) {
|
|||||||
- ✅ 测试通过:查询test0102项目,ID 7患者详细信息
|
- ✅ 测试通过:查询test0102项目,ID 7患者详细信息
|
||||||
|
|
||||||
**模块进度**:60%完成(Phase 1.5)
|
**模块进度**:60%完成(Phase 1.5)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**RVW稿件审查模块开发完成(2026-01-07)**:
|
||||||
|
|
||||||
|
### Phase 1:后端模块迁移与扩展
|
||||||
|
- ✅ 创建 `backend/src/modules/rvw/` 模块结构
|
||||||
|
- ✅ 迁移 reviewService、editorialService、methodologyService
|
||||||
|
- ✅ 实现智能体选择(selectedAgents)
|
||||||
|
- ✅ 实现批量运行API(batchRunReviewTasks)
|
||||||
|
- ✅ 替换 console.log 为 logger 服务
|
||||||
|
- ✅ 注册 v2 API路由(/api/v2/rvw)
|
||||||
|
|
||||||
|
### Phase 2:数据库字段扩展
|
||||||
|
- ✅ 添加 selectedAgents、editorialScore、methodologyStatus 字段
|
||||||
|
- ✅ 添加 picoExtract、isArchived、archivedAt 字段
|
||||||
|
- ✅ 使用 prisma db push 同步到数据库
|
||||||
|
|
||||||
|
### Phase 3:前端重构(frontend-v2)
|
||||||
|
- ✅ 创建 `frontend-v2/src/modules/rvw/index.tsx`(~503行)
|
||||||
|
- ✅ 实现 Dashboard 组件(任务列表、筛选、批量操作)
|
||||||
|
- ✅ 实现 ReportDetail 组件(双标签页切换)
|
||||||
|
- ✅ 实现 AgentModal 组件(智能体选择弹窗)
|
||||||
|
- ✅ 注册到 moduleRegistry.ts
|
||||||
|
- ✅ 添加顶部导航"预审稿"入口
|
||||||
|
|
||||||
|
**技术亮点**:
|
||||||
|
- 🔥 **新旧API兼容**:v1 + v2 API同时运行
|
||||||
|
- 🔥 **智能体可选**:用户可选择运行稿约规范性/方法学/两者
|
||||||
|
- 🔥 **批量操作**:支持多选任务批量运行
|
||||||
|
- 🔥 **云原生改造**:使用 logger 服务,遵循开发规范
|
||||||
|
|
||||||
|
**模块进度**:85%完成(Phase 1-3)
|
||||||
|
|||||||
@@ -606,5 +606,6 @@ async saveProcessedData(recordId, newData) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -793,5 +793,6 @@ export const AsyncProgressBar: React.FC<AsyncProgressBarProps> = ({
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1286,5 +1286,6 @@ interface FulltextScreeningResult {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -400,5 +400,6 @@ GET /api/v1/asl/fulltext-screening/tasks/:taskId/export
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -343,5 +343,6 @@ Linter错误:0个
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -502,5 +502,6 @@ Failed to open file '\\tmp\\extraction_service\\temp_10000_test.pdf'
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -568,5 +568,6 @@ df['creatinine'] = pd.to_numeric(df['creatinine'], errors='coerce')
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -406,5 +406,6 @@ npm run dev
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -983,5 +983,6 @@ export const aiController = new AIController();
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1317,5 +1317,6 @@ npm install react-markdown
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -225,5 +225,6 @@ FMA___基线 | FMA___1个月 | FMA___2个月
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -383,5 +383,6 @@ formula = "FMA总分(0-100) / 100"
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -217,5 +217,6 @@ async handleFillnaMice(request, reply) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -189,5 +189,6 @@ method: 'mean' | 'median' | 'mode' | 'constant' | 'ffill' | 'bfill'
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -339,5 +339,6 @@ Changes:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -411,5 +411,6 @@ cd path; command
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -640,5 +640,6 @@ import { logger } from '../../../../common/logging/index.js';
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -644,5 +644,6 @@ Content-Length: 45234
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -296,5 +296,6 @@ Response:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -449,5 +449,6 @@ Response:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -443,5 +443,6 @@ import { ChatContainer } from '@/shared/components/Chat';
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -353,5 +353,6 @@ const initialMessages = defaultMessages.length > 0 ? defaultMessages : [{
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -393,5 +393,6 @@ python main.py
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -641,5 +641,6 @@ http://localhost:5173/data-cleaning/tool-c
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -251,5 +251,6 @@ Day 5 (6-8小时):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -429,5 +429,6 @@ Docs: docs/03-业务模块/DC-数据清洗整理/06-开发记录/DC模块重建
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -404,5 +404,6 @@ const mockAssets: Asset[] = [
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -388,5 +388,6 @@ frontend-v2/src/modules/dc/
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -348,5 +348,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -302,5 +302,6 @@ ConflictDetectionService // 冲突检测(字段级对比)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -351,5 +351,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -314,5 +314,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -378,5 +378,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -466,5 +466,6 @@ Tool B后端代码**100%复用**了平台通用能力层,无任何重复开发
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -312,5 +312,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user