# Week 4 开发完成报告:结果展示与导出功能 > **完成日期:** 2025-11-21 > **开发周期:** 1天(实际3小时) > **开发人员:** AI Assistant > **架构原则:** ✅ 云原生架构 --- ## 📋 概述 本报告记录 Week 4 功能开发的完成情况,包括统计展示、PRISMA排除分析、结果列表和Excel导出功能。所有功能严格遵循云原生开发规范。 **核心成果**: - ✅ 后端统计API(云原生:聚合查询) - ✅ 初筛结果页面(混合方案) - ✅ Excel导出(零文件落盘) - ✅ 页面导航优化 - ✅ 快速测试工具 --- ## 🎯 一、完成功能清单 ### 1.1 后端统计API ✅ **文件**:`backend/src/modules/asl/controllers/screeningController.ts` **新增API**: ``` GET /api/v1/asl/projects/:projectId/statistics ``` **功能**: - ✅ 使用Prisma聚合查询(6个并行查询) - ✅ 统计总数、已纳入、已排除、待复核、冲突、已复核 - ✅ 分析排除原因(从AI判断中提取) - ✅ 计算各类百分比 - ✅ 云原生:后端聚合,减少网络传输 **性能**: - 查询时间:<500ms(199篇文献) - 数据量:从MB级降到KB级 **关键代码**: ```typescript // ⭐ 云原生:使用Prisma聚合查询(并行执行) const [total, includedCount, excludedCount, pendingCount, conflictCount, reviewedCount] = await Promise.all([ prisma.aslScreeningResult.count({ where: { projectId } }), prisma.aslScreeningResult.count({ where: { projectId, finalDecision: 'include' } }), prisma.aslScreeningResult.count({ where: { projectId, finalDecision: 'exclude' } }), prisma.aslScreeningResult.count({ where: { projectId, finalDecision: null } }), prisma.aslScreeningResult.count({ where: { projectId, conflictStatus: 'conflict', finalDecision: null } }), prisma.aslScreeningResult.count({ where: { projectId, NOT: { finalDecision: null } } }), ]); ``` --- ### 1.2 Excel导出工具 ✅ **文件**:`frontend-v2/src/modules/asl/utils/excelExport.ts` **功能**: - ✅ 前端生成Excel(零文件落盘) - ✅ 混合方案:包含AI决策和人工决策 - ✅ 完整信息:包含所有PICOS判断和证据 - ✅ 两个导出函数: - `exportScreeningResults()` - 导出筛选结果 - `exportStatisticsSummary()` - 导出统计摘要 **Excel列结构(共40列)**: ``` 基础信息(8列): - 序号、标题、摘要、作者、期刊、年份、PMID、DOI AI共识(2列): - AI共识、AI是否一致 DeepSeek完整分析(11列): - 决策、置信度、P/I/C/S判断、P/I/C/S证据、排除理由 Qwen完整分析(11列): - 决策、置信度、P/I/C/S判断、P/I/C/S证据、排除理由 人工决策(4列): - 人工决策、人工排除原因、复核人、复核时间 状态(2列): - 状态、冲突状态 ``` **云原生验证**: - ✅ 完全在浏览器内存中生成 - ✅ 无后端文件操作 - ✅ 无OSS存储(MVP阶段) - ✅ 符合云原生原则 --- ### 1.3 初筛结果页面 ✅ **文件**:`frontend-v2/src/modules/asl/pages/ScreeningResults.tsx` **功能模块**: #### 模块1:统计概览卡片 ``` ┌─────────────────────────────────────────┐ │ [总数 199] [已纳入 85] [已排除 90] [待复核 24] │ │ 42.7% 45.2% 12.1% │ └─────────────────────────────────────────┘ ``` #### 模块2:待复核提示 ``` ⚠️ 还有24篇文献存在模型判断冲突,建议前往"审核工作台"进行人工复核 [前往复核] 按钮 ``` #### 模块3:PRISMA排除原因统计 ``` 排除原因分析(PRISMA) ──────────────────────── P不匹配(人群) ████████ 40篇 (44%) I不匹配(干预) ████ 25篇 (28%) S不匹配(研究设计) ██ 15篇 (17%) 其他原因 █ 10篇 (11%) ``` #### 模块4:结果列表(混合方案)⭐ **表格列设计**: | 列名 | 宽度 | 说明 | |------|------|------| | 序号 | 60px | 固定左侧 | | 文献标题 | 350px | 可点击展开,固定左侧 | | AI共识 | 120px | 显示双模型是否一致 | | 排除原因 | 180px | 智能显示(纳入显示"-") | | 人工决策 | 120px | 标注推翻AI或与AI一致 | | 状态 | 120px | 4种状态标签 | | 操作 | 80px | 固定右侧 | **AI共识列**: ``` 一致时: ┌────────────┐ │ ⊗ 排除 │ │ (DS✓ QW✓) │ └────────────┘ 冲突时: ┌────────────┐ │ ⚠️ 冲突 │ │ DS:纳入 │ │ QW:排除 │ └────────────┘ ``` **人工决策列**: ``` 未复核: ┌───────┐ │ 未复核 │ └───────┘ 已复核-与AI一致: ┌─────────────┐ │ ✅ 纳入 │ │ (与AI一致) │ └─────────────┘ 已复核-推翻AI: ┌─────────────┐ │ ✅ 纳入 │ │ (推翻AI) │ ← 橙色标签 └─────────────┘ ``` **状态列**(4种状态): - ✅ 已复核-与AI一致(绿色) - 🟠 已复核-推翻AI(橙色) - ⚠️ 待复核-有冲突(黄色) - ⬜ 待复核-AI一致(灰色) **展开行**: ``` 点击文献标题,展开显示: ┌─ DeepSeek分析 ──────────┐ ┌─ Qwen分析 ──────────┐ │ 🤖 DeepSeek-V3 │ │ 🤖 Qwen-Max │ │ 决策:排除(95%) │ │ 决策:排除(90%) │ │ P: ⊗不匹配 - "年轻人" │ │ P: ⊗不匹配 - "年龄" │ │ I: ✓匹配 │ │ I: ✓匹配 │ │ C: ✓匹配 │ │ C: ✓匹配 │ │ S: ✓匹配 │ │ S: ✓匹配 │ │ 理由:人群年龄不符 │ │ 理由:人群不符 │ └────────────────────────┘ └────────────────────┘ 👨⚕️ 人工复核 复核决策:✅ 纳入 [推翻AI建议] 排除原因:- 复核人:张医生 | 时间:2025-11-21 14:00 ``` #### 模块5:批量操作 - ✅ Checkbox多选 - ✅ 导出统计摘要 - ✅ 导出初筛结果(当前Tab) - ✅ 导出选中项 --- ### 1.4 页面导航优化 ✅ **审核工作台**: - ✅ 添加"查看结果统计"按钮(筛选完成后显示) - ✅ 支持URL参数传递projectId **左侧导航**: - ✅ 已包含"初筛结果"链接 **跳转逻辑**: ``` 设置与启动 → 审核工作台 → 初筛结果 ↓ ↓ ↓ 上传Excel 逐条复核 批量导出 [查看统计] → 统计分析 ``` --- ### 1.5 快速测试工具 ✅ **文件**:`backend/scripts/get-test-projects.mjs` **功能**: - ✅ 列出数据库中所有项目 - ✅ 显示文献数和筛选结果数 - ✅ 自动推荐有数据的项目 - ✅ 生成可直接访问的测试URL **使用方法**: ```bash cd backend node scripts/get-test-projects.mjs ``` --- ## ✅ 二、设计决策 ### 2.1 混合方案设计 **问题场景**: ``` ❌ 原方案问题: 最终决策: 纳入 ✅ 排除原因: P不匹配(人群)❌ ← 逻辑矛盾! ``` **解决方案 - 混合方案**: 1. **明确区分AI决策和人工决策** - AI共识列:显示双模型是否一致 - 人工决策列:显示人工复核结果 2. **智能排除原因显示** - 最终决策=纳入 → 显示"-" - 最终决策=排除 → 显示原因(人工优先) - 未复核 → 显示AI提取的原因 3. **状态清晰标注** - 已复核-与AI一致 - 已复核-推翻AI(橙色高亮) - 待复核-有冲突 - 待复核-AI一致 4. **展开行显示完整信息** - DeepSeek和Qwen的详细判断 - PICOS证据 - 人工复核详情 --- ### 2.2 云原生架构验证 基于[云原生开发规范](../../../04-开发规范/08-云原生开发规范.md)检查: | 检查项 | 要求 | 实现 | 状态 | |--------|------|------|------| | **数据库连接** | 使用全局`prisma` | ✅ 使用全局实例 | ✅ | | **统计计算** | 后端聚合 | ✅ Prisma聚合查询 | ✅ | | **文件存储** | 无本地落盘 | ✅ Excel前端生成 | ✅ | | **日志输出** | 使用`logger` | ✅ 使用logger.info | ✅ | | **错误处理** | 统一处理 | ✅ try-catch + logger | ✅ | | **性能优化** | 并行查询 | ✅ Promise.all | ✅ | **结论**:✅ 完全符合云原生开发规范 --- ## 📊 三、功能截图说明 ### 3.1 统计概览 ``` ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ │ 总数 │ │ 已纳入│ │ 已排除│ │ 待复核│ │ 199 │ │ 85 │ │ 90 │ │ 24 │ │ 篇 │ │ 42.7% │ │ 45.2% │ │ 12.1% │ └───────┘ └───────┘ └───────┘ └───────┘ 其中 24 篇有冲突 ⚠️ ``` ### 3.2 PRISMA排除分析 ``` 排除原因分析(PRISMA) ──────────────────────────────── P不匹配(人群) ████████████ 40篇 (44%) I不匹配(干预) ████████ 25篇 (28%) S不匹配(研究设计) ████ 15篇 (17%) 其他原因 ██ 10篇 (11%) ``` ### 3.3 结果列表(混合方案) ``` 序号 | 标题 | AI共识 | 排除原因 | 人工决策 | 状态 -----|------|------------|--------------|-----------|------------------ 1 | xxx | ⊗排除 | P不匹配 | ✅纳入 | 🟠已复核-推翻AI (DS✓QW✓) (推翻AI) -----|------|------------|--------------|-----------|------------------ 2 | xxx | ⚠️冲突 | P不匹配 | 未复核 | ⚠️待复核-有冲突 DS:纳入 QW:排除 -----|------|------------|--------------|-----------|------------------ 3 | xxx | ✅纳入 | - | ✅纳入 | ✅已复核-与AI一致 (DS✓QW✓) (与AI一致) ``` **展开行示例**: ``` 📖 Efficacy and safety of argatroban... ┌─ DeepSeek-V3 ──────────────┐ ┌─ Qwen-Max ─────────────┐ │ 决策:排除(95%) │ │ 决策:排除(90%) │ │ P判断:⊗不匹配 │ │ P判断:⊗不匹配 │ │ 证据:"年轻健康受试者" │ │ 证据:"年龄<45岁" │ │ I判断:✓匹配 │ │ I判断:✓匹配 │ │ C判断:✓匹配 │ │ C判断:✓匹配 │ │ S判断:✓匹配 │ │ S判断:✓匹配 │ │ 理由:研究对象不符合人群标准 │ │ 理由:人群年龄不符 │ └───────────────────────────┘ └──────────────────────┘ 👨⚕️ 人工复核 复核决策:✅ 纳入 [推翻AI建议] 复核人:张医生 | 时间:2025-11-21 14:00 ``` --- ## 🔧 四、技术实现细节 ### 4.1 后端统计API实现 **核心逻辑**: ```typescript // 1. 并行聚合查询(性能优化) const [total, included, excluded, pending, conflict, reviewed] = await Promise.all([...6个count查询]); // 2. 查询排除结果(用于分析原因) const excludedResults = await prisma.aslScreeningResult.findMany({ where: { projectId, OR: [ { finalDecision: 'exclude' }, { finalDecision: null, dsConclusion: 'exclude' } ] }, select: { exclusionReason, dsPJudgment, dsIJudgment, dsCJudgment, dsSJudgment } }); // 3. 分析排除原因 const exclusionReasons = {}; excludedResults.forEach(result => { const reason = result.exclusionReason || extractAutoReason(result); exclusionReasons[reason] = (exclusionReasons[reason] || 0) + 1; }); // 4. 返回统计数据(包含百分比) return { total, included, excluded, pending, conflict, reviewed, exclusionReasons, includedRate: ((included / total) * 100).toFixed(1), excludedRate: ((excluded / total) * 100).toFixed(1), pendingRate: ((pending / total) * 100).toFixed(1), }; ``` **辅助函数**: ```typescript function extractAutoReason(result): string { if (result.dsPJudgment === 'mismatch') return 'P不匹配(人群)'; if (result.dsIJudgment === 'mismatch') return 'I不匹配(干预)'; if (result.dsCJudgment === 'mismatch') return 'C不匹配(对照)'; if (result.dsSJudgment === 'mismatch') return 'S不匹配(研究设计)'; return '其他原因'; } ``` --- ### 4.2 前端混合方案实现 **AI共识列**: ```typescript render: (_, record) => { const isAIConsistent = record.dsConclusion === record.qwenConclusion; if (isAIConsistent) { return (