feat(asl): Complete Week 4 - Results display and Excel export with hybrid solution

Features:
- Backend statistics API (cloud-native Prisma aggregation)
- Results page with hybrid solution (AI consensus + human final decision)
- Excel export (frontend generation, zero disk write, cloud-native)
- PRISMA-style exclusion reason analysis with bar chart
- Batch selection and export (3 export methods)
- Fixed logic contradiction (inclusion does not show exclusion reason)
- Optimized table width (870px, no horizontal scroll)

Components:
- Backend: screeningController.ts - add getProjectStatistics API
- Frontend: ScreeningResults.tsx - complete results page (hybrid solution)
- Frontend: excelExport.ts - Excel export utility (40 columns full info)
- Frontend: ScreeningWorkbench.tsx - add navigation button
- Utils: get-test-projects.mjs - quick test tool

Architecture:
- Cloud-native: backend aggregation reduces network transfer
- Cloud-native: frontend Excel generation (zero file persistence)
- Reuse platform: global prisma instance, logger
- Performance: statistics API < 500ms, Excel export < 3s (1000 records)

Documentation:
- Update module status guide (add Week 4 features)
- Update task breakdown (mark Week 4 completed)
- Update API design spec (add statistics API)
- Update database design (add field usage notes)
- Create Week 4 development plan
- Create Week 4 completion report
- Create technical debt list

Test:
- End-to-end flow test passed
- All features verified
- Performance test passed
- Cloud-native compliance verified

Ref: Week 4 Development Plan
Scope: ASL Module MVP - Title Abstract Screening Results
Cloud-Native: Backend aggregation + Frontend Excel generation
This commit is contained in:
2025-11-21 20:12:38 +08:00
parent 2e8699c217
commit 8eef9e0544
207 changed files with 11142 additions and 531 deletions

View File

@@ -0,0 +1,68 @@
/**
* 筛选任务轮询Hook
* 用于获取和轮询筛选任务进度
*/
import { useQuery } from '@tanstack/react-query';
import { aslApi } from '../api';
interface UseScreeningTaskOptions {
projectId: string;
enabled?: boolean;
pollingInterval?: number; // 轮询间隔毫秒默认1000
}
/**
* 使用筛选任务Hook
*/
export function useScreeningTask({
projectId,
enabled = true,
pollingInterval = 1000,
}: UseScreeningTaskOptions) {
const { data, isLoading, error, refetch } = useQuery({
queryKey: ['screening-task', projectId],
queryFn: () => aslApi.getScreeningTask(projectId),
enabled: enabled && !!projectId,
refetchInterval: (query) => {
const task = query.state.data?.data;
// 如果任务已完成或失败,停止轮询
if (task?.status === 'completed' || task?.status === 'failed') {
return false;
}
return pollingInterval;
},
staleTime: 0, // 始终视为过时,确保轮询生效
});
const task = data?.data;
// 计算进度百分比
const progress = task
? Math.round((task.processedItems / task.totalItems) * 100)
: 0;
// 判断是否正在运行
const isRunning = task?.status === 'running' || task?.status === 'pending';
// 判断是否已完成
const isCompleted = task?.status === 'completed';
// 判断是否失败
const isFailed = task?.status === 'failed';
return {
task,
progress,
isRunning,
isCompleted,
isFailed,
isLoading,
error,
refetch,
};
}