Files
AIclinicalresearch/backend/prisma/schema.prisma
HaHafeng 96290d2f76 feat(aia): Implement Protocol Agent MVP with reusable Agent framework
Sprint 1-3 Completed (Backend + Frontend):

Backend (Sprint 1-2):
- Implement 5-layer Agent framework (Query->Planner->Executor->Tools->Reflection)
- Create agent_schema with 6 tables (agent_definitions, stages, prompts, sessions, traces, reflexion_rules)
- Create protocol_schema with 2 tables (protocol_contexts, protocol_generations)
- Implement Protocol Agent core services (Orchestrator, ContextService, PromptBuilder)
- Integrate LLM service adapter (DeepSeek/Qwen/GPT-5/Claude)
- 6 API endpoints with full authentication
- 10/10 API tests passed

Frontend (Sprint 3):
- Add Protocol Agent entry in AgentHub (indigo theme card)
- Implement ProtocolAgentPage with 3-column layout
- Collapsible sidebar (Gemini style, 48px <-> 280px)
- StatePanel with 5 stage cards (scientific_question, pico, study_design, sample_size, endpoints)
- ChatArea with sync button and action cards integration
- 100% prototype design restoration (608 lines CSS)
- Detailed endpoints structure: baseline, exposure, outcomes, confounders

Features:
- 5-stage dialogue flow for research protocol design
- Conversation-driven interaction with sync-to-protocol button
- Real-time context state management
- One-click protocol generation button (UI ready, backend pending)

Database:
- agent_schema: 6 tables for reusable Agent framework
- protocol_schema: 2 tables for Protocol Agent
- Seed data: 1 agent + 5 stages + 9 prompts + 4 reflexion rules

Code Stats:
- Backend: 13 files, 4338 lines
- Frontend: 14 files, 2071 lines
- Total: 27 files, 6409 lines

Status: MVP core functionality completed, pending frontend-backend integration testing

Next: Sprint 4 - One-click protocol generation + Word export
2026-01-24 17:29:24 +08:00

1696 lines
70 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
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.
generator client {
provider = "prisma-client-js"
previewFeatures = ["multiSchema"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
schemas = ["admin_schema", "agent_schema", "aia_schema", "asl_schema", "capability_schema", "common_schema", "dc_schema", "ekb_schema", "iit_schema", "pkb_schema", "platform_schema", "protocol_schema", "public", "rvw_schema", "ssa_schema", "st_schema"]
}
/// 应用缓存表 - Postgres-Only架构
/// 用于替代Redis缓存支持LLM结果缓存、健康检查缓存等
model AppCache {
id Int @id @default(autoincrement())
key String @unique @db.VarChar(500)
value Json
expiresAt DateTime @map("expires_at") @db.Timestamp(6)
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamp(6)
@@index([expiresAt], map: "idx_app_cache_expires")
@@index([key, expiresAt], map: "idx_app_cache_key_expires")
@@map("app_cache")
@@schema("platform_schema")
}
model User {
id String @id @default(uuid())
phone String @unique
password String
email String? @unique
is_default_password Boolean @default(true)
password_changed_at DateTime?
name String
tenant_id String
department_id String?
avatarUrl String? @map("avatar_url")
role UserRole @default(USER)
status String @default("active")
kbQuota Int @default(3) @map("kb_quota")
kbUsed Int @default(0) @map("kb_used")
trialEndsAt DateTime? @map("trial_ends_at")
isTrial Boolean @default(true) @map("is_trial")
lastLoginAt DateTime? @map("last_login_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
tenant_members tenant_members[]
user_modules user_modules[]
departments departments? @relation(fields: [department_id], references: [id])
tenants tenants @relation(fields: [tenant_id], references: [id])
@@index([createdAt], map: "idx_platform_users_created_at")
@@index([email], map: "idx_platform_users_email")
@@index([status], map: "idx_platform_users_status")
@@index([phone], map: "idx_platform_users_phone")
@@index([tenant_id], map: "idx_platform_users_tenant_id")
@@map("users")
@@schema("platform_schema")
}
model Project {
id String @id @default(uuid())
userId String @map("user_id")
name String
background String @default("")
researchType String @default("observational") @map("research_type")
conversationCount Int @default(0) @map("conversation_count")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
conversations Conversation[] @relation("ProjectConversations")
@@index([createdAt], map: "idx_aia_projects_created_at")
@@index([deletedAt], map: "idx_aia_projects_deleted_at")
@@index([userId], map: "idx_aia_projects_user_id")
@@map("projects")
@@schema("aia_schema")
}
model Conversation {
id String @id @default(uuid())
userId String @map("user_id")
projectId String? @map("project_id")
agentId String @map("agent_id")
title String
modelName String @default("deepseek-v3") @map("model_name")
messageCount Int @default(0) @map("message_count")
totalTokens Int @default(0) @map("total_tokens")
metadata Json?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
project Project? @relation("ProjectConversations", fields: [projectId], references: [id])
messages Message[] @relation("ConversationMessages")
@@index([agentId], map: "idx_aia_conversations_agent_id")
@@index([createdAt], map: "idx_aia_conversations_created_at")
@@index([deletedAt], map: "idx_aia_conversations_deleted_at")
@@index([projectId], map: "idx_aia_conversations_project_id")
@@index([userId], map: "idx_aia_conversations_user_id")
@@map("conversations")
@@schema("aia_schema")
}
model Message {
id String @id @default(uuid())
conversationId String @map("conversation_id")
role String
content String
model String?
metadata Json?
tokens Int?
isPinned Boolean @default(false) @map("is_pinned")
createdAt DateTime @default(now()) @map("created_at")
// V2.1 新增字段
thinkingContent String? @map("thinking_content") @db.Text // 深度思考内容 <think>...</think>
attachments Json? @db.JsonB // 附件数组上限5个单个≤20MB提取文本≤30K tokens
conversation Conversation @relation("ConversationMessages", fields: [conversationId], references: [id], onDelete: Cascade)
@@index([conversationId], map: "idx_aia_messages_conversation_id")
@@index([createdAt], map: "idx_aia_messages_created_at")
@@index([isPinned], map: "idx_aia_messages_is_pinned")
@@map("messages")
@@schema("aia_schema")
}
model KnowledgeBase {
id String @id @default(uuid())
userId String @map("user_id")
name String
description String?
difyDatasetId String @map("dify_dataset_id")
fileCount Int @default(0) @map("file_count")
totalSizeBytes BigInt @default(0) @map("total_size_bytes")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
batchTasks BatchTask[] @relation("KnowledgeBaseBatchTasks")
documents Document[] @relation("KnowledgeBaseDocuments")
@@index([difyDatasetId], map: "idx_pkb_knowledge_bases_dify_dataset_id")
@@index([userId], map: "idx_pkb_knowledge_bases_user_id")
@@map("knowledge_bases")
@@schema("pkb_schema")
}
model Document {
id String @id @default(uuid())
kbId String @map("kb_id")
userId String @map("user_id")
filename String
fileType String @map("file_type")
fileSizeBytes BigInt @map("file_size_bytes")
fileUrl String @map("file_url")
storageKey String @map("dify_document_id") // 原 difyDocumentId现存储 OSS 路径
status String @default("uploading")
progress Int @default(0)
errorMessage String? @map("error_message")
segmentsCount Int? @map("segments_count")
tokensCount Int? @map("tokens_count")
extractionMethod String? @map("extraction_method")
extractionQuality Float? @map("extraction_quality")
charCount Int? @map("char_count")
language String?
extractedText String? @map("extracted_text")
uploadedAt DateTime @default(now()) @map("uploaded_at")
processedAt DateTime? @map("processed_at")
batchResults BatchResult[] @relation("DocumentBatchResults")
knowledgeBase KnowledgeBase @relation("KnowledgeBaseDocuments", fields: [kbId], references: [id], onDelete: Cascade)
@@index([storageKey], map: "idx_pkb_documents_dify_document_id") // 保留原索引名
@@index([extractionMethod], map: "idx_pkb_documents_extraction_method")
@@index([kbId], map: "idx_pkb_documents_kb_id")
@@index([status], map: "idx_pkb_documents_status")
@@index([userId], map: "idx_pkb_documents_user_id")
@@map("documents")
@@schema("pkb_schema")
}
model BatchTask {
id String @id @default(uuid())
userId String @map("user_id")
kbId String @map("kb_id")
name String
templateType String @map("template_type")
templateId String? @map("template_id")
prompt String
status String
totalDocuments Int @map("total_documents")
completedCount Int @default(0) @map("completed_count")
failedCount Int @default(0) @map("failed_count")
modelType String @map("model_type")
concurrency Int @default(3)
startedAt DateTime? @map("started_at")
completedAt DateTime? @map("completed_at")
durationSeconds Int? @map("duration_seconds")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
results BatchResult[] @relation("TaskBatchResults")
knowledgeBase KnowledgeBase @relation("KnowledgeBaseBatchTasks", fields: [kbId], references: [id], onDelete: Cascade)
@@index([createdAt], map: "idx_pkb_batch_tasks_created_at")
@@index([kbId], map: "idx_pkb_batch_tasks_kb_id")
@@index([status], map: "idx_pkb_batch_tasks_status")
@@index([userId], map: "idx_pkb_batch_tasks_user_id")
@@map("batch_tasks")
@@schema("pkb_schema")
}
model BatchResult {
id String @id @default(uuid())
taskId String @map("task_id")
documentId String @map("document_id")
status String
data Json?
rawOutput String? @map("raw_output")
errorMessage String? @map("error_message")
processingTimeMs Int? @map("processing_time_ms")
tokensUsed Int? @map("tokens_used")
createdAt DateTime @default(now()) @map("created_at")
document Document @relation("DocumentBatchResults", fields: [documentId], references: [id], onDelete: Cascade)
task BatchTask @relation("TaskBatchResults", fields: [taskId], references: [id], onDelete: Cascade)
@@index([documentId], map: "idx_pkb_batch_results_document_id")
@@index([status], map: "idx_pkb_batch_results_status")
@@index([taskId], map: "idx_pkb_batch_results_task_id")
@@map("batch_results")
@@schema("pkb_schema")
}
model TaskTemplate {
id String @id @default(uuid())
userId String @map("user_id")
name String
description String?
prompt String
isPublic Boolean @default(false) @map("is_public")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
outputFields Json
@@index([userId], map: "idx_pkb_task_templates_user_id")
@@map("task_templates")
@@schema("pkb_schema")
}
model AdminLog {
id Int @id @default(autoincrement())
adminId String @map("admin_id")
action String
resourceType String? @map("resource_type")
resourceId String? @map("resource_id")
details Json?
ipAddress String? @map("ip_address")
userAgent String? @map("user_agent")
createdAt DateTime @default(now()) @map("created_at")
admin users @relation(fields: [adminId], references: [id], onDelete: Cascade)
@@index([adminId])
@@index([createdAt])
@@index([action])
@@map("admin_logs")
@@schema("public")
}
// GeneralConversation 和 GeneralMessage 已删除2026-01-11
// 原因:与 Conversation/Message 功能重叠,使用 conversations.project_id = NULL 表示无项目对话
model ReviewTask {
id String @id @default(uuid())
userId String @map("user_id")
fileName String @map("file_name")
fileSize Int @map("file_size")
filePath String? @map("file_path")
extractedText String @map("extracted_text")
wordCount Int? @map("word_count")
status String @default("pending")
selectedAgents String[] @default(["editorial", "methodology"]) @map("selected_agents")
editorialReview Json? @map("editorial_review")
methodologyReview Json? @map("methodology_review")
overallScore Float? @map("overall_score")
editorialScore Float? @map("editorial_score")
methodologyScore Float? @map("methodology_score")
methodologyStatus String? @map("methodology_status")
picoExtract Json? @map("pico_extract")
isArchived Boolean @default(false) @map("is_archived")
archivedAt DateTime? @map("archived_at")
modelUsed String? @map("model_used")
startedAt DateTime? @map("started_at")
completedAt DateTime? @map("completed_at")
durationSeconds Int? @map("duration_seconds")
errorMessage String? @map("error_message")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 注意userId 暂不添加外键约束,因为用户来自不同 schema (platform_schema.users)
// 跨 schema 外键在 PostgreSQL 中需要特殊处理
@@index([userId])
@@index([status])
@@index([createdAt])
@@index([isArchived])
@@map("review_tasks")
@@schema("rvw_schema")
}
model AslScreeningProject {
id String @id @default(uuid())
userId String @map("user_id")
projectName String @map("project_name")
picoCriteria Json @map("pico_criteria")
inclusionCriteria String @map("inclusion_criteria")
exclusionCriteria String @map("exclusion_criteria")
status String @default("draft")
screeningConfig Json? @map("screening_config")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
fulltextResults AslFulltextScreeningResult[] @relation("ProjectFulltextResults")
fulltextTasks AslFulltextScreeningTask[] @relation("ProjectFulltextTasks")
literatures AslLiterature[] @relation("ProjectLiteratures")
screeningResults AslScreeningResult[] @relation("ProjectScreeningResults")
screeningTasks AslScreeningTask[] @relation("ProjectScreeningTasks")
@@index([status], map: "idx_screening_projects_status")
@@index([userId], map: "idx_screening_projects_user_id")
@@map("screening_projects")
@@schema("asl_schema")
}
model AslLiterature {
id String @id @default(uuid())
projectId String @map("project_id")
pmid String?
title String
abstract String
authors String?
journal String?
publicationYear Int? @map("publication_year")
doi String?
pdfUrl String? @map("pdf_url")
pdfOssKey String? @map("pdf_oss_key")
pdfFileSize Int? @map("pdf_file_size")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
stage String @default("imported") @map("stage")
hasPdf Boolean @default(false) @map("has_pdf")
pdfStorageType String? @map("pdf_storage_type")
pdfStorageRef String? @map("pdf_storage_ref")
pdfStatus String? @map("pdf_status")
pdfUploadedAt DateTime? @map("pdf_uploaded_at")
fullTextStorageType String? @map("full_text_storage_type")
fullTextStorageRef String? @map("full_text_storage_ref")
fullTextUrl String? @map("full_text_url")
fullTextFormat String? @map("full_text_format")
fullTextSource String? @map("full_text_source")
fullTextTokenCount Int? @map("full_text_token_count")
fullTextExtractedAt DateTime? @map("full_text_extracted_at")
fulltextResults AslFulltextScreeningResult[] @relation("LiteratureFulltextResults")
project AslScreeningProject @relation("ProjectLiteratures", fields: [projectId], references: [id], onDelete: Cascade)
screeningResults AslScreeningResult[] @relation("LiteratureScreeningResults")
@@unique([projectId, pmid], map: "unique_project_pmid")
@@index([doi], map: "idx_literatures_doi")
@@index([hasPdf], map: "idx_literatures_has_pdf")
@@index([pdfStatus], map: "idx_literatures_pdf_status")
@@index([projectId], map: "idx_literatures_project_id")
@@index([stage], map: "idx_literatures_stage")
@@map("literatures")
@@schema("asl_schema")
}
model AslScreeningResult {
id String @id @default(uuid())
projectId String @map("project_id")
literatureId String @map("literature_id")
dsModelName String @map("ds_model_name")
dsPJudgment String? @map("ds_p_judgment")
dsIJudgment String? @map("ds_i_judgment")
dsCJudgment String? @map("ds_c_judgment")
dsSJudgment String? @map("ds_s_judgment")
dsConclusion String? @map("ds_conclusion")
dsConfidence Float? @map("ds_confidence")
dsPEvidence String? @map("ds_p_evidence")
dsIEvidence String? @map("ds_i_evidence")
dsCEvidence String? @map("ds_c_evidence")
dsSEvidence String? @map("ds_s_evidence")
dsReason String? @map("ds_reason")
qwenModelName String @map("qwen_model_name")
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")
qwenPEvidence String? @map("qwen_p_evidence")
qwenIEvidence String? @map("qwen_i_evidence")
qwenCEvidence String? @map("qwen_c_evidence")
qwenSEvidence String? @map("qwen_s_evidence")
qwenReason String? @map("qwen_reason")
conflictStatus String @default("none") @map("conflict_status")
conflictFields Json? @map("conflict_fields")
finalDecision String? @map("final_decision")
finalDecisionBy String? @map("final_decision_by")
finalDecisionAt DateTime? @map("final_decision_at")
exclusionReason String? @map("exclusion_reason")
aiProcessingStatus String @default("pending") @map("ai_processing_status")
aiProcessedAt DateTime? @map("ai_processed_at")
aiErrorMessage String? @map("ai_error_message")
promptVersion String @default("v1.0.0") @map("prompt_version")
rawOutput Json? @map("raw_output")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
literature AslLiterature @relation("LiteratureScreeningResults", fields: [literatureId], references: [id], onDelete: Cascade)
project AslScreeningProject @relation("ProjectScreeningResults", fields: [projectId], references: [id], onDelete: Cascade)
@@unique([projectId, literatureId], map: "unique_project_literature")
@@index([conflictStatus], map: "idx_screening_results_conflict_status")
@@index([finalDecision], map: "idx_screening_results_final_decision")
@@index([literatureId], map: "idx_screening_results_literature_id")
@@index([projectId], map: "idx_screening_results_project_id")
@@map("screening_results")
@@schema("asl_schema")
}
model AslScreeningTask {
id String @id @default(uuid())
projectId String @map("project_id")
taskType String @map("task_type")
status String @default("pending")
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")
startedAt DateTime? @map("started_at")
completedAt DateTime? @map("completed_at")
estimatedEndAt DateTime? @map("estimated_end_at")
errorMessage String? @map("error_message")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
project AslScreeningProject @relation("ProjectScreeningTasks", fields: [projectId], references: [id], onDelete: Cascade)
@@index([projectId], map: "idx_screening_tasks_project_id")
@@index([status], map: "idx_screening_tasks_status")
@@map("screening_tasks")
@@schema("asl_schema")
}
model AslFulltextScreeningTask {
id String @id @default(uuid())
projectId String @map("project_id")
modelA String @map("model_a")
modelB String @map("model_b")
promptVersion String @default("v1.0.0") @map("prompt_version")
status String @default("pending")
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")
totalTokens Int @default(0) @map("total_tokens")
totalCost Float @default(0) @map("total_cost")
startedAt DateTime? @map("started_at")
completedAt DateTime? @map("completed_at")
estimatedEndAt DateTime? @map("estimated_end_at")
errorMessage String? @map("error_message")
errorStack String? @map("error_stack")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
results AslFulltextScreeningResult[] @relation("TaskFulltextResults")
project AslScreeningProject @relation("ProjectFulltextTasks", fields: [projectId], references: [id], onDelete: Cascade)
@@index([createdAt], map: "idx_fulltext_tasks_created_at")
@@index([projectId], map: "idx_fulltext_tasks_project_id")
@@index([status], map: "idx_fulltext_tasks_status")
@@map("fulltext_screening_tasks")
@@schema("asl_schema")
}
/// 智能文献检索任务DeepSearch
model AslResearchTask {
id String @id @default(uuid())
// 关联
projectId String @map("project_id")
userId String @map("user_id")
// 检索输入
query String // 用户的自然语言查询
filters Json? // 🔜 后续:高级筛选 { yearFrom, yearTo, articleTypes }
// unifuncs 任务
externalTaskId String? @map("external_task_id")
// 状态
status String @default("pending") // pending/processing/completed/failed
errorMessage String? @map("error_message")
// 结果
resultCount Int? @map("result_count")
rawResult String? @map("raw_result") @db.Text
reasoningContent String? @map("reasoning_content") @db.Text // AI思考过程
literatures Json? // 解析后的文献列表
// 统计(🔜 后续展示)
tokenUsage Json? @map("token_usage")
searchCount Int? @map("search_count")
readCount Int? @map("read_count")
iterations Int?
// 时间
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
completedAt DateTime? @map("completed_at")
@@index([projectId], map: "idx_research_tasks_project_id")
@@index([userId], map: "idx_research_tasks_user_id")
@@index([status], map: "idx_research_tasks_status")
@@index([createdAt], map: "idx_research_tasks_created_at")
@@map("research_tasks")
@@schema("asl_schema")
}
model AslFulltextScreeningResult {
id String @id @default(uuid())
taskId String @map("task_id")
projectId String @map("project_id")
literatureId String @map("literature_id")
modelAName String @map("model_a_name")
modelAStatus String @map("model_a_status")
modelAFields Json @map("model_a_fields")
modelAOverall Json @map("model_a_overall")
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")
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")
medicalLogicIssues Json? @map("medical_logic_issues")
evidenceChainIssues Json? @map("evidence_chain_issues")
isConflict Boolean @default(false) @map("is_conflict")
conflictSeverity String? @map("conflict_severity")
conflictFields String[] @map("conflict_fields")
conflictDetails Json? @map("conflict_details")
reviewPriority Int? @map("review_priority")
reviewDeadline DateTime? @map("review_deadline")
finalDecision String? @map("final_decision")
finalDecisionBy String? @map("final_decision_by")
finalDecisionAt DateTime? @map("final_decision_at")
exclusionReason String? @map("exclusion_reason")
reviewNotes String? @map("review_notes")
processingStatus String @default("pending") @map("processing_status")
isDegraded Boolean @default(false) @map("is_degraded")
degradedModel String? @map("degraded_model")
processedAt DateTime? @map("processed_at")
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")
literature AslLiterature @relation("LiteratureFulltextResults", fields: [literatureId], references: [id], onDelete: Cascade)
project AslScreeningProject @relation("ProjectFulltextResults", fields: [projectId], references: [id], onDelete: Cascade)
task AslFulltextScreeningTask @relation("TaskFulltextResults", fields: [taskId], references: [id], onDelete: Cascade)
@@unique([projectId, literatureId], map: "unique_project_literature_fulltext")
@@index([finalDecision], map: "idx_fulltext_results_final_decision")
@@index([isConflict], map: "idx_fulltext_results_is_conflict")
@@index([literatureId], map: "idx_fulltext_results_literature_id")
@@index([projectId], map: "idx_fulltext_results_project_id")
@@index([reviewPriority], map: "idx_fulltext_results_review_priority")
@@index([taskId], map: "idx_fulltext_results_task_id")
@@map("fulltext_screening_results")
@@schema("asl_schema")
}
model DCHealthCheck {
id String @id @default(uuid())
userId String @map("user_id")
fileName String @map("file_name")
columnName String @map("column_name")
emptyRate Float @map("empty_rate")
avgLength Float @map("avg_length")
totalRows Int @map("total_rows")
estimatedTokens Int @map("estimated_tokens")
status String @map("status")
message String @map("message")
createdAt DateTime @default(now()) @map("created_at")
@@index([userId, fileName])
@@map("dc_health_checks")
@@schema("dc_schema")
}
model DCTemplate {
id String @id @default(uuid())
diseaseType String @map("disease_type")
reportType String @map("report_type")
displayName String @map("display_name")
fields Json @map("fields")
promptTemplate String @map("prompt_template")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([diseaseType, reportType])
@@map("dc_templates")
@@schema("dc_schema")
}
model DCExtractionTask {
id String @id @default(uuid())
userId String @map("user_id")
projectName String @map("project_name")
sourceFileKey String @map("source_file_key")
textColumn String @map("text_column")
diseaseType String @map("disease_type")
reportType String @map("report_type")
targetFields Json @map("target_fields")
modelA String @default("deepseek-v3") @map("model_a")
modelB String @default("qwen-max") @map("model_b")
status String @default("pending") @map("status")
totalCount Int @default(0) @map("total_count")
processedCount Int @default(0) @map("processed_count")
cleanCount Int @default(0) @map("clean_count")
conflictCount Int @default(0) @map("conflict_count")
failedCount Int @default(0) @map("failed_count")
totalTokens Int @default(0) @map("total_tokens")
totalCost Float @default(0) @map("total_cost")
error String? @map("error")
createdAt DateTime @default(now()) @map("created_at")
startedAt DateTime? @map("started_at")
completedAt DateTime? @map("completed_at")
items DCExtractionItem[]
@@index([userId, status])
@@map("dc_extraction_tasks")
@@schema("dc_schema")
}
model DCExtractionItem {
id String @id @default(uuid())
taskId String @map("task_id")
rowIndex Int @map("row_index")
originalText String @map("original_text")
resultA Json? @map("result_a")
resultB Json? @map("result_b")
status String @default("pending") @map("status")
conflictFields String[] @default([]) @map("conflict_fields")
finalResult Json? @map("final_result")
tokensA Int @default(0) @map("tokens_a")
tokensB Int @default(0) @map("tokens_b")
error String? @map("error")
createdAt DateTime @default(now()) @map("created_at")
resolvedAt DateTime? @map("resolved_at")
task DCExtractionTask @relation(fields: [taskId], references: [id], onDelete: Cascade)
@@index([taskId, status])
@@map("dc_extraction_items")
@@schema("dc_schema")
}
model DcToolCSession {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
userId String @map("user_id") @db.VarChar(255)
fileName String @map("file_name") @db.VarChar(500)
fileKey String @map("file_key") @db.VarChar(500)
totalRows Int? @map("total_rows")
totalCols Int? @map("total_cols")
columns Json? @map("columns")
encoding String? @map("encoding") @db.VarChar(50)
fileSize Int @map("file_size")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(6)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamp(6)
expiresAt DateTime @map("expires_at") @db.Timestamp(6)
dataStats Json? @map("data_stats")
columnMapping Json? @map("column_mapping")
cleanDataKey String? @map("clean_data_key") @db.VarChar(1000)
@@index([expiresAt], map: "idx_dc_tool_c_sessions_expires_at")
@@index([userId], map: "idx_dc_tool_c_sessions_user_id")
@@map("dc_tool_c_sessions")
@@schema("dc_schema")
}
model DcToolCAiHistory {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
sessionId String @map("session_id") @db.VarChar(255)
userId String @map("user_id") @db.VarChar(255)
role String @map("role") @db.VarChar(50)
content String
generatedCode String? @map("generated_code")
codeExplanation String? @map("code_explanation")
executeStatus String? @map("execute_status") @db.VarChar(50)
executeResult Json? @map("execute_result")
executeError String? @map("execute_error")
retryCount Int? @default(0) @map("retry_count")
model String? @map("model") @db.VarChar(100)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(6)
@@index([createdAt], map: "idx_dc_tool_c_ai_history_created_at")
@@index([sessionId], map: "idx_dc_tool_c_ai_history_session_id")
@@index([userId], map: "idx_dc_tool_c_ai_history_user_id")
@@map("dc_tool_c_ai_history")
@@schema("dc_schema")
}
/// This table is a partition table and requires additional setup for migrations. Visit https://pris.ly/d/partition-tables for more info.
model job {
id String @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String
priority Int @default(0)
data Json?
state job_state @default(created)
retry_limit Int @default(2)
retry_count Int @default(0)
retry_delay Int @default(0)
retry_backoff Boolean @default(false)
retry_delay_max Int?
expire_seconds Int @default(900)
deletion_seconds Int @default(604800)
singleton_key String?
singleton_on DateTime? @db.Timestamp(6)
start_after DateTime @default(now()) @db.Timestamptz(6)
created_on DateTime @default(now()) @db.Timestamptz(6)
started_on DateTime? @db.Timestamptz(6)
completed_on DateTime? @db.Timestamptz(6)
keep_until DateTime @default(dbgenerated("(now() + '336:00:00'::interval)")) @db.Timestamptz(6)
output Json?
dead_letter String?
policy String?
@@id([name, id])
@@schema("platform_schema")
}
/// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info.
model queue {
name String @id
policy String
retry_limit Int
retry_delay Int
retry_backoff Boolean
retry_delay_max Int?
expire_seconds Int
retention_seconds Int
deletion_seconds Int
dead_letter String?
partition Boolean
table_name String
deferred_count Int @default(0)
queued_count Int @default(0)
warning_queued Int @default(0)
active_count Int @default(0)
total_count Int @default(0)
singletons_active String[]
monitor_on DateTime? @db.Timestamptz(6)
maintain_on DateTime? @db.Timestamptz(6)
created_on DateTime @default(now()) @db.Timestamptz(6)
updated_on DateTime @default(now()) @db.Timestamptz(6)
queue queue? @relation("queueToqueue", fields: [dead_letter], references: [name], onDelete: NoAction, onUpdate: NoAction)
other_queue queue[] @relation("queueToqueue")
schedule schedule[]
subscription subscription[]
@@schema("platform_schema")
}
model schedule {
name String
key String @default("")
cron String
timezone String?
data Json?
options Json?
created_on DateTime @default(now()) @db.Timestamptz(6)
updated_on DateTime @default(now()) @db.Timestamptz(6)
queue queue @relation(fields: [name], references: [name], onDelete: Cascade, onUpdate: NoAction)
@@id([name, key])
@@schema("platform_schema")
}
model subscription {
event String
name String
created_on DateTime @default(now()) @db.Timestamptz(6)
updated_on DateTime @default(now()) @db.Timestamptz(6)
queue queue @relation(fields: [name], references: [name], onDelete: Cascade, onUpdate: NoAction)
@@id([event, name])
@@schema("platform_schema")
}
model version {
version Int @id
cron_on DateTime? @db.Timestamptz(6)
@@schema("platform_schema")
}
model users {
id String @id
email String @unique
password String
name String?
avatar_url String?
role String @default("user")
status String @default("active")
kb_quota Int @default(3)
kb_used Int @default(0)
trial_ends_at DateTime?
is_trial Boolean @default(true)
last_login_at DateTime?
created_at DateTime @default(now())
updated_at DateTime
adminLogs AdminLog[]
// reviewTasks 已移除,因为 ReviewTask.userId 现在不再引用此表
@@index([created_at])
@@index([email])
@@index([status])
@@schema("public")
}
/// IIT项目表
model IitProject {
id String @id @default(uuid())
name String
description String?
difyDatasetId String? @unique @map("dify_dataset_id")
protocolFileKey String? @map("protocol_file_key")
cachedRules Json? @map("cached_rules")
fieldMappings Json @map("field_mappings")
redcapProjectId String @map("redcap_project_id")
redcapApiToken String @map("redcap_api_token")
redcapUrl String @map("redcap_url")
lastSyncAt DateTime? @map("last_sync_at")
status String @default("active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
auditLogs IitAuditLog[]
pendingActions IitPendingAction[]
taskRuns IitTaskRun[]
userMappings IitUserMapping[]
@@index([status, deletedAt])
@@map("projects")
@@schema("iit_schema")
}
/// 影子状态表(核心)
model IitPendingAction {
id String @id @default(uuid())
projectId String @map("project_id")
recordId String @map("record_id")
fieldName String @map("field_name")
currentValue Json? @map("current_value")
suggestedValue Json? @map("suggested_value")
status String
agentType String @map("agent_type")
reasoning String
evidence Json
approvedBy String? @map("approved_by")
approvedAt DateTime? @map("approved_at")
rejectionReason String? @map("rejection_reason")
executedAt DateTime? @map("executed_at")
errorMessage String? @map("error_message")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
project IitProject @relation(fields: [projectId], references: [id])
@@index([projectId, status])
@@index([projectId, recordId])
@@index([status, createdAt])
@@map("pending_actions")
@@schema("iit_schema")
}
/// 任务运行记录(与 pg-boss 关联)
model IitTaskRun {
id String @id @default(uuid())
projectId String @map("project_id")
taskType String @map("task_type")
jobId String? @unique @map("job_id")
status String
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")
startedAt DateTime? @map("started_at")
completedAt DateTime? @map("completed_at")
duration Int?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
project IitProject @relation(fields: [projectId], references: [id])
@@index([projectId, taskType, status])
@@index([jobId])
@@map("task_runs")
@@schema("iit_schema")
}
/// 用户映射表(异构系统身份关联)
model IitUserMapping {
id String @id @default(uuid())
projectId String @map("project_id")
systemUserId String @map("system_user_id")
redcapUsername String @map("redcap_username")
wecomUserId String? @map("wecom_user_id")
miniProgramOpenId String? @unique @map("mini_program_open_id")
sessionKey String? @map("session_key")
role String
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
project IitProject @relation(fields: [projectId], references: [id])
@@unique([projectId, systemUserId])
@@unique([projectId, redcapUsername])
@@index([wecomUserId])
@@index([miniProgramOpenId])
@@map("user_mappings")
@@schema("iit_schema")
}
/// 审计日志表
model IitAuditLog {
id String @id @default(uuid())
projectId String @map("project_id")
userId String @map("user_id")
actionType String @map("action_type")
entityType String @map("entity_type")
entityId String @map("entity_id")
details Json?
traceId String @map("trace_id")
createdAt DateTime @default(now()) @map("created_at")
project IitProject @relation(fields: [projectId], references: [id])
@@index([projectId, createdAt])
@@index([userId, createdAt])
@@index([actionType, createdAt])
@@index([traceId])
@@map("audit_logs")
@@schema("iit_schema")
}
model admin_operation_logs {
id Int @id @default(autoincrement())
admin_id String
operation_type String
target_type String
target_id String
module String?
before_data Json?
after_data Json?
ip_address String?
user_agent String?
created_at DateTime @default(now())
@@index([admin_id], map: "idx_admin_logs_admin_id")
@@index([created_at], map: "idx_admin_logs_created_at")
@@index([module], map: "idx_admin_logs_module")
@@index([operation_type], map: "idx_admin_logs_operation_type")
@@schema("admin_schema")
}
model departments {
id String @id
tenant_id String
name String
parent_id String?
description String?
created_at DateTime @default(now())
updated_at DateTime
departments departments? @relation("departmentsTodepartments", fields: [parent_id], references: [id])
other_departments departments[] @relation("departmentsTodepartments")
tenants tenants @relation(fields: [tenant_id], references: [id], onDelete: Cascade)
users User[]
@@index([parent_id], map: "idx_departments_parent_id")
@@index([tenant_id], map: "idx_departments_tenant_id")
@@schema("platform_schema")
}
/// The underlying table does not contain a valid unique identifier and can therefore currently not be handled by Prisma Client.
model job_common {
id String @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String
priority Int @default(0)
data Json?
state job_state @default(created)
retry_limit Int @default(2)
retry_count Int @default(0)
retry_delay Int @default(0)
retry_backoff Boolean @default(false)
retry_delay_max Int?
expire_seconds Int @default(900)
deletion_seconds Int @default(604800)
singleton_key String?
singleton_on DateTime? @db.Timestamp(6)
start_after DateTime @default(now()) @db.Timestamptz(6)
created_on DateTime @default(now()) @db.Timestamptz(6)
started_on DateTime? @db.Timestamptz(6)
completed_on DateTime? @db.Timestamptz(6)
keep_until DateTime @default(dbgenerated("(now() + '336:00:00'::interval)")) @db.Timestamptz(6)
output Json?
dead_letter String?
policy String?
@@ignore
@@schema("platform_schema")
}
model permissions {
id Int @id @default(autoincrement())
code String @unique
name String
description String?
module String?
created_at DateTime @default(now())
role_permissions role_permissions[]
@@schema("platform_schema")
}
model role_permissions {
id Int @id @default(autoincrement())
role UserRole
permission_id Int
created_at DateTime @default(now())
permissions permissions @relation(fields: [permission_id], references: [id], onDelete: Cascade)
@@unique([role, permission_id])
@@schema("platform_schema")
}
model tenant_members {
id String @id
tenant_id String
user_id String
role UserRole
joined_at DateTime @default(now())
tenants tenants @relation(fields: [tenant_id], references: [id], onDelete: Cascade)
users User @relation(fields: [user_id], references: [id], onDelete: Cascade)
@@unique([tenant_id, user_id])
@@index([tenant_id], map: "idx_tenant_members_tenant_id")
@@index([user_id], map: "idx_tenant_members_user_id")
@@schema("platform_schema")
}
/// 系统模块配置表(动态管理可用模块)
model modules {
code String @id // 模块代码: RVW, PKB, ASL, DC, IIT, AIA
name String // 显示名称
description String? // 模块描述
icon String? // 图标(可选)
is_active Boolean @default(true) // 是否上线
sort_order Int @default(0) // 排序
created_at DateTime @default(now())
updated_at DateTime @updatedAt
@@index([is_active])
@@index([sort_order])
@@schema("platform_schema")
}
/// 租户模块订阅表
model tenant_modules {
id String @id @default(uuid())
tenant_id String
module_code String
is_enabled Boolean @default(true)
expires_at DateTime?
created_at DateTime @default(now())
tenants tenants @relation(fields: [tenant_id], references: [id], onDelete: Cascade)
@@unique([tenant_id, module_code])
@@index([tenant_id])
@@index([module_code])
@@schema("platform_schema")
}
/// 用户模块权限表(精细控制用户可访问的模块)
model user_modules {
id String @id @default(uuid())
user_id String
tenant_id String /// 在哪个租户内的权限
module_code String /// 模块代码: RVW, PKB, ASL, DC, IIT, AIA
is_enabled Boolean @default(true) /// 是否启用
created_at DateTime @default(now())
updated_at DateTime @updatedAt
user User @relation(fields: [user_id], references: [id], onDelete: Cascade)
tenant tenants @relation(fields: [tenant_id], references: [id], onDelete: Cascade)
@@unique([user_id, tenant_id, module_code])
@@index([user_id])
@@index([tenant_id])
@@index([module_code])
@@map("user_modules")
@@schema("platform_schema")
}
model tenant_quota_allocations {
id Int @id @default(autoincrement())
tenant_id String
target_type String
target_key String
limit_amount BigInt
used_amount BigInt @default(0)
created_at DateTime @default(now())
updated_at DateTime
tenants tenants @relation(fields: [tenant_id], references: [id], onDelete: Cascade)
@@unique([tenant_id, target_type, target_key])
@@index([tenant_id], map: "idx_quota_allocations_tenant_id")
@@schema("platform_schema")
}
model tenant_quotas {
id String @id
tenant_id String
quota_type String
total_amount BigInt
used_amount BigInt @default(0)
reset_period String?
last_reset_at DateTime?
created_at DateTime @default(now())
updated_at DateTime
tenants tenants @relation(fields: [tenant_id], references: [id], onDelete: Cascade)
@@unique([tenant_id, quota_type])
@@schema("platform_schema")
}
model tenants {
id String @id
code String @unique
name String
type TenantType
status TenantStatus @default(ACTIVE)
config Json? @default("{}")
total_quota BigInt @default(0)
used_quota BigInt @default(0)
contact_name String?
contact_phone String?
contact_email String?
created_at DateTime @default(now())
updated_at DateTime
expires_at DateTime?
departments departments[]
tenant_members tenant_members[]
tenant_modules tenant_modules[]
tenant_quota_allocations tenant_quota_allocations[]
tenant_quotas tenant_quotas[]
users User[]
user_modules user_modules[]
@@index([code], map: "idx_tenants_code")
@@index([status], map: "idx_tenants_status")
@@index([type], map: "idx_tenants_type")
@@schema("platform_schema")
}
model verification_codes {
id Int @id @default(autoincrement())
phone String
code String @db.VarChar(6)
type VerificationType
expires_at DateTime
is_used Boolean @default(false)
attempts Int @default(0)
created_at DateTime @default(now())
@@index([expires_at], map: "idx_verification_codes_expires")
@@index([phone, type, is_used], map: "idx_verification_codes_phone_type")
@@schema("platform_schema")
}
enum job_state {
created
retry
active
completed
cancelled
failed
@@schema("platform_schema")
}
enum TenantStatus {
ACTIVE
SUSPENDED
EXPIRED
@@schema("platform_schema")
}
enum TenantType {
HOSPITAL // 医院
PHARMA // 药企
INTERNAL // 内部(公司自己)
PUBLIC // 个人用户公共池
@@schema("platform_schema")
}
enum UserRole {
SUPER_ADMIN
PROMPT_ENGINEER
HOSPITAL_ADMIN
PHARMA_ADMIN
DEPARTMENT_ADMIN
USER
@@schema("platform_schema")
}
enum VerificationType {
LOGIN
RESET_PASSWORD
BIND_PHONE
@@schema("platform_schema")
}
// ============================================
// Prompt Management System (capability_schema)
// ============================================
/// Prompt模板 - 存储Prompt的元信息
model prompt_templates {
id Int @id @default(autoincrement())
code String @unique /// 唯一标识符,如 'RVW_EDITORIAL'
name String /// 人类可读名称,如 "稿约规范性评估"
module String /// 所属模块: RVW, ASL, DC, IIT, PKB, AIA
description String? /// 描述
variables Json? /// 预期变量列表,如 ["title", "abstract"]
versions prompt_versions[]
created_at DateTime @default(now())
updated_at DateTime @updatedAt
@@index([module], map: "idx_prompt_templates_module")
@@map("prompt_templates")
@@schema("capability_schema")
}
/// Prompt版本 - 存储Prompt的具体内容和版本历史
model prompt_versions {
id Int @id @default(autoincrement())
template_id Int /// 关联的模板ID
version Int /// 版本号: 1, 2, 3...
content String @db.Text /// Prompt内容支持Handlebars模板
model_config Json? /// 模型参数: {"temperature": 0.3, "model": "deepseek-v3"}
status PromptStatus @default(DRAFT)
changelog String? /// 变更说明
created_by String? /// 修改人userId审计用
template prompt_templates @relation(fields: [template_id], references: [id])
created_at DateTime @default(now())
@@index([template_id, status], map: "idx_prompt_versions_template_status")
@@index([status], map: "idx_prompt_versions_status")
@@map("prompt_versions")
@@schema("capability_schema")
}
/// Prompt状态枚举
enum PromptStatus {
DRAFT /// 草稿(仅调试者可见)
ACTIVE /// 线上生效(默认可见)
ARCHIVED /// 归档
@@schema("capability_schema")
}
// ============================================================
// EKB Schema - 知识库引擎 (Enterprise Knowledge Base)
// 参考文档: docs/02-通用能力层/03-RAG引擎/04-数据模型设计.md
// ============================================================
/// 知识库容器表 - 管理知识库的归属和策略配置
model EkbKnowledgeBase {
id String @id @default(uuid())
name String /// 知识库名称
description String? /// 描述
/// 核心隔离字段
/// USER: 用户私有ownerId = userId
/// SYSTEM: 系统公共ownerId = moduleId (如 "ASL", "AIA")
type String @default("USER") /// USER | SYSTEM
ownerId String @map("owner_id") /// userId 或 moduleId
/// 策略配置 (JSONB)
/// { chunkSize, topK, enableRerank, embeddingModel }
config Json? @db.JsonB
documents EkbDocument[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([ownerId], map: "idx_ekb_kb_owner")
@@index([type], map: "idx_ekb_kb_type")
@@map("ekb_knowledge_base")
@@schema("ekb_schema")
}
/// 文档表 - 存储上传的文档及其元数据
model EkbDocument {
id String @id @default(uuid())
kbId String @map("kb_id") /// 所属知识库
userId String @map("user_id") /// 上传者(冗余存储)
// ===== Layer 1: 基础信息(必须)=====
filename String /// 文件名
fileType String @map("file_type") /// pdf, docx, pptx, xlsx, md, txt
fileSizeBytes BigInt @map("file_size_bytes") /// 文件大小(字节)
fileUrl String @map("file_url") /// OSS 存储路径
fileHash String? @map("file_hash") /// SHA256 哈希(秒传去重)
status String @default("pending") /// pending, processing, completed, failed
errorMessage String? @map("error_message") @db.Text
// ===== Layer 0: RAG 核心(必须)=====
extractedText String? @map("extracted_text") @db.Text /// Markdown 全文
// ===== Layer 2: 内容增强(可选)=====
summary String? @db.Text /// AI 摘要
tokenCount Int? @map("token_count") /// Token 数量
pageCount Int? @map("page_count") /// 页数
// ===== Layer 3: 分类标签(可选)=====
contentType String? @map("content_type") /// 内容类型
tags String[] /// 用户标签
category String? /// 分类目录
// ===== Layer 4: 结构化数据(可选)=====
metadata Json? @db.JsonB /// 文献属性 JSONB
structuredData Json? @map("structured_data") @db.JsonB /// 类型特定数据 JSONB
// ===== 关联 =====
knowledgeBase EkbKnowledgeBase @relation(fields: [kbId], references: [id], onDelete: Cascade)
chunks EkbChunk[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([kbId], map: "idx_ekb_doc_kb")
@@index([userId], map: "idx_ekb_doc_user")
@@index([status], map: "idx_ekb_doc_status")
@@index([contentType], map: "idx_ekb_doc_content_type")
@@index([fileHash], map: "idx_ekb_doc_file_hash")
@@map("ekb_document")
@@schema("ekb_schema")
}
/// 切片表 - 存储文档切片和向量嵌入
model EkbChunk {
id String @id @default(uuid())
documentId String @map("document_id") /// 所属文档
// ===== 核心内容 =====
content String @db.Text /// 切片文本Markdown
chunkIndex Int @map("chunk_index") /// 切片序号(从 0 开始)
// ===== 向量 =====
/// pgvector 1024 维向量
/// 注意:需要手动创建 HNSW 索引
embedding Unsupported("vector(1024)")?
// ===== 溯源信息(可选)=====
pageNumber Int? @map("page_number") /// 页码PDF 溯源)
sectionType String? @map("section_type") /// 章节类型
// ===== 扩展元数据(可选)=====
metadata Json? @db.JsonB /// 切片级元数据 JSONB
document EkbDocument @relation(fields: [documentId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
@@index([documentId], map: "idx_ekb_chunk_doc")
@@map("ekb_chunk")
@@schema("ekb_schema")
}
// ============================================================
// Agent Framework Schema (agent_schema)
// 通用Agent框架 - 可复用于多种Agent类型
// ============================================================
/// Agent定义表 - 存储Agent的基本配置
model AgentDefinition {
id String @id @default(uuid())
code String @unique /// 唯一标识: protocol_agent, stat_agent
name String /// 显示名称
description String? /// 描述
version String @default("1.0.0") /// 版本号
/// Agent配置
config Json? @db.JsonB /// 全局配置 { defaultModel, maxTurns, timeout }
/// 状态
isActive Boolean @default(true) @map("is_active")
/// 关联
stages AgentStage[]
prompts AgentPrompt[]
sessions AgentSession[]
reflexionRules ReflexionRule[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([code], map: "idx_agent_def_code")
@@index([isActive], map: "idx_agent_def_active")
@@map("agent_definitions")
@@schema("agent_schema")
}
/// Agent阶段配置表 - 定义Agent的工作流阶段
model AgentStage {
id String @id @default(uuid())
agentId String @map("agent_id") /// 关联的Agent
/// 阶段标识
stageCode String @map("stage_code") /// 阶段代码: scientific_question, pico
stageName String @map("stage_name") /// 阶段名称: 科学问题梳理
sortOrder Int @map("sort_order") /// 排序顺序
/// 阶段配置
config Json? @db.JsonB /// 阶段特定配置
/// 状态机配置
nextStages String[] @map("next_stages") /// 可转换的下一阶段列表
isInitial Boolean @default(false) @map("is_initial") /// 是否为起始阶段
isFinal Boolean @default(false) @map("is_final") /// 是否为结束阶段
/// 关联
agent AgentDefinition @relation(fields: [agentId], references: [id], onDelete: Cascade)
prompts AgentPrompt[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([agentId, stageCode], map: "unique_agent_stage")
@@index([agentId], map: "idx_agent_stage_agent")
@@index([sortOrder], map: "idx_agent_stage_order")
@@map("agent_stages")
@@schema("agent_schema")
}
/// Agent Prompt模板表 - 存储各阶段的Prompt
model AgentPrompt {
id String @id @default(uuid())
agentId String @map("agent_id") /// 关联的Agent
stageId String? @map("stage_id") /// 关联的阶段可选null表示通用Prompt
/// Prompt标识
promptType String @map("prompt_type") /// system, stage, extraction, reflexion
promptCode String @map("prompt_code") /// 唯一代码
/// Prompt内容
content String @db.Text /// Prompt模板内容支持变量
variables String[] /// 预期变量列表
/// 版本控制
version Int @default(1) /// 版本号
isActive Boolean @default(true) @map("is_active")
/// 关联
agent AgentDefinition @relation(fields: [agentId], references: [id], onDelete: Cascade)
stage AgentStage? @relation(fields: [stageId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([agentId, promptCode, version], map: "unique_agent_prompt_version")
@@index([agentId, promptType], map: "idx_agent_prompt_type")
@@index([stageId], map: "idx_agent_prompt_stage")
@@map("agent_prompts")
@@schema("agent_schema")
}
/// Agent会话表 - 存储Agent的运行时会话状态
model AgentSession {
id String @id @default(uuid())
agentId String @map("agent_id") /// 关联的Agent定义
conversationId String @map("conversation_id") /// 关联的对话IDaia_schema.conversations
userId String @map("user_id") /// 用户ID
/// 当前状态
currentStage String @map("current_stage") /// 当前阶段代码
status String @default("active") /// active, completed, paused, error
/// 上下文数据具体Agent的上下文存储在对应schema
contextRef String? @map("context_ref") /// 上下文引用ID如protocol_schema.protocol_contexts.id
/// 统计信息
turnCount Int @default(0) @map("turn_count") /// 对话轮数
totalTokens Int @default(0) @map("total_tokens") /// 总Token数
/// 关联
agent AgentDefinition @relation(fields: [agentId], references: [id])
traces AgentTrace[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([conversationId], map: "unique_agent_session_conv")
@@index([agentId], map: "idx_agent_session_agent")
@@index([userId], map: "idx_agent_session_user")
@@index([status], map: "idx_agent_session_status")
@@map("agent_sessions")
@@schema("agent_schema")
}
/// Agent执行追踪表 - 记录每一步的执行详情
model AgentTrace {
id String @id @default(uuid())
sessionId String @map("session_id") /// 关联的会话
/// 追踪信息
traceId String @map("trace_id") /// 请求追踪ID用于关联日志
stepIndex Int @map("step_index") /// 步骤序号
stepType String @map("step_type") /// query, plan, execute, reflect, tool_call
/// 输入输出
input Json? @db.JsonB /// 步骤输入
output Json? @db.JsonB /// 步骤输出
/// 执行信息
stageCode String? @map("stage_code") /// 执行时的阶段
modelUsed String? @map("model_used") /// 使用的模型
tokensUsed Int? @map("tokens_used") /// 消耗的Token
durationMs Int? @map("duration_ms") /// 执行时长(毫秒)
/// 错误信息
errorType String? @map("error_type") /// 错误类型
errorMsg String? @map("error_msg") /// 错误信息
/// 关联
session AgentSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
@@index([sessionId, stepIndex], map: "idx_agent_trace_session_step")
@@index([traceId], map: "idx_agent_trace_trace_id")
@@index([stepType], map: "idx_agent_trace_step_type")
@@map("agent_traces")
@@schema("agent_schema")
}
/// Reflexion规则表 - 定义质量检查规则
model ReflexionRule {
id String @id @default(uuid())
agentId String @map("agent_id") /// 关联的Agent
/// 规则标识
ruleCode String @map("rule_code") /// 规则代码
ruleName String @map("rule_name") /// 规则名称
/// 触发条件
triggerStage String? @map("trigger_stage") /// 触发阶段null表示全局
triggerTiming String @map("trigger_timing") /// on_sync, on_stage_complete, on_generate
/// 规则类型
ruleType String @map("rule_type") /// rule_based, prompt_based
/// 规则内容
conditions Json? @db.JsonB /// 规则条件rule_based时使用
promptTemplate String? @map("prompt_template") @db.Text /// Prompt模板prompt_based时使用
/// 行为配置
severity String @default("warning") /// error, warning, info
failureAction String @default("warn") @map("failure_action") /// block, warn, log
/// 状态
isActive Boolean @default(true) @map("is_active")
sortOrder Int @default(0) @map("sort_order")
/// 关联
agent AgentDefinition @relation(fields: [agentId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([agentId, ruleCode], map: "unique_agent_rule")
@@index([agentId, triggerStage], map: "idx_reflexion_rule_agent_stage")
@@index([isActive], map: "idx_reflexion_rule_active")
@@map("reflexion_rules")
@@schema("agent_schema")
}
// ============================================================
// Protocol Agent Schema (protocol_schema)
// Protocol Agent专用 - 研究方案制定
// ============================================================
/// Protocol Context表 - 存储研究方案的核心上下文数据
model ProtocolContext {
id String @id @default(uuid())
conversationId String @unique @map("conversation_id") /// 关联的对话ID
userId String @map("user_id")
/// 当前状态
currentStage String @default("scientific_question") @map("current_stage")
status String @default("in_progress") /// in_progress, completed, abandoned
/// ===== 5个核心阶段数据 =====
/// 阶段1: 科学问题
scientificQuestion Json? @map("scientific_question") @db.JsonB
/// { content, background, significance, confirmed, confirmedAt }
/// 阶段2: PICO
pico Json? @db.JsonB
/// { P: {value, details}, I: {}, C: {}, O: {}, confirmed, confirmedAt }
/// 阶段3: 研究设计
studyDesign Json? @map("study_design") @db.JsonB
/// { type, blinding, randomization, duration, multiCenter, confirmed }
/// 阶段4: 样本量
sampleSize Json? @map("sample_size") @db.JsonB
/// { total, perGroup, alpha, power, effectSize, dropoutRate, justification, confirmed }
/// 阶段5: 观察指标(终点指标)
endpoints Json? @db.JsonB
/// { primary: [{name, definition, method, timePoint}], secondary: [], safety: [], confirmed }
/// ===== 元数据 =====
completedStages String[] @default([]) @map("completed_stages") /// 已完成的阶段列表
lastActiveAt DateTime @default(now()) @map("last_active_at")
/// 关联
generations ProtocolGeneration[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([userId], map: "idx_protocol_context_user")
@@index([status], map: "idx_protocol_context_status")
@@index([currentStage], map: "idx_protocol_context_stage")
@@map("protocol_contexts")
@@schema("protocol_schema")
}
/// Protocol生成记录表 - 存储一键生成的研究方案
model ProtocolGeneration {
id String @id @default(uuid())
contextId String @map("context_id") /// 关联的Context
userId String @map("user_id")
/// 生成内容
generatedContent String @map("generated_content") @db.Text /// 生成的研究方案全文Markdown
contentVersion Int @default(1) @map("content_version") /// 版本号
/// 使用的Prompt
promptUsed String @map("prompt_used") @db.Text /// 实际使用的Prompt
/// 生成参数
modelUsed String @map("model_used") /// 使用的模型
tokensUsed Int? @map("tokens_used") /// 消耗的Token
durationMs Int? @map("duration_ms") /// 生成耗时(毫秒)
/// 导出记录
wordFileKey String? @map("word_file_key") /// Word文件OSS Key
lastExportedAt DateTime? @map("last_exported_at")
/// 状态
status String @default("completed") /// generating, completed, failed
errorMessage String? @map("error_message")
/// 关联
context ProtocolContext @relation(fields: [contextId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([contextId], map: "idx_protocol_gen_context")
@@index([userId, createdAt], map: "idx_protocol_gen_user_time")
@@map("protocol_generations")
@@schema("protocol_schema")
}