Files
AIclinicalresearch/docs/03-业务模块/ASL-AI智能文献/02-技术设计/01-数据库设计.md
HaHafeng 1b53ab9d52 feat(aia): Complete AIA V2.0 with universal streaming capabilities
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%)
2026-01-14 19:15:01 +08:00

925 lines
35 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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>å§ç‰ˆæœ¬