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:
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个智能体');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user