Files
AIclinicalresearch/frontend-v2/src/modules/asl/api/index.ts
HaHafeng 87655ea7e6 feat(rvw,asl): RVW V3.0 smart review + ASL deep research history + stability
RVW module (V3.0 Smart Review Enhancement):
- Add LLM data validation via PromptService (RVW_DATA_VALIDATION)
- Add ClinicalAssessmentSkill with FINER-based evaluation (RVW_CLINICAL)
- Remove all numeric scores from UI (editorial, methodology, overall)
- Implement partial_completed status with Promise.allSettled
- Add error_details JSON field to ReviewTask for granular failure info
- Fix overallStatus logic: warning status now counts as success
- Restructure ForensicsReport: per-table LLM results, remove top-level block
- Refactor ClinicalReport: structured collapsible sections
- Increase all skill timeouts to 300s for long manuscripts (20+ pages)
- Increase DataForensics LLM timeout to 180s, pg-boss to 15min
- Executor default fallback timeout 30s -> 60s

ASL module:
- Add deep research history with sidebar accordion UI
- Implement waterfall flow for historical task display
- Upgrade Unifuncs DeepSearch API from S2 to S3 with fallback
- Add ASL_SR module seed for admin configurability
- Fix new search button inconsistency

Docs:
- Update RVW module status to V3.0
- Update deployment changelist
- Add 0305 deployment summary

DB Migration:
- Add error_details JSONB column to rvw_schema.review_tasks

Tested: All 4 review modules verified, partial completion working
Made-with: Cursor
2026-03-07 19:24:21 +08:00

758 lines
17 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.
/**
* ASL模块 - API客户端
*
* 负责所有与后端的API交互
*/
import type {
ScreeningProject,
CreateProjectRequest,
Literature,
ScreeningResult,
ScreeningTask,
ApiResponse,
PaginatedResponse,
ProjectStatistics
} from '../types';
import { getAccessToken } from '../../../framework/auth/api';
// API基础URL
const API_BASE_URL = '/api/v1/asl';
/**
* 获取带认证的请求头
*/
function getAuthHeaders(): HeadersInit {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
const token = getAccessToken();
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
return headers;
}
// 通用请求函数
async function request<T = any>(
url: string,
options?: RequestInit
): Promise<T> {
const response = await fetch(`${API_BASE_URL}${url}`, {
...options,
headers: {
...getAuthHeaders(),
...options?.headers,
},
});
if (!response.ok) {
// 尝试解析错误响应
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
try {
const errorData = await response.json();
errorMessage = errorData.error || errorData.message || errorMessage;
} catch (e) {
// 如果响应体不是JSON使用状态文本
const text = await response.text().catch(() => '');
if (text) {
errorMessage = text;
}
}
console.error('❌ API请求失败:', { url: `${API_BASE_URL}${url}`, status: response.status, error: errorMessage });
throw new Error(errorMessage);
}
return response.json();
}
// ==================== 项目管理API ====================
/**
* 创建筛选项目
*/
export async function createProject(
data: CreateProjectRequest
): Promise<ApiResponse<ScreeningProject>> {
return request('/projects', {
method: 'POST',
body: JSON.stringify(data),
});
}
/**
* 获取项目列表
*/
export async function listProjects(): Promise<ApiResponse<ScreeningProject[]>> {
return request('/projects');
}
/**
* 获取项目详情
*/
export async function getProject(
projectId: string
): Promise<ApiResponse<ScreeningProject>> {
return request(`/projects/${projectId}`);
}
/**
* 更新项目
*/
export async function updateProject(
projectId: string,
data: Partial<CreateProjectRequest>
): Promise<ApiResponse<ScreeningProject>> {
return request(`/projects/${projectId}`, {
method: 'PUT',
body: JSON.stringify(data),
});
}
/**
* 删除项目
*/
export async function deleteProject(
projectId: string
): Promise<ApiResponse<void>> {
return request(`/projects/${projectId}`, {
method: 'DELETE',
});
}
// ==================== 文献管理API ====================
/**
* 批量导入文献JSON格式
*/
export async function importLiteratures(data: {
projectId: string;
literatures: Array<Omit<Literature, 'id' | 'projectId' | 'createdAt'>>;
}): Promise<ApiResponse<{
importedCount: number;
}>> {
return request('/literatures/import', {
method: 'POST',
body: JSON.stringify(data),
});
}
/**
* 获取文献列表
*/
export async function listLiteratures(
projectId: string,
params?: {
page?: number;
pageSize?: number;
}
): Promise<ApiResponse<PaginatedResponse<Literature>>> {
const queryString = new URLSearchParams(
params as Record<string, string>
).toString();
return request(`/projects/${projectId}/literatures?${queryString}`);
}
/**
* 删除文献
*/
export async function deleteLiterature(
projectId: string,
literatureId: string
): Promise<ApiResponse<void>> {
return request(`/projects/${projectId}/literatures/${literatureId}`, {
method: 'DELETE',
});
}
// ==================== 筛选任务API ====================
/**
* 启动筛选任务
*/
export async function startScreening(
projectId: string
): Promise<ApiResponse<{ taskId: string }>> {
return request(`/projects/${projectId}/screening/start`, {
method: 'POST',
});
}
/**
* 查询任务进度
*/
export async function getTaskProgress(
taskId: string
): Promise<ApiResponse<ScreeningTask>> {
return request(`/screening/tasks/${taskId}/progress`);
}
// ==================== 筛选结果API ====================
/**
* 获取筛选结果列表
*/
export async function getScreeningResults(
projectId: string,
params?: {
conflictOnly?: boolean;
finalDecision?: 'include' | 'exclude' | 'pending';
page?: number;
pageSize?: number;
}
): Promise<ApiResponse<PaginatedResponse<ScreeningResult>>> {
const queryString = new URLSearchParams(
params as Record<string, string>
).toString();
return request(`/projects/${projectId}/screening/results?${queryString}`);
}
/**
* 更新单个筛选结果
*/
export async function updateScreeningResult(
resultId: string,
data: {
finalDecision?: 'include' | 'exclude' | 'pending';
reviewComment?: string;
}
): Promise<ApiResponse<ScreeningResult>> {
return request(`/screening/results/${resultId}`, {
method: 'PUT',
body: JSON.stringify(data),
});
}
/**
* 批量更新筛选结果
*/
export async function batchUpdateScreeningResults(data: {
resultIds: string[];
finalDecision: 'include' | 'exclude' | 'pending';
decisionMethod?: string;
reviewComment?: string;
}): Promise<ApiResponse<{ updated: number }>> {
return request('/screening/results/batch-update', {
method: 'POST',
body: JSON.stringify(data),
});
}
// ==================== 导出API ====================
/**
* 导出筛选结果为Excel
*/
export async function exportScreeningResults(
projectId: string,
params?: {
filter?: 'all' | 'included' | 'excluded' | 'pending';
}
): Promise<Blob> {
const queryString = new URLSearchParams(
params as Record<string, string>
).toString();
const response = await fetch(
`${API_BASE_URL}/projects/${projectId}/screening/results/export?${queryString}`,
{ headers: getAuthHeaders() }
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.blob();
}
// ==================== 统计API ====================
/**
* 获取项目统计信息
*/
export async function getProjectStatistics(
projectId: string
): Promise<ApiResponse<ProjectStatistics>> {
return request(`/projects/${projectId}/statistics`);
}
// ==================== Day 3 新增API ====================
/**
* 获取筛选任务进度(新)
* GET /projects/:projectId/screening-task
*/
export async function getScreeningTask(
projectId: string
): Promise<ApiResponse<ScreeningTask>> {
return request(`/projects/${projectId}/screening-task`);
}
/**
* 获取筛选结果列表(新,支持分页和筛选)
* GET /projects/:projectId/screening-results
*/
export async function getScreeningResultsList(
projectId: string,
params?: {
page?: number;
pageSize?: number;
filter?: 'all' | 'conflict' | 'included' | 'excluded' | 'pending' | 'reviewed';
}
): Promise<ApiResponse<{
items: ScreeningResult[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}>> {
const queryString = new URLSearchParams(
params as Record<string, string>
).toString();
return request(`/projects/${projectId}/screening-results?${queryString}`);
}
/**
* 获取单个筛选结果详情(新)
* GET /screening-results/:resultId
*/
export async function getScreeningResultDetail(
resultId: string
): Promise<ApiResponse<ScreeningResult>> {
return request(`/screening-results/${resultId}`);
}
/**
* 提交人工复核(新)
* POST /screening-results/:resultId/review
*/
export async function reviewScreeningResult(
resultId: string,
data: {
decision: 'include' | 'exclude';
note?: string;
}
): Promise<ApiResponse<ScreeningResult>> {
return request(`/screening-results/${resultId}/review`, {
method: 'POST',
body: JSON.stringify(data),
});
}
// ==================== 健康检查API ====================
/**
* 模块健康检查
*/
export async function healthCheck(): Promise<ApiResponse<{
status: string;
module: string;
}>> {
return request('/health');
}
// ==================== 全文复筛API (Day 5-8 新增) ====================
/**
* 创建全文复筛任务
*/
export async function createFulltextTask(data: {
projectId: string;
literatureIds: string[];
modelA?: string;
modelB?: string;
}): Promise<ApiResponse<{
taskId: string;
projectId: string;
status: string;
totalCount: number;
}>> {
return request('/fulltext-screening/tasks', {
method: 'POST',
body: JSON.stringify(data),
});
}
/**
* 获取全文复筛任务进度
*/
export async function getFulltextTaskProgress(
taskId: string
): Promise<ApiResponse<any>> {
return request(`/fulltext-screening/tasks/${taskId}`);
}
/**
* 获取全文复筛任务结果
*/
export async function getFulltextTaskResults(
taskId: string,
params?: {
filter?: 'all' | 'conflict' | 'pending' | 'reviewed';
page?: number;
pageSize?: number;
sortBy?: 'priority' | 'createdAt';
sortOrder?: 'asc' | 'desc';
}
): Promise<ApiResponse<any>> {
const queryString = new URLSearchParams(
params as Record<string, string>
).toString();
return request(`/fulltext-screening/tasks/${taskId}/results?${queryString}`);
}
/**
* 更新全文复筛人工决策
*/
export async function updateFulltextDecision(
resultId: string,
data: {
finalDecision: 'include' | 'exclude';
exclusionReason?: string;
reviewNotes?: string;
}
): Promise<ApiResponse<any>> {
return request(`/fulltext-screening/results/${resultId}/decision`, {
method: 'PUT',
body: JSON.stringify(data),
});
}
/**
* 导出全文复筛结果Excel
*/
export async function exportFulltextResults(
taskId: string
): Promise<Blob> {
const response = await fetch(
`${API_BASE_URL}/fulltext-screening/tasks/${taskId}/export`,
{ headers: getAuthHeaders() }
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.blob();
}
// ==================== 智能文献检索API (DeepSearch) ====================
/**
* 创建智能文献检索任务
*/
export async function createResearchTask(data: {
projectId: string;
query: string;
}): Promise<ApiResponse<{
id: string;
status: string;
}>> {
return request('/research/tasks', {
method: 'POST',
body: JSON.stringify(data),
});
}
/**
* 获取智能文献检索任务状态
*/
export async function getResearchTaskStatus(
taskId: string
): Promise<ApiResponse<{
taskId: string;
status: 'processing' | 'ready' | 'error';
progress: number;
query?: string;
resultCount?: number;
reasoningContent?: string;
literatures?: Array<{
pmid: string;
title: string;
authors: string;
journal: string;
year: number;
}>;
errorMessage?: string;
}>> {
return request(`/research/tasks/${taskId}/status`);
}
// ==================== Deep Research V2.0 API ====================
import type {
DataSourceConfig,
GenerateRequirementRequest,
GenerateRequirementResponse,
DeepResearchTask,
DeepResearchTaskSummary,
} from '../types/deepResearch';
/**
* 获取数据源配置列表
*/
export async function getDeepResearchDataSources(): Promise<ApiResponse<DataSourceConfig[]>> {
return request('/research/data-sources');
}
/**
* 需求扩写PICOS + MeSH
*/
export async function generateRequirement(
data: GenerateRequirementRequest
): Promise<ApiResponse<GenerateRequirementResponse>> {
return request('/research/generate-requirement', {
method: 'POST',
body: JSON.stringify(data),
});
}
/**
* 启动异步执行
*/
export async function executeDeepResearchTask(
taskId: string,
confirmedRequirement: string
): Promise<ApiResponse<void>> {
return request(`/research/tasks/${taskId}/execute`, {
method: 'PUT',
body: JSON.stringify({ confirmedRequirement }),
});
}
/**
* 获取 V2.0 任务历史列表(最近 100 条,不含 draft
*/
export async function listDeepResearchTasks(): Promise<ApiResponse<DeepResearchTaskSummary[]>> {
return request('/research/v2/tasks');
}
/**
* 删除 V2.0 检索任务
*/
export async function deleteDeepResearchTask(taskId: string): Promise<ApiResponse<void>> {
return request(`/research/tasks/${taskId}`, { method: 'DELETE' });
}
/**
* 获取 V2.0 任务详情(状态 + 日志 + 结果)
*/
export async function getDeepResearchTask(
taskId: string
): Promise<ApiResponse<DeepResearchTask>> {
return request(`/research/tasks/${taskId}`);
}
// ==================== 工具 3全文智能提取 API ====================
export async function getExtractionTemplates(): Promise<ApiResponse<any[]>> {
return request('/extraction/templates');
}
export async function getExtractionTemplate(templateId: string): Promise<ApiResponse<any>> {
return request(`/extraction/templates/${templateId}`);
}
export async function cloneExtractionTemplate(
projectId: string,
baseTemplateId: string
): Promise<ApiResponse<any>> {
return request('/extraction/templates/clone', {
method: 'POST',
body: JSON.stringify({ projectId, baseTemplateId }),
});
}
export async function getExtractionKnowledgeBases(): Promise<ApiResponse<any[]>> {
return request('/extraction/knowledge-bases');
}
export async function getExtractionDocuments(kbId: string): Promise<ApiResponse<any[]>> {
return request(`/extraction/knowledge-bases/${kbId}/documents`);
}
export async function createExtractionTask(params: {
projectId: string;
projectTemplateId: string;
pkbKnowledgeBaseId: string;
documentIds: string[];
idempotencyKey?: string;
}): Promise<ApiResponse<{ taskId: string }>> {
return request('/extraction/tasks', {
method: 'POST',
body: JSON.stringify(params),
});
}
export async function getExtractionTaskStatus(
taskId: string
): Promise<ApiResponse<{
taskId: string;
status: string;
totalCount: number;
completedCount: number;
errorCount: number;
extractingCount: number;
pendingCount: number;
percent: number;
}>> {
return request(`/extraction/tasks/${taskId}`);
}
export async function getExtractionTaskResults(
taskId: string
): Promise<ApiResponse<any[]>> {
return request(`/extraction/tasks/${taskId}/results`);
}
export async function getExtractionResultDetail(
resultId: string
): Promise<ApiResponse<any>> {
return request(`/extraction/results/${resultId}`);
}
export async function reviewExtractionResult(
resultId: string,
data: { reviewStatus: 'approved' | 'rejected' }
): Promise<ApiResponse<any>> {
return request(`/extraction/results/${resultId}/review`, {
method: 'PUT',
body: JSON.stringify(data),
});
}
export async function exportExtractionResults(
taskId: string
): Promise<Blob> {
const response = await fetch(
`${API_BASE_URL}/extraction/tasks/${taskId}/export`,
{ headers: getAuthHeaders() }
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.blob();
}
// ==================== 工具 4SR 图表生成器 API ====================
export async function getChartingPrismaData(
projectId: string
): Promise<ApiResponse<any>> {
return request(`/charting/prisma-data/${projectId}`);
}
export async function getChartingBaselineData(
projectId: string
): Promise<ApiResponse<any>> {
return request(`/charting/baseline-data/${projectId}`);
}
// ==================== 工具 5Meta 分析引擎 API ====================
export async function runMetaAnalysis(body: {
data: Record<string, unknown>[];
params: {
data_type: string;
model?: string;
effect_measure?: string;
};
}): Promise<any> {
return request('/meta-analysis/run', {
method: 'POST',
body: JSON.stringify(body),
});
}
export async function getMetaProjectData(
projectId: string
): Promise<{ rows: any[]; detectedType: string | null; count: number }> {
return request(`/meta-analysis/project-data/${projectId}`);
}
export async function getMetaHealthCheck(): Promise<{ rServiceAvailable: boolean }> {
return request('/meta-analysis/health');
}
// ==================== 统一导出API对象 ====================
/**
* ASL API统一导出对象
*/
export const aslApi = {
// 项目管理
createProject,
listProjects,
getProject,
updateProject,
deleteProject,
// 文献管理
importLiteratures,
listLiteratures,
deleteLiterature,
// 筛选任务
startScreening,
getTaskProgress,
getScreeningTask, // Day 3 新增
// 筛选结果
getScreeningResults,
getScreeningResultsList, // Day 3 新增(分页版本)
getScreeningResultDetail, // Day 3 新增
updateScreeningResult,
batchUpdateScreeningResults,
reviewScreeningResult, // Day 3 新增(人工复核)
// 导出
exportScreeningResults,
// 统计
getProjectStatistics,
// 全文复筛 (Day 5-8 新增)
createFulltextTask,
getFulltextTaskProgress,
getFulltextTaskResults,
updateFulltextDecision,
exportFulltextResults,
// 健康检查
healthCheck,
// 智能文献检索 (DeepSearch V1.x)
createResearchTask,
getResearchTaskStatus,
// Deep Research V2.0
getDeepResearchDataSources,
generateRequirement,
executeDeepResearchTask,
listDeepResearchTasks,
deleteDeepResearchTask,
getDeepResearchTask,
// 工具 3全文智能提取
getExtractionTemplates,
getExtractionTemplate,
cloneExtractionTemplate,
getExtractionKnowledgeBases,
getExtractionDocuments,
createExtractionTask,
getExtractionTaskStatus,
getExtractionTaskResults,
getExtractionResultDetail,
reviewExtractionResult,
exportExtractionResults,
// 工具 4SR 图表生成器
getChartingPrismaData,
getChartingBaselineData,
// 工具 5Meta 分析引擎
runMetaAnalysis,
getMetaProjectData,
getMetaHealthCheck,
};