docs: complete documentation system (250+ files)

- System architecture and design documentation
- Business module docs (ASL/AIA/PKB/RVW/DC/SSA/ST)
- ASL module complete design (quality assurance, tech selection)
- Platform layer and common capabilities docs
- Development standards and API specifications
- Deployment and operations guides
- Project management and milestone tracking
- Architecture implementation reports
- Documentation templates and guides
This commit is contained in:
2025-11-16 15:43:55 +08:00
parent 0fe6821a89
commit e52020409c
173 changed files with 46227 additions and 11964 deletions

View File

@@ -0,0 +1,99 @@
# AI智能文献模块 - 系统架构设计
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
> **最后更新:** 2025-10-29
---
## 📋 文档说明
本文档描述AI智能文献模块的系统架构设计包括模块架构、技术架构、数据架构等。
---
## 🏗️ 模块架构
### 核心功能模块
```
AI智能文献模块
├── 1. 研究方案生成
├── 2. 智能文献检索
├── 3. 标题摘要初筛 ⭐ 当前优先开发
├── 4. 全文复筛
├── 5. 全文解析与数据提取
└── 6. 数据综合分析与报告生成
```
### 模块间关系
```
研究方案生成 → 指导后续所有模块
智能文献检索 → 生成文献列表
标题摘要初筛 → 初步筛选结果
全文复筛 → 最终纳入列表
全文解析与数据提取 → 结构化数据
数据综合分析与报告生成 → 最终报告
```
---
## 🔧 技术架构
### 前端架构
- **框架**: React + TypeScript
- **UI库**: Ant Design + Tailwind CSS
- **状态管理**: Zustand / React Query
- **路由**: React Router
### 后端架构
- **框架**: Node.js + TypeScript + Fastify
- **数据库**: PostgreSQL + Prisma ORM
- **文件存储**: 本地存储 / 对象存储
- **AI集成**: DeepSeek-V3、Qwen3-72B
### AI服务架构
- **双模型系统**: DS模型 + Q3模型
- **流式输出**: Server-Sent Events (SSE)
- **批处理**: 支持批量任务处理
---
## 📊 数据架构
### 核心数据实体
- **研究方案** (Research Protocol)
- **文献项目** (Literature Project)
- **文献条目** (Literature Item)
- **筛选结果** (Screening Result)
- **提取数据** (Extracted Data)
---
## ⏳ 待完善内容
本文档内容正在完善中,后续将补充:
- 详细的架构图
- 模块间通信机制
- 数据流设计
- 安全性设计
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,24 @@
# 模块关联关系设计
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
---
## ⏳ 待完善
本文档内容待规划完善,目前仅作为占位文档。
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,24 @@
# 数据流设计
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
---
## ⏳ 待完善
本文档内容待规划完善,目前仅作为占位文档。
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,24 @@
# 技术选型说明
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
---
## ⏳ 待完善
本文档内容待规划完善,目前仅作为占位文档。
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,61 @@
# 需求总览
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
---
## 📋 文档说明
本文档提供AI智能文献模块的需求总览详细需求请参考PRD文档和各子模块需求文档。
---
## 🎯 核心功能模块
### 1. 研究方案生成
**功能定位**: 定义研究方案、PICO、变量清单
**状态**: 待开发
### 2. 智能文献检索
**功能定位**: PubMed检索、语义排序
**状态**: 待开发
### 3. 标题摘要初筛 ⭐
**功能定位**: 基于标题和摘要的快速筛选
**状态**: 当前优先开发
**详细需求**: 见 [标题摘要初筛需求详述](./02-标题摘要初筛需求详述.md)
### 4. 全文复筛
**功能定位**: 基于全文内容的二次筛选
**状态**: 待开发
### 5. 全文解析与数据提取
**功能定位**: 结构化数据提取
**状态**: 待开发
### 6. 数据综合分析与报告生成
**功能定位**: 证据图谱、Meta分析、报告生成
**状态**: 待开发
---
## 📚 相关文档
- [PRD文档 - 产品概览](../../00-项目概述/AI智能文献PRD1-产品概览.md)
- [PRD文档 - 初筛与复筛](../../00-项目概述/AI智能文献PRD2-初筛与复筛.md)
- [PRD文档 - 提取与分析](../../00-项目概述/AI智能文献PRD3-提取与分析模块.md)
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,199 @@
# 标题摘要初筛需求详述
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
> **最后更新:** 2025-10-29
---
## 📋 文档说明
本文档详细描述标题摘要初筛模块的功能需求基于PRD文档进行扩展和细化。
---
## 🎯 功能概述
**标题摘要初筛**是筛选流程的第一阶段,仅基于文献的标题和摘要进行快速筛选。
### 核心子视图
1. **设置与启动视图** - 配置筛选标准、导入文献、启动筛选
2. **表格化审核工作台** - 双模型PICS判断、冲突管理、决策制定
3. **初筛结果视图** - 统计概览、结果列表、导出功能
---
## 📝 功能需求详述
### 需求1: 设置与启动视图
#### FR-TSCR-01.1: 标准参考展示
- **需求**: 页面顶部展示从"研究方案"继承的PICO和入排标准只读
- **优先级**: 高
- **验收标准**:
- 清晰展示PICO框架内容
- 展示纳入标准和排除标准
- 提供"调整本次筛选标准"入口
#### FR-TSCR-01.2: 临时调整筛选标准
- **需求**: 允许用户进行仅对本次筛选生效的临时修改
- **优先级**: 高
- **验收标准**:
- 可修改PICO和入排标准
- 修改不影响原始研究方案
- 在审核工作台有明确提示
#### FR-TSCR-01.3: 文献导入
- **需求**: 仅支持"上传Excel文件"方式,提供模板下载
- **优先级**: 高
- **验收标准**:
- 支持Excel文件上传
- 提供标准模板下载
- 文件格式验证
- 导入结果预览
#### FR-TSCR-01.4: 启动筛选
- **需求**: 导入文献后,激活"开始AI初筛"按钮
- **优先级**: 高
- **验收标准**:
- 按钮状态控制
- 点击后启动AI筛选任务
- 显示任务进度
---
### 需求2: 表格化审核工作台视图 ⭐⭐⭐
#### FR-TSCR-02.1: 表格结构设计
- **需求**: 高信息密度的表格化布局
- **优先级**: 极高
- **验收标准**:
- 两行表头结构(模型区域 + P/I/C/S列
- 主行包含所有关键信息
- 展开行显示证据短语
- 支持展开/收起
#### FR-TSCR-02.2: 双模型PICS判断
- **需求**: DS和Q3模型分别对P/I/C/S四个维度进行判断
- **优先级**: 极高
- **验收标准**:
- 每个维度显示判断结果(✓/✗/?
- 展示双模型的判断结果
- 支持查看详细判断理由
#### FR-TSCR-02.3: 证据短语提取
- **需求**: 展示AI提取的关键证据短语
- **优先级**: 极高
- **验收标准**:
- 展开行显示DS和Q3的证据短语
- 短语清晰标注对应的P/I/C/S维度
- 支持点击查看原文
#### FR-TSCR-02.4: 原文审查模态框
- **需求**: 点击判断图标弹出双视图审查模态框
- **优先级**: 极高
- **验收标准**:
- 左侧显示摘要
- 右侧显示双模型判断详情
- 原文自动高亮
- 支持来源引用查看
---
### 需求3: 冲突管理
#### FR-TSCR-03.1: 冲突识别
- **需求**: 自动识别两模型判断不一致的文献
- **优先级**: 高
- **验收标准**:
- 自动检测冲突项
- 视觉高亮(如行背景色)
- 支持冲突项筛选
#### FR-TSCR-03.2: 冲突处理
- **需求**: 允许用户对冲突项进行人工仲裁
- **优先级**: 高
- **验收标准**:
- 支持查看冲突详情
- 支持选择最终决策
- 记录决策原因
---
### 需求4: 批量操作
#### FR-TSCR-04.1: 批量决策
- **需求**: 支持对非冲突项进行批量决策
- **优先级**: 中
- **验收标准**:
- 支持多选文献
- 批量设置决策结果
- 批量设置决策原因
---
### 需求5: 初筛结果视图
#### FR-TSCR-05.1: 统计概览
- **需求**: 展示筛选统计信息
- **优先级**: 高
- **验收标准**:
- 卡片形式展示统计数字
- 包括:总计、纳入、排除数量
#### FR-TSCR-05.2: PRISMA式排除总结
- **需求**: 展示主要排除原因及数量
- **优先级**: 高
- **验收标准**:
- 列表展示排除原因
- 显示对应文献数量
- 支持点击查看详情
#### FR-TSCR-05.3: 结果列表
- **需求**: Tab页切换查看纳入和排除的文献列表
- **优先级**: 高
- **验收标准**:
- 支持Tab切换
- 表格展示文献信息
- 支持筛选和搜索
#### FR-TSCR-05.4: 导出功能
- **需求**: 将结果列表导出为Excel
- **优先级**: 高
- **验收标准**:
- 支持导出为Excel格式
- 包含完整的文献信息
- 格式规范清晰
---
## 📊 非功能性需求
- **性能**: 单次筛选支持1000+文献
- **响应时间**: 单个文献判断 < 5秒
- **并发**: 支持多用户同时筛选
- **可靠性**: 支持任务中断和恢复
---
## 📚 相关文档
- [PRD文档 - 初筛与复筛](../../00-项目概述/AI智能文献PRD2-初筛与复筛.md)
- [原型图](../../01-设计文档/AI智能文献-标题摘要初筛原型.html)
- [数据库设计](../02-技术设计/01-数据库设计.md)
- [API设计规范](../02-技术设计/02-API设计规范.md)
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,26 @@
# 全文复筛需求详述
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
---
## ⏳ 待完善
本文档内容待规划完善,目前仅作为占位文档。
详细需求请参考PRD文档`../00-项目概述/AI智能文献PRD2-初筛与复筛.md`
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,24 @@
# 用户故事和验收标准
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
---
## ⏳ 待完善
本文档内容待规划完善,目前仅作为占位文档。
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,24 @@
# 非功能性需求
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
---
## ⏳ 待完善
本文档内容待规划完善,目前仅作为占位文档。
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,89 @@
# **PRD V4.0 \- 第一部分:产品概述与核心流程**
版本: 4.0
日期: 2025-10-21
## **1\. 引言**
### **1.1. 项目背景**
当前医学科研人员在进行系统性文献回顾、Meta分析或新药研发等工作时面临着海量文献带来的巨大挑战。传统的文献处理流程包括检索、筛选、数据提取和分析是高度劳动密集型的工作不仅耗时巨大而且容易引入人为偏见和错误直接影响科研结论的质量和时效性。本项目旨在开发一个基于人工智能的医学文献应用系统通过自动化和智能化的方式赋能科研人员将他们从繁琐的重复性工作中解放出来聚焦于更高层次的科学洞见和创新。
### **1.2. 产品目标**
* **效率提升:** 将传统需要数月完成的系统性文献回顾流程,缩短至数天或数周。
* **质量保障:** 通过先进的AI模型和校验机制提升文献筛选和数据提取的准确性与一致性降低人为偏见。
* **深度洞察:** 在精准提取的数据基础上,提供多维度的综合分析和可视化工具,帮助用户快速生成高质量的证据图谱、综合评价报告等应用。
* **全程可溯:** 确保AI处理的每一个环节筛选、提取、分析都有据可查所有结论均可追溯至原文满足科研的严谨性要求。
### **1.3. 目标用户**
* 医学科研人员(高校、研究所)
* 临床医生与医学专家
* 制药公司及CRO合同研究组织的研发、医学和市场准入部门人员
* 循证医学与卫生技术评估HTA机构的研究员
* 高等院校的医学/药学专业学生及教员
### **1.4. 名词解释**
* **PRD:** Product Requirement Document产品需求文档。
* **PICO(S):** 系统评价中用于确定文献纳入排除标准的研究要素框架,包括:
* **P (Patient/Population):** 研究对象
* **I (Intervention):** 干预措施
* **C (Comparison):** 对照措施
* **O (Outcome):** 结局指标
* **S (Study Design):** 研究设计
* **证据图谱 (Evidence Mapping):** 一种系统性地识别、描述和分类特定健康领域现有研究证据的方法,通常以可视化的方式呈现。
* **Meta分析:** 对相同研究问题的多个独立研究结果进行定量合并分析的统计方法。
## **2\. 产品概述**
### **2.1. 产品定位**
一个**遵循循证医学标准**、专业、高效、可信赖的AI医学科研**工具箱**。它以**研究方案**为核心,驱动从文献检索到深度分析报告生成的全流程解决方案。
### **2.2. 核心价值**
* **自动化:** 自动执行文献筛选、数据提取等繁重任务。
* **精准化:** 深度理解医学语言和PICO保障筛选和提取的准确性。
* **结构化:** 将非结构化的文献全文转化为结构化的数据资产。
* **智能化:** 基于结构化数据,提供多样的下游分析应用和报告。
### **2.3. 功能总览图 (V4.0 更新)**
graph TD
A\[用户管理\] \--\> B(项目管理)
B \--\> C\[1. 研究方案生成\]
C \-- "指导后续所有流程" \--\> D
subgraph D\[研究执行流程\]
D1\[2. 智能文献检索\]
D2\[3. 标题摘要初筛\]
D3\[4. 全文复筛\]
D4\[5. 全文解析与数据提取\]
D5\[6. 数据综合分析与报告生成\]
end
D1 \--\> D2
D2 \--\> D3
D3 \--\> D4
D4 \--\> D5
D5 \--\> E1\[证据图谱\]
D5 \--\> E2\[Meta分析数据集\]
D5 \--\> E3\[药物综合评价报告\]
subgraph "产出应用"
E1
E2
E3
end
### **2.4. 设计哲学:集成化与模块化**
本产品的核心设计哲学是“**集成化与模块化并存**”。
* **集成化:** 六大核心功能模块(研究方案、智能检索、标题摘要初筛、全文复筛、全文提取、综合分析)无缝衔接,为用户提供一个从课题构想到报告产出的端到端、流畅连贯的系统性文献研究工作流。
* **模块化:** 每个核心功能模块都可以作为一个独立的工具来解决特定的科研痛点。用户无需遵循完整的流程可以根据自己的实际需求从任意环节如仅使用检索、或仅上传Excel进行筛选切入使用相应的功能模块。
这种设计旨在最大化产品的灵活性和适用性,服务于不同需求层次的广大科研用户。

View File

@@ -0,0 +1,59 @@
# **PRD V4.0 \- 第二部分:研究方案与筛选模块**
版本: 4.0
日期: 2025-10-21
## **3\. 功能需求详述 (续)**
### **3.1. 研究方案生成模块 (V4.0 新增)**
**本模块是所有研究工作的起点和“唯一事实来源”其配置将指导后续所有模块的AI行为和界面展示。**
| 需求ID | 需求描述 | 优先级 |
| :---- | :---- | :---- |
| **FR-PROT-01** | **结构化方案定义:** 提供一个向导式界面引导用户或由AI辅助定义一个完整的循证研究方案。 | **极高** |
| **FR-PROT-02** | **PICO 与纳入/排除标准定义:** 在方案中提供结构化表单让用户清晰定义研究的PICO以及详细的文本格式的纳入和排除标准。这将是后续筛选模块的“宪法”。 | **极高** |
| **FR-PROT-03** | **数据提取变量清单配置:** 在方案中,提供一个变量清单管理功能。 a) **预设清单:** 根据用户选择的研究目的如证据图谱、Meta分析提供通用的、标准的变量提取清单。 b) **自定义清单:** 允许用户在预设清单的基础上,自定义(增、删、改)需要提取的数据变量。此清单将作为“数据提取模块”的唯一依据。 | **极高** |
| **FR-PROT-04** | **方案锁定与版本控制:** 研究方案一旦确立并开始用于筛选后,应被锁定或进行版本控制。任何后续的修改都应被记录,以保证科研过程的透明性和严谨性。 | 高 |
### **3.2. 智能文献检索模块**
(原3.2模块,序号调整)
| 需求ID | 需求描述 | 优先级 |
| :---- | :---- | :---- |
| **FR-SEARCH-01** | **关键词/主题词检索:** 提供输入框支持用户使用标准关键词、MeSH主题词及布尔运算符AND, OR, NOT构建PubMed查询。 | 高 |
| **FR-SEARCH-02** | **交互式检索策略与语义排序:** 提供一个与AI对话的界面通过多轮交流引导用户明确其核心研究问题。AI根据对话内容帮助用户构建和优化专业的检索策略。在执行检索后系统对返回的文献列表进行二次语义相关度分析并按相关性从高到低进行排序将最匹配的文献呈现在最前面。 | 高 |
| **FR-SEARCH-03** | **PubMed API集成:** 系统通过API实时连接PubMed数据库执行检索并获取最新文献列表。 | 高 |
| **FR-SEARCH-04** | **结果展示与导入:** 以列表形式清晰展示检索结果,包括标题、作者、期刊、摘要等。用户可勾选相关文献,一键**导入到项目中进行下一步筛选**。 | 高 |
### **3.3. 标题摘要初筛模块 (V4.0 重构 \- V7原型更新)**
**本模块是筛选流程的第一阶段,仅基于文献的标题和摘要进行快速筛选。** 包含设置与启动、审核工作台、初筛结果三个子视图。
| 需求ID | 需求描述 | 优先级 | | |
| :---- | :---- | :---- | :---- | :---- |
| **FR-TSCR-01** | **设置与启动视图:** a) **标准参考:** 页面顶部展示从“研究方案”继承的PICO和入排标准只读并提供“ | $$调整本次筛选标准$$ | ”入口,允许用户进行**仅对本次筛选生效**的临时修改。 b) **文献导入:** 页面下方提供文献导入功能,\*\*仅支持“上传Excel文件”\*\*方式,并提供模板下载。 c) **启动筛选:** 导入文献后激活“开始AI初筛”按钮。 | 高 |
| **FR-TSCR-02** | **表格化审核工作台视图:** 核心审核界面采用高信息密度的**表格化布局**。 a) **标准参考:** 工作台顶部提供**可折叠**的“研究方案概览”面板展示PICO与入排标准。若应用了临时调整需有提示。 b) **表格结构:** 每一行代表一篇文献,并支持展开显示第二行。 **表头**采用两行结构第一行合并单元格标示模型区域第二行在各模型下细分P/I/C/S/结论列。 **主行 (Row 1):** 包含展开/收起按钮, 文献ID (PMID), 研究ID (作者姓+年份), 文献来源 (期刊/DOI链接), DS-P(✓/✗/?), DS-I, DS-C, DS-S, DS-结论(纳入/排除), Q3-P, Q3-I, Q3-C, Q3-S, Q3-结论, 冲突状态, 最终决策(下拉选择框)。 **展开行 (Row 2):** 默认隐藏点击按钮后展开。仅包含两列分别显示DS 证据: (P/I/C/S对应的关键短语) 和 Q3 证据: (P/I/C/S对应的关键短语)。 c) **交互:** 点击主行中的P/I/C/S判断图标(✓/✗/?)**弹出“双视图原文审查模态框”**,左侧显示摘要,右侧显示双模型对该维度的详细判断、理由和来源引用,原文自动高亮。 | **极高** | | |
| **FR-TSCR-03** | **双模型PICS逐项判断:** 双模型DS/Q3需基于筛选标准对每篇文献的P/I/C/S四个维度**分别进行判断** (✓/✗/?),提取**关键证据短语**,并给出一个总览的“纳入/排除”建议。 | **极高** | | |
| **FR-TSCR-04** | **分级与冲突管理:** 系统需自动识别并高亮(如行背景色)两模型判断不一致的文献(冲突项),允许用户优先筛选处理。 | 高 | | |
| **FR-TSCR-05** | **批量操作:** 支持对非冲突项进行批量决策。 | 中 | | |
| **FR-TSCR-06** | **初筛结果视图 (V7 新增):** 提供独立的页面展示初筛的最终结果。 a) **统计概览:** 以卡片形式展示“总计筛选文献数”、“初步纳入文献数”、“排除文献数”。 b) **PRISMA式排除总结:** 以列表形式展示主要的排除原因及其对应的文献数量非RCT n=X, 非目标人群 n=Y ...)。 c) **结果列表:** 提供Tab页切换查看“初步纳入”和“排除”的文献列表。表格需包含列文献ID, 研究ID, 文献来源, 标题, 摘要(可展开), P(内容短语), I, C, S, 结论(纳入/排除), 排除理由。 d) **导出功能:** 提供将结果列表导出为Excel的功能。 | 高 | | |
### **3.4. 全文复筛模块 (V4.0 新增 \- V2原型更新)**
**本模块是筛选流程的第二阶段,对第一阶段初步纳入的文献,基于全文内容进行更严格的二次筛选。** 包含设置与启动、审核工作台、复筛结果三个子视图。
| 需求ID | 需求描述 | 优先级 |
| :---- | :---- | :---- |
| **FR-FSCR-01** | **设置与启动视图:** a) **标准参考:** 与标题摘要阶段类似,提供标准参考区和临时调整入口。 b) **启动路径:** 支持两种启动方式1) **流程衔接:** 自动加载从“标题摘要初筛”模块传递过来的“初步纳入”文献列表2) **独立启动:** 若无传入数据则显示导入选项上传Excel, 从知识库添加)。 c) **全文获取与管理:** 以表格形式展示待复筛文献列表包含列文献ID, 文献标题, 获取状态 (需模拟多种状态如获取中/成功/失败)。系统自动尝试获取全文。 d) **手动上传与知识库:** 获取失败的文献提供\[上传全文\]按钮;提供\[+ 从知识库添加\]按钮入口。 e) **启动复筛:** 只有当列表内所有文献状态均为获取成功时,激活“开始全文复筛”按钮(原型中可放宽此限制以方便演示)。 | 高 |
| **FR-FSCR-02** | **基于全文的PICS判断:** 双模型DS/Q3需读取文献**全文**并再次基于筛选标准对P/I/C/S四个维度进行判断 (✓/✗/?),提取**关键证据短语**,并给出结论建议。 | **极高** |
| **FR-FSCR-03** | **表格化审核工作台视图:** **复用**与“标题摘要初筛”模块**完全一致的表格化审核工作台**组件包括表头结构、可展开行、点击弹窗交互确保用户体验的连贯性。区别在于AI判断的数据源是全文弹窗中展示的是PDF全文阅读器。 | **极高** |
| **FR-FSCR-04** | **产出最终纳入列表:** 本模块审核通过的文献,将形成“最终纳入文献列表”,作为下一个“全文解析与数据提取”模块的输入。 | 高 |
| **FR-FSCR-05** | **复筛结果视图 (V2 新增):** 提供独立的页面展示复筛的最终结果。 a) **统计概览:** 展示“总计复筛文献数”、“最终纳入文献数”、“排除文献数”。 b) **PRISMA式排除总结:** 展示基于全文复筛阶段的排除原因及数量。 c) **结果列表:** 提供Tab页切换查看“最终纳入”和“排除”的文献列表。表格需包含列文献ID, 研究ID, 文献来源, 标题, 最终决策, 决策方式, 排除理由。 d) **导出功能:** 提供将结果列表导出为Excel的功能。 | 高 |
\<\!-- \#\#\# 3.5. 全文解析与数据提取模块 (V4.0 重构) ... (内容见第三部分文档) ...
### **3.6. 数据综合分析与报告生成模块**
... (内容见第三部分文档) ... \--\>

View File

@@ -0,0 +1,51 @@
# **PRD V4.0 \- 第三部分:提取与分析模块**
版本: 4.0
日期: 2025-10-21
## **3\. 功能需求详述 (续)**
### **3.5. 全文解析与数据提取模块 (V4.0 重构)**
**本模块负责对“全文复筛”阶段最终纳入的文献,根据“研究方案”中定义的变量清单,进行精准的数据提取和质量评价。**
| 需求ID | 需求描述 | 优先级 |
| :---- | :---- | :---- |
| **FR-EXT-01** | **基于方案的批量提取:** 系统根据“研究方案”中配置的“数据提取变量清单”对所有“最终纳入”的文献全文启动双模型如DS, Q3批量数据提取。需提供任务状态监控。 | **极高** |
| **FR-EXT-02** | **表格化数据审查台:** 核心审查界面采用**表格化布局**。 a) **表格结构:** 每一**行**代表一篇文献,每一**列**代表一个在方案中定义的提取变量包括研究特征、PICO要素、结局数据、质量评价项等。 b) **单元格内容:** 每个单元格清晰展示模型A和模型B的提取结果并高亮结果不一致的冲突项“待仲裁”。 | **极高** |
| **FR-EXT-03** | **点击查看原文 (模态框):** 用户点击任何一个需要仲裁(或需要核对)的单元格时,系统**弹出一个“双视图原文审查模态框”**。 a) **左侧:** PDF全文阅读器自动滚动并高亮AI提取该数据点所依据的原文片段。 b) **右侧:** 聚焦展示该数据点,包含双模型结果、理由、来源引用,并提供输入框供用户确认或修正最终值。 | **极高** |
| **FR-EXT-04** | **集成式批判性评价:** 用户在“研究方案”中选择的质量/偏倚风险评估工具如Cochrane RoB 2其评价条目将作为普通变量**一同整合**在表格化审查台的列中,允许用户在提取数据的同时完成质量评价,并为评价结果链接原文证据。 | 高 |
| **FR-EXT-05** | **数据汇总与导出:** 提供“数据汇总”页面将所有已审查确认的数据包括提取的变量和质量评价结果汇总到一个总表中支持筛选、搜索并提供一键导出为Excel的功能。页面应包含模型表现评估如正确率。 | 高 |
### **3.6. 数据综合分析与报告生成模块**
**本模块是一个交互式的分析平台,旨在将“全文解析与数据提取模块”产出的结构化数据,转化为富有洞见的专业分析和报告。**
| 需求ID | 需求描述 | 优先级 |
| :---- | :---- | :---- |
| **FR-ANLS-01** | **统一数据导入:** 模块的入口是“**3.5 全文解析与数据提取模块**”产出的最终结构化数据表。系统自动加载该数据作为分析的数据源。 | 高 |
#### **3.6.1 应用一:证据图谱生成 (Evidence Mapping)**
| 需求ID | 需求描述 | 优先级 |
| :---- | :---- | :---- |
| **FR-ANLS-MAP-01** | **图谱框架配置向导:** 提供一个向导式界面,让用户定义图谱的“坐标轴”和视觉元素: a) **Y轴配置 (干预措施):** 允许用户对提取的“干预措施”数据进行拖拽、分组和重命名。 b) **X轴配置 (结局指标):** 允许用户对提取的“结局指标”数据进行类似的分组和重命名。 c) **气泡含义配置:** 允许用户定义气泡颜色所代表的维度,如“研究质量/偏倚风险”或“研究设计”。气泡大小固定为“研究数量”。 | 高 |
| **FR-ANLS-MAP-02** | **交互式证据图谱生成:** 基于用户配置,生成一个交互式的气泡图矩阵。 a) **可视化展示:** Y轴为干预措施X轴为结局指标。交叉点上的气泡大小代表研究数量颜色代表用户选择的维度。没有气泡的单元格即为“证据空白”。 b) **悬停交互:** 鼠标悬停在气泡上显示总结信息研究数量高质量RCT数量等。 c) **点击交互:** 点击气泡,图表下方应动态展示该气泡包含的所有研究的详细列表。 | **极高** |
| **FR-ANLS-MAP-03** | **动态筛选器:** 在图谱旁边提供一组动态筛选器,允许用户根据“发表年份”、“研究设计”、“样本量”、“国家/地区”、“**特殊人群**”等维度实时过滤数据。图谱和下方的统计数据会随之动态更新。 | 高 |
| **FR-ANLS-MAP-04** | **自动化描述性统计:** 在图谱下方,根据当前筛选的数据,自动生成描述性统计图表和文字摘要,如研究类型分布饼图、研究趋势折线图等。 | 高 |
| **FR-ANLS-MAP-05** | **一键报告生成:** 提供“生成报告”功能,进入一个**智能报告编辑器**。编辑器预载由AI生成的报告初稿含图谱、统计图、AI文字解读、PRISMA流程、方法描述、附录等用户可在线编辑后导出为Word或PDF。 | 高 |
#### **3.6.2 应用二Meta分析数据准备**
| 需求ID | 需求描述 | 优先级 |
| :---- | :---- | :---- |
| **FR-ANLS-META-01** | **数据格式化导出:** 根据用户选择将从“数据提取模块”获取的结局指标数据如均值、标准差、事件数等自动整理并导出为适配主流Meta分析软件如RevMan, Stata格式的数据文件。 | 中 |
| **FR-ANLS-META-02** | **森林图预览:** 对于二分类或连续性变量提供一个基础的森林图Forest Plot预览功能让用户在导出前对数据合并的趋势有一个初步的视觉判断。 | 低 |
#### **3.6.3 应用三:药物综合评价报告**
| 需求ID | 需求描述 | 优先级 |
| :---- | :---- | :---- |
| **FR-ANLS-DRUG-01** | **多维数据整合:** 允许用户选择多个维度(如有效性指标、安全性指标、患者报告结局等),系统自动从数据源中整合相关数据。 | 中 |
| **FR-ANLS-DRUG-02** | **模板化报告生成:** 基于预设的药物综合评价报告模板自动将整合后的数据填充到报告的各个章节中生成包含PICO总结表、有效性-安全性矩阵图、关键研究列表等内容的Word或PDF报告初稿。 | 中 |

View File

@@ -0,0 +1,202 @@
# AI智能文献模块 - 数据库设计
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
> **最后更新:** 2025-10-29
---
## 📋 文档说明
本文档描述AI智能文献模块的数据库设计包括数据表结构、关系设计、索引设计等。
---
## 🗄️ 核心数据表
### 1. 文献筛选项目表 (literature_screening_projects)
```sql
CREATE TABLE literature_screening_projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
project_name VARCHAR(255) NOT NULL,
protocol_id UUID, -- 研究方案ID未来关联
-- PICO标准
pico_criteria JSONB, -- PICO结构化数据
-- 筛选标准
inclusion_criteria TEXT,
exclusion_criteria TEXT,
-- 状态
status VARCHAR(50) DEFAULT 'draft', -- draft, screening, completed
-- 筛选配置
screening_config JSONB, -- 筛选配置(双模型选择等)
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
### 2. 文献条目表 (literature_items)
```sql
CREATE TABLE literature_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES literature_screening_projects(id) ON DELETE CASCADE,
-- 文献基本信息
pmid VARCHAR(50),
title TEXT,
authors TEXT,
journal VARCHAR(255),
publication_year INTEGER,
doi VARCHAR(255),
abstract TEXT,
-- 文件信息
full_text_file_path VARCHAR(500),
full_text_status VARCHAR(50), -- not_required, pending, downloaded, uploaded, failed
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(project_id, pmid)
);
```
### 3. 标题摘要初筛结果表 (title_abstract_screening_results)
```sql
CREATE TABLE title_abstract_screening_results (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES literature_screening_projects(id) ON DELETE CASCADE,
literature_item_id UUID NOT NULL REFERENCES literature_items(id) ON DELETE CASCADE,
-- DS模型判断
ds_p_judgment VARCHAR(10), -- ✓, ✗, ?
ds_i_judgment VARCHAR(10),
ds_c_judgment VARCHAR(10),
ds_s_judgment VARCHAR(10),
ds_conclusion VARCHAR(20), -- include, exclude
-- DS模型证据
ds_p_evidence TEXT,
ds_i_evidence TEXT,
ds_c_evidence TEXT,
ds_s_evidence TEXT,
-- Q3模型判断
q3_p_judgment VARCHAR(10),
q3_i_judgment VARCHAR(10),
q3_c_judgment VARCHAR(10),
q3_s_judgment VARCHAR(10),
q3_conclusion VARCHAR(20),
-- Q3模型证据
q3_p_evidence TEXT,
q3_i_evidence TEXT,
q3_c_evidence TEXT,
q3_s_evidence TEXT,
-- 冲突状态
conflict_status VARCHAR(20) DEFAULT 'none', -- none, conflict, resolved
-- 最终决策
final_decision VARCHAR(20), -- include, exclude, pending
final_decision_by UUID REFERENCES users(id),
final_decision_at TIMESTAMP,
exclusion_reason TEXT,
-- AI处理状态
ai_processing_status VARCHAR(50) DEFAULT 'pending', -- pending, processing, completed, failed
ai_processed_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(project_id, literature_item_id)
);
```
### 4. 筛选任务表 (screening_tasks)
```sql
CREATE TABLE screening_tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES literature_screening_projects(id) ON DELETE CASCADE,
task_type VARCHAR(50) NOT NULL, -- title_abstract, full_text
status VARCHAR(50) DEFAULT 'pending', -- pending, running, completed, failed
total_items INTEGER,
processed_items INTEGER DEFAULT 0,
success_items INTEGER DEFAULT 0,
failed_items INTEGER DEFAULT 0,
started_at TIMESTAMP,
completed_at TIMESTAMP,
error_message TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
---
## 📊 数据关系图
```
literature_screening_projects (1) ──< (N) literature_items
literature_screening_projects (1) ──< (N) title_abstract_screening_results
literature_items (1) ──< (1) title_abstract_screening_results
literature_screening_projects (1) ──< (N) screening_tasks
```
---
## 🔍 索引设计
```sql
-- 文献条目表索引
CREATE INDEX idx_literature_items_project_id ON literature_items(project_id);
CREATE INDEX idx_literature_items_pmid ON literature_items(pmid);
-- 筛选结果表索引
CREATE INDEX idx_screening_results_project_id ON title_abstract_screening_results(project_id);
CREATE INDEX idx_screening_results_item_id ON title_abstract_screening_results(literature_item_id);
CREATE INDEX idx_screening_results_conflict ON title_abstract_screening_results(conflict_status);
CREATE INDEX idx_screening_results_decision ON title_abstract_screening_results(final_decision);
-- 任务表索引
CREATE INDEX idx_screening_tasks_project_id ON screening_tasks(project_id);
CREATE INDEX idx_screening_tasks_status ON screening_tasks(status);
```
---
## ⏳ 待完善内容
后续将补充:
- 全文复筛相关表结构
- 数据提取相关表结构
- 数据迁移方案
- 数据字典
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,238 @@
# AI智能文献模块 - API设计规范
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
> **最后更新:** 2025-10-29
---
## 📋 文档说明
本文档描述AI智能文献模块的API设计规范包括接口定义、请求响应格式、错误处理等。
---
## 🔌 API设计原则
1. **RESTful设计**: 遵循RESTful API设计规范
2. **统一响应格式**: 统一的成功/错误响应结构
3. **分页支持**: 列表接口支持分页
4. **版本控制**: API版本化管理
5. **认证授权**: 所有接口需要JWT认证
---
## 📡 核心API接口
### 1. 项目管理
#### 创建筛选项目
```
POST /api/literature/projects
Request Body:
{
"projectName": "string",
"picoCriteria": {...},
"inclusionCriteria": "string",
"exclusionCriteria": "string"
}
Response:
{
"code": 200,
"data": {
"id": "uuid",
"projectName": "string",
...
}
}
```
#### 获取项目列表
```
GET /api/literature/projects?page=1&pageSize=10
```
#### 获取项目详情
```
GET /api/literature/projects/:projectId
```
#### 更新项目
```
PUT /api/literature/projects/:projectId
```
#### 删除项目
```
DELETE /api/literature/projects/:projectId
```
### 2. 文献管理
#### 导入文献Excel
```
POST /api/literature/projects/:projectId/items/import
Content-Type: multipart/form-data
Body: file (Excel文件)
Response:
{
"code": 200,
"data": {
"importedCount": 100,
"items": [...]
}
}
```
#### 获取文献列表
```
GET /api/literature/projects/:projectId/items?page=1&pageSize=50
```
#### 获取文献详情
```
GET /api/literature/projects/:projectId/items/:itemId
```
### 3. 标题摘要初筛
#### 启动筛选任务
```
POST /api/literature/projects/:projectId/screening/start
Request Body:
{
"screeningType": "title_abstract",
"modelConfig": {
"ds": true,
"q3": true
}
}
Response:
{
"code": 200,
"data": {
"taskId": "uuid",
"status": "running"
}
}
```
#### 获取筛选结果
```
GET /api/literature/projects/:projectId/screening/results
Query Params:
- page: 页码
- pageSize: 每页数量
- conflictOnly: 只显示冲突项
- decision: include/exclude/pending
```
#### 更新最终决策
```
PUT /api/literature/projects/:projectId/screening/results/:resultId
Request Body:
{
"finalDecision": "include", // include/exclude
"exclusionReason": "string" // 排除时必填
}
```
#### 批量更新决策
```
POST /api/literature/projects/:projectId/screening/results/batch-update
Request Body:
{
"itemIds": ["uuid1", "uuid2", ...],
"finalDecision": "include",
"exclusionReason": "string"
}
```
### 4. 任务管理
#### 获取任务状态
```
GET /api/literature/projects/:projectId/tasks/:taskId
Response:
{
"code": 200,
"data": {
"id": "uuid",
"status": "running", // pending/running/completed/failed
"totalItems": 100,
"processedItems": 45,
"progress": 45
}
}
```
#### 任务进度流式推送SSE
```
GET /api/literature/projects/:projectId/tasks/:taskId/progress
Accept: text/event-stream
```
---
## 📋 响应格式规范
### 成功响应
```json
{
"code": 200,
"message": "success",
"data": {...}
}
```
### 错误响应
```json
{
"code": 400,
"message": "错误描述",
"error": {
"code": "ERROR_CODE",
"details": "..."
}
}
```
### 分页响应
```json
{
"code": 200,
"data": {
"items": [...],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 100,
"totalPages": 5
}
}
}
```
---
## ⏳ 待完善内容
后续将补充:
- 完整的API文档所有接口详细说明
- 请求/响应示例
- 错误码定义
- 接口测试用例
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,155 @@
# AI智能文献模块 - 前端组件设计
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
> **最后更新:** 2025-10-29
---
## 📋 文档说明
本文档描述AI智能文献模块的前端组件设计包括组件结构、组件接口、交互设计等。
---
## 🧩 组件架构
### 标题摘要初筛模块组件结构
```
LiteratureScreeningModule/
├── TitleAbstractScreening/
│ ├── SetupView/ # 设置与启动视图
│ │ ├── CriteriaReference.tsx # 标准参考面板
│ │ ├── CriteriaAdjustment.tsx # 临时调整标准
│ │ ├── LiteratureImport.tsx # 文献导入组件
│ │ └── StartScreeningButton.tsx # 启动筛选按钮
│ │
│ ├── ReviewTableView/ # 表格化审核工作台 ⭐核心
│ │ ├── ScreeningTable.tsx # 主表格组件
│ │ ├── TableHeader.tsx # 表头(双行结构)
│ │ ├── TableRow.tsx # 表格行
│ │ ├── ExpandableRow.tsx # 可展开行(证据展示)
│ │ ├── JudgmentCell.tsx # 判断单元格(✓/✗/?
│ │ ├── ConflictIndicator.tsx # 冲突状态指示器
│ │ └── DecisionSelector.tsx # 最终决策选择器
│ │
│ ├── EvidenceModal/ # 双视图原文审查模态框
│ │ ├── ModalContainer.tsx # 模态框容器
│ │ ├── AbstractView.tsx # 左侧:摘要视图
│ │ ├── EvidenceView.tsx # 右侧:证据视图
│ │ └── HighlightedText.tsx # 高亮文本组件
│ │
│ ├── ResultView/ # 结果展示视图
│ │ ├── StatisticsCards.tsx # 统计卡片
│ │ ├── PrismaSummary.tsx # PRISMA式排除总结
│ │ ├── ResultTabs.tsx # 结果Tab页
│ │ └── ResultTable.tsx # 结果表格
│ │
│ └── shared/ # 共享组件
│ ├── ProtocolOverview.tsx # 研究方案概览面板
│ ├── BatchOperation.tsx # 批量操作组件
│ └── ExportButton.tsx # 导出按钮
```
---
## 🎨 核心组件设计
### 1. ScreeningTable (表格化审核工作台)
**组件职责**:
- 展示文献列表和筛选结果
- 支持展开/收起查看证据
- 支持点击判断查看详情
- 支持批量操作
**Props接口**:
```typescript
interface ScreeningTableProps {
projectId: string;
items: LiteratureItem[];
results: ScreeningResult[];
onDecisionChange: (itemId: string, decision: string) => void;
onBatchUpdate: (itemIds: string[], decision: string) => void;
}
```
### 2. EvidenceModal (双视图原文审查模态框)
**组件职责**:
- 左侧显示摘要/全文
- 右侧显示AI判断和证据
- 支持文本高亮
- 支持查看引用来源
**Props接口**:
```typescript
interface EvidenceModalProps {
visible: boolean;
itemId: string;
dimension: 'P' | 'I' | 'C' | 'S';
onClose: () => void;
}
```
### 3. ReviewTableView (审核工作台主视图)
**组件职责**:
- 整合表格和模态框
- 管理筛选状态
- 处理用户交互
---
## 🔄 状态管理
### 使用Zustand管理筛选状态
```typescript
interface ScreeningStore {
currentProject: Project | null;
items: LiteratureItem[];
results: ScreeningResult[];
selectedItems: string[];
loading: boolean;
// Actions
loadProject: (projectId: string) => Promise<void>;
updateDecision: (itemId: string, decision: string) => Promise<void>;
batchUpdate: (itemIds: string[], decision: string) => Promise<void>;
}
```
---
## 📱 响应式设计
### 表格布局适配
- **桌面端**: 完整表格显示
- **平板端**: 可横向滚动,关键列固定
- **移动端**: 卡片式布局替代表格
---
## ⏳ 待完善内容
后续将补充:
- 详细组件接口定义
- 组件交互流程图
- 样式设计规范
- 组件使用示例
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,24 @@
# AI模型集成设计
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
---
## ⏳ 待完善
本文档内容待规划完善,目前仅作为占位文档。
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,24 @@
# 文件处理设计
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
---
## ⏳ 待完善
本文档内容待规划完善,目前仅作为占位文档。
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,949 @@
# ASL 质量保障与可追溯策略
> **文档版本:** V1.0
> **创建日期:** 2025-11-15
> **适用模块:** AI 智能文献ASL
> **目标:** 分阶段提升文献筛选、数据提取的准确率、质量控制和可追溯性
---
## 📋 文档概述
本文档定义了 ASL 模块在 **MVP → V1.0 → V2.0** 三个阶段中,如何逐步提升:
1. **提取准确率**:从基础可用 → 高质量 → 医学级标准
2. **质量控制**:从人工抽查 → 自动验证 → 智能仲裁
3. **可追溯性**:从基本记录 → 完整证据链 → 审计级日志
### 核心设计原则
| 原则 | 说明 |
|------|------|
| **成本可控** | MVP 阶段优先使用 DeepSeek + Qwen3成本敏感 |
| **质量可升级** | 可切换到 GPT-5-Pro + Claude-4.5 高端组合 |
| **分步实施** | 避免过度设计,每个阶段交付可用功能 |
| **医学场景优化** | 针对英文医学文献的特点优化策略 |
---
## 🎯 三阶段路线图
```
MVP (4周) V1.0 (6周) V2.0 (8周)
├─ 基础双模型验证 ├─ 智能质量控制 ├─ 医学级质量保障
├─ JSON Schema 约束 ├─ 分段提取优化 ├─ 多模型共识仲裁
├─ 置信度评分 ├─ 证据链完整追溯 ├─ 自动质量审计
├─ 人工复核机制 ├─ 规则引擎验证 ├─ 提示词版本管理
└─ 基本追溯日志 └─ Few-shot 示例库 └─ HITL 智能分流
↓ ↓ ↓
可用 高质量 医学级
```
---
## 🚀 MVP 阶段4 周)
### 目标定位
- **准确率目标**:≥ 85%
- **成本预算**:筛选 1000 篇文献 ≤ ¥50
- **交付标准**:基础功能可用,支持双模型对比
### 一、模型选择策略
#### 1.1 主力模型组合(成本优先)
| 角色 | 模型 | Model ID | 用途 | 成本 |
|------|------|---------|------|------|
| **模型 A** | DeepSeek-V3 | `deepseek-chat` | 快速初筛 | ¥0.001/1K tokens |
| **模型 B** | Qwen3-72B | `qwen-max` | 交叉验证 | ¥0.004/1K tokens |
**切换选项**(质量优先):
- **高端组合**GPT-5-Pro (`gpt-5-pro`) + Claude-4.5-Sonnet (`claude-sonnet-4-5-20250929`)
- **成本增加**:约 3-5 倍
- **准确率提升**85% → 92%+
#### 1.2 模型调用策略
```typescript
// 双模型并行调用
async function dualModelScreening(
literature: Literature,
protocol: Protocol
) {
// 并行调用两个模型
const [resultA, resultB] = await Promise.all([
llmService.chat('deepseek', buildPrompt(literature, protocol)),
llmService.chat('qwen', buildPrompt(literature, protocol))
]);
// 解析 JSON 结果
const decisionA = parseJSON(resultA.content);
const decisionB = parseJSON(resultB.content);
// 一致性判断
if (decisionA.decision === decisionB.decision) {
return {
finalDecision: decisionA.decision,
consensus: 'high',
needReview: false,
models: [decisionA, decisionB]
};
}
// 冲突 → 人工复核
return {
finalDecision: 'uncertain',
consensus: 'conflict',
needReview: true,
models: [decisionA, decisionB]
};
}
```
### 二、核心技术策略
#### 2.1 ✅ 双模型交叉验证
**实施方案**
- 所有筛选任务同时调用两个模型
- 自动对比结果,标记差异
- 一致率作为质量指标(目标 ≥ 80%
**代码示例**
```typescript
interface DualModelResult {
consensus: 'high' | 'conflict';
finalDecision: 'include' | 'exclude' | 'uncertain';
needReview: boolean;
models: ModelDecision[];
}
```
#### 2.2 ✅ JSON Schema 约束
**实施方案**
- 定义严格的输出格式
- 使用枚举限制取值
- 区分必填/可选字段
**Schema 定义**
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["decision", "reason", "confidence", "pico"],
"properties": {
"decision": {
"type": "string",
"enum": ["include", "exclude", "uncertain"]
},
"reason": {
"type": "string",
"minLength": 10,
"maxLength": 500
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"pico": {
"type": "object",
"required": ["population", "intervention", "comparison", "outcome"],
"properties": {
"population": {
"type": "string",
"enum": ["match", "partial", "mismatch"]
},
"intervention": {
"type": "string",
"enum": ["match", "partial", "mismatch"]
},
"comparison": {
"type": "string",
"enum": ["match", "partial", "mismatch", "not_applicable"]
},
"outcome": {
"type": "string",
"enum": ["match", "partial", "mismatch"]
}
}
},
"studyDesign": {
"type": "string",
"enum": ["RCT", "cohort", "case-control", "cross-sectional", "other"]
}
}
}
```
**提示词模板**
```typescript
const prompt = `
你是一位医学文献筛选专家。请根据以下 PICO 标准判断这篇文献是否应该纳入系统评价。
# PICO 标准
- Population: ${protocol.population}
- Intervention: ${protocol.intervention}
- Comparison: ${protocol.comparison}
- Outcome: ${protocol.outcome}
# 文献信息
标题: ${literature.title}
摘要: ${literature.abstract}
# 输出要求
请严格按照以下 JSON Schema 输出结果:
${JSON.stringify(schema, null, 2)}
注意:
1. decision 只能是 "include"、"exclude" 或 "uncertain"
2. reason 必须具体说明判断依据10-500字
3. confidence 为 0-1 之间的数值,表示你的判断把握
4. pico 字段逐项评估匹配程度
`;
```
#### 2.3 ✅ 置信度评分
**实施方案**
- 要求模型对每个判断给出置信度0-1
- 置信度 < 0.7 自动标记为需人工复核
- 记录置信度分布,优化阈值
**自动分流规则**
```typescript
function autoTriage(result: DualModelResult) {
const avgConfidence = (
result.models[0].confidence +
result.models[1].confidence
) / 2;
// 规则1冲突 → 必须复核
if (result.consensus === 'conflict') {
return { needReview: true, priority: 'high' };
}
// 规则2低置信度 → 需要复核
if (avgConfidence < 0.7) {
return { needReview: true, priority: 'medium' };
}
// 规则3高置信度 + 一致 → 自动通过
return { needReview: false, priority: 'low' };
}
```
#### 2.4 ✅ 基础可追溯
**实施方案**
- 保存原始提示词和模型输出
- 记录模型版本和时间戳
- 关联人工复核记录
**数据库设计**
```prisma
model ScreeningResult {
id String @id @default(uuid())
literatureId String
protocolId String
// 模型A结果
modelAName String // "deepseek-chat"
modelAOutput Json // 原始JSON输出
modelAConfidence Float
// 模型B结果
modelBName String // "qwen-max"
modelBOutput Json
modelBConfidence Float
// 最终决策
finalDecision String // "include"/"exclude"/"uncertain"
consensus String // "high"/"conflict"
needReview Boolean
// 人工复核
reviewedBy String?
reviewedAt DateTime?
reviewDecision String?
reviewNotes String?
// 可追溯信息
promptTemplate String @db.Text // 使用的提示词模板
createdAt DateTime @default(now())
@@map("asl_screening_results")
}
```
### 三、MVP 成本预算
**场景:筛选 1000 篇文献**
| 项目 | DeepSeek | Qwen3 | 合计 |
|------|----------|-------|------|
| 输入 tokens平均 | 800 | 800 | - |
| 输出 tokens平均 | 200 | 200 | - |
| 单次成本 | ¥0.001 | ¥0.004 | ¥0.005 |
| **1000 篇总成本** | ¥1 | ¥4 | **¥5** |
**冲突率 20% 人工复核**
- 自动通过800 篇 × ¥0.005 = ¥4
- 人工复核200 篇 × 2 分钟 = 6.7 小时
- **总成本**¥4 + 人工成本
### 四、MVP 验收标准
| 指标 | 目标 | 验证方法 |
|------|------|----------|
| 双模型一致率 | ≥ 80% | 统计报表 |
| JSON Schema 验证通过率 | ≥ 95% | 自动检查 |
| 人工复核队列占比 | ≤ 20% | 系统统计 |
| 提取结果可追溯 | 100% | 审计检查 |
| 成本控制 | ≤ ¥50/1000 篇 | 账单监控 |
---
## 📈 V1.0 阶段6 周)
### 目标定位
- **准确率目标**:≥ 90%
- **成本预算**:筛选 1000 篇文献 ≤ ¥80
- **交付标准**:高质量输出,智能质量控制
### 一、模型策略优化
#### 1.1 成本优化策略
**核心思路**80% 用低成本模型20% 高价值任务用顶级模型
```typescript
async function smartScreening(literature: Literature, protocol: Protocol) {
// 第一阶段快速初筛DeepSeek
const quickResult = await llmService.chat('deepseek', buildPrompt(...));
const quickDecision = parseJSON(quickResult.content);
// 如果高置信度 + 明确结论 → 直接采纳
if (
quickDecision.confidence > 0.85 &&
quickDecision.decision !== 'uncertain'
) {
return {
finalDecision: quickDecision.decision,
strategy: 'cost-optimized',
models: [quickDecision]
};
}
// 否则 → 启用高端模型复核
const detailedResult = await llmService.chat('gpt5', buildPrompt(...));
return {
finalDecision: detailedResult.decision,
strategy: 'quality-assured',
models: [quickDecision, detailedResult]
};
}
```
**预期成本节省**
- 80% 任务用 DeepSeek800 × ¥0.001 = ¥0.8
- 20% 任务用 GPT-5200 × ¥0.10 = ¥20
- **总成本**¥20.8(相比全用 GPT-5 节省 80%
### 二、核心技术增强
#### 2.1 ✅ Few-shot 示例库
**实施方案**
- 人工标注 20-30 个高质量示例
- 针对不同研究类型分类RCT、队列、病例对照
- 动态选择相似示例嵌入提示词
**示例格式**
```json
{
"examples": [
{
"title": "Effect of aspirin on cardiovascular events in patients with diabetes",
"abstract": "...",
"goldStandard": {
"decision": "include",
"reason": "RCT研究人群为糖尿病患者匹配P干预为阿司匹林匹配I对照为安慰剂匹配C结局为心血管事件匹配O",
"pico": {
"population": "match",
"intervention": "match",
"comparison": "match",
"outcome": "match"
},
"studyDesign": "RCT"
}
}
]
}
```
**提示词增强**
```typescript
const promptWithExamples = `
# 参考示例
以下是 3 个标注好的示例,帮助你理解判断标准:
${examples.map((ex, i) => `
## 示例 ${i + 1}
标题: ${ex.title}
摘要: ${ex.abstract}
判断: ${ex.goldStandard.decision}
理由: ${ex.goldStandard.reason}
`).join('\n')}
# 待筛选文献
标题: ${literature.title}
摘要: ${literature.abstract}
请参考上述示例输出你的判断结果JSON格式
`;
```
#### 2.2 ✅ 分段提取
**实施方案**
- 针对全文数据提取,按章节分段处理
- 每段独立提取,减少上下文混淆
- 最后合并结果,交叉验证一致性
**分段策略**
```typescript
async function segmentedExtraction(fullText: string, protocol: Protocol) {
// 分段
const sections = {
methods: extractSection(fullText, 'methods'),
results: extractSection(fullText, 'results'),
tables: extractTables(fullText),
};
// 并行提取
const [methodsData, resultsData, tablesData] = await Promise.all([
extractFromMethods(sections.methods, protocol),
extractFromResults(sections.results, protocol),
extractFromTables(sections.tables, protocol),
]);
// 合并结果
return mergeExtractionResults([methodsData, resultsData, tablesData]);
}
```
**提取示例(方法学部分)**
```typescript
const methodsPrompt = `
请从以下方法学部分提取研究设计信息:
# 方法学原文
${methodsSection}
# 提取字段
- 研究设计类型RCT/cohort/case-control等
- 样本量(干预组/对照组)
- 纳入标准
- 排除标准
- 随机化方法(如适用)
- 盲法(如适用)
# 输出格式JSON
${methodsSchema}
`;
```
#### 2.3 ✅ 规则引擎验证
**实施方案**
- 定义业务规则,自动检查逻辑错误
- 数值范围验证
- 必填字段完整性检查
**验证规则**
```typescript
const validationRules = [
{
name: '样本量合理性',
check: (data) => {
const total = data.sampleSize.intervention + data.sampleSize.control;
return total >= 10 && total <= 100000;
},
errorMessage: '样本量超出合理范围10-100000'
},
{
name: 'P值范围',
check: (data) => {
return data.pValue >= 0 && data.pValue <= 1;
},
errorMessage: 'P值必须在0-1之间'
},
{
name: '必填字段完整性',
check: (data) => {
const required = ['studyDesign', 'sampleSize', 'primaryOutcome'];
return required.every(field => data[field] != null);
},
errorMessage: '缺少必填字段'
}
];
function validateExtraction(data: ExtractionResult): ValidationReport {
const errors = [];
for (const rule of validationRules) {
if (!rule.check(data)) {
errors.push(rule.errorMessage);
}
}
return {
isValid: errors.length === 0,
errors
};
}
```
#### 2.4 ✅ 完整证据链
**实施方案**
- 记录原文引用位置(页码、段落、句子)
- 保存模型完整输出(含中间推理)
- 关联所有人工修改记录
**数据库增强**
```prisma
model ExtractionResult {
id String @id @default(uuid())
// 提取内容
extractedData Json
// 证据链(新增)
evidenceChain Json // {
// "sampleSize": {
// "value": 150,
// "source": {
// "page": 3,
// "paragraph": 2,
// "text": "A total of 150 patients were enrolled..."
// }
// }
// }
// 模型信息
modelName String
modelVersion String
promptVersion String // "v1.2.0"
rawOutput String @db.Text // 原始输出含CoT推理
// 修改历史
revisions ExtractionRevision[]
createdAt DateTime @default(now())
@@map("asl_extraction_results")
}
model ExtractionRevision {
id String @id @default(uuid())
extractionId String
fieldName String // 修改的字段
oldValue Json
newValue Json
reason String // 修改理由
revisedBy String
revisedAt DateTime @default(now())
extraction ExtractionResult @relation(fields: [extractionId], references: [id])
@@map("asl_extraction_revisions")
}
```
### 三、V1.0 成本预算
**场景:筛选 1000 篇 + 提取 200 篇全文**
| 任务 | 策略 | 成本 |
|------|------|------|
| 标题摘要筛选 | 80% DeepSeek + 20% GPT-5 | ¥21 |
| 全文数据提取 | 分段提取GPT-5 | ¥60 |
| **总成本** | - | **¥81** |
### 四、V1.0 验收标准
| 指标 | 目标 | 验证方法 |
|------|------|----------|
| 提取准确率 | ≥ 90% | 人工抽查 50 篇 |
| Few-shot 示例库 | ≥ 20 个 | 人工标注 |
| 规则引擎覆盖率 | ≥ 80% | 代码审查 |
| 证据链完整性 | 100% | 审计检查 |
| 成本控制 | ≤ ¥80/项目 | 账单监控 |
---
## 🏆 V2.0 阶段8 周)
### 目标定位
- **准确率目标**:≥ 95%(医学级)
- **成本预算**:按需配置
- **交付标准**:自动化质量审计,符合临床研究规范
### 一、医学级质量保障
#### 1.1 ✅ 三模型共识仲裁
**实施方案**
- 双模型冲突时,自动启用第三方仲裁
- 三模型投票决策
- 记录仲裁过程
```typescript
async function threeModelArbitration(
literature: Literature,
protocol: Protocol
) {
// 第一轮:双模型
const [resultA, resultB] = await Promise.all([
llmService.chat('deepseek', buildPrompt(...)),
llmService.chat('qwen', buildPrompt(...))
]);
// 如果一致,直接返回
if (resultA.decision === resultB.decision) {
return { finalDecision: resultA.decision, arbitration: false };
}
// 冲突 → 启用 Claude 仲裁
console.log('检测到冲突,启用 Claude-4.5 仲裁...');
const resultC = await llmService.chat('claude', buildPrompt(...));
// 三模型投票
const votes = [resultA.decision, resultB.decision, resultC.decision];
const voteCount = {
include: votes.filter(v => v === 'include').length,
exclude: votes.filter(v => v === 'exclude').length,
uncertain: votes.filter(v => v === 'uncertain').length,
};
// 多数决
const winner = Object.entries(voteCount)
.sort((a, b) => b[1] - a[1])[0][0];
return {
finalDecision: winner,
arbitration: true,
votes: { resultA, resultB, resultC },
consensus: voteCount[winner] >= 2 ? 'strong' : 'weak'
};
}
```
**成本控制**
- 仅在冲突时启用仲裁(预计 10-15%
- 单次仲裁额外成本¥0.021Claude-4.5
#### 1.2 ✅ HITL 智能分流
**实施方案**
- 基于规则的智能优先级排序
- 高价值/高风险任务优先人工复核
- 低风险任务自动化处理
**分流规则**
```typescript
function intelligentTriage(result: ScreeningResult): TriageDecision {
let priority = 0;
let needReview = false;
// 规则1三模型仍不一致 → 最高优先级
if (result.arbitration && result.consensus === 'weak') {
priority = 100;
needReview = true;
}
// 规则2RCT 研究 → 中等优先级
else if (result.studyDesign === 'RCT') {
priority = 70;
needReview = result.confidence < 0.9;
}
// 规则3关键结局指标 → 高优先级
else if (result.outcome.includes('mortality')) {
priority = 80;
needReview = result.confidence < 0.85;
}
// 规则4高置信度 + 一致 → 自动通过
else if (result.confidence > 0.95 && result.consensus === 'high') {
priority = 10;
needReview = false;
}
return { priority, needReview };
}
```
#### 1.3 ✅ 提示词版本管理
**实施方案**
- Git 管理提示词模板
- 版本号标记(语义化版本)
- A/B 测试不同版本效果
**目录结构**
```
backend/prompts/asl/
├── screening/
│ ├── v1.0.0-basic.txt
│ ├── v1.1.0-with-examples.txt
│ └── v1.2.0-cot.txt
├── extraction/
│ ├── v1.0.0-methods.txt
│ └── v1.1.0-methods-segmented.txt
└── changelog.md
```
**版本记录**
```prisma
model PromptVersion {
id String @id @default(uuid())
name String // "screening-v1.2.0"
content String @db.Text
version String // "1.2.0"
changelog String // "增加 Few-shot 示例"
// 性能指标
accuracy Float? // 0.92
usageCount Int @default(0)
isActive Boolean @default(false)
createdAt DateTime @default(now())
@@map("asl_prompt_versions")
}
```
#### 1.4 ✅ 自动质量审计
**实施方案**
- 定期批量抽查10%
- 自动生成质量报告
- 异常检测和告警
**审计报表**
```typescript
interface QualityAuditReport {
period: { start: Date; end: Date };
totalTasks: number;
sampledTasks: number;
metrics: {
accuracy: number; // 准确率
interRaterAgreement: number; // 人机一致性
falsePositiveRate: number; // 假阳性率
falseNegativeRate: number; // 假阴性率
};
modelPerformance: {
deepseek: { accuracy: number; avgConfidence: number };
qwen: { accuracy: number; avgConfidence: number };
gpt5: { accuracy: number; avgConfidence: number };
};
issues: {
type: string;
count: number;
examples: string[];
}[];
recommendations: string[];
}
```
### 二、高级提示词工程
#### 2.1 ✅ Chain of Thought (CoT)
**实施方案**
- 要求模型输出推理过程
- 分步骤判断 PICO 匹配度
- 最后给出综合结论
**提示词示例**
```
请按照以下步骤判断这篇文献是否应该纳入:
# Step 1: 研究设计判断
- 识别研究类型RCT/队列/病例对照等)
- 判断是否符合纳入标准
# Step 2: PICO 逐项评估
- Population: 详细分析人群是否匹配
- Intervention: 详细分析干预措施是否匹配
- Comparison: 详细分析对照是否匹配
- Outcome: 详细分析结局指标是否匹配
# Step 3: 综合判断
- 汇总以上分析
- 给出最终决策include/exclude/uncertain
- 评估置信度0-1
# 输出格式
{
"reasoning": {
"studyDesign": "这是一项...",
"population": "人群匹配度分析...",
"intervention": "干预措施分析...",
"comparison": "对照分析...",
"outcome": "结局指标分析..."
},
"decision": "include",
"confidence": 0.95,
"reason": "基于以上分析..."
}
```
#### 2.2 ✅ 动态示例选择
**实施方案**
- 计算待筛选文献与示例库的语义相似度
- 动态选择最相似的 3-5 个示例
- 嵌入提示词
```typescript
async function selectSimilarExamples(
literature: Literature,
examplePool: Example[]
): Promise<Example[]> {
// 使用嵌入模型计算相似度
const literatureEmbedding = await getEmbedding(
`${literature.title} ${literature.abstract}`
);
const similarities = examplePool.map(ex => ({
example: ex,
similarity: cosineSimilarity(literatureEmbedding, ex.embedding)
}));
// 返回最相似的 5 个
return similarities
.sort((a, b) => b.similarity - a.similarity)
.slice(0, 5)
.map(s => s.example);
}
```
### 三、V2.0 成本预算
**场景:高质量系统评价项目(筛选 5000 篇 + 提取 300 篇)**
| 任务 | 策略 | 成本 |
|------|------|------|
| 标题摘要筛选 | 成本优化 + 15% 仲裁 | ¥120 |
| 全文数据提取 | GPT-5 + Claude 双模型 | ¥350 |
| 质量审计 | 10% 抽查 | ¥30 |
| **总成本** | - | **¥500** |
### 四、V2.0 验收标准
| 指标 | 目标 | 验证方法 |
|------|------|----------|
| 提取准确率 | ≥ 95% | 人工抽查 100 篇 |
| 人机一致性 | ≥ 90% | Cohen's Kappa |
| 假阳性率 | ≤ 5% | 统计分析 |
| 假阴性率 | ≤ 3% | 统计分析 |
| 提示词版本管理 | 100% | Git 历史 |
| 自动化审计 | 每周 1 次 | 系统报表 |
---
## 📊 三阶段对比总结
| 维度 | MVP | V1.0 | V2.0 |
|------|-----|------|------|
| **准确率** | 85% | 90% | 95% |
| **模型组合** | DeepSeek + Qwen3 | 成本优化策略 | 三模型仲裁 |
| **质量控制** | 双模型验证 | 规则引擎 + Few-shot | HITL + 自动审计 |
| **可追溯性** | 基本日志 | 完整证据链 | 审计级记录 |
| **成本/1000 篇** | ¥5 | ¥21 | ¥24 + 仲裁 |
| **开发周期** | 4 周 | 6 周 | 8 周 |
| **适用场景** | 快速验证 | 常规项目 | 高质量发表 |
---
## 🔄 实施路径
### 阶段 1: MVP 开发Week 1-4
**Week 1**:基础架构
- [ ] LLM 服务封装DeepSeek + Qwen3
- [ ] JSON Schema 定义
- [ ] 数据库表设计
**Week 2**:核心功能
- [ ] 双模型并行调用
- [ ] 一致性判断逻辑
- [ ] 人工复核队列
**Week 3**:前端开发
- [ ] 筛选工作台
- [ ] 冲突对比视图
- [ ] 人工复核界面
**Week 4**:测试验收
- [ ] 功能测试
- [ ] 准确率评估
- [ ] 成本监控
### 阶段 2: V1.0 增强Week 5-10
**Week 5-6**:智能优化
- [ ] 成本优化策略
- [ ] Few-shot 示例库
- [ ] 动态示例选择
**Week 7-8**:质量控制
- [ ] 分段提取
- [ ] 规则引擎
- [ ] 证据链完整化
**Week 9-10**:测试优化
- [ ] A/B 测试
- [ ] 准确率提升
- [ ] 文档完善
### 阶段 3: V2.0 完善Week 11-18
**Week 11-13**:高级功能
- [ ] 三模型仲裁
- [ ] HITL 智能分流
- [ ] 提示词版本管理
**Week 14-16**:质量审计
- [ ] 自动审计系统
- [ ] 质量报表
- [ ] 异常检测
**Week 17-18**:发布准备
- [ ] 全量测试
- [ ] 医学专家验证
- [ ] 文档和培训
---
## 📚 相关文档
- [CloseAI 集成指南](../../../02-通用能力层/01-LLM大模型网关/03-CloseAI集成指南.md)
- [AI 模型集成设计](./04-AI模型集成设计.md)
- [数据库设计](./01-数据库设计.md)
- [API 设计规范](./02-API设计规范.md)
---
**更新日志**
- 2025-11-15: 创建文档,定义 MVP/V1.0/V2.0 三阶段策略

View File

@@ -0,0 +1,87 @@
# 标题摘要初筛UI设计
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
---
## 📋 文档说明
本文档描述标题摘要初筛模块的UI设计包括页面布局、交互设计、视觉规范等。
---
## 🎨 页面视图
### 1. 设置与启动视图
**布局结构**:
- 顶部: 标准参考面板(可折叠)
- 中间: 临时调整入口(可选)
- 底部: 文献导入区域 + 启动按钮
**设计要点**:
- 清晰展示从研究方案继承的PICO和入排标准
- 提供简洁的文献导入入口Excel上传
- 导入后激活启动按钮
### 2. 表格化审核工作台视图 ⭐核心
**表格结构**:
- **表头**: 两行结构
- 第一行: 合并单元格标示模型区域DS模型 | Q3模型 | 决策)
- 第二行: 各模型下细分P/I/C/S/结论列
- **主行**:
- 展开/收起按钮
- 文献ID、研究ID、文献来源
- DS-P/I/C/S/结论判断(✓/✗/?
- Q3-P/I/C/S/结论判断
- 冲突状态指示
- 最终决策下拉框
- **展开行**:
- DS证据列: P/I/C/S对应的关键短语
- Q3证据列: P/I/C/S对应的关键短语
**交互设计**:
- 点击判断图标(✓/✗/?) → 弹出双视图原文审查模态框
- 冲突项行背景高亮显示
- 支持批量选择和非冲突项批量决策
### 3. 结果展示视图
**布局结构**:
- 顶部: 统计卡片(总计、纳入、排除)
- 中间: PRISMA式排除总结
- 底部: 结果列表Tab切换纳入/排除)+ 导出按钮
---
## 🖼️ 原型参考
详细原型请参考: `../../01-设计文档/AI智能文献-标题摘要初筛原型.html`
---
## ⏳ 待完善内容
后续将补充:
- 详细的页面布局图
- 交互流程图
- 视觉设计规范
- 组件样式规范
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,24 @@
# 全文复筛UI设计
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
---
## ⏳ 待完善
本文档内容待规划完善,目前仅作为占位文档。
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,24 @@
# UI设计规范
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
---
## ⏳ 待完善
本文档内容待规划完善,目前仅作为占位文档。
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,524 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>全文复筛原型 V2</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/heroicons/2.1.3/24/outline/css/heroicons.min.css" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; }
.sidebar { background-color: #f8fafc; }
.sidebar-link.active { background-color: #e0f2fe; color: #0c4a6e; font-weight: 600; }
.main-content { margin-left: 16rem; }
.view { display: none; }
.view.active { display: block; }
.modal-backdrop { background-color: rgba(0, 0, 0, 0.5); }
.table-conflict-row > td:not(.expanded-row-cell) { background-color: #fee2e2; }
.table-cell-judge { cursor: pointer; text-align: center; }
.abstract-highlight { background-color: #fef9c3; }
.nav-parent { font-weight: bold; color: #374151; }
.nav-child { padding-left: 1.5rem; }
.sub-nav-link { color: #6b7280; }
.sub-nav-link.active { color: #0c4a6e; font-weight: 600; background-color: #f0f9ff; }
.sidebar-link.disabled { color: #9ca3af; cursor: not-allowed; background-color: transparent !important; }
.sidebar-link.disabled:hover { background-color: transparent !important; }
.result-tab.active { border-bottom-color: #0ea5e9; color: #0ea5e9; font-weight: 600;}
.expand-row-btn { cursor: pointer; width: 20px; text-align: center; }
.expanded-row { display: none; background-color: #f9fafb; }
.expanded-row.shown { display: table-row; }
.expanded-row-cell { padding: 0.75rem 1rem; border-top: 1px solid #e5e7eb; }
.evidence-grid { display: grid; grid-template-columns: auto 1fr; gap: 0.25rem 1rem; }
.evidence-grid dt { font-weight: 500; color: #4b5563; text-align: right;}
.evidence-grid dd { color: #1f2937; }
.abstract-cell .full-abstract { display: none; }
.abstract-cell.expanded .short-abstract { display: none; }
.abstract-cell.expanded .full-abstract { display: block; }
.results-pics-cell { font-size: 0.7rem; line-height: 1.2; color: #4b5563; }
.results-pics-cell strong { color: #1f2937; }
</style>
</head>
<body class="bg-gray-100 text-gray-800">
<div class="h-screen flex">
<!-- 模拟的左侧主导航 -->
<aside id="sidebar" class="sidebar w-64 border-r border-gray-200 p-4 flex-shrink-0 flex flex-col fixed h-full">
<div class="text-xl font-bold text-gray-800 mb-8">AI文献平台</div>
<nav class="flex-grow space-y-2">
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700">1. 研究方案生成</a>
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700">2. 智能文献检索</a>
<!-- Title Screening Section (Not active) -->
<div class="space-y-1">
<span class="nav-parent group flex items-center px-3 py-2 text-sm rounded-md text-gray-800">3. 标题摘要初筛</span>
<div class="space-y-1 nav-child">
<a href="#" class="sidebar-link sub-nav-link group flex items-center px-3 py-2 text-sm font-medium rounded-md">设置与启动</a>
<a href="#" class="sidebar-link sub-nav-link group flex items-center px-3 py-2 text-sm font-medium rounded-md">审核工作台</a>
<a href="#" class="sidebar-link sub-nav-link group flex items-center px-3 py-2 text-sm font-medium rounded-md">初筛结果</a>
</div>
</div>
<!-- Full-text Rescreening Section (Active) -->
<div class="space-y-1">
<span class="nav-parent group flex items-center px-3 py-2 text-sm rounded-md text-gray-800">4. 全文复筛</span>
<div class="space-y-1 nav-child">
<a href="#" id="nav-fulltext-settings" data-view="fulltext-settings" class="sidebar-link sub-nav-link active group flex items-center px-3 py-2 text-sm font-medium rounded-md">设置与启动</a>
<a href="#" id="nav-fulltext-workbench" data-view="fulltext-workbench" class="sidebar-link sub-nav-link group flex items-center px-3 py-2 text-sm font-medium rounded-md">审核工作台</a>
<a href="#" id="nav-fulltext-results" data-view="fulltext-results" class="sidebar-link sub-nav-link group flex items-center px-3 py-2 text-sm font-medium rounded-md">复筛结果</a>
</div>
</div>
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700">5. 全文解析与数据提取</a>
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700">6. 数据综合分析与报告</a>
</nav>
</aside>
<!-- 主内容区 -->
<div class="main-content flex-grow flex flex-col w-full">
<header class="bg-white shadow-sm flex-shrink-0 z-10 p-4 border-b">
<h1 id="header-title" class="text-xl font-bold">全文复筛 / 设置与启动</h1>
</header>
<div id="view-container" class="flex-grow p-6 overflow-auto">
<!-- Placeholder for Title views, not functional -->
<div id="title-settings-view" class="view"></div>
<div id="title-workbench-view" class="view"></div>
<div id="title-results-view" class="view"></div>
<!-- 全文复筛 - 设置与启动 -->
<div id="fulltext-settings-view" class="view active">
<h2 class="text-2xl font-bold mb-4">全文复筛 - 设置与启动</h2>
<div class="mb-6 bg-white p-6 rounded-lg shadow space-y-4">
<div class="flex justify-between items-center"><h3 class="text-lg font-semibold">筛选标准 (来自研究方案)</h3><button class="text-sm text-sky-600 hover:underline" onclick="alert('弹出模态框允许临时调整标准')">[调整本次筛选标准]</button></div>
<div class="text-sm text-gray-600 grid grid-cols-2 gap-4 pt-4 border-t"> <div class="space-y-1"><h4 class="font-bold">PICO 标准</h4><p>P: 2型糖尿病成人患者</p><p>I: SGLT2抑制剂</p><p>C: 安慰剂或其他常规降糖疗法</p><p>S: 随机对照试验 (RCT)</p></div><div class="space-y-1"><h4 class="font-bold">纳入/排除标准</h4><ul class="list-disc list-inside"><li class="text-green-600">纳入:英文</li><li class="text-red-600">排除:病例报告, 综述</li></ul><p id="fulltext-criteria-adjustment-notice" class="hidden mt-2 text-orange-600 font-semibold">注意:本次筛选正在使用临时调整的标准。</p></div></div>
</div>
<div id="fulltext-independent-start" class="hidden bg-white p-6 rounded-lg shadow mb-6">
<h3 class="text-lg font-semibold mb-4">导入待复筛文献</h3>
<div class="grid grid-cols-2 gap-6">
<div class="text-center p-6 border rounded-lg">
<h4 class="font-bold text-lg mb-3">上传 Excel 文件</h4>
<p class="text-sm text-gray-500 mb-4">上传包含文献ID和标题的列表。</p>
<div class="flex items-center justify-center space-x-2"><button class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-2 px-4 rounded-lg">选择文件</button><a href="#" class="text-sky-600 hover:text-sky-800 font-semibold text-sm">下载模板</a></div>
</div>
<div class="text-center p-6 border rounded-lg">
<h4 class="font-bold text-lg mb-3">从知识库添加</h4>
<p class="text-sm text-gray-500 mb-4">从您已有的知识库中选择文献。</p>
<button onclick="addFromKB()" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-2 px-4 rounded-lg">选择文献</button>
</div>
</div>
</div>
<div id="fulltext-acquisition-step">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">全文获取与管理 (<span id="fulltext-count">0</span>篇)</h3>
<button onclick="addFromKB()" class="bg-blue-100 text-blue-700 font-semibold py-2 px-4 rounded-lg text-sm hover:bg-blue-200">+ 从知识库添加</button>
</div>
<div class="bg-white rounded-lg shadow overflow-hidden mb-6">
<table class="min-w-full divide-y divide-gray-200 text-sm">
<thead class="bg-gray-50"><tr><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase">文献ID</th><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase">文献标题</th><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase">获取状态</th><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase">操作</th></tr></thead>
<tbody id="fulltext-list-body"></tbody>
</table>
</div>
<div class="text-center">
<!-- V2: Button always enabled for prototype demonstration -->
<button id="start-fulltext-screening-btn" class="bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-8 rounded-lg shadow">开始全文复筛</button>
<p id="fulltext-ready-hint" class="hidden text-sm text-red-500 mt-2">(实际应用中需确保所有文献全文均已就绪)</p>
</div>
</div>
</div>
<!-- 全文复筛 - 审核工作台 -->
<div id="fulltext-workbench-view" class="view">
<details class="bg-white rounded-lg shadow p-4 mb-6" open> <summary class="font-semibold cursor-pointer text-lg">▼ 查看当前筛选标准</summary><div class="mt-4 pt-4 border-t text-sm text-gray-600 grid grid-cols-2 gap-4"> <div class="space-y-1"><h4 class="font-bold">PICO 标准</h4><p>P: 2型糖尿病成人患者</p><p>I: SGLT2抑制剂</p><p>C: 安慰剂或其他常规降糖疗法</p><p>S: 随机对照试验 (RCT)</p></div><div class="space-y-1"><h4 class="font-bold">纳入/排除标准</h4><ul class="list-disc list-inside"><li class="text-green-600">纳入:英文</li><li class="text-red-600">排除:病例报告, 综述</li></ul><p id="fulltext-criteria-adjustment-notice-workbench" class="hidden mt-2 text-orange-600 font-semibold">注意:本次筛选正在使用临时调整的标准。</p></div></div></details>
<div class="bg-white rounded-lg shadow overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 text-sm">
<thead class="bg-gray-50">
<tr>
<th rowspan="2" class="px-2 py-3 w-8"></th>
<th rowspan="2" class="px-3 py-3 text-left font-medium text-gray-500 uppercase tracking-wider w-24">文献ID</th>
<th rowspan="2" class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider w-32">研究ID</th>
<th rowspan="2" class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider w-1/4">文献来源</th>
<th colspan="5" class="px-4 py-2 text-center font-medium text-gray-500 uppercase tracking-wider border-l border-r">DS 判断</th>
<th colspan="5" class="px-4 py-2 text-center font-medium text-gray-500 uppercase tracking-wider border-r">Q3 判断</th>
<th rowspan="2" class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">冲突状态</th>
<th rowspan="2" class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">最终决策</th>
</tr>
<tr>
<th class="px-2 py-2 font-medium text-gray-500 border-l">P</th><th class="px-2 py-2 font-medium text-gray-500">I</th><th class="px-2 py-2 font-medium text-gray-500">C</th><th class="px-2 py-2 font-medium text-gray-500">S</th><th class="px-2 py-2 font-medium text-gray-500 border-r">结论</th>
<th class="px-2 py-2 font-medium text-gray-500">P</th><th class="px-2 py-2 font-medium text-gray-500">I</th><th class="px-2 py-2 font-medium text-gray-500">C</th><th class="px-2 py-2 font-medium text-gray-500">S</th><th class="px-2 py-2 font-medium text-gray-500 border-r">结论</th>
</tr>
</thead>
<tbody id="fulltext-screening-table-body" class="bg-white divide-y divide-gray-200"></tbody>
</table>
</div>
<div class="mt-6 flex justify-end">
<button onclick="completeFulltextScreening()" class="bg-green-600 text-white font-bold py-2 px-6 rounded-lg shadow hover:bg-green-700">完成全文复筛 (模拟)</button>
</div>
</div>
<!-- 全文复筛 - 复筛结果 -->
<div id="fulltext-results-view" class="view">
<h2 class="text-2xl font-bold mb-6">全文复筛 - 结果</h2>
<div class="grid grid-cols-3 gap-6 mb-8 text-center">
<div class="bg-white p-4 rounded-lg shadow"><div class="text-3xl font-bold" id="fulltext-reviewed-count">100</div><div class="text-gray-500">总计复筛</div></div>
<div class="bg-green-50 p-4 rounded-lg shadow"><div class="text-3xl font-bold text-green-600" id="fulltext-included-count">55</div><div class="text-gray-500">最终纳入</div></div>
<div class="bg-red-50 p-4 rounded-lg shadow"><div class="text-3xl font-bold text-red-600" id="fulltext-excluded-count">45</div><div class="text-gray-500">排除</div></div>
</div>
<div class="mb-8 bg-white p-6 rounded-lg shadow">
<h3 class="text-lg font-semibold mb-4">排除原因统计 (模拟)</h3>
<ul class="text-sm text-gray-600 space-y-1 list-disc list-inside">
<li>排除文献总数: n=<span id="fulltext-prisma-total-excluded">45</span></li>
<li>非随机对照研究: n=<span id="fulltext-prisma-s-excluded">5</span></li>
<li>非目标人群 (P): n=<span id="fulltext-prisma-p-excluded">7</span></li>
<li>干预/对照不符 (I/C): n=<span id="fulltext-prisma-ic-excluded">18</span></li>
<li>结局指标不符 (O): n=<span id="fulltext-prisma-o-excluded">9</span></li>
<li>其他原因 (如数据不全): n=<span id="fulltext-prisma-other-excluded">6</span></li>
</ul>
</div>
<div class="bg-white rounded-lg shadow p-6">
<div class="border-b border-gray-200 mb-4">
<nav id="fulltext-result-tabs" class="-mb-px flex space-x-8" aria-label="Tabs">
<button data-tab="included" class="result-tab whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm border-sky-500 text-sky-600 active" onclick="switchResultTab('fulltext', 'included')">最终纳入 (<span id="fulltext-included-tab-count">55</span>)</button>
<button data-tab="excluded" class="result-tab whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300" onclick="switchResultTab('fulltext', 'excluded')">排除 (<span id="fulltext-excluded-tab-count">45</span>)</button>
</nav>
</div>
<div class="flex justify-between items-center mb-4">
<input type="text" id="fulltext-result-search" placeholder="搜索文献..." class="w-1/3 p-2 border rounded text-sm">
<button class="bg-gray-700 hover:bg-gray-800 text-white font-bold py-2 px-4 rounded-lg text-sm">↓ 导出为Excel</button>
</div>
<div class="overflow-x-auto">
<table id="fulltext-results-table" class="min-w-full divide-y divide-gray-200 text-sm">
<!-- Fix: Add ID to thead -->
<thead id="fulltext-results-table-head" class="bg-gray-50">
<!-- Header row dynamically generated -->
</thead>
<tbody id="fulltext-results-table-body" class="bg-white divide-y divide-gray-200">
<!-- V2: Simulated Result Rows -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 任务状态面板 (模态框) -->
<div id="task-status-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center">
<div class="modal-backdrop absolute inset-0"></div><div class="bg-white rounded-lg shadow-xl p-8 w-full max-w-lg z-10"><h2 id="task-modal-title" class="text-2xl font-bold text-center mb-4">AI处理进行中...</h2><div id="progress-bar" class="w-full bg-gray-200 rounded-full h-4 mb-4"><div id="progress-bar-inner" class="bg-sky-500 h-4 rounded-full" style="width: 0%"></div></div><p id="progress-text" class="text-center text-gray-500 mb-6">已处理 0 / 0</p><button id="show-workbench-btn" class="hidden w-full bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-lg">处理完成!进入审核工作台</button></div>
</div>
<!-- 双视图原文审查 (模态框 V7) -->
<div id="review-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center p-8">
<div class="modal-backdrop absolute inset-0" onclick="closeReviewModal()"></div><div class="bg-white rounded-lg shadow-xl w-full max-w-6xl h-[90vh] flex flex-col z-10"> <div class="flex justify-between items-center p-4 border-b"><h2 id="modal-title" class="text-xl font-bold">原文审查</h2><button onclick="closeReviewModal()" class="text-gray-500 hover:text-gray-800 text-2xl">&times;</button></div><div class="flex-grow flex overflow-hidden"><div class="w-1/2 p-6 overflow-y-auto border-r"><div id="modal-meta" class="mb-4 border-b pb-4"><h3 id="modal-doc-title" class="font-bold text-lg"></h3><p id="modal-doc-authors" class="text-sm text-gray-600"></p><p id="modal-doc-journal" class="text-sm text-gray-500"></p></div><h3 id="modal-text-source-title" class="font-bold mb-2">摘要/全文</h3><div id="modal-text-content" class="text-gray-700 leading-relaxed text-sm prose max-w-none"></div></div><div class="w-1/2 p-6 overflow-y-auto bg-gray-50"><h3 id="modal-dimension-title" class="font-bold mb-4 text-lg">PICO(S) 判断依据</h3><div id="modal-reasoning" class="space-y-6"></div></div></div></div>
</div>
<script>
// --- STATE MANAGEMENT ---
// Using placeholder data assuming title screening produced some results
let fulltextDocs = [
{ id: 1, pmid: `PMID1234501`, study_id: `AuthorA1 2021`, source: `J Med 2021;11(1):e124`, title: `第1篇研究...`, final_decision: '待定', decision_method: '待定', pdf_status: undefined, model_a: {p: '✓', i: '✓', c:'✓', s:'✓', conclusion: '纳入', evidence: {p:'成人',i:'药物A',c:'安慰剂',s:'RCT'}, p_reason:"...", i_reason:"...", c_reason:"...", s_reason:"..."}, model_b: {p: '✓', i: '✓', c:'✓', s:'✓', conclusion: '纳入', evidence: {p:'成人',i:'药物A',c:'安慰剂',s:'RCT'}, p_reason:"...", i_reason:"...", c_reason:"...", s_reason:"..."}, exclusion_reason: null},
{ id: 3, pmid: `PMID1234503`, study_id: `AuthorA3 2023`, source: `Lancet 2023;3(1):e125`, title: `第3篇研究...`, final_decision: '待定', decision_method: '待定', pdf_status: undefined, model_a: {p: '✓', i: '✓', c:'?', s:'✓', conclusion: '纳入', evidence: {p:'成人',i:'药物A',c:'常规疗法',s:'RCT'}, p_reason:"...", i_reason:"...", c_reason:"...", s_reason:"..."}, model_b: {p: '✓', i: '✓', c:'?', s:'✓', conclusion: '纳入', evidence: {p:'成人',i:'药物A',c:'常规疗法',s:'RCT'}, p_reason:"...", i_reason:"...", c_reason:"...", s_reason:"..."} , exclusion_reason: null},
{ id: 5, pmid: `PMID1234505`, study_id: `AuthorA5 2025`, source: `BMJ 2025;5(1):e126`, title: `第5篇研究...`, final_decision: '待定', decision_method: '待定', pdf_status: undefined, model_a: {p: '✓', i: '✓', c:'✓', s:'✗', conclusion: '排除', evidence: {p:'成人',i:'药物A',c:'安慰剂',s:'观察性'}, p_reason:"...", i_reason:"...", c_reason:"...", s_reason:"..."}, model_b: {p: '✓', i: '✓', c:'✓', s:'✓', conclusion: '纳入', evidence: {p:'成人',i:'药物A',c:'安慰剂',s:'RCT'}, p_reason:"...", i_reason:"...", c_reason:"...", s_reason:"..."} , exclusion_reason: "非RCT"},
{ id: 7, pmid: `PMID1234507`, study_id: `AuthorA7 2022`, source: `Diabetes Care 2022;45(2):e127`, title: `第7篇研究...`, final_decision: '待定', decision_method: '待定', pdf_status: undefined, model_a: {p: '✓', i: '✓', c:'✓', s:'✓', conclusion: '纳入', evidence: {p:'成人T2DM',i:'药物A',c:'安慰剂',s:'RCT'}}, model_b: {p: '✓', i: '✓', c:'✓', s:'✓', conclusion: '纳入', evidence: {p:'成人T2DM',i:'药物A',c:'安慰剂',s:'RCT'}} , exclusion_reason: null},
{ id: 9, pmid: `PMID1234509`, study_id: `AuthorA9 2024`, source: `JAMA 2024;331(3):e128`, title: `第9篇研究...`, final_decision: '待定', decision_method: '待定', pdf_status: undefined, model_a: {p: '✗', i: '✓', c:'✓', s:'✓', conclusion: '排除', evidence: {p:'青少年',i:'药物A',c:'安慰剂',s:'RCT'}}, model_b: {p: '✗', i: '✓', c:'✓', s:'✓', conclusion: '排除', evidence: {p:'青少年',i:'药物A',c:'安慰剂',s:'RCT'}} , exclusion_reason: "非目标人群"},
];
let currentViewId = 'fulltext-settings'; // Start at fulltext settings
let simulatedFulltextResults = []; // For results page simulation
// --- DOM ELEMENTS ---
const views = {};
const navLinks = {};
document.querySelectorAll('.view').forEach(v => {
if(v.id.includes('fulltext')) { views[v.id.replace('-view','')] = v; }
});
document.querySelectorAll('.sub-nav-link').forEach(l => {
if(l.id.includes('fulltext')) { navLinks[l.id.replace('nav-','')] = l; }
});
const headerTitle = document.getElementById('header-title');
const taskStatusModal = document.getElementById('task-status-modal');
const reviewModal = document.getElementById('review-modal');
const fulltextAcquisitionStep = document.getElementById('fulltext-acquisition-step');
const fulltextListBody = document.getElementById('fulltext-list-body');
const startFulltextScreeningBtn = document.getElementById('start-fulltext-screening-btn');
const fulltextIndependentStart = document.getElementById('fulltext-independent-start');
const fulltextReadyHint = document.getElementById('fulltext-ready-hint');
const fulltextScreeningTableBody = document.getElementById('fulltext-screening-table-body');
const fulltextReviewedCount = document.getElementById('fulltext-reviewed-count');
const fulltextIncludedCount = document.getElementById('fulltext-included-count');
const fulltextExcludedCount = document.getElementById('fulltext-excluded-count');
const fulltextIncludedTabCount = document.getElementById('fulltext-included-tab-count');
const fulltextExcludedTabCount = document.getElementById('fulltext-excluded-tab-count');
const fulltextResultsTableBody = document.getElementById('fulltext-results-table-body');
const ftPrismaTotalExcluded = document.getElementById('fulltext-prisma-total-excluded');
const ftPrismaSExcluded = document.getElementById('fulltext-prisma-s-excluded');
const ftPrismaPExcluded = document.getElementById('fulltext-prisma-p-excluded');
const ftPrismaICExcluded = document.getElementById('fulltext-prisma-ic-excluded');
const ftPrismaOExcluded = document.getElementById('fulltext-prisma-o-excluded');
const ftPrismaOtherExcluded = document.getElementById('fulltext-prisma-other-excluded');
// --- SIMULATED DATA ---
function generateSimulatedFulltextResultsData() { /* ... unchanged V2 version ... */ simulatedFulltextResults = []; const totalSimulated = 100; const includedSimulated = 55; for (let i = 1; i <= totalSimulated; i++) { const isIncluded = i <= includedSimulated; const final_decision = isIncluded ? '纳入' : '排除'; const decision_method = Math.random() > 0.6 ? '人工审核' : (isIncluded ? 'AI全文纳入' : 'AI全文排除'); let exclusion_reason = null; if (!isIncluded) { const reasons = ["非RCT", "非目标人群", "干预/对照不符", "结局指标不符", "数据不全"]; exclusion_reason = reasons[Math.floor(Math.random() * reasons.length)]; } simulatedFulltextResults.push({ id: i + 3000, pmid: `PMID${3234500 + i}`, study_id: `FullSim${i} ${2022}`, source: `Full J ${2022};${i % 10 + 1}:f${i}`, title: `模拟全文文献 ${i}: 进一步研究`, final_decision: final_decision, decision_method: decision_method, exclusion_reason: exclusion_reason, }); } }
// --- INITIALIZATION ---
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('fulltext-count').textContent = fulltextDocs.length;
renderFulltextList();
generateSimulatedFulltextResultsData();
showView('fulltext-settings');
});
// --- NAVIGATION & VIEW MANAGEMENT ---
Object.keys(navLinks).forEach(key => {
navLinks[key].addEventListener('click', (e) => {
e.preventDefault();
const viewId = navLinks[key].getAttribute('data-view');
// V2: Allow navigating to results anytime for demo
// if(navLinks[key].classList.contains('disabled')) return;
showView(viewId);
});
});
function showView(viewId) {
currentViewId = viewId;
Object.values(views).forEach(v => v.classList.remove('active'));
if (views[viewId]) {
views[viewId].classList.add('active');
} else {
console.error("View not found:", viewId);
views['fulltext-settings'].classList.add('active'); // Fallback
viewId = 'fulltext-settings';
}
const stage = 'fulltext';
const subView = viewId.split('-')[1];
const stageTitles = { 'fulltext': '全文复筛' };
const subViewTitles = { 'settings': '设置与启动', 'workbench': '审核工作台', 'results': '复筛结果' };
headerTitle.textContent = `${stageTitles[stage]} / ${subViewTitles[subView]}`;
Object.values(navLinks).forEach(link => link.classList.remove('active'));
if (navLinks[viewId]) navLinks[viewId].classList.add('active');
if (viewId === 'fulltext-settings') {
renderFulltextList();
}
// V2: Render results page whenever navigated to
if (viewId === 'fulltext-results') renderResultsPage('fulltext');
}
// --- FULLTEXT SCREENING ---
function renderFulltextList() {
fulltextListBody.innerHTML = fulltextDocs.map((doc, index) => {
let status, action;
if (!doc.pdf_status) {
const statusRand = Math.random();
if (statusRand < 0.6) doc.pdf_status = 'ready';
else if (statusRand < 0.8) doc.pdf_status = 'fetching';
else doc.pdf_status = 'failed';
}
switch(doc.pdf_status) {
case 'ready': status = `<span class="text-green-600 font-semibold">获取成功</span>`; action = `<span class="text-gray-400">已就绪</span>`; break;
case 'fetching': status = `<span class="text-blue-500">获取中...</span>`; action = `<span class="text-gray-400">请稍候</span>`; break;
default: status = `<span class="text-red-600 font-semibold">获取失败</span>`; action = `<button class="text-sky-600 hover:underline" onclick="uploadFulltext(${doc.id})">上传全文</button>`;
}
return `<tr><td class="px-4 py-3">${doc.pmid}</td><td class="px-4 py-3">${doc.title}</td><td class="px-4 py-3">${status}</td><td class="px-4 py-3">${action}</td></tr>`;
}).join('');
checkFulltextReady();
}
function uploadFulltext(docId) {
const doc = fulltextDocs.find(d => d.id === docId);
if(doc) {
doc.pdf_status = 'ready';
alert(`模拟为文献 ${doc.pmid} 上传全文成功!`);
}
renderFulltextList();
}
function addFromKB() { /* ... unchanged V1 version ... */ const newDocIdStart = 2000; for(let i=1; i<=3; i++){ fulltextDocs.push({ id: newDocIdStart + i, pmid: `KB${newDocIdStart+i}`, study_id: `KB_Author ${2022+i}`, source: `Know Base J ${2022+i}`, title: `知识库文献 ${i}`, final_decision: '待定', decision_method: '待定', pdf_status: 'ready', model_a: {p: '✓', i: '✓', c:'?', s:'✓', conclusion: '纳入', evidence:{p:'...',i:'...',c:'...',s:'...'}}, model_b: {p: '✓', i: '✓', c:'?', s:'✓', conclusion: '纳入', evidence:{p:'...',i:'...',c:'...',s:'...'}} }); } document.getElementById('fulltext-count').textContent = fulltextDocs.length; renderFulltextList(); alert("已从知识库添加3篇文献。"); }
function checkFulltextReady() {
const allReady = fulltextDocs.every(doc => doc.pdf_status === 'ready');
// V2: Logic removed, button always enabled for demo
startFulltextScreeningBtn.disabled = false; // Always enable for demo
if(allReady) {
startFulltextScreeningBtn.classList.add('bg-green-600', 'hover:bg-green-700');
fulltextReadyHint.classList.add('hidden');
} else {
startFulltextScreeningBtn.classList.add('bg-gray-400', 'cursor-not-allowed'); // Keep visual cue
startFulltextScreeningBtn.classList.remove('bg-green-600', 'hover:bg-green-700');
fulltextReadyHint.classList.remove('hidden');
}
}
startFulltextScreeningBtn.addEventListener('click', () => {
taskStatusModal.classList.remove('hidden');
document.getElementById('task-modal-title').textContent = 'AI全文复筛进行中...';
let progress = 0;
const totalToProcess = fulltextDocs.length;
document.getElementById('progress-text').textContent = `已处理 0 / ${totalToProcess}`;
document.getElementById('show-workbench-btn').onclick = () => {
taskStatusModal.classList.add('hidden');
showView('fulltext-workbench');
renderTable('fulltext');
};
const interval = setInterval(() => {
progress += Math.ceil(100 / (totalToProcess || 1));
if (progress > 100) progress = 100;
document.getElementById('progress-bar-inner').style.width = `${progress}%`;
document.getElementById('progress-text').textContent = `已处理 ${Math.ceil(progress/100 * totalToProcess)} / ${totalToProcess}`;
if (progress >= 100) {
clearInterval(interval);
fulltextDocs.forEach(doc => {
doc.final_decision = '待定';
doc.model_a.conclusion = Math.random() > 0.3 ? '纳入' : '排除';
doc.model_b.conclusion = Math.random() > 0.25 ? '纳入' : '排除';
doc.model_a.s = Math.random() > 0.1 ? '✓' : '✗';
doc.model_b.s = Math.random() > 0.15 ? '✓' : '✗';
if (doc.model_a.conclusion === '排除' || doc.model_b.conclusion === '排除') {
const reasons = ["非RCT", "非目标人群", "干预/对照不符", "结局指标不符", "数据不全"];
doc.exclusion_reason = reasons[Math.floor(Math.random() * reasons.length)];
} else { doc.exclusion_reason = null; }
});
document.getElementById('show-workbench-btn').classList.remove('hidden');
}
}, 200);
});
// V7: Render Table with Original Header Structure (Applied to Fulltext)
function renderTable(stage) {
if (stage !== 'fulltext') return; // Only render for fulltext stage
const tableBodyId = 'fulltext-screening-table-body';
const tableBody = document.getElementById(tableBodyId);
if (!tableBody) return;
const dataToRender = fulltextDocs; // Use fulltext data
tableBody.innerHTML = dataToRender.map(doc => {
const isConflict = doc.model_a.conclusion !== doc.model_b.conclusion;
const rowId = `${stage}-row-${doc.id}`;
const expandedRowId = `${stage}-expanded-row-${doc.id}`;
const judgeCell = (model, dim) => `<td class="table-cell-judge px-2 py-3 border-l" onclick="openReviewModal(${doc.id}, '${model}', '${dim}', '${stage}')">${{ '✓': '<span class="text-green-600 font-bold">✓</span>', '✗': '<span class="text-red-600 font-bold">✗</span>', '?': '<span class="text-yellow-600 font-bold">?</span>' }[doc[model][dim]]}</td>`;
return `
<tr id="${rowId}" class="${isConflict ? 'table-conflict-row' : ''}">
<td class="px-2 py-3 expand-row-btn" onclick="toggleRow('${expandedRowId}', this)">+</td>
<td class="px-3 py-3 font-mono text-xs text-gray-500">${doc.pmid}</td>
<td class="px-4 py-3 font-semibold text-xs">${doc.study_id}</td>
<td class="px-4 py-3 text-xs text-gray-500 truncate" title="${doc.source}">${doc.source.split('.')[0]}...</td>
<!-- DS Judgements -->
${judgeCell('model_a', 'p')} ${judgeCell('model_a', 'i')} ${judgeCell('model_a', 'c')} ${judgeCell('model_a', 's')}
<td class="px-2 py-3 font-semibold border-r ${doc.model_a.conclusion === '纳入' ? 'text-green-700' : 'text-red-700'}">${doc.model_a.conclusion}</td>
<!-- Q3 Judgements -->
${judgeCell('model_b', 'p')} ${judgeCell('model_b', 'i')} ${judgeCell('model_b', 'c')} ${judgeCell('model_b', 's')}
<td class="px-2 py-3 font-semibold border-r ${doc.model_b.conclusion === '纳入' ? 'text-green-700' : 'text-red-700'}">${doc.model_b.conclusion}</td>
<!-- Status & Decision -->
<td class="px-4 py-3 border-l">${isConflict ? '<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">冲突</span>' : '<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">一致</span>'}</td>
<td class="px-4 py-3 border-l"><select class="rounded-md border-gray-300 text-xs" data-id="${doc.id}" data-stage="${stage}" onchange="updateDecision(this)"><option value="待定" ${doc.final_decision === '待定' ? 'selected' : ''}>待定</option><option value="纳入" ${doc.final_decision === '纳入' ? 'selected' : ''}>纳入</option><option value="排除" ${doc.final_decision === '排除' ? 'selected' : ''}>排除</option></select></td>
</tr>
<tr id="${expandedRowId}" class="expanded-row">
<td colspan="15" class="expanded-row-cell text-xs">
<div class="grid grid-cols-2 gap-4">
<div><strong class="text-gray-600">DS 证据 (基于全文):</strong><dl class="mt-1 evidence-grid"><dt>P:</dt><dd>${doc.model_a.evidence.p} (全文)</dd><dt>I:</dt><dd>${doc.model_a.evidence.i} (全文)</dd><dt>C:</dt><dd>${doc.model_a.evidence.c} (全文)</dd><dt>S:</dt><dd>${doc.model_a.evidence.s} (全文)</dd></dl></div>
<div><strong class="text-gray-600">Q3 证据 (基于全文):</strong><dl class="mt-1 evidence-grid"><dt>P:</dt><dd>${doc.model_b.evidence.p} (全文)</dd><dt>I:</dt><dd>${doc.model_b.evidence.i} (全文)</dd><dt>C:</dt><dd>${doc.model_b.evidence.c} (全文)</dd><dt>S:</dt><dd>${doc.model_b.evidence.s} (全文)</dd></dl></div>
</div>
</td>
</tr>
`;
}).join('');
}
function toggleRow(rowId, btn) { /* ... unchanged ... */ const row = document.getElementById(rowId); const isShown = row.classList.toggle('shown'); btn.textContent = isShown ? '-' : '+'; }
function updateDecision(selectElement) { /* ... unchanged V7 version ... */ const docId = parseInt(selectElement.dataset.id); const stage = selectElement.dataset.stage; const dataSet = stage === 'title' ? literatureData : fulltextDocs; const doc = dataSet.find(d => d.id === docId); if (doc) { doc.final_decision = selectElement.value; if (doc.final_decision !== '待定') { doc.decision_method = '人工审核'; } else { doc.decision_method = '待定'; } } }
function openReviewModal(docId, modelKey, dimensionKey, stage) { /* ... unchanged V7 version ... */ const dataSet = stage === 'title' ? literatureData : fulltextDocs; const doc = dataSet.find(d => d.id === docId); if (!doc) return; const dimensionMap = { p: '人群 (P)', i: '干预 (I)', c: '对照 (C)', s: '研究设计 (S)' }; document.getElementById('modal-title').textContent = `审查: ${doc.title}`; document.getElementById('modal-doc-title').textContent = doc.title; document.getElementById('modal-doc-authors').textContent = doc.authors; document.getElementById('modal-doc-journal').textContent = `${doc.journal} (${doc.pmid})`; const textContentEl = document.getElementById('modal-text-content'); const textSourceTitle = document.getElementById('modal-text-source-title'); let textContent = ""; if (stage === 'fulltext') { textSourceTitle.textContent = "全文片段"; textContent = `<h4 class='font-semibold'>方法部分:</h4><p>...<span class='s-source'>随机对照试验(RCT)</span>...招募了<span class='p-source'>2型糖尿病成人患者</span>...</p> <h4 class='font-semibold mt-4'>干预部分:</h4><p>...患者接受<span class='i-source'>SGLT2药物</span>或<span class='c-source'>安慰剂</span>治疗...</p>`; } else { textSourceTitle.textContent = "摘要"; textContent = doc.abstract; } const sourceSpan = textContent.match(new RegExp(`<span class='${dimensionKey.toLowerCase()}-source'>(.*?)</span>`)); let sourceText = sourceSpan ? sourceSpan[1] : ""; textContent = textContent.replace(/<span class='.-source'>/g, '').replace(/<\/span>/g, ''); if (sourceText) { textContent = textContent.replace(sourceText, `<mark class="abstract-highlight">${sourceText}</mark>`); } textContentEl.innerHTML = textContent; setTimeout(() => { const mark = textContentEl.querySelector('mark'); if (mark) { mark.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }, 100); document.getElementById('modal-dimension-title').textContent = `PICO(S) 判断依据 (基于${stage==='title'?'摘要':'全文'})`; const reasoningContainer = document.getElementById('modal-reasoning'); let reasoningHTML = `<div class="grid grid-cols-2 gap-6">`; ['model_a', 'model_b'].forEach(mKey => { const modelName = mKey === 'model_a' ? 'DeepSeek' : 'Qwen3'; reasoningHTML += `<div class="border rounded-lg p-4 bg-white"><h4 class="font-bold text-gray-700 mb-3">${modelName} 判断</h4><div class="space-y-2 text-sm mb-3">`; ['p', 'i', 'c', 's'].forEach(dim => { const icon = { '✓': '<span class="text-green-600 font-bold">✓ 符合</span>', '✗': '<span class="text-red-600 font-bold">✗ 不符</span>', '?': '<span class="text-yellow-600 font-bold">? 不确定</span>' }[doc[mKey][dim]]; reasoningHTML += `<p><strong>${dimensionMap[dim]}:</strong> ${icon}</p>`; }); reasoningHTML += `</div><p class="text-sm mb-2"><strong>总览结论:</strong> <span class="font-semibold ${doc[mKey].conclusion === '纳入' ? 'text-green-700' : 'text-red-700'}">${doc[mKey].conclusion}</span></p><p class="text-sm text-gray-600"><strong>"${dimensionMap[dimensionKey]}" 判断理由:</strong> ${doc[mKey][`${dimensionKey}_reason`]}</p></div>`; }); reasoningHTML += `</div>`; reasoningContainer.innerHTML = reasoningHTML; reviewModal.classList.remove('hidden'); }
function closeReviewModal() { reviewModal.classList.add('hidden'); }
// --- RESULTS PAGES (V2 Independent Data) ---
function renderResultsPage(stage) {
// V2: Use simulated data for counts and table FOR FULLTEXT as well
const dataSet = stage === 'title' ? simulatedResultsData : simulatedFulltextResults;
const included = dataSet.filter(doc => doc.final_decision === '纳入');
const excluded = dataSet.filter(doc => doc.final_decision === '排除');
const totalDocsInStage = dataSet.length;
if (stage === 'title') {
// ... (title result logic using simulatedResultsData - unchanged from V1 of this specific prototype) ...
} else { // fulltext
fulltextReviewedCount.textContent = totalDocsInStage;
fulltextIncludedCount.textContent = included.length;
fulltextExcludedCount.textContent = excluded.length;
fulltextIncludedTabCount.textContent = included.length;
fulltextExcludedTabCount.textContent = excluded.length;
// Simulate Prisma for fulltext results
ftPrismaTotalExcluded.textContent = excluded.length;
ftPrismaSExcluded.textContent = excluded.filter(d => d.exclusion_reason === '非RCT').length;
ftPrismaPExcluded.textContent = excluded.filter(d => d.exclusion_reason === '非目标人群').length;
ftPrismaICExcluded.textContent = excluded.filter(d => d.exclusion_reason === '干预/对照不符').length;
ftPrismaOExcluded.textContent = excluded.filter(d => d.exclusion_reason === '结局指标不符').length;
ftPrismaOtherExcluded.textContent = excluded.filter(d => !["非RCT", "非目标人群", "干预/对照不符", "结局指标不符"].includes(d.exclusion_reason)).length;
renderResultsTable('fulltext', document.querySelector('#fulltext-result-tabs .result-tab.active')?.dataset.tab || 'included'); // V2: Added fallback
}
}
function switchResultTab(stage, tab) {
if (stage !== 'fulltext') return; // Only handle fulltext stage
const tabContainerId = 'fulltext-result-tabs';
document.querySelectorAll(`#${tabContainerId} button`).forEach(btn => { btn.classList.remove('active', 'border-sky-500', 'text-sky-600'); btn.classList.add('border-transparent', 'text-gray-500'); });
const activeBtn = document.querySelector(`#${tabContainerId} button[data-tab="${tab}"]`);
if (activeBtn) { // V2: Check if button exists
activeBtn.classList.add('active', 'border-sky-500', 'text-sky-600');
activeBtn.classList.remove('border-transparent', 'text-gray-500');
renderResultsTable('fulltext', tab);
}
}
// V7: Render Enhanced Results Table (Now uses appropriate data source)
function renderResultsTable(stage, tab) {
if (stage !== 'fulltext') return; // Only handle fulltext stage
const tableBodyId = 'fulltext-results-table-body';
const tableBody = document.getElementById(tableBodyId);
const tableHead = document.getElementById('fulltext-results-table-head'); // V2: Get head by ID
if (!tableBody || !tableHead) return;
// V2 Logic: Use simulated data
const dataSet = simulatedFulltextResults;
const filteredData = dataSet.filter(doc => doc.final_decision === (tab === 'included' ? '纳入' : '排除'));
// Simplified header for fulltext results
let headerHTML = `<tr><th class="px-3 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">文献ID</th><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">研究ID</th><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">文献来源</th><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">标题</th><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">最终决策</th><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">决策方式</th><th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">排除理由</th></tr>`;
tableHead.innerHTML = headerHTML;
tableBody.innerHTML = filteredData.map(doc => {
return `<tr>
<td class="px-3 py-3 font-mono text-xs">${doc.pmid}</td><td class="px-4 py-3 text-xs">${doc.study_id}</td>
<td class="px-4 py-3 text-xs truncate" title="${doc.source}">${doc.source}</td><td class="px-4 py-3 text-xs font-medium">${doc.title}</td>
<td class="px-4 py-3"><span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${doc.final_decision === '纳入' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}">${doc.final_decision}</span></td>
<td class="px-4 py-3 text-xs text-gray-500">${doc.decision_method || 'N/A'}</td>
<td class="px-4 py-3 text-xs text-gray-500">${doc.final_decision === '排除' ? (doc.exclusion_reason || '全文排除-未指定') : 'N/A'}</td></tr>`;
}).join('');
}
function toggleAbstract(link, event) { /* ... unchanged ... */ event.preventDefault(); const cell = link.closest('.abstract-cell'); const isExpanded = cell.classList.toggle('expanded'); link.textContent = isExpanded ? '收起' : '显示更多'; }
// V2: Simplified completion logic for demo purposes
function completeFulltextScreening() {
// Just navigate to results page which uses simulated data
showView('fulltext-results');
}
</script>
</body>
</html>

View File

@@ -0,0 +1,450 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>标题摘要初筛原型 V4</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/heroicons/2.1.3/24/outline/css/heroicons.min.css" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; }
.sidebar { background-color: #f8fafc; }
.sidebar-link.active { background-color: #e0f2fe; color: #0c4a6e; font-weight: 600; }
.main-content { margin-left: 16rem; }
.view { display: none; }
.view.active { display: block; }
.modal-backdrop { background-color: rgba(0, 0, 0, 0.5); }
.table-conflict-row > td:not(.expanded-row-cell) { background-color: #fee2e2; }
.table-cell-judge { cursor: pointer; text-align: center; }
.abstract-highlight { background-color: #fef9c3; }
.nav-parent { font-weight: bold; color: #374151; }
.nav-child { padding-left: 1.5rem; }
.sub-nav-link { color: #6b7280; }
.sub-nav-link.active { color: #0c4a6e; font-weight: 600; background-color: #f0f9ff; }
.sidebar-link.disabled { color: #9ca3af; cursor: not-allowed; background-color: transparent !important; }
.sidebar-link.disabled:hover { background-color: transparent !important; }
.result-tab.active { border-bottom-color: #0ea5e9; color: #0ea5e9; font-weight: 600;}
.expand-row-btn { cursor: pointer; width: 20px; text-align: center; }
.expanded-row { display: none; background-color: #f9fafb; }
.expanded-row.shown { display: table-row; }
.expanded-row-cell { padding: 0.75rem 1rem; border-top: 1px solid #e5e7eb; }
.evidence-grid { display: grid; grid-template-columns: auto 1fr; gap: 0.25rem 1rem; }
.evidence-grid dt { font-weight: 500; color: #4b5563; text-align: right;}
.evidence-grid dd { color: #1f2937; }
.abstract-cell .full-abstract { display: none; }
.abstract-cell.expanded .short-abstract { display: none; }
.abstract-cell.expanded .full-abstract { display: block; }
/* V4 specific style for results table PICS */
.results-pics-cell { font-size: 0.7rem; line-height: 1.2; color: #4b5563; }
.results-pics-cell strong { color: #1f2937; }
</style>
</head>
<body class="bg-gray-100 text-gray-800">
<div class="h-screen flex">
<!-- 模拟的左侧主导航 -->
<aside id="sidebar" class="sidebar w-64 border-r border-gray-200 p-4 flex-shrink-0 flex flex-col fixed h-full">
<div class="text-xl font-bold text-gray-800 mb-8">AI文献平台</div>
<nav class="flex-grow space-y-2">
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700">1. 研究方案生成</a>
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700">2. 智能文献检索</a>
<!-- 标题摘要初筛 Section -->
<div class="space-y-1">
<span class="nav-parent group flex items-center px-3 py-2 text-sm rounded-md text-gray-800">3. 标题摘要初筛</span>
<div class="space-y-1 nav-child">
<a href="#" id="nav-title-settings" data-view="title-settings" class="sidebar-link sub-nav-link active group flex items-center px-3 py-2 text-sm font-medium rounded-md">设置与启动</a>
<a href="#" id="nav-title-workbench" data-view="title-workbench" class="sidebar-link sub-nav-link group flex items-center px-3 py-2 text-sm font-medium rounded-md">审核工作台</a>
<a href="#" id="nav-title-results" data-view="title-results" class="sidebar-link sub-nav-link group flex items-center px-3 py-2 text-sm font-medium rounded-md">初筛结果</a>
</div>
</div>
<!-- Full-text Rescreening Section (Disabled Placeholder) -->
<div class="space-y-1">
<span class="nav-parent group flex items-center px-3 py-2 text-sm rounded-md text-gray-400">4. 全文复筛</span>
<div class="space-y-1 nav-child">
<a href="#" class="sidebar-link sub-nav-link disabled group flex items-center px-3 py-2 text-sm font-medium rounded-md">设置与启动</a>
<a href="#" class="sidebar-link sub-nav-link disabled group flex items-center px-3 py-2 text-sm font-medium rounded-md">审核工作台</a>
<a href="#" class="sidebar-link sub-nav-link disabled group flex items-center px-3 py-2 text-sm font-medium rounded-md">复筛结果</a>
</div>
</div>
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700">5. 全文解析与数据提取</a>
<a href="#" class="sidebar-link group flex items-center px-3 py-2 text-sm font-medium rounded-md text-gray-700">6. 数据综合分析与报告</a>
</nav>
</aside>
<!-- 主内容区 -->
<div class="main-content flex-grow flex flex-col w-full">
<header class="bg-white shadow-sm flex-shrink-0 z-10 p-4 border-b">
<h1 id="header-title" class="text-xl font-bold">标题摘要初筛 / 设置与启动</h1>
</header>
<div id="view-container" class="flex-grow p-6 overflow-auto">
<!-- 标题摘要初筛 - 设置与启动 -->
<div id="title-settings-view" class="view active">
<!-- content from v7, unchanged -->
<h2 class="text-2xl font-bold mb-4">标题摘要初筛 - 设置与启动</h2><div class="mb-6 bg-white p-6 rounded-lg shadow space-y-4"><div class="flex justify-between items-center"><h3 class="text-lg font-semibold">筛选标准 (来自研究方案)</h3><button class="text-sm text-sky-600 hover:underline" onclick="alert('弹出模态框允许临时调整标准')">[调整本次筛选标准]</button></div><div class="text-sm text-gray-600 grid grid-cols-2 gap-4 pt-4 border-t"><div class="space-y-1"><h4 class="font-bold">PICO 标准</h4><p>P: 2型糖尿病成人患者</p><p>I: SGLT2抑制剂</p><p>C: 安慰剂或其他常规降糖疗法</p><p>S: 随机对照试验 (RCT)</p></div><div class="space-y-1"><h4 class="font-bold">纳入/排除标准</h4><ul class="list-disc list-inside"><li class="text-green-600">纳入:英文</li><li class="text-red-600">排除:病例报告, 综述</li></ul></div></div></div><div class="bg-white p-6 rounded-lg shadow"><h3 class="text-lg font-semibold mb-4">导入文献</h3><div class="text-center p-6 border rounded-lg max-w-md mx-auto"><h4 class="font-bold text-lg mb-3">上传 Excel 文件</h4><p class="text-sm text-gray-500 mb-4">请按模板上传包含文献标题和摘要的列表。</p><div class="flex items-center justify-center space-x-2"><button onclick="importLiterature('excel')" class="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-2 px-4 rounded-lg">选择文件</button><a href="#" class="text-sky-600 hover:text-sky-800 font-semibold text-sm">下载模板</a></div></div><div class="mt-8 text-center"><button id="start-title-screening-btn" disabled class="bg-gray-400 text-white font-bold py-3 px-8 rounded-lg cursor-not-allowed">开始AI初筛</button><p id="title-import-hint" class="text-sm text-red-500 mt-2">请先导入文献</p></div></div>
</div>
<!-- 标题摘要初筛 - 审核工作台 -->
<div id="title-workbench-view" class="view">
<!-- content from v7, unchanged -->
<details class="bg-white rounded-lg shadow p-4 mb-6" open> <summary class="font-semibold cursor-pointer text-lg">▼ 查看当前筛选标准</summary><div class="mt-4 pt-4 border-t text-sm text-gray-600 grid grid-cols-2 gap-4"> <div class="space-y-1"><h4 class="font-bold">PICO 标准</h4><p>P: 2型糖尿病成人患者</p><p>I: SGLT2抑制剂</p><p>C: 安慰剂或其他常规降糖疗法</p><p>S: 随机对照试验 (RCT)</p></div><div class="space-y-1"><h4 class="font-bold">纳入/排除标准</h4><ul class="list-disc list-inside"><li class="text-green-600">纳入:英文</li><li class="text-red-600">排除:病例报告, 综述</li></ul><p id="title-criteria-adjustment-notice" class="hidden mt-2 text-orange-600 font-semibold">注意:本次筛选正在使用临时调整的标准。</p></div></div></details><div class="bg-white rounded-lg shadow overflow-x-auto"><table class="min-w-full divide-y divide-gray-200 text-sm"><thead class="bg-gray-50"><tr><th rowspan="2" class="px-2 py-3 w-8"></th><th rowspan="2" class="px-3 py-3 text-left font-medium text-gray-500 uppercase tracking-wider w-24">文献ID</th><th rowspan="2" class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider w-32">研究ID</th><th rowspan="2" class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider w-1/4">文献来源</th><th colspan="5" class="px-4 py-2 text-center font-medium text-gray-500 uppercase tracking-wider border-l border-r">DS 判断</th><th colspan="5" class="px-4 py-2 text-center font-medium text-gray-500 uppercase tracking-wider border-r">Q3 判断</th><th rowspan="2" class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">冲突状态</th><th rowspan="2" class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">最终决策</th></tr><tr><th class="px-2 py-2 font-medium text-gray-500 border-l">P</th><th class="px-2 py-2 font-medium text-gray-500">I</th><th class="px-2 py-2 font-medium text-gray-500">C</th><th class="px-2 py-2 font-medium text-gray-500">S</th><th class="px-2 py-2 font-medium text-gray-500 border-r">结论</th><th class="px-2 py-2 font-medium text-gray-500">P</th><th class="px-2 py-2 font-medium text-gray-500">I</th><th class="px-2 py-2 font-medium text-gray-500">C</th><th class="px-2 py-2 font-medium text-gray-500">S</th><th class="px-2 py-2 font-medium text-gray-500 border-r">结论</th></tr></thead><tbody id="title-screening-table-body" class="bg-white divide-y divide-gray-200"></tbody></table></div><div class="mt-6 flex justify-end"><button id="complete-title-screening-btn" onclick="showView('title-results')" class="bg-green-600 text-white font-bold py-2 px-6 rounded-lg shadow hover:bg-green-700">完成标题摘要初筛 (模拟)</button></div>
</div>
<!-- 标题摘要初筛 - 初筛结果 (V7 Enhanced & V4 Update) -->
<div id="title-results-view" class="view">
<h2 class="text-2xl font-bold mb-6">标题摘要初筛 - 结果</h2>
<div class="grid grid-cols-3 gap-6 mb-8 text-center">
<div class="bg-white p-4 rounded-lg shadow"><div class="text-3xl font-bold" id="title-total-count">1000</div><div class="text-gray-500">总计筛选</div></div>
<div class="bg-green-50 p-4 rounded-lg shadow"><div class="text-3xl font-bold text-green-600" id="title-included-count">120</div><div class="text-gray-500">初步纳入</div></div>
<div class="bg-red-50 p-4 rounded-lg shadow"><div class="text-3xl font-bold text-red-600" id="title-excluded-count">880</div><div class="text-gray-500">排除</div></div>
</div>
<div class="mb-8 bg-white p-6 rounded-lg shadow">
<h3 class="text-lg font-semibold mb-4">排除原因统计 (模拟)</h3>
<ul class="text-sm text-gray-600 space-y-1 list-disc list-inside">
<li>排除文献总数: n=<span id="prisma-total-excluded">880</span></li>
<li>非随机对照研究: n=<span id="prisma-s-excluded">250</span></li>
<li>非目标人群 (P): n=<span id="prisma-p-excluded">180</span></li>
<li>干预措施不符 (I): n=<span id="prisma-i-excluded">100</span></li>
<li>对照措施不符 (C): n=<span id="prisma-c-excluded">90</span></li>
<li>重复研究: n=<span id="prisma-duplicate-excluded">150</span></li>
<li>其他 (综述、病例报告等): n=<span id="prisma-other-excluded">110</span></li>
</ul>
</div>
<div class="bg-white rounded-lg shadow p-6">
<div class="border-b border-gray-200 mb-4">
<nav id="title-result-tabs" class="-mb-px flex space-x-8" aria-label="Tabs">
<button data-tab="included" class="result-tab whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm border-sky-500 text-sky-600 active" onclick="switchResultTab('title', 'included')">初步纳入 (<span id="title-included-tab-count">120</span>)</button>
<button data-tab="excluded" class="result-tab whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300" onclick="switchResultTab('title', 'excluded')">排除 (<span id="title-excluded-tab-count">880</span>)</button>
</nav>
</div>
<div class="flex justify-between items-center mb-4">
<input type="text" id="title-result-search" placeholder="搜索文献..." class="w-1/3 p-2 border rounded text-sm">
<button class="bg-gray-700 hover:bg-gray-800 text-white font-bold py-2 px-4 rounded-lg text-sm">↓ 导出为Excel</button>
</div>
<div class="overflow-x-auto">
<!-- V4: Updated Results Table Header -->
<table class="min-w-full divide-y divide-gray-200 text-sm">
<thead class="bg-gray-50">
<tr>
<th class="px-3 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">文献ID</th>
<th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">研究ID</th>
<th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">文献来源</th>
<th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">标题</th>
<th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">摘要</th>
<th class="px-2 py-3 text-center font-medium text-gray-500 uppercase tracking-wider">P</th>
<th class="px-2 py-3 text-center font-medium text-gray-500 uppercase tracking-wider">I</th>
<th class="px-2 py-3 text-center font-medium text-gray-500 uppercase tracking-wider">C</th>
<th class="px-2 py-3 text-center font-medium text-gray-500 uppercase tracking-wider">S</th>
<th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">结论</th>
<th class="px-4 py-3 text-left font-medium text-gray-500 uppercase tracking-wider">排除理由</th>
</tr>
</thead>
<tbody id="title-results-table-body" class="bg-white divide-y divide-gray-200">
<!-- V4: Simulated Result Rows -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Placeholder for Fulltext views, not functional in this prototype -->
<div id="fulltext-settings-view" class="view"></div>
<div id="fulltext-workbench-view" class="view"></div>
<div id="fulltext-results-view" class="view"></div>
</div>
</div>
</div>
<!-- 任务状态面板 (模态框) -->
<div id="task-status-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center">
<!-- content from v7, unchanged -->
<div class="modal-backdrop absolute inset-0"></div><div class="bg-white rounded-lg shadow-xl p-8 w-full max-w-lg z-10"><h2 id="task-modal-title" class="text-2xl font-bold text-center mb-4">AI初筛进行中...</h2><div id="progress-bar" class="w-full bg-gray-200 rounded-full h-4 mb-4"><div id="progress-bar-inner" class="bg-sky-500 h-4 rounded-full" style="width: 0%"></div></div><p id="progress-text" class="text-center text-gray-500 mb-6">已处理 0 / 1000</p><button id="show-workbench-btn" class="hidden w-full bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-lg">处理完成!进入审核工作台</button></div>
</div>
<!-- 双视图原文审查 (模态框 V7) -->
<div id="review-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center p-8">
<!-- content from v7, unchanged -->
<div class="modal-backdrop absolute inset-0" onclick="closeReviewModal()"></div><div class="bg-white rounded-lg shadow-xl w-full max-w-6xl h-full flex flex-col z-10"><div class="flex justify-between items-center p-4 border-b"><h2 id="modal-title" class="text-xl font-bold">原文审查</h2><button onclick="closeReviewModal()" class="text-gray-500 hover:text-gray-800 text-2xl">&times;</button></div><div class="flex-grow flex overflow-hidden"><div class="w-1/2 p-6 overflow-y-auto border-r"><div id="modal-meta" class="mb-4 border-b pb-4"><h3 id="modal-doc-title" class="font-bold text-lg"></h3><p id="modal-doc-authors" class="text-sm text-gray-600"></p><p id="modal-doc-journal" class="text-sm text-gray-500"></p></div><h3 class="font-bold mb-2">摘要</h3><p id="modal-abstract" class="text-gray-700 leading-relaxed text-sm"></p></div><div class="w-1/2 p-6 overflow-y-auto bg-gray-50"><h3 id="modal-dimension-title" class="font-bold mb-4 text-lg">PICO(S) 判断依据</h3><div id="modal-reasoning" class="space-y-6"></div></div></div></div>
</div>
<script>
// --- STATE MANAGEMENT ---
let literatureData = [];
let simulatedResultsData = [];
let currentViewId = 'title-settings';
// --- DOM ELEMENTS ---
// (Simplified for brevity - assuming all elements defined in V7 are captured)
const views = {
'title-settings': document.getElementById('title-settings-view'),
'title-workbench': document.getElementById('title-workbench-view'),
'title-results': document.getElementById('title-results-view'),
};
const navLinks = {
'title-settings': document.getElementById('nav-title-settings'),
'title-workbench': document.getElementById('nav-title-workbench'),
'title-results': document.getElementById('nav-title-results'),
};
const headerTitle = document.getElementById('header-title');
const startTitleScreeningBtn = document.getElementById('start-title-screening-btn');
const titleImportHint = document.getElementById('title-import-hint');
const taskStatusModal = document.getElementById('task-status-modal');
const screeningTableBody = document.getElementById('title-screening-table-body');
const reviewModal = document.getElementById('review-modal');
const completeTitleScreeningBtn = document.getElementById('complete-title-screening-btn');
// Result page elements
const titleIncludedCount = document.getElementById('title-included-count');
const titleExcludedCount = document.getElementById('title-excluded-count');
const titleIncludedTabCount = document.getElementById('title-included-tab-count');
const titleExcludedTabCount = document.getElementById('title-excluded-tab-count');
const titleResultsTableBody = document.getElementById('title-results-table-body');
const titleTotalCount = document.getElementById('title-total-count');
const prismaTotalExcluded = document.getElementById('prisma-total-excluded');
const prismaSExcluded = document.getElementById('prisma-s-excluded');
const prismaPExcluded = document.getElementById('prisma-p-excluded');
const prismaIExcluded = document.getElementById('prisma-i-excluded');
const prismaCExcluded = document.getElementById('prisma-c-excluded');
const prismaDuplicateExcluded = document.getElementById('prisma-duplicate-excluded');
const prismaOtherExcluded = document.getElementById('prisma-other-excluded');
// --- DATA SIMULATION ---
function generateData() { /* ... unchanged V7 version ... */ literatureData = [];for (let i = 1; i <= 10; i++) { const conflict = Math.random() < 0.3; const model_a_p = Math.random() > 0.2 ? '✓' : '✗'; const model_a_i = Math.random() > 0.1 ? '✓' : '✗'; const model_a_c = Math.random() > 0.3 ? '✓' : '?'; const model_a_s = Math.random() > 0.1 ? '✓' : '✗'; let model_b_s = model_a_s; if (conflict && model_a_s === '✓') model_b_s = '✗'; else if (conflict && model_a_s === '✗') model_b_s = '✓'; const model_a_conclusion = [model_a_p, model_a_i, model_a_s].includes('✗') ? '排除' : '纳入'; const model_b_conclusion = [model_a_p, model_a_i, model_b_s].includes('✗') ? '排除' : '纳入'; const evidence_a = { p: "2型糖尿病成人患者", i: "SGLT2药物", c: "安慰剂", s: "RCT" }; const evidence_b = { p: "成人T2DM患者", i: "SGLT2抑制剂", c: "安慰剂组", s: "随机对照" }; let exclusion_reason = null; if (model_a_conclusion === '排除' || model_b_conclusion === '排除') { const reasons = ["非RCT", "非目标人群", "干预不符", "对照不符", "综述", "重复研究"]; exclusion_reason = reasons[Math.floor(Math.random() * reasons.length)]; } literatureData.push({ id: i, pmid: `PMID${1234500 + i}`, study_id: `AuthorA${i} ${2020 + i}`, source: `J Med ${2020 + i};${10 + i}(${i}):e${123 + i}`, title: `${i}篇研究: SGLT2抑制剂对2型糖尿病心血管结局的影响`, authors: `Author A${i}, Author B${i}`, journal: `Journal of Medicine ${2020 + i}`, abstract: `背景: SGLT2抑制剂是一类新型降糖药物。本研究旨在评估其在<span class='p-source'>2型糖尿病成人患者</span>中的心血管保护作用。方法: 我们进行了一项多中心、双盲、<span class='s-source'>随机对照试验(RCT)</span>纳入了7020名患有心血管疾病的2型糖尿病患者。患者被随机分配接受每日10mg或25mg的<span class='i-source'>SGLT2药物</span>或<span class='c-source'>安慰剂</span>治疗。主要终点为主要心血管不良事件(MACE)。结论: 与安慰剂相比SGLT2抑制剂显著降低了MACE风险。`, model_a: { p: model_a_p, i: model_a_i, c: model_a_c, s: model_a_s, conclusion: model_a_conclusion, p_reason: "摘要明确提到研究对象为'2型糖尿病成人患者'。", i_reason: "干预措施为'SGLT2药物',符合要求。", c_reason: "对照组为'安慰剂',符合标准。", s_reason: "研究设计明确说明为'随机对照试验(RCT)'。", evidence: evidence_a }, model_b: { p: model_a_p, i: model_a_i, c: model_a_c, s: model_b_s, conclusion: model_b_conclusion, p_reason: "文中确认患者群体为'2型糖尿病成人'。", i_reason: "治疗方案包含'SGLT2药物'。", c_reason: "对照组为'安慰剂'。", s_reason: model_b_s === '✓' ? "方法部分描述了'随机对照试验'。" : "研究方法非RCT。", evidence: evidence_b }, final_decision: '待定', decision_method: '待定', exclusion_reason: exclusion_reason });}}
// V3: Generate separate simulated data for the results page
function generateSimulatedResultsData() {
simulatedResultsData = [];
const totalSimulated = 1000;
const includedSimulated = 120;
for (let i = 1; i <= totalSimulated; i++) {
const isIncluded = i <= includedSimulated;
const final_decision = isIncluded ? '纳入' : '排除';
const decision_method = Math.random() > 0.7 ? '人工审核' : (isIncluded ? 'AI自动纳入' : 'AI自动排除'); // More specific method
let exclusion_reason = null;
if (!isIncluded) {
const reasons = ["非RCT", "非目标人群", "干预不符", "对照不符", "综述", "重复研究", "语言不符"];
exclusion_reason = reasons[Math.floor(Math.random() * reasons.length)];
}
// Simulate PICS content based on decision (more realistic)
const sim_p = (isIncluded || Math.random() > 0.2) ? "2型糖尿病成人患者" : "1型糖尿病";
const sim_i = (isIncluded || Math.random() > 0.1) ? "SGLT2抑制剂" : "二甲双胍";
const sim_c = (isIncluded || Math.random() > 0.3) ? "安慰剂" : "生活方式干预";
const sim_s = (isIncluded || Math.random() > 0.1) ? "RCT" : "观察性研究";
simulatedResultsData.push({
id: i + 1000,
pmid: `PMID${2234500 + i}`,
study_id: `SimAuthor${i} ${2021}`,
source: `Sim J ${2021};${i % 10 + 1}:s${i}`,
title: `模拟文献 ${i}: 关于SGLT2的研究`,
abstract: `这是一个模拟的摘要内容,长度可能会比较长,用于演示结果页面的摘要展示和展开收起功能...模拟摘要结束。`,
final_decision: final_decision,
decision_method: decision_method,
exclusion_reason: exclusion_reason,
// V4: Store simulated PICS content
sim_p: sim_p, sim_i: sim_i, sim_c: sim_c, sim_s: sim_s
});
}
}
// --- INITIALIZATION ---
document.addEventListener('DOMContentLoaded', () => {
generateData(); // For workbench
generateSimulatedResultsData(); // V3: For results page
showView('title-settings');
});
// --- NAVIGATION & VIEW MANAGEMENT ---
Object.keys(navLinks).forEach(key => { /* ... unchanged ... */ navLinks[key].addEventListener('click', (e) => { e.preventDefault(); const viewId = navLinks[key].getAttribute('data-view'); showView(viewId); }); });
function showView(viewId) {
currentViewId = viewId;
Object.values(views).forEach(v => v.classList.remove('active'));
if (views[viewId]) {
views[viewId].classList.add('active');
} else {
console.error("View not found:", viewId);
views['title-settings'].classList.add('active');
viewId = 'title-settings';
}
const stage = 'title';
const subView = viewId.split('-')[1];
const stageTitles = { 'title': '标题摘要初筛' };
const subViewTitles = { 'settings': '设置与启动', 'workbench': '审核工作台', 'results': '初筛结果' };
headerTitle.textContent = `${stageTitles[stage]} / ${subViewTitles[subView]}`;
Object.values(navLinks).forEach(link => link.classList.remove('active'));
if (navLinks[viewId]) navLinks[viewId].classList.add('active');
// V3: Always render results page with simulated data when viewed
if (viewId === 'title-results') {
renderResultsPage('title');
}
}
// --- TITLE SCREENING ---
function importLiterature(source) { /* ... unchanged ... */ startTitleScreeningBtn.disabled = false; startTitleScreeningBtn.classList.remove('bg-gray-400', 'cursor-not-allowed'); startTitleScreeningBtn.classList.add('bg-green-600', 'hover:bg-green-700'); titleImportHint.classList.add('hidden'); alert("模拟导入10篇文献成功"); literatureData.forEach(d=>d.final_decision='待定'); }
startTitleScreeningBtn.addEventListener('click', () => { /* ... unchanged ... */ taskStatusModal.classList.remove('hidden'); document.getElementById('task-modal-title').textContent = 'AI标题摘要初筛进行中...'; let progress = 0; const interval = setInterval(() => { progress += 10; document.getElementById('progress-bar-inner').style.width = `${progress}%`; document.getElementById('progress-text').textContent = `已处理 ${progress * (literatureData.length / 10)} / ${literatureData.length}`; if (progress >= 100) { clearInterval(interval); document.getElementById('show-workbench-btn').classList.remove('hidden'); } }, 100); });
document.getElementById('show-workbench-btn').addEventListener('click', () => {
taskStatusModal.classList.add('hidden');
showView('title-workbench');
renderTable('title');
});
// V7: Render Table with Original Header Structure
function renderTable(stage) {
const tableBodyId = 'title-screening-table-body'; // Only title stage
const tableBody = document.getElementById(tableBodyId);
if (!tableBody) return;
const dataToRender = literatureData; // Workbench uses the interactive data
tableBody.innerHTML = dataToRender.map(doc => {
const isConflict = doc.model_a.conclusion !== doc.model_b.conclusion;
const rowId = `${stage}-row-${doc.id}`;
const expandedRowId = `${stage}-expanded-row-${doc.id}`;
const judgeCell = (model, dim) => `<td class="table-cell-judge px-2 py-3 border-l" onclick="openReviewModal(${doc.id}, '${model}', '${dim}', '${stage}')">${{ '✓': '<span class="text-green-600 font-bold">✓</span>', '✗': '<span class="text-red-600 font-bold">✗</span>', '?': '<span class="text-yellow-600 font-bold">?</span>' }[doc[model][dim]]}</td>`;
return `
<tr id="${rowId}" class="${isConflict ? 'table-conflict-row' : ''}">
<td class="px-2 py-3 expand-row-btn" onclick="toggleRow('${expandedRowId}', this)">+</td>
<td class="px-3 py-3 font-mono text-xs text-gray-500">${doc.pmid}</td>
<td class="px-4 py-3 font-semibold text-xs">${doc.study_id}</td>
<td class="px-4 py-3 text-xs text-gray-500 truncate" title="${doc.source}">${doc.source.split('.')[0]}...</td>
<!-- DS Judgements -->
${judgeCell('model_a', 'p')} ${judgeCell('model_a', 'i')} ${judgeCell('model_a', 'c')} ${judgeCell('model_a', 's')}
<td class="px-2 py-3 font-semibold border-r ${doc.model_a.conclusion === '纳入' ? 'text-green-700' : 'text-red-700'}">${doc.model_a.conclusion}</td>
<!-- Q3 Judgements -->
${judgeCell('model_b', 'p')} ${judgeCell('model_b', 'i')} ${judgeCell('model_b', 'c')} ${judgeCell('model_b', 's')}
<td class="px-2 py-3 font-semibold border-r ${doc.model_b.conclusion === '纳入' ? 'text-green-700' : 'text-red-700'}">${doc.model_b.conclusion}</td>
<!-- Status & Decision -->
<td class="px-4 py-3 border-l">${isConflict ? '<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">冲突</span>' : '<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">一致</span>'}</td>
<td class="px-4 py-3 border-l"><select class="rounded-md border-gray-300 text-xs" data-id="${doc.id}" data-stage="${stage}" onchange="updateDecision(this)"><option value="待定" ${doc.final_decision === '待定' ? 'selected' : ''}>待定</option><option value="纳入" ${doc.final_decision === '纳入' ? 'selected' : ''}>纳入</option><option value="排除" ${doc.final_decision === '排除' ? 'selected' : ''}>排除</option></select></td>
</tr>
<tr id="${expandedRowId}" class="expanded-row">
<td colspan="15" class="expanded-row-cell text-xs">
<div class="grid grid-cols-2 gap-4">
<div><strong class="text-gray-600">DS 证据:</strong><dl class="mt-1 evidence-grid"><dt>P:</dt><dd>${doc.model_a.evidence.p}</dd><dt>I:</dt><dd>${doc.model_a.evidence.i}</dd><dt>C:</dt><dd>${doc.model_a.evidence.c}</dd><dt>S:</dt><dd>${doc.model_a.evidence.s}</dd></dl></div>
<div><strong class="text-gray-600">Q3 证据:</strong><dl class="mt-1 evidence-grid"><dt>P:</dt><dd>${doc.model_b.evidence.p}</dd><dt>I:</dt><dd>${doc.model_b.evidence.i}</dd><dt>C:</dt><dd>${doc.model_b.evidence.c}</dd><dt>S:</dt><dd>${doc.model_b.evidence.s}</dd></dl></div>
</div>
</td>
</tr>
`;
}).join('');
}
function toggleRow(rowId, btn) { /* ... unchanged ... */ const row = document.getElementById(rowId); const isShown = row.classList.toggle('shown'); btn.textContent = isShown ? '-' : '+'; }
function updateDecision(selectElement) { /* ... unchanged ... */ const docId = parseInt(selectElement.dataset.id); const stage = selectElement.dataset.stage; const dataSet = literatureData; /* Only title data here */ const doc = dataSet.find(d => d.id === docId); if (doc) { doc.final_decision = selectElement.value; if (doc.final_decision !== '待定') { doc.decision_method = '人工审核'; } else { doc.decision_method = '待定'; } } }
function openReviewModal(docId, modelKey, dimensionKey, stage) { /* ... unchanged V7 version ... */ const dataSet = literatureData; /* Only title data */ const doc = dataSet.find(d => d.id === docId); if (!doc) return; const dimensionMap = { p: '人群 (P)', i: '干预 (I)', c: '对照 (C)', s: '研究设计 (S)' }; document.getElementById('modal-title').textContent = `审查: ${doc.title}`; document.getElementById('modal-doc-title').textContent = doc.title; document.getElementById('modal-doc-authors').textContent = doc.authors; document.getElementById('modal-doc-journal').textContent = `${doc.journal} (${doc.pmid})`; const abstractEl = document.getElementById('modal-abstract'); let abstractContent = doc.abstract; const sourceSpan = doc.abstract.match(new RegExp(`<span class='${dimensionKey.toLowerCase()}-source'>(.*?)</span>`)); let sourceText = sourceSpan ? sourceSpan[1] : ""; abstractContent = abstractContent.replace(/<span class='.-source'>/g, '').replace(/<\/span>/g, ''); if (sourceText) { abstractContent = abstractContent.replace(sourceText, `<mark class="abstract-highlight">${sourceText}</mark>`); } abstractEl.innerHTML = abstractContent; setTimeout(() => { const mark = abstractEl.querySelector('mark'); if (mark) { mark.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }, 100); document.getElementById('modal-dimension-title').textContent = `PICO(S) 判断依据`; const reasoningContainer = document.getElementById('modal-reasoning'); let reasoningHTML = `<div class="grid grid-cols-2 gap-6">`; ['model_a', 'model_b'].forEach(mKey => { const modelName = mKey === 'model_a' ? 'DeepSeek' : 'Qwen3'; reasoningHTML += `<div class="border rounded-lg p-4 bg-white"><h4 class="font-bold text-gray-700 mb-3">${modelName} 判断</h4><div class="space-y-2 text-sm mb-3">`; ['p', 'i', 'c', 's'].forEach(dim => { const icon = { '✓': '<span class="text-green-600 font-bold">✓ 符合</span>', '✗': '<span class="text-red-600 font-bold">✗ 不符</span>', '?': '<span class="text-yellow-600 font-bold">? 不确定</span>' }[doc[mKey][dim]]; reasoningHTML += `<p><strong>${dimensionMap[dim]}:</strong> ${icon}</p>`; }); reasoningHTML += `</div><p class="text-sm mb-2"><strong>总览结论:</strong> <span class="font-semibold ${doc[mKey].conclusion === '纳入' ? 'text-green-700' : 'text-red-700'}">${doc[mKey].conclusion}</span></p><p class="text-sm text-gray-600"><strong>"${dimensionMap[dimensionKey]}" 判断理由:</strong> ${doc[mKey][`${dimensionKey}_reason`]}</p></div>`; }); reasoningHTML += `</div>`; reasoningContainer.innerHTML = reasoningHTML; reviewModal.classList.remove('hidden'); }
function closeReviewModal() { reviewModal.classList.add('hidden'); }
// V3: Simplified button action - just go to results view
completeTitleScreeningBtn.addEventListener('click', () => {
showView('title-results');
});
// --- TITLE RESULTS PAGE (V7 Enhanced & V3 Independent Data) ---
function renderResultsPage(stage) {
if (stage !== 'title') return;
// V3: Use simulated data for counts and table
const included = simulatedResultsData.filter(doc => doc.final_decision === '纳入');
const excluded = simulatedResultsData.filter(doc => doc.final_decision === '排除');
const totalSimulated = simulatedResultsData.length;
titleTotalCount.textContent = totalSimulated;
titleIncludedCount.textContent = included.length;
titleExcludedCount.textContent = excluded.length;
titleIncludedTabCount.textContent = included.length;
titleExcludedTabCount.textContent = excluded.length;
// V7: Prisma Summary Simulation based on simulated excluded data
prismaTotalExcluded.textContent = excluded.length;
prismaSExcluded.textContent = excluded.filter(d => d.exclusion_reason === '非RCT').length;
prismaPExcluded.textContent = excluded.filter(d => d.exclusion_reason === '非目标人群').length;
prismaIExcluded.textContent = excluded.filter(d => d.exclusion_reason === '干预不符').length;
prismaCExcluded.textContent = excluded.filter(d => d.exclusion_reason === '对照不符').length;
prismaDuplicateExcluded.textContent = excluded.filter(d => d.exclusion_reason === '重复研究').length;
prismaOtherExcluded.textContent = excluded.filter(d => !["非RCT", "非目标人群", "干预不符", "对照不符", "重复研究"].includes(d.exclusion_reason)).length;
renderResultsTable('title', 'included'); // Default view uses simulated data now
}
function switchResultTab(stage, tab) {
if (stage !== 'title') return;
const tabContainerId = 'title-result-tabs';
document.querySelectorAll(`#${tabContainerId} button`).forEach(btn => { btn.classList.remove('active', 'border-sky-500', 'text-sky-600'); btn.classList.add('border-transparent', 'text-gray-500'); });
const activeBtn = document.querySelector(`#${tabContainerId} button[data-tab="${tab}"]`);
activeBtn.classList.add('active', 'border-sky-500', 'text-sky-600');
activeBtn.classList.remove('border-transparent', 'text-gray-500');
renderResultsTable('title', tab); // Will use simulated data
}
// V7: Render Enhanced Results Table (Now uses simulatedResultsData)
// V4 Update: Display content instead of icons for PICS
function renderResultsTable(stage, tab) {
if (stage !== 'title') return;
const tableBodyId = 'title-results-table-body';
const tableBody = document.getElementById(tableBodyId);
if (!tableBody) return;
const dataSet = simulatedResultsData;
const filteredData = dataSet.filter(doc => doc.final_decision === (tab === 'included' ? '纳入' : '排除'));
tableBody.innerHTML = filteredData.map(doc => {
// V4: Get content for PICS instead of icons
const pContent = doc.sim_p === '✓' ? "2型糖尿病成人患者" : "非目标人群";
const iContent = doc.sim_i === '✓' ? "SGLT2抑制剂" : "非SGLT2";
const cContent = doc.sim_c === '✓' ? "安慰剂" : (doc.sim_c === '?' ? "不适用/未报告" : "非安慰剂");
const sContent = doc.sim_s === '✓' ? "RCT" : "非RCT";
const shortAbstract = doc.abstract.substring(0, 100) + '...';
const fullAbstract = doc.abstract;
return `<tr>
<td class="px-3 py-3 font-mono text-xs">${doc.pmid}</td>
<td class="px-4 py-3 text-xs">${doc.study_id}</td>
<td class="px-4 py-3 text-xs truncate" title="${doc.source}">${doc.source}</td>
<td class="px-4 py-3 text-xs font-medium">${doc.title}</td>
<td class="px-4 py-3 text-xs text-gray-500 abstract-cell">
<span class="short-abstract">${shortAbstract}</span>
<span class="full-abstract">${fullAbstract}</span>
<a href="#" class="text-sky-600 text-[10px] block mt-1" onclick="toggleAbstract(this, event)">显示更多</a>
</td>
<td class="px-2 py-3 text-center results-pics-cell">${pContent}</td>
<td class="px-2 py-3 text-center results-pics-cell">${iContent}</td>
<td class="px-2 py-3 text-center results-pics-cell">${cContent}</td>
<td class="px-2 py-3 text-center results-pics-cell">${sContent}</td>
<td class="px-4 py-3">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${doc.final_decision === '纳入' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}">${doc.final_decision}</span>
</td>
<td class="px-4 py-3 text-xs text-gray-500">${doc.final_decision === '排除' ? (doc.exclusion_reason || '未指定') : 'N/A'}</td>
</tr>`;
}).join('');
}
// V7: Toggle Abstract in results table
function toggleAbstract(link, event) { /* ... unchanged ... */ event.preventDefault(); const cell = link.closest('.abstract-cell'); const isExpanded = cell.classList.toggle('expanded'); link.textContent = isExpanded ? '收起' : '显示更多'; }
// Remove fulltext related functions as they are not needed in this prototype
</script>
</body>
</html>

View File

@@ -0,0 +1,72 @@
# 开发里程碑
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
> **最后更新:** 2025-10-29
---
## 📋 文档说明
本文档描述AI智能文献模块的开发里程碑规划。
---
## 🗓️ 开发阶段
### 阶段一:标题摘要初筛模块(当前阶段)
**目标**: 完成标题摘要初筛核心功能
**时间**: 4-6周
**里程碑**:
- ✅ 需求分析和设计文档
- 🔄 数据库设计和API设计
- ⏳ 前端框架搭建
- ⏳ 后端API开发
- ⏳ AI模型集成
- ⏳ 功能测试和优化
### 阶段二:全文复筛模块
**目标**: 完成全文复筛功能
**时间**: 4-6周
**状态**: 待开始
### 阶段三:其他模块
**目标**: 完成剩余功能模块
**时间**: 待定
**状态**: 规划中
---
## 📊 进度跟踪
| 模块 | 状态 | 进度 | 完成时间 |
|------|------|------|----------|
| 研究方案生成 | 规划中 | 0% | - |
| 智能文献检索 | 规划中 | 0% | - |
| 标题摘要初筛 | 进行中 | 20% | - |
| 全文复筛 | 待开始 | 0% | - |
| 全文解析与数据提取 | 规划中 | 0% | - |
| 数据综合分析与报告 | 规划中 | 0% | - |
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,99 @@
# 标题摘要初筛模块开发计划
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
> **最后更新:** 2025-10-29
---
## 📋 文档说明
本文档详细描述标题摘要初筛模块的开发计划,包括任务分解、时间安排、技术方案等。
---
## 🎯 开发目标
完成标题摘要初筛模块的核心功能,包括:
1. 设置与启动视图
2. 表格化审核工作台
3. 结果展示视图
4. AI双模型筛选功能
---
## 📅 开发时间规划
### Week 1-2: 设计阶段
- [x] 需求分析完成
- [ ] 数据库设计完成
- [ ] API设计完成
- [ ] 前端组件设计完成
### Week 3-4: 前端开发
- [ ] 设置与启动视图开发
- [ ] 表格化审核工作台开发
- [ ] 结果展示视图开发
- [ ] 组件集成
### Week 5-6: 后端开发
- [ ] 项目管理API
- [ ] 文献管理API
- [ ] 筛选任务API
- [ ] AI模型集成
### Week 7-8: 集成测试与优化
- [ ] 功能测试
- [ ] 性能优化
- [ ] Bug修复
- [ ] 用户验收测试
---
## 📋 任务分解
### 1. 数据库设计和迁移
- [ ] 设计数据表结构
- [ ] 编写迁移脚本
- [ ] 创建索引
### 2. API开发
- [ ] 项目管理API
- [ ] 文献导入API
- [ ] 筛选任务API
- [ ] 筛选结果API
### 3. 前端开发
- [ ] 设置与启动视图
- [ ] 表格化审核工作台
- [ ] 结果展示视图
- [ ] 双视图原文审查模态框
### 4. AI集成
- [ ] 双模型调用封装
- [ ] 批处理任务管理
- [ ] 结果解析和存储
---
## ⏳ 待完善内容
后续将补充:
- 详细任务分解(按功能点)
- 技术方案细节
- 风险评估
- 依赖关系
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,24 @@
# 任务分解
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
---
## ⏳ 待完善
本文档内容待规划完善,目前仅作为占位文档。
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,24 @@
# 测试计划
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
---
## ⏳ 待完善
本文档内容待规划完善,目前仅作为占位文档。
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,24 @@
# 标题摘要初筛测试用例
> **文档版本:** v1.0
> **创建日期:** 2025-10-29
> **维护者:** AI智能文献开发团队
---
## ⏳ 待完善
本文档内容待规划完善,目前仅作为占位文档。
---
**文档版本:** v1.0
**最后更新:** 2025-10-29

View File

@@ -0,0 +1,272 @@
# ASL 测试数据集
> **创建日期:** 2025-11-15
> **维护人:** ASL 开发团队
> **用途:** 用于测试 AI 智能文献模块的准确率和质量
---
## 📋 数据集概览
本目录包含用于测试 ASL 模块各项功能的测试数据集,包括:
| 测试类型 | 文件夹 | 数据量 | 状态 |
|---------|--------|--------|------|
| **标题摘要初筛** | `screening/` | 199 篇 | ✅ 待导入 |
| **PDF 全文提取** | `pdf-extraction/` | 待补充 | ⏳ 待补充 |
---
## 📁 文件夹结构
```
03-测试数据/
├── README.md ← 当前文件
├── screening/ ← 标题摘要初筛测试数据
│ ├── literature-list-199.xlsx ← 199 篇文献列表(标题+摘要)
│ ├── picos-criteria.txt ← PICOS 标准定义
│ ├── inclusion-criteria.txt ← 纳入标准
│ ├── exclusion-criteria.txt ← 排除标准
│ └── gold-standard.json ← 人工标注的正确结果(金标准)
└── pdf-extraction/ ← PDF 全文提取测试数据
├── sample-01-rct.pdf ← RCT 研究样本
├── sample-02-cohort.pdf ← 队列研究样本
├── sample-03-with-tables.pdf ← 包含复杂表格的样本
├── sample-04-chinese.pdf ← 中文文献样本
└── README.md
```
---
## 🎯 使用方法
### 1. 导入测试数据
**请按以下步骤导入您的测试数据:**
#### 1标题摘要初筛测试数据
**文件清单:**
- `literature-list-199.xlsx`199 篇英文文献列表
- `picos-criteria.txt`PICOS 标准Population, Intervention, Comparison, Outcome, Study Design
- `gold-standard.json`:人工标注的正确结果
**Excel 文件格式要求:**
```
列名(必须):
- Title标题
- Abstract摘要
- DOI可选
- Authors作者可选
- Year年份可选
- Journal期刊可选
示例:
| Title | Abstract | DOI | Authors | Year | Journal |
|--------------------------------|---------------------------|---------------|--------------|------|---------|
| Effect of aspirin on ... | Background: ... | 10.1038/... | Smith J, ... | 2020 | NEJM |
```
**PICOS 标准格式:**
```txt
# PICOS 标准
## Population人群
- 成年高血压患者(年龄 ≥ 18 岁)
- 无心血管疾病史
## Intervention干预
- 每日服用阿司匹林 100mg
## Comparison对照
- 安慰剂或无治疗
## Outcome结局
- 主要结局:心血管事件发生率
- 次要结局:全因死亡率
## Study Design研究设计
- 随机对照试验RCT
- 队列研究Cohort Study
```
**金标准格式JSON**
```json
{
"metadata": {
"total": 199,
"annotatedBy": "医学专家姓名",
"annotatedDate": "2025-11-15",
"expectedAccuracy": 0.90
},
"results": [
{
"id": 1,
"doi": "10.1038/nature12373",
"title": "...",
"decision": "include",
"reason": "符合 PICO 标准:人群为成年高血压患者,干预为阿司匹林...",
"confidence": 1.0
},
{
"id": 2,
"decision": "exclude",
"reason": "不符合纳入标准:人群为儿童患者",
"confidence": 0.95
}
]
}
```
#### 2PDF 全文提取测试数据
**建议准备的样本类型:**
- RCT 研究(随机对照试验)
- 队列研究Cohort Study
- 包含复杂表格的文献
- 包含数学公式的文献
- 中文医学文献(测试语言检测)
**样本数量建议:** 5-10 篇
### 2. 运行测试
#### 1标题摘要初筛测试
```bash
# 进入后端目录
cd AIclinicalresearch/backend
# 运行初筛测试
npm run test:asl:screening
# 或者手动测试:
# 1. 启动后端服务
npm run dev
# 2. 通过前端上传 literature-list-199.xlsx
# 3. 配置 PICOS 标准(复制 picos-criteria.txt 内容)
# 4. 运行批量筛选
# 5. 导出结果,与 gold-standard.json 对比
```
#### 2评估准确率
```bash
# 自动评估准确率(与金标准对比)
npm run test:asl:evaluate -- \
--result ./screening-result.json \
--gold-standard ./gold-standard.json
# 输出示例:
# ✅ 准确率: 92.5%
# ✅ 一致率: 88.9%
# ⚠️ 假阳性率: 5.2%
# ⚠️ 假阴性率: 2.3%
```
### 3. 质量指标
| 指标 | MVP 目标 | V1.0 目标 | V2.0 目标 |
|------|---------|----------|----------|
| **准确率** | ≥ 85% | ≥ 90% | ≥ 95% |
| **一致率**(双模型) | ≥ 80% | ≥ 85% | ≥ 90% |
| **假阳性率** | ≤ 10% | ≤ 5% | ≤ 3% |
| **假阴性率** | ≤ 5% | ≤ 3% | ≤ 2% |
---
## 📊 测试数据统计
### 标题摘要初筛数据集
**基本信息:**
- **总数量**: 199 篇
- **数据来源**: [请填写数据来源]
- **领域**: 医学/临床研究
- **语言**: 英文
- **年份范围**: [请填写]
**预期分布:**
```
纳入Include: ~45 篇23%
排除Exclude: ~132 篇66%
不确定Uncertain: ~22 篇11%
```
**研究类型分布(预估):**
```
RCT: ~60 篇30%
队列研究: ~50 篇25%
病例对照: ~30 篇15%
横断面研究: ~30 篇15%
其他: ~29 篇15%
```
### PDF 全文提取数据集
**待补充**
---
## ⚠️ 数据使用注意事项
### 1. 版权声明
- 本测试数据集仅用于 ASL 模块开发和测试
- 不得用于商业用途
- 不得公开分发或传播
- 请遵守原文献的版权许可
### 2. 数据隐私
- 确保测试数据不包含敏感信息
- 如包含患者数据,必须已脱敏处理
- 遵守 GDPR、HIPAA 等数据保护法规
### 3. 质量要求
- **金标准必须由医学专家标注**
- 标注人需具备相关领域专业知识
- 标注过程需有质量控制机制
- 建议双人独立标注,冲突需第三方仲裁
---
## 🔄 数据更新记录
| 日期 | 更新内容 | 更新人 |
|------|---------|--------|
| 2025-11-15 | 创建测试数据目录结构 | ASL 团队 |
| 待更新 | 导入 199 篇文献测试数据 | - |
| 待更新 | 添加 PDF 样本数据 | - |
---
## 📞 联系方式
如有问题,请联系:
- **项目负责人**: [姓名]
- **邮箱**: [邮箱]
- **文档维护**: [文档路径]
---
## 📚 相关文档
- [标题摘要初筛测试用例](../02-标题摘要初筛测试用例.md)
- [测试计划](../01-测试计划.md)
- [文献处理技术选型](../../02-技术设计/07-文献处理技术选型.md)
- [质量保障与可追溯策略](../../02-技术设计/06-质量保障与可追溯策略.md)
---
**下一步行动:**
1. ✅ 创建测试数据目录结构
2. ⏳ 导入您的 199 篇文献测试数据(`literature-list-199.xlsx`
3. ⏳ 创建 PICOS 标准文件(`picos-criteria.txt`
4. ⏳ 准备金标准标注(`gold-standard.json`
5. ⏳ 补充 PDF 样本数据

View File

@@ -0,0 +1,77 @@
测试案例的PICOS、纳入标准、排除标准
一、PICOS
Patients with non-cardioembolic ischemic stroke (NCIS) 非心源性缺血性卒中、亚洲人群
亚组人群:
均在非心源性卒中范畴内找:
1. NIHSS评分亚组卒中人群mild/moderate stroke;
2. 不同TOAST分型different TOAST subtypesexcluding cardioembolic stroke;
3. 高危TIA人群high-risk TIA population;
4. 新发卒中/复发性/进展性卒中患者new-onset stroke/recurrent /progressive stroke patients
5 颅内动脉粥样硬化性狭窄/大动脉粥样硬化患者ICAS/LAA
6. 不同疾病特征人群:
1合并冠心病CAD患者/外周动脉疾病PAD
2合并中重度肾功能不全透析患者patients with moderate-severe renal insufficiency on dialysis;
3合并糖尿病患者patients with diabetes mellitus;
4老年或脆弱患者elderly/fragile patients;
5氯吡格雷抵抗人群clopidogrel-resistant population;
6Breakthrough stroke
7特殊情况如卵圆孔未闭PFO颈动脉夹层cervical artery dissection,合并肿瘤cancer
Intervention/Comparator
抗血小板治疗药物:阿司匹林,氯吡格雷,奥扎格雷,贝前列素,西洛他唑,替罗非班,替格瑞洛,吲哚布芬,沙格雷酯,氯吡格雷阿司匹林,双嘧达莫等。
抗凝药物阿加曲班asundexianmilvexian华法林、低分子肝素、肝素等。
溶栓药物:链激酶、尿激酶、阿替普酶、替奈普酶等。
Outcome
疗效安全性Progressive stroke卒中的进展or神经功能恶化Recurrent ischemic stroke 卒中的复发DisabilityDeathNIHSS评分变化VTEefficacy/effective/effectiveness/疗效/有效性痴呆、认知功能减退、疲乏、抑郁safety/安全性。
Time
检索时间近五年2020年之后至今文献
Study design
系统评价SR、随机对照试验RCT、真实世界研究RWE、观察性研究OBS
二、 纳入标准:
非心源性缺血性卒中、亚洲患者
Patients post-Ischemic Stroke (IS) a that are on Secondary Stroke Prevention (SSP) treatment
Patients post-IS a that are not on SSP treatment (if captured within a study where the focus is on patients that
are on SSP treatment)
干预和对照
抗血小板治疗药物:阿司匹林,氯吡格雷,奥扎格雷,贝前列素,西洛他唑,替罗非班,
替格瑞洛,吲哚布芬,沙格雷酯,氯吡格雷阿司匹林,双嘧达莫等。
抗凝药物阿加曲班asundexianmilvexian华法林、低分子肝素、肝素等。
溶栓药物:链激酶、尿激酶、阿替普酶、替奈普酶等。
结局:
疗效安全性Progressive stroke卒中的进展or神经功能恶化Recurrent ischemic stroke 卒中的复发,
DisabilityDeathNIHSS评分变化VTEefficacy/effective/effectiveness/疗效/有效性,痴呆、
认知功能减退、疲乏、抑郁safety/安全性。
研究类型
系统评价SR、随机对照试验RCT、真实世界研究RWE、观察性研究OBS
研究时间
近五年2020年之后的文献
- 包含“secondary prevention”
- 包含“prevention of recurrence”
- 包含“for stroke prevention”
- 包含涉及抗血小板或抗凝药物
- 涉及抗血小板或抗凝药物
如果出现了抗血小板或抗凝药物,并且没有出现任何排除关键词(急性期相关术语),则纳入。
三、 排除标准:
1. 心源性卒中患者、非亚洲
2. Patients post any other stroke
3. Patients on antiplatelet therapy for Acute Coronary Syndrome (ACS) without previously identified stroke
4. Patients who have Atrial Fibrillation (AF)
5. Mixed populations (when the population includes patients that are not post IS)
6. 病例报告等
7. 非中英文文献
8. 包含急性期治疗关键词如acute, thrombolysis, thrombectomy等没有出现抗血小板或抗凝药物也没有出现二级预防关键词。

View File

@@ -0,0 +1,82 @@
# ASL - AI智能文献
> **模块代号:** ASL (AI Smart Literature)
> **开发状态:** ⏳ 下一步开发Week 2-4
> **商业价值:** ⭐⭐⭐⭐⭐ 可独立售卖
> **独立性:** ⭐⭐⭐⭐⭐
> **优先级:** P0
---
## 📋 模块概述
AI智能文献筛选系统帮助研究者快速筛选和分析文献。
**核心价值:** 核心差异化功能,可独立售卖
---
## 🎯 核心功能6个模块
1.**标题摘要初筛** - 双模型AI判断
2.**全文复筛** - PDF全文分析
3.**全文解析与数据提取**
4.**数据分析与报告生成**
5.**系统评价与Meta分析**
6.**文献管理**
**本周重点:** 标题摘要初筛 + 全文复筛
---
## 📂 文档结构
```
ASL-AI智能文献/
├── [AI对接] ASL快速上下文.md # ⏳ 待创建
├── 00-项目概述/
│ ├── 01-产品需求文档(PRD).md # ⏳ 待合并3个PRD
│ └── ...
├── 01-设计文档/
│ ├── 02-数据库设计.md
│ ├── 03-API设计.md
│ └── 07-UI设计/
│ ├── 标题摘要初筛原型.html
│ └── 全文复筛原型.html
└── README.md # ✅ 当前文档
```
---
## 🔗 依赖的通用能力
- **LLM网关** - 双模型AI判断
- **文档处理引擎** - PDF全文提取
- **RAG引擎** - 文献内容检索
---
## 🎯 商业模式
**目标客户:** 系统评价研究者、循证医学中心
**售卖方式:** 独立产品
**定价策略:** 按项目数或按月订阅
---
**最后更新:** 2025-11-06
**维护人:** 技术架构师

View File

@@ -0,0 +1,321 @@
# [AI对接] ASL快速上下文
> **阅读时间:** 3-5分钟 | **Token消耗** ~2000 tokens
> **层级:** L2 | **前置阅读:** 00-系统总体设计/[AI对接] 快速上下文.md
---
## 📋 模块定位
**AI智能文献筛选系统**,帮助研究者快速筛选和分析大量文献,提高系统评价效率。
**商业价值:** ⭐⭐⭐⭐⭐ 可独立售卖
**开发状态:** ⏳ 即将开发Week 2-4
**依赖能力:** LLM网关P0、文档处理引擎、RAG引擎
---
## 🎯 核心功能6个模块
1.**标题摘要初筛** - 双模型AI判断 → Week 2-3重点
2.**全文复筛** - PDF全文分析 → Week 3-4重点
3. ⏳ 全文解析与数据提取
4. ⏳ 数据分析与报告生成
5. ⏳ 系统评价与Meta分析
6. ⏳ 文献管理
**本次开发重点:** 标题摘要初筛 + 全文复筛
---
## 🏗️ 技术架构一览
### 前端React
```
src/pages/Literature/
├── ProjectManagement/ # 文献项目管理
├── TitleScreening/ # 标题摘要初筛 ⭐
├── FullTextScreening/ # 全文复筛 ⭐
├── DataExtraction/ # 数据提取
└── Management/ # 文献管理
```
### 后端Node.js
```
backend/src/modules/asl/
├── controllers/
│ ├── projectController.ts # 项目管理
│ ├── screeningController.ts # 筛选控制 ⭐
│ └── extractionController.ts # 数据提取
├── services/
│ ├── screeningService.ts # 筛选业务逻辑 ⭐
│ └── extractionService.ts
└── routes/
└── literatureRoutes.ts
```
### 数据库asl_schema
```sql
CREATE SCHEMA asl_schema;
- literature_projects #
- literature_items # CSV导入
- pico_configs # PICO(S)
- screening_results # INCLUDE/EXCLUDE/UNCERTAIN
- screening_history #
- extraction_tasks #
- extraction_results #
```
---
## 💡 核心业务流程
### 标题摘要初筛流程 ⭐
```
1. 用户上传CSV文件包含标题、摘要、作者等
2. 配置PICO(S)纳入/排除标准
- P: Population研究对象
- I: Intervention干预措施
- C: Comparison对照
- O: Outcome结局指标
- S: Study Design研究类型
3. AI双模型判断DeepSeek + Qwen3
- 每篇文献独立判断
- 两个模型投票
- 固定3并发处理
4. 返回结果INCLUDE / EXCLUDE / UNCERTAIN
- INCLUDE: 两个模型都认为应纳入
- EXCLUDE: 两个模型都认为应排除
- UNCERTAIN: 两个模型意见不一致,需人工复核
5. 导出Excel双Sheet设计
- Sheet1: 通过的文献INCLUDE
- Sheet2: 未通过的文献EXCLUDE + UNCERTAIN
```
### AI判断逻辑关键
```typescript
// 双模型投票机制
if (deepseekResult === "INCLUDE" && qwen3Result === "INCLUDE") {
finalResult = "INCLUDE";
} else if (deepseekResult === "EXCLUDE" && qwen3Result === "EXCLUDE") {
finalResult = "EXCLUDE";
} else {
// 意见不一致
finalResult = "UNCERTAIN"; // 标记为需要人工复核
}
```
---
## 📚 已有设计文档
### PRD文档完整
- `00-项目概述/AI智能文献PRD1-产品概览.md`
- `00-项目概述/AI智能文献PRD2-初筛与复筛.md`
- `00-项目概述/AI智能文献PRD3-提取与分析模块.md`
**内容:** 完整的功能需求、用户故事、验收标准
### 技术设计(完整!)
- `01-设计文档/02-数据库设计.md` - 完整表结构
- `01-设计文档/03-API设计.md` - 所有API端点
- `01-设计文档/04-前端组件设计.md` - 组件树
- `01-设计文档/05-AI模型集成设计.md` - 双模型投票逻辑
### UI原型完整
- `01-设计文档/07-UI设计/标题摘要初筛原型.html`
- `01-设计文档/07-UI设计/全文复筛原型.html`
---
## 🔗 依赖的通用能力
### 1. LLM网关❌ 待实现P0⭐ **必须先实现**
**为什么ASL需要LLM网关**
- 标题摘要初筛需要调用2个LLM模型
- 全文复筛需要调用1个LLM模型
- 需要成本控制和配额管理
**接口需求:**
```typescript
// ASL模块需要的接口
interface LLMGateway {
// 单次调用(非流式)
chat(params: {
userId: string;
modelType: 'deepseek-v3' | 'qwen3';
messages: Message[];
}): Promise<{
content: string;
tokenUsage: number;
}>;
// 检查配额
checkQuota(userId: string): Promise<boolean>;
}
```
**实施建议:** Week 2 Day 1-3 同步开发LLM网关
---
### 2. 文档处理引擎(✅ 已实现)
**ASL使用场景**
- 全文复筛PDF全文提取
**已有接口:**
```typescript
// extraction_service已提供
POST /api/extract/pdf
```
---
### 3. RAG引擎✅ 已实现,可选)
**ASL使用场景可选**
- 文献内容检索
- 文献相似度分析
---
## 📋 API端点清单
### 项目管理
```
POST /api/v1/literature/projects # 创建文献项目
GET /api/v1/literature/projects # 获取项目列表
GET /api/v1/literature/projects/:id # 获取项目详情
PUT /api/v1/literature/projects/:id # 更新项目
DELETE /api/v1/literature/projects/:id # 删除项目
```
### 标题摘要初筛 ⭐
```
POST /api/v1/literature/projects/:id/items/import # 导入CSV
POST /api/v1/literature/projects/:id/pico # 配置PICO
POST /api/v1/literature/projects/:id/screening/title # 执行初筛
GET /api/v1/literature/projects/:id/screening/status # 查询进度
GET /api/v1/literature/projects/:id/screening/results # 获取结果
POST /api/v1/literature/projects/:id/screening/export # 导出Excel
```
### 全文复筛
```
POST /api/v1/literature/projects/:id/screening/fulltext # 执行全文筛选
```
---
## 📅 开发计划
### Week 211月11-15日
- **Day 1-2** 项目管理基础CRUD
- **Day 3-4** 标题摘要初筛后端含LLM网关
- **Day 5** 标题摘要初筛前端
### Week 311月18-22日
- **Day 1-2** 全文复筛后端
- **Day 3-4** 全文复筛前端
- **Day 5** 测试和优化
### Week 411月25-29日
- **Day 1-2** 数据提取功能
- **Day 3-5** 整体测试和文档完善
---
## ⚠️ 关键技术难点
### 1. AI判断准确率
**解决方案:**
- 双模型投票机制
- 优化PICO提示词
- 提供人工复核入口UNCERTAIN项
### 2. 大批量处理
**解决方案:**
- 固定3并发p-queue
- 实时进度显示
- 失败重试机制
### 3. CSV解析
**解决方案:**
- 使用papaparse库
- 支持多种编码UTF-8、GBK
- 容错处理
### 4. PDF全文提取
**解决方案:**
- 调用extraction_service
- 降级策略Nougat → PyMuPDF
---
## ✅ 快速开发检查清单
**开始开发前确认:**
- [ ] LLM网关是否已实现如未实现Week 2 Day 1-3同步开发
- [ ] 数据库Schema是否已创建asl_schema
- [ ] Prisma Schema是否已更新
- [ ] API路由是否已注册
- [ ] 前端路由是否已配置?
**常见问题:**
**Q: LLM调用超时怎么办**
A: 设置timeout=60s添加重试机制最多3次
**Q: CSV解析失败怎么办**
A: 检查编码格式,提供明确的错误提示,支持重新上传
**Q: 两个模型都返回UNCERTAIN怎么办**
A: 标记为UNCERTAIN提示用户需要人工复核
**Q: PDF提取失败怎么办**
A: 降级策略Nougat → PyMuPDF → 提示用户手动处理
---
## 📖 更多详细信息
**需要完整PRD**
`00-项目概述/AI智能文献PRD1-3.md`3个文档
**需要数据库详情:**
→ AI智能文献目录下的 `02-技术设计/01-数据库设计.md`
**需要API详情**
→ AI智能文献目录下的 `02-技术设计/02-API设计规范.md`
**需要UI设计**
`01-设计文档/AI智能文献-标题摘要初筛原型.html`
`01-设计文档/AI智能文献-全文复筛.html`
---
**最后更新:** 2025-11-06
**维护人:** 技术架构师