Files
AIclinicalresearch/backend/src/modules/rvw/services/utils.ts
HaHafeng 707f783229 feat(rvw): complete tenant portal polish and ops assignment fixes
Finalize RVW tenant portal UX and reliability updates by aligning login/profile interactions, stabilizing SMS code sends in weak-network scenarios, and fixing multi-tenant assignment payload handling to prevent runtime errors. Refresh RVW status and deployment checklist docs with SAE routing, frontend image build, and post-release validation guidance.

Made-with: Cursor
2026-03-15 18:22:01 +08:00

172 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* RVW稿件审查模块 - 工具函数
* @module rvw/services/utils
*/
import { MethodologyReview, MethodologyStatus } from '../types/index.js';
import { jsonrepair } from 'jsonrepair';
/**
* 从LLM响应中解析JSON
* 支持多种格式纯JSON、```json代码块、混合文本
*/
export function parseJSONFromLLMResponse<T>(content: string): T {
try {
// 1. 尝试直接解析
return JSON.parse(content) as T;
} catch {
// 1.1 先尝试 jsonrepair处理尾逗号、引号缺失等常见脏 JSON
try {
const repaired = jsonrepair(content);
return JSON.parse(repaired) 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 {
// 尝试修复代码块 JSON
try {
const repaired = jsonrepair(jsonMatch[1].trim());
return JSON.parse(repaired) as T;
} catch {
// 继续尝试其他方法
}
}
}
// 3. 尝试提取{}或[]包裹的内容
const objectMatch = content.match(/(\{[\s\S]*\})/);
if (objectMatch) {
try {
return JSON.parse(objectMatch[1]) as T;
} catch {
try {
const repaired = jsonrepair(objectMatch[1]);
return JSON.parse(repaired) as T;
} catch {
// 继续尝试其他方法
}
}
}
const arrayMatch = content.match(/(\[[\s\S]*\])/);
if (arrayMatch) {
try {
return JSON.parse(arrayMatch[1]) as T;
} catch {
try {
const repaired = jsonrepair(arrayMatch[1]);
return JSON.parse(repaired) 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 综合分数保留1位小数
*/
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;
let score: number | null = null;
if (hasEditorial && hasMethodology) {
// 两个都选稿约40% + 方法学60%
score = editorialScore! * 0.4 + methodologyScore! * 0.6;
} else if (hasEditorial) {
// 只选规范性
score = editorialScore!;
} else if (hasMethodology) {
// 只选方法学
score = methodologyScore!;
}
// 修复浮点数精度问题保留1位小数
return score !== null ? Math.round(score * 10) / 10 : null;
}
/**
* 验证智能体选择
* @param agents 选择的智能体列表
* @throws 如果选择无效
*/
export function validateAgentSelection(agents: string[]): void {
if (!agents || agents.length === 0) {
throw new Error('请至少选择一个智能体');
}
const validAgents = ['editorial', 'methodology', 'clinical'];
for (const agent of agents) {
if (!validAgents.includes(agent)) {
throw new Error(`无效的智能体类型: ${agent}`);
}
}
if (agents.length > 3) {
throw new Error('最多只能选择3个智能体');
}
}