Major Changes: - Add StreamingService with OpenAI Compatible format - Upgrade Chat component V2 with Ant Design X integration - Implement AIA module with 12 intelligent agents - Update API routes to unified /api/v1 prefix - Update system documentation Backend (~1300 lines): - common/streaming: OpenAI Compatible adapter - modules/aia: 12 agents, conversation service, streaming integration - Update route versions (RVW, PKB to v1) Frontend (~3500 lines): - modules/aia: AgentHub + ChatWorkspace (100% prototype restoration) - shared/Chat: AIStreamChat, ThinkingBlock, useAIStream Hook - Update API endpoints to v1 Documentation: - AIA module status guide - Universal capabilities catalog - System overview updates - All module documentation sync Tested: Stream response verified, authentication working Status: AIA V2.0 core completed (85%)
925 lines
35 KiB
Markdown
925 lines
35 KiB
Markdown
# AI智能文献模å<C2A1>— - æ•°æ<C2B0>®åº“设è®?
|
||
> **文档版本�* v3.0
|
||
> **创建日期�* 2025-10-29
|
||
> **维护者:** AI智能文献开å<E282AC>‘团é˜?
|
||
> **最å<E282AC>Žæ›´æ–°ï¼š** 2025-11-22(Day 4:全文å¤<C3A5>ç›æ•°æ<C2B0>®åº“设计ï¼?
|
||
> **更新说明ï¼?* 新增全文å¤<C3A5>ç›ç›¸å…³è¡¨ï¼ˆ`AslLiterature`扩展ã€<C3A3>`AslFulltextScreeningTask`ã€<C3A3>`AslFulltextScreeningResult`ï¼?
|
||
---
|
||
|
||
## 📋 文档说明
|
||
|
||
本文档æ<EFBFBD><EFBFBD>è¿°AI智能文献模å<EFBFBD>—的数æ<EFBFBD>®åº“设计,包括数æ<EFBFBD>®è¡¨ç»“æž„ã€<EFBFBD>关系设计ã€<EFBFBD>索引设计ç‰ã€?
|
||
**æŠ€æœ¯æ ˆ**:
|
||
- æ•°æ<C2B0>®åº“:PostgreSQL 16+
|
||
- ORM:Prisma
|
||
- Schema隔离:`asl_schema`
|
||
- å…³è<C2B3>”用户表:`platform_schema.users`
|
||
|
||
---
|
||
|
||
## ðŸ<C3B0>—ï¸?Schemaæž¶æž„
|
||
|
||
ASL模å<EFBFBD>—使用独立çš?`asl_schema` 进行数æ<C2B0>®éš”离,确ä¿<C3A4>模å<C2A1>—独立性和数æ<C2B0>®å®‰å…¨ã€?
|
||
```
|
||
platform_schema
|
||
└── users (用户�
|
||
�asl_schema
|
||
├── screening_projects (ç›é€‰é¡¹ç›?
|
||
├── literatures (文献æ<C2AE>¡ç›®)
|
||
├── screening_results (æ ‡é¢˜åˆ<C3A5>ç›ç»“æžœ)
|
||
├── screening_tasks (æ ‡é¢˜åˆ<C3A5>ç›ä»»åŠ¡)
|
||
├── fulltext_screening_tasks (全文å¤<C3A5>ç›ä»»åŠ¡) â?Day 4新增
|
||
└── fulltext_screening_results (全文å¤<C3A5>ç›ç»“æžœ) â?Day 4新增
|
||
```
|
||
|
||
**v3.0 更新说明ï¼?025-11-22ï¼?*ï¼?- âœ?扩展 `literatures` 表:支æŒ<C3A6>全文生命周期管ç<C2A1>†ã€<C3A3>PDFå˜å‚¨ã€<C3A3>全文内容引ç”?- âœ?新增 `fulltext_screening_tasks` 表:管ç<C2A1>†å…¨æ–‡å¤<C3A5>ç›æ‰¹å¤„ç<E2809E>†ä»»åŠ?- âœ?新增 `fulltext_screening_results` 表:å˜å‚¨12å—æ®µè¯„估结果
|
||
- âœ?符å<C2A6>ˆäº‘原生规范:全文内容å˜å‚¨å¼•用而é<C592>žç›´æŽ¥å˜å‚¨
|
||
|
||
---
|
||
|
||
## 🗄ï¸?æ ¸å¿ƒæ•°æ<C2B0>®è¡?
|
||
### 1. ç›é€‰é¡¹ç›®è¡¨ (screening_projects)
|
||
|
||
**Prisma模型å<E280B9>?*: `AslScreeningProject`
|
||
**表å<C2A8><C3A5>**: `asl_schema.screening_projects`
|
||
|
||
```prisma
|
||
model AslScreeningProject {
|
||
id String @id @default(uuid())
|
||
userId String @map("user_id")
|
||
user User @relation("AslProjects", fields: [userId], references: [id], onDelete: Cascade)
|
||
|
||
projectName String @map("project_name")
|
||
|
||
// PICOæ ‡å‡†
|
||
picoCriteria Json @map("pico_criteria")
|
||
// âš ï¸<C3AF> æ ¼å¼<C3A5>兼容性说明:
|
||
// å‰<C3A5>端使用: { P, I, C, O, S }
|
||
// å<>Žç«¯å…¼å®¹: { P, I, C, O, S } æˆ?{ population, intervention, comparison, outcome, studyDesign }
|
||
// screeningService.ts 䏿œ‰å—æ®µæ˜ å°„é€»è¾‘
|
||
|
||
// ç›é€‰æ ‡å‡? inclusionCriteria String @map("inclusion_criteria") @db.Text
|
||
exclusionCriteria String @map("exclusion_criteria") @db.Text
|
||
|
||
// 状� status String @default("draft")
|
||
// å<>¯é€‰å€? draft, screening, completed
|
||
|
||
// ç›é€‰é…<C3A9>ç½? screeningConfig Json? @map("screening_config")
|
||
// 结构: { models: ["DeepSeek-V3", "Qwen-Max"], style: "standard" }
|
||
// âš ï¸<C3AF> 模型å<E280B9><C3A5>ç§°æ˜ å°„ï¼? // å‰<C3A5>端展示å<C2BA>? DeepSeek-V3 â†?APIå<49>? deepseek-chat
|
||
// å‰<C3A5>端展示å<C2BA>? Qwen-Max â†?APIå<49>? qwen-max
|
||
// screeningService.ts 䏿œ‰æ¨¡åž‹å<E280B9><C3A5>æ˜ å°„é€»è¾‘
|
||
|
||
// å…³è<C2B3>”
|
||
literatures AslLiterature[]
|
||
screeningTasks AslScreeningTask[]
|
||
screeningResults AslScreeningResult[]
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@map("screening_projects")
|
||
@@schema("asl_schema")
|
||
@@index([userId])
|
||
@@index([status])
|
||
}
|
||
```
|
||
|
||
**SQL表结�*:
|
||
```sql
|
||
CREATE TABLE asl_schema.screening_projects (
|
||
id TEXT PRIMARY KEY,
|
||
user_id TEXT NOT NULL,
|
||
project_name TEXT NOT NULL,
|
||
pico_criteria JSONB NOT NULL,
|
||
inclusion_criteria TEXT NOT NULL,
|
||
exclusion_criteria TEXT NOT NULL,
|
||
status TEXT NOT NULL DEFAULT 'draft',
|
||
screening_config JSONB,
|
||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
CONSTRAINT fk_user FOREIGN KEY (user_id)
|
||
REFERENCES platform_schema.users(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE INDEX idx_screening_projects_user_id ON asl_schema.screening_projects(user_id);
|
||
CREATE INDEX idx_screening_projects_status ON asl_schema.screening_projects(status);
|
||
```
|
||
|
||
---
|
||
|
||
### 2. 文献æ<C2AE>¡ç›®è¡?(literatures) â?v3.0æ›´æ–°
|
||
|
||
**Prisma模型å<E280B9>?*: `AslLiterature`
|
||
**表å<C2A8><C3A5>**: `asl_schema.literatures`
|
||
|
||
**v3.0 更新说明**ï¼?- âœ?新增 `stage` å—æ®µï¼šè¿½è¸ªæ–‡çŒ®ç”Ÿå‘½å‘¨æœŸï¼ˆimported â†?title_screened â†?pdf_acquired â†?fulltext_screened â†?data_extractedï¼?- âœ?新增 PDFå˜å‚¨å—段:支æŒ<C3A6>Dify/OSSå<53>Œé€‚é…<C3A9>(`pdfStorageType`, `pdfStorageRef`, `pdfStatus`ï¼?- âœ?新增 全文å˜å‚¨å—段ï¼?*符å<C2A6>ˆäº‘原生规范,å˜å‚¨å¼•用而é<C592>žå†…容**(`fullTextStorageRef`, `fullTextUrl`ï¼?- âœ?新增索引:`stage`, `hasPdf`, `pdfStatus` æ<><C3A6>å<EFBFBD>‡æŸ¥è¯¢æ€§èƒ½
|
||
|
||
```prisma
|
||
model AslLiterature {
|
||
id String @id @default(uuid())
|
||
projectId String @map("project_id")
|
||
project AslScreeningProject @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
|
||
// 文献基本信æ<C2A1>¯
|
||
pmid String?
|
||
title String @db.Text
|
||
abstract String @db.Text
|
||
authors String?
|
||
journal String?
|
||
publicationYear Int? @map("publication_year")
|
||
doi String?
|
||
|
||
// â?v3.0 新增:文献阶段(生命周期管ç<C2A1>†ï¼? stage String @default("imported") @map("stage")
|
||
// imported | title_screened | title_included | pdf_acquired | fulltext_screened | data_extracted
|
||
|
||
// 云原生å˜å‚¨å—段(V1.0 阶段使用,MVP阶段预留ï¼? pdfUrl String? @map("pdf_url") // PDF访问URL
|
||
pdfOssKey String? @map("pdf_oss_key") // OSSå˜å‚¨Keyï¼ˆç”¨äºŽåˆ é™¤ï¼‰
|
||
pdfFileSize Int? @map("pdf_file_size") // 文件大å°<C3A5>(å—节)
|
||
|
||
// â?v3.0 新增:PDFå˜å‚¨ï¼ˆDify/OSSå<53>Œé€‚é…<C3A9>ï¼? hasPdf Boolean @default(false) @map("has_pdf")
|
||
pdfStorageType String? @map("pdf_storage_type") // "dify" | "oss"
|
||
pdfStorageRef String? @map("pdf_storage_ref") // Dify: document_id, OSS: object_key
|
||
pdfStatus String? @map("pdf_status") // "uploading" | "ready" | "failed"
|
||
pdfUploadedAt DateTime? @map("pdf_uploaded_at")
|
||
|
||
// â?v3.0 新增:全文内容å˜å‚¨ï¼ˆäº‘原生:å˜å‚¨å¼•用而é<C592>žå†…容ï¼? fullTextStorageType String? @map("full_text_storage_type") // "dify" | "oss"
|
||
fullTextStorageRef String? @map("full_text_storage_ref") // document_id �object_key
|
||
fullTextUrl String? @map("full_text_url") // 访问URL
|
||
fullTextFormat String? @map("full_text_format") // "markdown" | "plaintext"
|
||
fullTextSource String? @map("full_text_source") // "nougat" | "pymupdf"
|
||
fullTextTokenCount Int? @map("full_text_token_count")
|
||
fullTextExtractedAt DateTime? @map("full_text_extracted_at")
|
||
|
||
// å…³è<C2B3>”
|
||
screeningResults AslScreeningResult[]
|
||
fulltextScreeningResults AslFulltextScreeningResult[] // â?v3.0 新增
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@map("literatures")
|
||
@@schema("asl_schema")
|
||
@@index([projectId])
|
||
@@index([doi])
|
||
@@index([stage]) // â?v3.0 新增
|
||
@@index([hasPdf]) // â?v3.0 新增
|
||
@@index([pdfStatus]) // â?v3.0 新增
|
||
@@unique([projectId, pmid])
|
||
}
|
||
```
|
||
|
||
**SQL表结�*(v3.0�
|
||
```sql
|
||
CREATE TABLE asl_schema.literatures (
|
||
id TEXT PRIMARY KEY,
|
||
project_id TEXT NOT NULL,
|
||
|
||
-- 文献基本信æ<C2A1>¯
|
||
pmid TEXT,
|
||
title TEXT NOT NULL,
|
||
abstract TEXT NOT NULL,
|
||
authors TEXT,
|
||
journal TEXT,
|
||
publication_year INTEGER,
|
||
doi TEXT,
|
||
|
||
-- 文献阶段
|
||
stage TEXT NOT NULL DEFAULT 'imported',
|
||
|
||
-- PDFå˜å‚¨ï¼ˆæ—§å—段,V1.0预留ï¼? pdf_url TEXT,
|
||
pdf_oss_key TEXT,
|
||
pdf_file_size INTEGER,
|
||
|
||
-- PDFå˜å‚¨ï¼ˆæ–°å—段,Dify/OSSå<53>Œé€‚é…<C3A9>ï¼? has_pdf BOOLEAN NOT NULL DEFAULT false,
|
||
pdf_storage_type TEXT,
|
||
pdf_storage_ref TEXT,
|
||
pdf_status TEXT,
|
||
pdf_uploaded_at TIMESTAMP(3),
|
||
|
||
-- 全文内容å˜å‚¨ï¼ˆå¼•用)
|
||
full_text_storage_type TEXT,
|
||
full_text_storage_ref TEXT,
|
||
full_text_url TEXT,
|
||
full_text_format TEXT,
|
||
full_text_source TEXT,
|
||
full_text_token_count INTEGER,
|
||
full_text_extracted_at TIMESTAMP(3),
|
||
|
||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
|
||
CONSTRAINT fk_project FOREIGN KEY (project_id)
|
||
REFERENCES asl_schema.screening_projects(id) ON DELETE CASCADE,
|
||
CONSTRAINT unique_project_pmid UNIQUE (project_id, pmid)
|
||
);
|
||
|
||
CREATE INDEX idx_literatures_project_id ON asl_schema.literatures(project_id);
|
||
CREATE INDEX idx_literatures_doi ON asl_schema.literatures(doi);
|
||
CREATE INDEX idx_literatures_stage ON asl_schema.literatures(stage);
|
||
CREATE INDEX idx_literatures_has_pdf ON asl_schema.literatures(has_pdf);
|
||
CREATE INDEX idx_literatures_pdf_status ON asl_schema.literatures(pdf_status);
|
||
```
|
||
|
||
**å—æ®µè¯´æ˜Ž**ï¼?
|
||
| å—æ®µ | 类型 | 说明 | 设计ç<C2A1>†ç”± |
|
||
|------|------|------|----------|
|
||
| `stage` | String | 文献阶段 | 追踪文献在整个æµ<C3A6>程ä¸çš„ä½<C3A4>ç½?|
|
||
| `pdfStorageType` | String | PDFå˜å‚¨ç±»åž‹ | "dify"\|"oss",支æŒ<C3A6>å<EFBFBD>Œé€‚é…<C3A9>å™?|
|
||
| `pdfStorageRef` | String | PDFå˜å‚¨å¼•用 | Difyçš„document_id或OSSçš„object_key |
|
||
| `fullTextStorageType` | String | 全文å˜å‚¨ç±»åž‹ | 云原生:ä¸<C3A4>直接å˜å…¨æ–‡ï¼Œå˜å¼•用 âœ?|
|
||
| `fullTextStorageRef` | String | 全文å˜å‚¨å¼•用 | 指å<E280A1>‘Dify或OSSä¸çš„全文文档 âœ?|
|
||
| `fullTextUrl` | String | 全文访问URL | 直接访问全文的URL |
|
||
| `fullTextTokenCount` | Int | Tokenæ•°é‡<C3A9> | 用于æˆ<C3A6>本估算和LLM调用优化 |
|
||
|
||
**云原生设计亮ç‚?* â<C3A2>:
|
||
- âœ?全文内容å˜å‚¨åœ¨OSS/Dify,数æ<C2B0>®åº“å<E2809C>ªå˜å¼•用(符å<C2A6>ˆäº‘原生规范ï¼?- âœ?支æŒ<C3A6>Dify â†?OSSæ— ç¼<C3A7>è¿<C3A8>移(å<CB86>ªéœ€åˆ‡æ<E280A1>¢storageTypeï¼?- âœ?æ•°æ<C2B0>®åº“è½»é‡<C3A9>,é<C592>¿å…<C3A5>大é‡<C3A9>TEXTå—æ®µ
|
||
|
||
---
|
||
|
||
### 3. ç›é€‰ç»“果表 (screening_results)
|
||
|
||
**Prisma模型å<E280B9>?*: `AslScreeningResult`
|
||
**表å<C2A8><C3A5>**: `asl_schema.screening_results`
|
||
|
||
**设计亮点**:支æŒ<C3A6>å<EFBFBD>Œæ¨¡åž‹ï¼ˆDeepSeek + Qwen)并行验è¯<C3A8>,包å<E280A6>«å®Œæ•´çš„判æ–ã€<C3A3>è¯<C3A8>æ<EFBFBD>®å’Œå†²çª<C3A7>检测ã€?
|
||
```prisma
|
||
model AslScreeningResult {
|
||
id String @id @default(uuid())
|
||
projectId String @map("project_id")
|
||
project AslScreeningProject @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
literatureId String @map("literature_id")
|
||
literature AslLiterature @relation(fields: [literatureId], references: [id], onDelete: Cascade)
|
||
|
||
// DeepSeek模型判æ–
|
||
dsModelName String @map("ds_model_name") // "deepseek-chat"
|
||
dsPJudgment String? @map("ds_p_judgment") // "match" | "partial" | "mismatch"
|
||
dsIJudgment String? @map("ds_i_judgment")
|
||
dsCJudgment String? @map("ds_c_judgment")
|
||
dsSJudgment String? @map("ds_s_judgment")
|
||
dsConclusion String? @map("ds_conclusion") // "include" | "exclude" | "uncertain"
|
||
dsConfidence Float? @map("ds_confidence") // 0-1
|
||
|
||
// DeepSeek模型è¯<C3A8>æ<EFBFBD>®
|
||
dsPEvidence String? @map("ds_p_evidence") @db.Text
|
||
dsIEvidence String? @map("ds_i_evidence") @db.Text
|
||
dsCEvidence String? @map("ds_c_evidence") @db.Text
|
||
dsSEvidence String? @map("ds_s_evidence") @db.Text
|
||
dsReason String? @map("ds_reason") @db.Text
|
||
|
||
// Qwen模型判æ–
|
||
qwenModelName String @map("qwen_model_name") // "qwen-max"
|
||
qwenPJudgment String? @map("qwen_p_judgment")
|
||
qwenIJudgment String? @map("qwen_i_judgment")
|
||
qwenCJudgment String? @map("qwen_c_judgment")
|
||
qwenSJudgment String? @map("qwen_s_judgment")
|
||
qwenConclusion String? @map("qwen_conclusion")
|
||
qwenConfidence Float? @map("qwen_confidence")
|
||
|
||
// Qwen模型è¯<C3A8>æ<EFBFBD>®
|
||
qwenPEvidence String? @map("qwen_p_evidence") @db.Text
|
||
qwenIEvidence String? @map("qwen_i_evidence") @db.Text
|
||
qwenCEvidence String? @map("qwen_c_evidence") @db.Text
|
||
qwenSEvidence String? @map("qwen_s_evidence") @db.Text
|
||
qwenReason String? @map("qwen_reason") @db.Text
|
||
|
||
// 冲çª<C3A7>状æ€? conflictStatus String @default("none") @map("conflict_status")
|
||
// å<>¯é€‰å€? none, conflict, resolved
|
||
conflictFields Json? @map("conflict_fields")
|
||
// 示例: ["P", "I", "conclusion"]
|
||
|
||
// 最终决ç–(Week 4 æ··å<C2B7>ˆæ–¹æ¡ˆä½¿ç”¨ï¼? finalDecision String? @map("final_decision") // "include" | "exclude" | null
|
||
// â?Week 4 说明:人工å¤<C3A5>æ ¸å<C2B8>Žè®¾ç½®æ¤å—段,作为最终决ç? // - include: 人工决定纳入(å<CB86>¯èƒ½æŽ¨ç¿»AI建议ï¼? // - exclude: 人工决定排除(å<CB86>¯èƒ½æŽ¨ç¿»AI建议ï¼? // - null: 未å¤<C3A5>æ ¸ï¼Œä½¿ç”¨AI决ç–
|
||
|
||
finalDecisionBy String? @map("final_decision_by") // userId
|
||
finalDecisionAt DateTime? @map("final_decision_at")
|
||
|
||
exclusionReason String? @map("exclusion_reason") @db.Text
|
||
// â?Week 4 è¯´æ˜Žï¼šäººå·¥å¡«å†™çš„æŽ’é™¤åŽŸå› ï¼ˆä¼˜å…ˆçº§é«˜äºŽAIæ<49><C3A6>å<EFBFBD>–ï¼? // - 如果finalDecision=exclude,æ¤å—段å˜å‚¨äººå·¥å¡«å†™çš„原å›? // - 如果为null,å‰<C3A5>端自动从AI判æ–䏿<C2AD><C3A6>å<EFBFBD>–(dsPJudgment/dsIJudgmentç‰ï¼‰
|
||
// - Week 4 åˆ<C3A5>ç›ç»“果页使用æ¤å—段显示排除原å›
|
||
|
||
// AI处ç<E2809E>†çжæ€? aiProcessingStatus String @default("pending") @map("ai_processing_status")
|
||
// å<>¯é€‰å€? pending, processing, completed, failed
|
||
aiProcessedAt DateTime? @map("ai_processed_at")
|
||
aiErrorMessage String? @map("ai_error_message") @db.Text
|
||
|
||
// å<>¯è¿½æº¯ä¿¡æ<C2A1>? promptVersion String @default("v1.0.0") @map("prompt_version")
|
||
rawOutput Json? @map("raw_output") // 原始LLM输出(备份)
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@map("screening_results")
|
||
@@schema("asl_schema")
|
||
@@index([projectId])
|
||
@@index([literatureId])
|
||
@@index([conflictStatus])
|
||
@@index([finalDecision])
|
||
@@unique([projectId, literatureId]) // 一篇文献在一个项目ä¸å<C2AD>ªæœ‰ä¸€ä¸ªç›é€‰ç»“æž?}
|
||
```
|
||
|
||
**SQL表结�*(简化版�
|
||
```sql
|
||
CREATE TABLE asl_schema.screening_results (
|
||
id TEXT PRIMARY KEY,
|
||
project_id TEXT NOT NULL,
|
||
literature_id TEXT NOT NULL,
|
||
|
||
-- DeepSeek判æ–
|
||
ds_model_name TEXT NOT NULL,
|
||
ds_p_judgment TEXT,
|
||
ds_i_judgment TEXT,
|
||
ds_c_judgment TEXT,
|
||
ds_s_judgment TEXT,
|
||
ds_conclusion TEXT,
|
||
ds_confidence DOUBLE PRECISION,
|
||
ds_p_evidence TEXT,
|
||
ds_i_evidence TEXT,
|
||
ds_c_evidence TEXT,
|
||
ds_s_evidence TEXT,
|
||
ds_reason TEXT,
|
||
|
||
-- Qwen判æ–
|
||
qwen_model_name TEXT NOT NULL,
|
||
qwen_p_judgment TEXT,
|
||
qwen_i_judgment TEXT,
|
||
qwen_c_judgment TEXT,
|
||
qwen_s_judgment TEXT,
|
||
qwen_conclusion TEXT,
|
||
qwen_confidence DOUBLE PRECISION,
|
||
qwen_p_evidence TEXT,
|
||
qwen_i_evidence TEXT,
|
||
qwen_c_evidence TEXT,
|
||
qwen_s_evidence TEXT,
|
||
qwen_reason TEXT,
|
||
|
||
-- 冲çª<C3A7>状æ€? conflict_status TEXT NOT NULL DEFAULT 'none',
|
||
conflict_fields JSONB,
|
||
|
||
-- 最终决ç? final_decision TEXT,
|
||
final_decision_by TEXT,
|
||
final_decision_at TIMESTAMP(3),
|
||
exclusion_reason TEXT,
|
||
|
||
-- AI处ç<E2809E>†çжæ€? ai_processing_status TEXT NOT NULL DEFAULT 'pending',
|
||
ai_processed_at TIMESTAMP(3),
|
||
ai_error_message TEXT,
|
||
|
||
-- å<>¯è¿½æº¯ä¿¡æ<C2A1>? prompt_version TEXT NOT NULL DEFAULT 'v1.0.0',
|
||
raw_output JSONB,
|
||
|
||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
|
||
CONSTRAINT fk_project_result FOREIGN KEY (project_id)
|
||
REFERENCES asl_schema.screening_projects(id) ON DELETE CASCADE,
|
||
CONSTRAINT fk_literature FOREIGN KEY (literature_id)
|
||
REFERENCES asl_schema.literatures(id) ON DELETE CASCADE,
|
||
CONSTRAINT unique_project_literature UNIQUE (project_id, literature_id)
|
||
);
|
||
|
||
CREATE INDEX idx_screening_results_project_id ON asl_schema.screening_results(project_id);
|
||
CREATE INDEX idx_screening_results_literature_id ON asl_schema.screening_results(literature_id);
|
||
CREATE INDEX idx_screening_results_conflict_status ON asl_schema.screening_results(conflict_status);
|
||
CREATE INDEX idx_screening_results_final_decision ON asl_schema.screening_results(final_decision);
|
||
```
|
||
|
||
---
|
||
|
||
### 4. ç›é€‰ä»»åŠ¡è¡¨ (screening_tasks)
|
||
|
||
**Prisma模型å<E280B9>?*: `AslScreeningTask`
|
||
**表å<C2A8><C3A5>**: `asl_schema.screening_tasks`
|
||
|
||
```prisma
|
||
model AslScreeningTask {
|
||
id String @id @default(uuid())
|
||
projectId String @map("project_id")
|
||
project AslScreeningProject @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
|
||
taskType String @map("task_type") // "title_abstract" | "full_text"
|
||
status String @default("pending")
|
||
// å<>¯é€‰å€? pending, running, completed, failed
|
||
|
||
// 进度统计
|
||
totalItems Int @map("total_items")
|
||
processedItems Int @default(0) @map("processed_items")
|
||
successItems Int @default(0) @map("success_items")
|
||
failedItems Int @default(0) @map("failed_items")
|
||
conflictItems Int @default(0) @map("conflict_items")
|
||
|
||
// æ—¶é—´ä¿¡æ<C2A1>¯
|
||
startedAt DateTime? @map("started_at")
|
||
completedAt DateTime? @map("completed_at")
|
||
estimatedEndAt DateTime? @map("estimated_end_at")
|
||
|
||
// 错误信æ<C2A1>¯
|
||
errorMessage String? @map("error_message") @db.Text
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@map("screening_tasks")
|
||
@@schema("asl_schema")
|
||
@@index([projectId])
|
||
@@index([status])
|
||
}
|
||
```
|
||
|
||
**SQL表结�*:
|
||
```sql
|
||
CREATE TABLE asl_schema.screening_tasks (
|
||
id TEXT PRIMARY KEY,
|
||
project_id TEXT NOT NULL,
|
||
task_type TEXT NOT NULL,
|
||
status TEXT NOT NULL DEFAULT 'pending',
|
||
total_items INTEGER NOT NULL,
|
||
processed_items INTEGER NOT NULL DEFAULT 0,
|
||
success_items INTEGER NOT NULL DEFAULT 0,
|
||
failed_items INTEGER NOT NULL DEFAULT 0,
|
||
conflict_items INTEGER NOT NULL DEFAULT 0,
|
||
started_at TIMESTAMP(3),
|
||
completed_at TIMESTAMP(3),
|
||
estimated_end_at TIMESTAMP(3),
|
||
error_message TEXT,
|
||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
CONSTRAINT fk_project_task FOREIGN KEY (project_id)
|
||
REFERENCES asl_schema.screening_projects(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE INDEX idx_screening_tasks_project_id ON asl_schema.screening_tasks(project_id);
|
||
CREATE INDEX idx_screening_tasks_status ON asl_schema.screening_tasks(status);
|
||
```
|
||
|
||
---
|
||
|
||
### 5. 全文å¤<C3A5>ç›ä»»åŠ¡è¡?(fulltext_screening_tasks) â?v3.0新增
|
||
|
||
**Prisma模型å<E280B9>?*: `AslFulltextScreeningTask`
|
||
**表å<C2A8><C3A5>**: `asl_schema.fulltext_screening_tasks`
|
||
|
||
**è®¾è®¡ç›®æ ‡**:管ç<C2A1>†å…¨æ–‡å¤<C3A5>ç›çš„æ‰¹å¤„ç<E2809E>†ä»»åŠ¡ï¼Œæ”¯æŒ<C3A6>å<EFBFBD>Œæ¨¡åž‹å¹¶è¡Œè°ƒç”¨ã€<C3A3>æˆ<C3A6>本追踪ã€<C3A3>é™<C3A9>级模å¼?
|
||
```prisma
|
||
model AslFulltextScreeningTask {
|
||
id String @id @default(uuid())
|
||
projectId String @map("project_id")
|
||
project AslScreeningProject @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
|
||
// 任务é…<C3A9>ç½®
|
||
modelA String @map("model_a") // "deepseek-v3"
|
||
modelB String @map("model_b") // "qwen-max"
|
||
promptVersion String @default("v1.0.0") @map("prompt_version")
|
||
|
||
// 任务状� status String @default("pending")
|
||
// "pending" | "running" | "completed" | "failed" | "cancelled"
|
||
|
||
// 进度统计
|
||
totalCount Int @map("total_count")
|
||
processedCount Int @default(0) @map("processed_count")
|
||
successCount Int @default(0) @map("success_count")
|
||
failedCount Int @default(0) @map("failed_count")
|
||
degradedCount Int @default(0) @map("degraded_count") // å<>•模型æˆ<C3A6>åŠ?
|
||
// æˆ<C3A6>本统计
|
||
totalTokens Int @default(0) @map("total_tokens")
|
||
totalCost Float @default(0) @map("total_cost")
|
||
|
||
// æ—¶é—´ä¿¡æ<C2A1>¯
|
||
startedAt DateTime? @map("started_at")
|
||
completedAt DateTime? @map("completed_at")
|
||
estimatedEndAt DateTime? @map("estimated_end_at")
|
||
|
||
// 错误信æ<C2A1>¯
|
||
errorMessage String? @map("error_message") @db.Text
|
||
errorStack String? @map("error_stack") @db.Text
|
||
|
||
// å…³è<C2B3>”
|
||
results AslFulltextScreeningResult[]
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@map("fulltext_screening_tasks")
|
||
@@schema("asl_schema")
|
||
@@index([projectId])
|
||
@@index([status])
|
||
@@index([createdAt])
|
||
}
|
||
```
|
||
|
||
**SQL表结�*:
|
||
```sql
|
||
CREATE TABLE asl_schema.fulltext_screening_tasks (
|
||
id TEXT PRIMARY KEY,
|
||
project_id TEXT NOT NULL,
|
||
|
||
-- 任务é…<C3A9>ç½®
|
||
model_a TEXT NOT NULL,
|
||
model_b TEXT NOT NULL,
|
||
prompt_version TEXT NOT NULL DEFAULT 'v1.0.0',
|
||
|
||
-- 任务状� status TEXT NOT NULL DEFAULT 'pending',
|
||
|
||
-- 进度统计
|
||
total_count INTEGER NOT NULL,
|
||
processed_count INTEGER NOT NULL DEFAULT 0,
|
||
success_count INTEGER NOT NULL DEFAULT 0,
|
||
failed_count INTEGER NOT NULL DEFAULT 0,
|
||
degraded_count INTEGER NOT NULL DEFAULT 0,
|
||
|
||
-- æˆ<C3A6>本统计
|
||
total_tokens INTEGER NOT NULL DEFAULT 0,
|
||
total_cost DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||
|
||
-- æ—¶é—´ä¿¡æ<C2A1>¯
|
||
started_at TIMESTAMP(3),
|
||
completed_at TIMESTAMP(3),
|
||
estimated_end_at TIMESTAMP(3),
|
||
|
||
-- 错误信æ<C2A1>¯
|
||
error_message TEXT,
|
||
error_stack TEXT,
|
||
|
||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
|
||
CONSTRAINT fk_project_fulltext_task FOREIGN KEY (project_id)
|
||
REFERENCES asl_schema.screening_projects(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE INDEX idx_fulltext_screening_tasks_project_id ON asl_schema.fulltext_screening_tasks(project_id);
|
||
CREATE INDEX idx_fulltext_screening_tasks_status ON asl_schema.fulltext_screening_tasks(status);
|
||
CREATE INDEX idx_fulltext_screening_tasks_created_at ON asl_schema.fulltext_screening_tasks(created_at);
|
||
```
|
||
|
||
**å—æ®µè¯´æ˜Ž**ï¼?
|
||
| å—æ®µ | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `modelA / modelB` | String | å<>Œæ¨¡åž‹å<E280B9><C3A5>称(deepseek-v3 + qwen-maxï¼?|
|
||
| `degradedCount` | Int | å<>•模型æˆ<C3A6>功的任务数(容错机制ï¼?|
|
||
| `totalTokens` | Int | 累计Token使用�|
|
||
| `totalCost` | Float | 累计æˆ<C3A6>本(元ï¼?|
|
||
| `promptVersion` | String | Prompt版本(å<CB86>¯è¿½æº¯ï¼?|
|
||
|
||
---
|
||
|
||
### 6. 全文å¤<C3A5>ç›ç»“æžœè¡?(fulltext_screening_results) â?v3.0新增
|
||
|
||
**Prisma模型å<E280B9>?*: `AslFulltextScreeningResult`
|
||
**表å<C2A8><C3A5>**: `asl_schema.fulltext_screening_results`
|
||
|
||
**è®¾è®¡ç›®æ ‡**:å˜å‚?2å—æ®µè¯¦ç»†è¯„估结果,支æŒ<C3A6>å<EFBFBD>Œæ¨¡åž‹å¯¹æ¯”ã€<C3A3>验è¯<C3A8>结果ã€<C3A3>冲çª<C3A7>检æµ?
|
||
**设计亮点**ï¼?- âœ?完整的å<E2809E>Œæ¨¡åž‹ç»“果(fields + overall + logsï¼?- âœ?医å¦é€»è¾‘验è¯<C3A8>å’Œè¯<C3A8>æ<EFBFBD>®é“¾éªŒè¯<C3A8>结果
|
||
- âœ?冲çª<C3A7>检测和å¤<C3A5>æ ¸ä¼˜å…ˆçº?- âœ?é™<C3A9>级模å¼<C3A5>支æŒ<C3A6>(å<CB86>•模型æˆ<C3A6>功ï¼?- âœ?JSONå˜å‚¨12å—æ®µè¯„估(符å<C2A6>ˆäº‘原生规范ï¼?
|
||
```prisma
|
||
model AslFulltextScreeningResult {
|
||
id String @id @default(uuid())
|
||
taskId String @map("task_id")
|
||
task AslFulltextScreeningTask @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
||
projectId String @map("project_id")
|
||
project AslScreeningProject @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||
literatureId String @map("literature_id")
|
||
literature AslLiterature @relation(fields: [literatureId], references: [id], onDelete: Cascade)
|
||
|
||
// ====== 模型A结果(DeepSeek-V3�=====
|
||
modelAName String @map("model_a_name")
|
||
modelAStatus String @map("model_a_status") // "success" | "failed"
|
||
modelAFields Json @map("model_a_fields") // 12å—æ®µè¯„ä¼° { field1: {...}, field2: {...}, ... }
|
||
modelAOverall Json @map("model_a_overall") // 总体评估 { decision, confidence, keyIssues }
|
||
modelAProcessingLog Json? @map("model_a_processing_log")
|
||
modelAVerification Json? @map("model_a_verification")
|
||
modelATokens Int? @map("model_a_tokens")
|
||
modelACost Float? @map("model_a_cost")
|
||
modelAError String? @map("model_a_error") @db.Text
|
||
|
||
// ====== 模型B结果(Qwen-Max�=====
|
||
modelBName String @map("model_b_name")
|
||
modelBStatus String @map("model_b_status")
|
||
modelBFields Json @map("model_b_fields")
|
||
modelBOverall Json @map("model_b_overall")
|
||
modelBProcessingLog Json? @map("model_b_processing_log")
|
||
modelBVerification Json? @map("model_b_verification")
|
||
modelBTokens Int? @map("model_b_tokens")
|
||
modelBCost Float? @map("model_b_cost")
|
||
modelBError String? @map("model_b_error") @db.Text
|
||
|
||
// ====== 验è¯<C3A8>结果 ======
|
||
medicalLogicIssues Json? @map("medical_logic_issues") // MedicalLogicValidator输出
|
||
evidenceChainIssues Json? @map("evidence_chain_issues") // EvidenceChainValidator输出
|
||
|
||
// ====== 冲çª<C3A7>检æµ?======
|
||
isConflict Boolean @default(false) @map("is_conflict")
|
||
conflictSeverity String? @map("conflict_severity") // "high" | "medium" | "low"
|
||
conflictFields String[] @map("conflict_fields") // ["field1", "field9", "overall"]
|
||
conflictDetails Json? @map("conflict_details")
|
||
reviewPriority Int? @map("review_priority") // 0-100å¤<C3A5>æ ¸ä¼˜å…ˆçº? reviewDeadline DateTime? @map("review_deadline")
|
||
|
||
// ====== 最终决ç?======
|
||
finalDecision String? @map("final_decision") // "include" | "exclude" | null
|
||
finalDecisionBy String? @map("final_decision_by")
|
||
finalDecisionAt DateTime? @map("final_decision_at")
|
||
exclusionReason String? @map("exclusion_reason") @db.Text
|
||
reviewNotes String? @map("review_notes") @db.Text
|
||
|
||
// ====== 处ç<E2809E>†çжæ€?======
|
||
processingStatus String @default("pending") @map("processing_status")
|
||
// "pending" | "processing" | "completed" | "failed" | "degraded"
|
||
isDegraded Boolean @default(false) @map("is_degraded")
|
||
degradedModel String? @map("degraded_model") // "modelA" | "modelB"
|
||
|
||
processedAt DateTime? @map("processed_at")
|
||
|
||
// ====== å<>¯è¿½æº¯ä¿¡æ<C2A1>?======
|
||
promptVersion String @default("v1.0.0") @map("prompt_version")
|
||
rawOutputA Json? @map("raw_output_a")
|
||
rawOutputB Json? @map("raw_output_b")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
@@map("fulltext_screening_results")
|
||
@@schema("asl_schema")
|
||
@@index([taskId])
|
||
@@index([projectId])
|
||
@@index([literatureId])
|
||
@@index([isConflict])
|
||
@@index([finalDecision])
|
||
@@index([reviewPriority])
|
||
@@unique([projectId, literatureId]) // 一篇文献å<C2AE>ªæœ‰ä¸€ä¸ªå…¨æ–‡å¤<C3A5>ç›ç»“æž?}
|
||
```
|
||
|
||
**SQL表结æž?*(简化版,实际包å<E280A6>«æ‰€æœ‰å—段):
|
||
```sql
|
||
CREATE TABLE asl_schema.fulltext_screening_results (
|
||
id TEXT PRIMARY KEY,
|
||
task_id TEXT NOT NULL,
|
||
project_id TEXT NOT NULL,
|
||
literature_id TEXT NOT NULL,
|
||
|
||
-- 模型A结果
|
||
model_a_name TEXT NOT NULL,
|
||
model_a_status TEXT NOT NULL,
|
||
model_a_fields JSONB NOT NULL,
|
||
model_a_overall JSONB NOT NULL,
|
||
model_a_processing_log JSONB,
|
||
model_a_verification JSONB,
|
||
model_a_tokens INTEGER,
|
||
model_a_cost DOUBLE PRECISION,
|
||
model_a_error TEXT,
|
||
|
||
-- 模型B结果(å<CB86>Œä¸Šï¼‰
|
||
model_b_name TEXT NOT NULL,
|
||
model_b_status TEXT NOT NULL,
|
||
model_b_fields JSONB NOT NULL,
|
||
model_b_overall JSONB NOT NULL,
|
||
model_b_processing_log JSONB,
|
||
model_b_verification JSONB,
|
||
model_b_tokens INTEGER,
|
||
model_b_cost DOUBLE PRECISION,
|
||
model_b_error TEXT,
|
||
|
||
-- 验è¯<C3A8>结果
|
||
medical_logic_issues JSONB,
|
||
evidence_chain_issues JSONB,
|
||
|
||
-- 冲çª<C3A7>检æµ? is_conflict BOOLEAN NOT NULL DEFAULT false,
|
||
conflict_severity TEXT,
|
||
conflict_fields TEXT[],
|
||
conflict_details JSONB,
|
||
review_priority INTEGER,
|
||
review_deadline TIMESTAMP(3),
|
||
|
||
-- 最终决ç? final_decision TEXT,
|
||
final_decision_by TEXT,
|
||
final_decision_at TIMESTAMP(3),
|
||
exclusion_reason TEXT,
|
||
review_notes TEXT,
|
||
|
||
-- 处ç<E2809E>†çжæ€? processing_status TEXT NOT NULL DEFAULT 'pending',
|
||
is_degraded BOOLEAN NOT NULL DEFAULT false,
|
||
degraded_model TEXT,
|
||
processed_at TIMESTAMP(3),
|
||
|
||
-- å<>¯è¿½æº¯ä¿¡æ<C2A1>? prompt_version TEXT NOT NULL DEFAULT 'v1.0.0',
|
||
raw_output_a JSONB,
|
||
raw_output_b JSONB,
|
||
|
||
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
|
||
CONSTRAINT fk_task FOREIGN KEY (task_id)
|
||
REFERENCES asl_schema.fulltext_screening_tasks(id) ON DELETE CASCADE,
|
||
CONSTRAINT fk_project_fulltext_result FOREIGN KEY (project_id)
|
||
REFERENCES asl_schema.screening_projects(id) ON DELETE CASCADE,
|
||
CONSTRAINT fk_literature_fulltext FOREIGN KEY (literature_id)
|
||
REFERENCES asl_schema.literatures(id) ON DELETE CASCADE,
|
||
CONSTRAINT unique_project_literature_fulltext UNIQUE (project_id, literature_id)
|
||
);
|
||
|
||
CREATE INDEX idx_fulltext_screening_results_task_id ON asl_schema.fulltext_screening_results(task_id);
|
||
CREATE INDEX idx_fulltext_screening_results_project_id ON asl_schema.fulltext_screening_results(project_id);
|
||
CREATE INDEX idx_fulltext_screening_results_literature_id ON asl_schema.fulltext_screening_results(literature_id);
|
||
CREATE INDEX idx_fulltext_screening_results_is_conflict ON asl_schema.fulltext_screening_results(is_conflict);
|
||
CREATE INDEX idx_fulltext_screening_results_final_decision ON asl_schema.fulltext_screening_results(final_decision);
|
||
CREATE INDEX idx_fulltext_screening_results_review_priority ON asl_schema.fulltext_screening_results(review_priority);
|
||
```
|
||
|
||
**JSONå—æ®µç¤ºä¾‹**ï¼?
|
||
**modelAFields (12å—æ®µè¯„ä¼°)**:
|
||
```json
|
||
{
|
||
"field1": {
|
||
"present": true,
|
||
"completeness": "完整",
|
||
"extractable": true,
|
||
"quote": "第一作者:Zhang et al., å<>‘表äº?JAMA 2023...",
|
||
"location": "Title page, Methods section",
|
||
"note": "文献æ<C2AE>¥æº<C3A6>ä¿¡æ<C2A1>¯å®Œæ•´"
|
||
},
|
||
"field2": { ... },
|
||
// ... field3-field12
|
||
}
|
||
```
|
||
|
||
**modelAOverall (总体评估)**:
|
||
```json
|
||
{
|
||
"decision": "include",
|
||
"confidence": 0.92,
|
||
"keyIssues": [
|
||
"éš<C3A9>机化方法æ<E280A2><C3A6>述完æ•?,
|
||
"盲法实施清晰",
|
||
"ç»“å±€æŒ‡æ ‡å<EFBFBD>¯æ<EFBFBD><EFBFBD>å<EFBFBD>?
|
||
]
|
||
}
|
||
```
|
||
|
||
**medicalLogicIssues (医å¦é€»è¾‘验è¯<C3A8>)**:
|
||
```json
|
||
{
|
||
"hasIssues": false,
|
||
"issues": []
|
||
}
|
||
```
|
||
|
||
**conflictDetails (冲çª<C3A7>详情)**:
|
||
```json
|
||
{
|
||
"field9": {
|
||
"modelA": "完整",
|
||
"modelB": "ä¸<C3A4>完æ•?,
|
||
"severity": "high"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 æ•°æ<C2B0>®å…³ç³»å›¾ï¼ˆv3.0æ›´æ–°ï¼?
|
||
```
|
||
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
|
||
```
|
||
|
||
---
|
||
|
||
## ðŸ”<C5B8> 索引设计汇总(v3.0æ›´æ–°ï¼?
|
||
| 表å<C2A8><C3A5> | ç´¢å¼•å—æ®µ | 索引类型 | 说明 |
|
||
|------|---------|---------|------|
|
||
| screening_projects | user_id | B-tree | 用户项目查询 |
|
||
| screening_projects | status | B-tree | 状æ€<C3A6>ç›é€?|
|
||
| literatures | project_id | B-tree | 项目文献查询 |
|
||
| literatures | doi | B-tree | DOI查é‡<C3A9> |
|
||
| literatures | stage â?| B-tree | 文献阶段查询 v3.0 |
|
||
| literatures | has_pdf â?| B-tree | PDF获å<C2B7>–状æ€?v3.0 |
|
||
| literatures | pdf_status â?| B-tree | PDFä¸Šä¼ çŠ¶æ€?v3.0 |
|
||
| literatures | (project_id, pmid) | Unique | 防æ¢é‡<C3A9>å¤<C3A5>导入 |
|
||
| screening_results | project_id | B-tree | 项目结果查询 |
|
||
| screening_results | literature_id | B-tree | 文献结果查询 |
|
||
| screening_results | conflict_status | B-tree | 冲çª<C3A7>ç›é€?|
|
||
| screening_results | final_decision | B-tree | 决ç–ç›é€?|
|
||
| screening_results | (project_id, literature_id) | Unique | 唯一性约æ<C2A6>?|
|
||
| screening_tasks | project_id | B-tree | 项目任务查询 |
|
||
| screening_tasks | status | B-tree | 任务状æ€<C3A6>ç›é€?|
|
||
| fulltext_screening_tasks â?| project_id | B-tree | 全文任务查询 v3.0 |
|
||
| fulltext_screening_tasks â?| status | B-tree | 任务状æ€<C3A6>ç›é€?v3.0 |
|
||
| fulltext_screening_tasks â?| created_at | B-tree | 时间排åº<C3A5> v3.0 |
|
||
| fulltext_screening_results â?| task_id | B-tree | 任务结果查询 v3.0 |
|
||
| fulltext_screening_results â?| project_id | B-tree | 项目结果查询 v3.0 |
|
||
| fulltext_screening_results â?| literature_id | B-tree | 文献结果查询 v3.0 |
|
||
| fulltext_screening_results â?| is_conflict | B-tree | 冲çª<C3A7>ç›é€?v3.0 |
|
||
| fulltext_screening_results â?| final_decision | B-tree | 决ç–ç›é€?v3.0 |
|
||
| fulltext_screening_results â?| review_priority | B-tree | å¤<C3A5>æ ¸ä¼˜å…ˆçº?v3.0 |
|
||
| fulltext_screening_results â?| (project_id, literature_id) | Unique | 唯一性约æ<C2A6>?v3.0 |
|
||
|
||
**索引总数**: 25个(v3.0新增13个)
|
||
**唯一约æ<C2A6>Ÿ**: 4个(v3.0新增1个)
|
||
|
||
**v3.0索引优化说明**ï¼?- âœ?`literatures.stage`: 快速查询特定阶段的文献(如"pdf_acquired"待全文å¤<C3A5>ç›ï¼‰
|
||
- âœ?`fulltext_screening_results.review_priority`: 优化人工å¤<C3A5>æ ¸é˜Ÿåˆ—æŽ’åº<C3A5>
|
||
- âœ?`fulltext_screening_tasks.created_at`: 任务历å<E280A0>²æŸ¥è¯¢ä¼˜åŒ–
|
||
|
||
---
|
||
|
||
## 💾 æ•°æ<C2B0>®å—å…¸
|
||
|
||
### PICOæ ‡å‡† (picoCriteria JSON)
|
||
```json
|
||
{
|
||
"population": "ç ”ç©¶äººç¾¤ï¼Œå¦‚ï¼?型糖尿病æˆ<C3A6>人患è€?,
|
||
"intervention": "干预措施,如:SGLT2抑制�,
|
||
"comparison": "对照,如:安慰剂或常规疗�,
|
||
"outcome": "ç»“å±€æŒ‡æ ‡ï¼Œå¦‚ï¼šå¿ƒè¡€ç®¡ç»“å±€",
|
||
"studyDesign": "ç ”ç©¶è®¾è®¡ï¼Œå¦‚ï¼šéš<EFBFBD>机对照试éª?(RCT)"
|
||
}
|
||
```
|
||
|
||
### ç›é€‰é…<C3A9>ç½?(screeningConfig JSON)
|
||
```json
|
||
{
|
||
"models": ["deepseek-chat", "qwen-max"],
|
||
"temperature": 0,
|
||
"maxRetries": 3
|
||
}
|
||
```
|
||
|
||
### 冲çª<C3A7>å—æ®µ (conflictFields JSON)
|
||
```json
|
||
["P", "I", "C", "S", "conclusion"]
|
||
```
|
||
|
||
### 原始输出 (rawOutput JSON)
|
||
```json
|
||
{
|
||
"deepseek": { "判æ–": {...}, "è¯<C3A8>æ<EFBFBD>®": {...} },
|
||
"qwen": { "判æ–": {...}, "è¯<C3A8>æ<EFBFBD>®": {...} }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔒 æ•°æ<C2B0>®å®‰å…¨
|
||
|
||
### Schema隔离
|
||
- 使用 `asl_schema` 与其他模å<C2A1>—æ•°æ<C2B0>®éš”ç¦?- 用户表在 `platform_schema`,统一管ç<EFBFBD>†
|
||
|
||
### 级è<C2A7>”åˆ é™¤
|
||
- åˆ é™¤ç”¨æˆ· â†?è‡ªåŠ¨åˆ é™¤æ‰€æœ‰ç›é€‰é¡¹ç›®å<C2AE>Šå…³è<C2B3>”æ•°æ<C2B0>®
|
||
- åˆ é™¤é¡¹ç›® â†?è‡ªåŠ¨åˆ é™¤æ–‡çŒ®ã€<C3A3>结果ã€<C3A3>ä»»åŠ?- åˆ é™¤æ–‡çŒ® â†?è‡ªåŠ¨åˆ é™¤ç›é€‰ç»“æž?
|
||
### 唯一性约æ<C2A6>?- å<>Œä¸€é¡¹ç›®ä¸PMID唯一(å…<C3A5>è®¸æ— PMIDï¼?- å<>Œä¸€é¡¹ç›®ä¸ä¸€ç¯‡æ–‡çŒ®å<C2AE>ªæœ‰ä¸€ä¸ªç›é€‰ç»“æž?
|
||
---
|
||
|
||
## 📈 æ•°æ<C2B0>®é‡<C3A9>预ä¼?
|
||
| 项目规模 | 文献æ•?| ç›é€‰ç»“æž?| å˜å‚¨ç©ºé—´ |
|
||
|---------|--------|---------|----------|
|
||
| å°<C3A5>åž‹ | 100-500 | 100-500 | < 10 MB |
|
||
| ä¸åž‹ | 500-2000 | 500-2000 | 10-50 MB |
|
||
| 大型 | 2000-5000 | 2000-5000 | 50-200 MB |
|
||
| 超大�| 5000+ | 5000+ | 200 MB+ |
|
||
|
||
**å<>•æ<E280A2>¡è®°å½•大å°<C3A5>ä¼°ç®—**:
|
||
- 文献æ<C2AE>¡ç›®ï¼š~2-5 KB
|
||
- ç›é€‰ç»“果:~5-10 KB(å<CB86>«å<C2AB>Œæ¨¡åž‹åˆ¤æ–å’Œè¯<C3A8>æ<EFBFBD>®ï¼?
|
||
---
|
||
|
||
## â<>?å<>Žç»è§„划
|
||
|
||
### Phase 2 (全文å¤<C3A5>ç›) âœ?v3.0已完æˆ?- [x] 扩展 `literatures` 表(生命周期管ç<C2A1>†ï¼?- [x] æ·»åŠ `fulltext_screening_tasks` è¡?- [x] æ·»åŠ `fulltext_screening_results` 表(12å—æ®µï¼?
|
||
### Phase 3 (æ•°æ<C2B0>®æ<C2AE><C3A6>å<EFBFBD>–) å¾…å¼€å<E282AC>?- [ ] å¤<C3A5>用 `fulltext_screening_tasks` 表(切æ<E280A1>¢æ¨¡å¼<C3A5>ï¼?- [ ] å¤<C3A5>用 `fulltext_screening_results` 表(å˜å‚¨æ<C2A8><C3A6>å<EFBFBD>–æ•°æ<C2B0>®ï¼?- [ ] 或新å¢?`data_extraction_results` 表(如需独立ï¼?
|
||
### Phase 4 (è´¨é‡<C3A9>评估) å¾…è§„åˆ?- [ ] è´¨é‡<C3A9>评估结果è¡?- [ ] å<><C3A5>倚风险评估表
|
||
- [ ] GRADEè¯<C3A8>æ<EFBFBD>®è´¨é‡<C3A9>è¡?
|
||
---
|
||
|
||
## ðŸ“<C5B8> v3.0 设计决ç–记录
|
||
|
||
### 决ç–1: 全文内容å˜å‚¨å¼•用而é<C592>žç›´æŽ¥å˜å‚¨ âœ?
|
||
**问题**:全文内容是å<C2AF>¦å˜å‚¨åœ¨æ•°æ<C2B0>®åº“?
|
||
|
||
**方案对比**�| 方案 | 优点 | 缺点 |
|
||
|------|------|------|
|
||
| å˜TEXT | LLM调用å¿?| è¿<C3A8>背云原生规范,数æ<C2B0>®åº“臃è‚?|
|
||
| å˜å¼•ç”?| 符å<C2A6>ˆè§„范,轻é‡?| LLMè°ƒç”¨å¢žåŠ 100-200ms |
|
||
|
||
**决ç–**:✅ 采用方案2(å˜å¼•用ï¼?- 符å<C2A6>ˆäº‘原生å˜å‚¨ä¸Žè®¡ç®—分离原则
|
||
- 支æŒ<C3A6>超大文献ï¼?1MBï¼?- RDSå˜å‚¨æˆ<C3A6>本是OSSçš?-10å€?
|
||
### 决ç–2: 12å—æ®µä½¿ç”¨JSONå˜å‚¨ âœ?
|
||
**问题**ï¼?2å—æ®µæ˜¯æ‹†åˆ†ä¸ºåˆ—还是JSONå˜å‚¨ï¼?
|
||
**决ç–**:✅ 使用PostgreSQL JSONB
|
||
- ä¸<C3A4>需è¦<C3A8>å<EFBFBD>•独查询æŸ<C3A6>ä¸ªå—æ®µå†…éƒ?- å—æ®µç»“æž„å¤<C3A5>æ<EFBFBD>‚ï¼?个å<C3A5>å—æ®µï¼?- JSONB性能优秀且支æŒ<C3A6>GIN索引
|
||
|
||
### 决ç–3: 独立全文å¤<C3A5>ç›ç»“æžœè¡?âœ?
|
||
**问题**:是å<C2AF>¦å¤<C3A5>ç”?`screening_results` 表?
|
||
|
||
**决ç–**:✅ 新增独立è¡?`fulltext_screening_results`
|
||
- æ•°æ<C2B0>®ç»“构完全ä¸<C3A4>å<EFBFBD>Œï¼ˆPICOS vs 12å—æ®µï¼?- é<>¿å…<C3A5>å—æ®µå†—余和逻辑耦å<C2A6>ˆ
|
||
- 便于独立维护和优�
|
||
---
|
||
|
||
**文档版本�* v3.0
|
||
**最å<E282AC>Žæ›´æ–°ï¼š** 2025-11-22(Day 4:全文å¤<C3A5>ç›æ•°æ<C2B0>®åº“设计ï¼?
|
||
**维护者:** AI智能文献开å<E282AC>‘团é˜?
|
||
**版本历å<E280A0>²**ï¼?- v3.0 (2025-11-22): 全文å¤<C3A5>ç›æ•°æ<C2B0>®åº“设计,新增3ä¸ªè¡¨å’Œç›¸å…³å—æ®?- v2.2 (2025-11-21): Week 4统计功能完æˆ<C3A6>
|
||
- v2.0 (2025-11-18): æ ‡é¢˜åˆ<C3A5>ç›æ•°æ<C2B0>®åº“设è®?- v1.0 (2025-10-29): åˆ<C3A5>始版本
|