Files
AIclinicalresearch/backend/prisma/schema.prisma
HaHafeng 203846968c feat(iit): Complete CRA Agent V3.0 P0 milestone - autonomous QC pipeline
P0-1: Variable list sync from REDCap metadata
P0-2: QC rule configuration with JSON Logic + AI suggestion
P0-3: Scheduled QC + report generation + eQuery closed loop
P0-4: Unified dashboard + AI stream timeline + critical events

Backend:
- Add IitEquery, IitCriticalEvent Prisma models + migration
- Add cronEnabled/cronExpression to IitProject
- Implement eQuery service/controller/routes (CRUD + respond/review/close)
- Implement DailyQcOrchestrator (report -> eQuery -> critical events -> notify)
- Add AI rule suggestion service
- Register daily QC cron worker and eQuery auto-review worker
- Extend QC cockpit with timeline, trend, critical events APIs
- Fix timeline issues field compat (object vs array format)

Frontend:
- Create IIT business module with 6 pages (Dashboard, AI Stream, eQuery,
  Reports, Variable List + project config pages)
- Migrate IIT config from admin panel to business module
- Implement health score, risk heatmap, trend chart, critical event alerts
- Register IIT module in App router and top navigation

Testing:
- Add E2E API test script covering 7 modules (46 assertions, all passing)

Tested: E2E API tests 46/46 passed, backend and frontend verified
Made-with: Cursor
2026-02-26 13:28:08 +08:00

2560 lines
109 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")
contextData Json? @map("context_data") /// Skills V2.0 执行上下文数据
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 V1.x + V2.0
model AslResearchTask {
id String @id @default(uuid())
// 关联
projectId String @map("project_id")
userId String @map("user_id")
// 检索输入
query String // 用户的自然语言查询V1.x 原始输入 / V2.0 Step 1 粗略想法)
filters Json? // 高级筛选 { yearRange, targetCount, requireOpenAccess }
// unifuncs 任务
externalTaskId String? @map("external_task_id")
// 状态: draft → pending → running → completed / failed
status String @default("pending")
errorMessage String? @map("error_message")
// V1.x 结果字段(保留向后兼容)
resultCount Int? @map("result_count")
rawResult String? @map("raw_result") @db.Text
reasoningContent String? @map("reasoning_content") @db.Text
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")
// ── V2.0 新增字段 ──────────────────────────────
targetSources Json? @map("target_sources") // 选中的数据源 ["https://pubmed.ncbi.nlm.nih.gov/", ...]
confirmedRequirement String? @map("confirmed_requirement") @db.Text // 用户核验后的自然语言检索指令书
aiIntentSummary Json? @map("ai_intent_summary") // PICOS + MeSH 结构化摘要
executionLogs Json? @map("execution_logs") // 终端日志数组 [{type, title, text, ts}]
synthesisReport String? @map("synthesis_report") @db.Text // AI综合报告Markdown
resultList Json? @map("result_list") // 结构化文献元数据列表
// ── 索引 ────────────────────────────
@@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")
}
// ═══════════════════════════════════════════════════════════════
// ASL 工具 3全文智能提取工作台 V2.0
// 架构:散装派发 + 独立 Worker + Aggregator 轮询收口
// ═══════════════════════════════════════════════════════════════
/// 系统内置提取模板RCT / Cohort / QC管理员维护用户只读
model AslExtractionTemplate {
id String @id @default(uuid())
code String @unique // RCT / Cohort / QC
name String // 随机对照试验 / 队列研究 / 质量改进
description String?
baseFields Json // { metadata: [...], baseline: [...], rob: [...], outcomes_survival: [...], ... }
isSystem Boolean @default(true) @map("is_system")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
projectTemplates AslProjectTemplate[] @relation("BaseTemplateProjectTemplates")
@@map("extraction_templates")
@@schema("asl_schema")
}
/// 项目级模板(克隆自系统模板 + 用户自定义字段插槽M3 启用自定义字段)
model AslProjectTemplate {
id String @id @default(uuid())
projectId String @map("project_id")
userId String @map("user_id")
baseTemplateId String @map("base_template_id")
outcomeType String @default("survival") @map("outcome_type") // survival | dichotomous | continuous
customFields Json @default("[]") @map("custom_fields") // M3: [{name, type, prompt}]
isLocked Boolean @default(false) @map("is_locked")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
baseTemplate AslExtractionTemplate @relation("BaseTemplateProjectTemplates", fields: [baseTemplateId], references: [id])
tasks AslExtractionTask[] @relation("TemplateExtractionTasks")
@@unique([projectId, baseTemplateId], map: "unique_extraction_project_base_template")
@@index([projectId], map: "idx_extraction_project_templates_project_id")
@@index([userId], map: "idx_extraction_project_templates_user_id")
@@map("extraction_project_templates")
@@schema("asl_schema")
}
/// 提取任务1 个任务 = 批量提取 N 篇文献),状态仅由 Aggregator 修改
model AslExtractionTask {
id String @id @default(uuid())
projectId String @map("project_id")
userId String @map("user_id")
projectTemplateId String @map("project_template_id")
pkbKnowledgeBaseId String @map("pkb_knowledge_base_id")
idempotencyKey String? @unique @map("idempotency_key")
totalCount Int @map("total_count")
status String @default("processing") // processing | completed | failed
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
completedAt DateTime? @map("completed_at")
projectTemplate AslProjectTemplate @relation("TemplateExtractionTasks", fields: [projectTemplateId], references: [id])
results AslExtractionResult[] @relation("TaskExtractionResults")
@@index([projectId], map: "idx_extraction_tasks_project_id")
@@index([userId], map: "idx_extraction_tasks_user_id")
@@index([status], map: "idx_extraction_tasks_status")
@@map("extraction_tasks")
@@schema("asl_schema")
}
/// 单篇文献提取结果Worker 只写自己的 Result 行,绝不碰 Task 表
model AslExtractionResult {
id String @id @default(uuid())
taskId String @map("task_id")
projectId String @map("project_id")
pkbDocumentId String @map("pkb_document_id")
snapshotStorageKey String @map("snapshot_storage_key") // API 层冻结的 PKB OSS 路径
snapshotFilename String @map("snapshot_filename") // API 层冻结的原始文件名
status String @default("pending") // pending | extracting | completed | error
extractedData Json? @map("extracted_data") // LLM 结构化提取 JSON
quoteVerification Json? @map("quote_verification") // fuzzyQuoteMatch 三级置信度结果
manualOverrides Json? @map("manual_overrides") // HITL 人工修改记录M2
reviewStatus String @default("pending") @map("review_status") // pending | approved
reviewedAt DateTime? @map("reviewed_at")
errorMessage String? @map("error_message")
processedAt DateTime? @map("processed_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
task AslExtractionTask @relation("TaskExtractionResults", fields: [taskId], references: [id], onDelete: Cascade)
@@index([taskId, status], map: "idx_extraction_results_task_status") // Aggregator groupBy 性能保障
@@index([taskId], map: "idx_extraction_results_task_id")
@@index([projectId], map: "idx_extraction_results_project_id")
@@map("extraction_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") // 已废弃,使用 knowledgeBaseId
knowledgeBaseId String? @map("knowledge_base_id") // 关联 ekb_schema.knowledge_bases
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")
cronEnabled Boolean @default(false) @map("cron_enabled")
cronExpression String? @map("cron_expression")
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])
@@index([knowledgeBaseId], map: "idx_iit_project_kb")
@@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")
}
// ============================================================
// IIT Manager Agent 新增表 (V2.9.1)
// Phase 1-6 完整数据库设计
// ============================================================
/// Skill 配置存储表 - 存储质控规则、SOP流程图
/// 支持 webhook/cron/event 三种触发方式
/// V3.0 扩展:支持 CRA 五大规则体系
model IitSkill {
id String @id @default(uuid())
projectId String @map("project_id")
skillType String @map("skill_type") // qc_process | daily_briefing | general_chat | weekly_report | visit_reminder | variable_qc | inclusion_exclusion | protocol_deviation | ae_monitoring | ethics_compliance
name String // 技能名称
description String? // 技能描述
config Json @db.JsonB // 核心配置 JSONSOP 流程图 / 规则定义)
isActive Boolean @default(true) @map("is_active")
version Int @default(1)
// V2.9 新增:主动触发能力
triggerType String @default("webhook") @map("trigger_type") // 'webhook' | 'cron' | 'manual'
cronSchedule String? @map("cron_schedule") // Cron 表达式,如 "0 2 * * *" (每日凌晨2点)
// V3.0 新增CRA 质控引擎支持
ruleType String @default("HARD_RULE") @map("rule_type") // 'HARD_RULE' | 'LLM_CHECK' | 'HYBRID'
level String @default("normal") @map("level") // 'blocking' | 'normal' - 阻断性检查优先
priority Int @default(100) @map("priority") // 执行优先级,数字越小越优先
requiredTags String[] @default([]) @map("required_tags") // 数据依赖标签,如 ['#lab', '#demographics']
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([projectId, skillType], map: "unique_iit_skill_project_type")
@@index([projectId], map: "idx_iit_skill_project")
@@index([isActive], map: "idx_iit_skill_active")
@@index([isActive, triggerType], map: "idx_iit_skill_active_trigger")
@@map("skills")
@@schema("iit_schema")
}
/// 质控报告存储表 - 存储预生成的 LLM 友好报告
/// 报告驱动模式:后台预计算 → LLM 阅读报告 → 回答用户
model IitQcReport {
id String @id @default(uuid())
projectId String @map("project_id")
// 报告类型
reportType String @default("daily") @map("report_type") // 'daily' | 'weekly' | 'on_demand'
// 统计摘要
summary Json @db.JsonB // { totalRecords, criticalIssues, pendingQueries, passRate }
// 详细问题列表
issues Json @db.JsonB // [{ record, rule, severity, description, evidence }]
// LLM 友好的 XML 报告
llmReport String @map("llm_report") @db.Text
// 报告生成时间
generatedAt DateTime @default(now()) @map("generated_at")
// 报告有效期(过期后需重新生成)
expiresAt DateTime? @map("expires_at")
@@index([projectId, reportType], map: "idx_qc_report_project_type")
@@index([generatedAt], map: "idx_qc_report_generated")
@@map("qc_reports")
@@schema("iit_schema")
}
/// 字段名映射字典表 - 解决 LLM 生成的字段名与 REDCap 实际字段名不一致的问题
model IitFieldMapping {
id String @id @default(uuid())
projectId String @map("project_id")
aliasName String @map("alias_name") // LLM 可能传的名称(如 "gender", "性别"
actualName String @map("actual_name") // REDCap 实际字段名(如 "sex"
fieldType String? @map("field_type") // 字段类型text, number, date, radio, checkbox
fieldLabel String? @map("field_label") // 字段显示标签
validation Json? @db.JsonB // 验证规则 { min, max, pattern, choices }
createdAt DateTime @default(now()) @map("created_at")
@@unique([projectId, aliasName], map: "unique_iit_field_mapping")
@@index([projectId], map: "idx_iit_field_mapping_project")
@@map("field_mapping")
@@schema("iit_schema")
}
/// 对话历史表(流水账)- 存储原始对话,用于生成周报
/// V2.9 新增反馈字段支持用户点赞/点踩
model IitConversationHistory {
id String @id @default(uuid())
projectId String @map("project_id")
userId String @map("user_id")
recordId String? @map("record_id") // 关联的患者(如有)
role String // user | assistant
content String @db.Text
intent String? // 识别出的意图类型
entities Json? @db.JsonB // 提取的实体 { record_id, visit, ... }
// V2.9 新增:反馈循环
feedback String? @map("feedback") // 'thumbs_up' | 'thumbs_down' | null
feedbackReason String? @map("feedback_reason") // 点踩原因:'too_long' | 'inaccurate' | 'unclear'
// 向量嵌入(用于语义搜索)
embedding Unsupported("vector(1536)")?
createdAt DateTime @default(now()) @map("created_at")
@@index([projectId, userId], map: "idx_iit_conv_project_user")
@@index([projectId, recordId], map: "idx_iit_conv_project_record")
@@index([createdAt], map: "idx_iit_conv_created")
@@map("conversation_history")
@@schema("iit_schema")
}
/// 项目级热记忆表 - 存储 Markdown 格式的热记忆
/// 每次对话都注入 System Prompt包含用户画像、当前状态、系统禁令
model IitProjectMemory {
id String @id @default(uuid())
projectId String @unique @map("project_id")
content String @db.Text // Markdown 格式的热记忆
lastUpdatedBy String @map("last_updated_by") // 'system_daily_job' | 'admin_user_id' | 'profiler_job'
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("project_memory")
@@schema("iit_schema")
}
/// 周报归档表(历史书)- 存储每周的关键决策、进度、踩坑记录
model IitWeeklyReport {
id String @id @default(uuid())
projectId String @map("project_id")
weekNumber Int @map("week_number") // 第几周(从项目开始计算)
weekStart DateTime @map("week_start") // 周起始日期
weekEnd DateTime @map("week_end") // 周结束日期
summary String @db.Text // Markdown 格式的周报内容
metrics Json? @db.JsonB // 结构化指标 { enrolled, queries, ... }
createdBy String @map("created_by") // 'system_scheduler' | 'admin_user_id'
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([projectId, weekNumber], map: "unique_iit_weekly_report")
@@index([projectId], map: "idx_iit_weekly_report_project")
@@map("weekly_reports")
@@schema("iit_schema")
}
/// ReAct 推理轨迹表 - 用于调试,记录 AI 的思考过程
model IitAgentTrace {
id String @id @default(uuid())
projectId String @map("project_id")
userId String @map("user_id")
query String @db.Text // 用户原始问题
intentType String? @map("intent_type") // 识别的意图类型
trace Json @db.JsonB // ReAct 的完整思考过程
tokenUsage Int? @map("token_usage") // 消耗的 Token 数
duration Int? // 执行时长ms
success Boolean @default(true)
errorMsg String? @map("error_msg")
createdAt DateTime @default(now()) @map("created_at")
@@index([projectId, createdAt], map: "idx_iit_trace_project_time")
@@index([userId], map: "idx_iit_trace_user")
@@map("agent_trace")
@@schema("iit_schema")
}
/// PII 脱敏审计日志表 - 记录所有发送给 LLM 的脱敏信息(合规必需)
/// 暂时创建表结构,功能延后到 Phase 1.5 实现
model IitPiiAuditLog {
id String @id @default(uuid())
projectId String @map("project_id")
userId String @map("user_id") // 操作者
sessionId String @map("session_id") // 会话 ID
// 脱敏内容(加密存储)
originalHash String @map("original_hash") // 原始内容的 SHA256 哈希
maskedPayload String @db.Text @map("masked_payload") // 脱敏后发送给 LLM 的内容
maskingMap String @db.Text @map("masking_map") // 加密存储的映射表
// 元数据
piiCount Int @map("pii_count") // 检测到的 PII 数量
piiTypes String[] @map("pii_types") // 检测到的 PII 类型
llmProvider String @map("llm_provider") // 'qwen' | 'deepseek' | 'openai'
createdAt DateTime @default(now()) @map("created_at")
@@index([projectId, userId], map: "idx_iit_pii_project_user")
@@index([sessionId], map: "idx_iit_pii_session")
@@index([createdAt], map: "idx_iit_pii_created")
@@map("pii_audit_log")
@@schema("iit_schema")
}
/// 表单模板表Phase 6 视觉能力)- 预留表结构
model IitFormTemplate {
id String @id @default(uuid())
projectId String @map("project_id")
formName String @map("form_name") // REDCap 表单名称
fieldSchema Json @db.JsonB @map("field_schema") // 表单字段结构
keywords String[] // 用于匹配的关键词
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([projectId, formName], map: "unique_iit_form_template")
@@index([projectId], map: "idx_iit_form_template_project")
@@map("form_templates")
@@schema("iit_schema")
}
// ============================================================
// IIT Manager Agent - 实时质控系统表 (V2.9.1)
// 参考文档: docs/03-业务模块/IIT Manager Agent/04-开发计划/06-实时质控系统开发计划.md
// ============================================================
/// REDCap 字段元数据表 - 从 REDCap 同步的字段信息,用于自动生成质控规则
/// 注意:与 IitFieldMapping别名映射表不同此表存储 REDCap 原始元数据
model IitFieldMetadata {
id String @id @default(uuid())
projectId String @map("project_id")
// REDCap 字段信息
fieldName String @map("field_name") // REDCap 字段名
fieldLabel String @map("field_label") // 字段标签(中文)
fieldType String @map("field_type") // text, radio, checkbox, dropdown, etc.
formName String @map("form_name") // ⭐ 所属表单名,用于单表质控规则过滤
sectionHeader String? @map("section_header") // 所属区段
// 验证规则(从 REDCap 元数据提取)
validation String? @map("validation") // text_validation_type
validationMin String? @map("validation_min") // 最小值
validationMax String? @map("validation_max") // 最大值
choices String? @map("choices") // 选项,如 "1, 男 | 2, 女"
required Boolean @default(false) // 是否必填
branching String? @map("branching") // 分支逻辑
// LLM 友好名称(可选)
alias String?
// 规则来源标记
ruleSource String? @map("rule_source") // 'auto' | 'manual'
// 时间戳
syncedAt DateTime @default(now()) @map("synced_at")
@@unique([projectId, fieldName], map: "unique_iit_field_metadata")
@@index([projectId], map: "idx_iit_field_metadata_project")
@@index([projectId, formName], map: "idx_iit_field_metadata_form")
@@map("field_metadata")
@@schema("iit_schema")
}
/// 质控日志表 - 仅新增,不覆盖,保留完整审计轨迹
/// 设计原则:每次质控都新增记录,用于审计轨迹和趋势分析
model IitQcLog {
id String @id @default(uuid())
projectId String @map("project_id")
recordId String @map("record_id")
eventId String? @map("event_id")
// 质控类型
qcType String @map("qc_type") // 'form' | 'holistic'
formName String? @map("form_name") // 单表质控时记录表单名
// 核心结果
status String // 'PASS' | 'FAIL' | 'WARNING'
// 字段级详情 (JSONB)
// 格式: [{ field: "age", rule: "range_check", level: "RED", message: "..." }, ...]
issues Json @default("[]") @db.JsonB
// 规则统计
rulesEvaluated Int @default(0) @map("rules_evaluated") // 实际评估的规则数
rulesSkipped Int @default(0) @map("rules_skipped") // 逻辑守卫跳过的规则数
rulesPassed Int @default(0) @map("rules_passed")
rulesFailed Int @default(0) @map("rules_failed")
// 规则版本(用于历史追溯)
ruleVersion String @map("rule_version")
// 入排标准检查(全案质控时填充)
inclusionPassed Boolean? @map("inclusion_passed")
exclusionPassed Boolean? @map("exclusion_passed")
// 审计信息
triggeredBy String @map("triggered_by") // 'webhook' | 'manual' | 'batch'
createdAt DateTime @default(now()) @map("created_at")
// 索引 - 支持历史查询和趋势分析
@@index([projectId, recordId, createdAt], map: "idx_iit_qc_log_record_time")
@@index([projectId, status, createdAt], map: "idx_iit_qc_log_status_time")
@@index([projectId, qcType, createdAt], map: "idx_iit_qc_log_type_time")
@@map("qc_logs")
@@schema("iit_schema")
}
/// 录入汇总表 - 记录入组和录入进度
/// 设计原则:使用 upsert每个记录只有一条汇总
model IitRecordSummary {
id String @id @default(uuid())
projectId String @map("project_id")
recordId String @map("record_id")
// 入组信息
enrolledAt DateTime? @map("enrolled_at") // 首次录入时间 = 入组时间
enrolledBy String? @map("enrolled_by") // 入组录入人REDCap username
// 最新录入信息
lastUpdatedAt DateTime @map("last_updated_at")
lastUpdatedBy String? @map("last_updated_by")
lastFormName String? @map("last_form_name") // 最后更新的表单
// 表单完成状态 (JSONB)
// 格式: { "demographics": 2, "baseline": 1, "visit1": 0 }
// 0=未开始, 1=进行中, 2=完成
formStatus Json @default("{}") @db.JsonB @map("form_status")
// 数据完整度
totalForms Int @default(0) @map("total_forms")
completedForms Int @default(0) @map("completed_forms")
completionRate Float @default(0) @map("completion_rate") // 0-100%
// 最新质控状态(冗余存储,查询更快)
latestQcStatus String? @map("latest_qc_status") // 'PASS' | 'FAIL' | 'WARNING'
latestQcAt DateTime? @map("latest_qc_at")
// 更新次数(用于趋势分析)
updateCount Int @default(0) @map("update_count")
// 时间戳
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 唯一约束 - 每个记录只有一条汇总
@@unique([projectId, recordId], map: "unique_iit_record_summary")
@@index([projectId, enrolledAt], map: "idx_iit_record_summary_enrolled")
@@index([projectId, latestQcStatus], map: "idx_iit_record_summary_qc_status")
@@index([projectId, completionRate], map: "idx_iit_record_summary_completion")
@@map("record_summary")
@@schema("iit_schema")
}
/// 项目级汇总表 - 用于 Dashboard 快速展示
/// 设计原则:使用 upsert每个项目只有一条汇总
model IitQcProjectStats {
id String @id @default(uuid())
projectId String @unique @map("project_id")
// 汇总统计
totalRecords Int @default(0) @map("total_records")
passedRecords Int @default(0) @map("passed_records")
failedRecords Int @default(0) @map("failed_records")
warningRecords Int @default(0) @map("warning_records")
// 入排标准统计
inclusionMet Int @default(0) @map("inclusion_met")
exclusionMet Int @default(0) @map("exclusion_met")
// 录入进度统计
avgCompletionRate Float @default(0) @map("avg_completion_rate")
// 更新时间
updatedAt DateTime @updatedAt @map("updated_at")
@@map("qc_project_stats")
@@schema("iit_schema")
}
/// eQuery 表 - AI 自动生成的电子质疑,具有完整生命周期
model IitEquery {
id String @id @default(uuid())
projectId String @map("project_id")
// 来源
recordId String @map("record_id")
eventId String? @map("event_id")
formName String? @map("form_name")
fieldName String? @map("field_name")
qcLogId String? @map("qc_log_id")
reportId String? @map("report_id")
// 质疑内容
queryText String @map("query_text") @db.Text
expectedAction String? @map("expected_action") @db.Text
severity String @default("warning")
category String?
// 状态机: pending → responded → reviewing → closed / reopened
status String @default("pending")
// CRC 回复
assignedTo String? @map("assigned_to")
respondedAt DateTime? @map("responded_at")
responseText String? @map("response_text") @db.Text
responseData Json? @map("response_data") @db.JsonB
// AI 复核
reviewResult String? @map("review_result")
reviewNote String? @map("review_note") @db.Text
reviewedAt DateTime? @map("reviewed_at")
// 关闭
closedAt DateTime? @map("closed_at")
closedBy String? @map("closed_by")
resolution String? @db.Text
// 时间线
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([projectId], map: "idx_iit_equery_project")
@@index([projectId, status], map: "idx_iit_equery_project_status")
@@index([recordId], map: "idx_iit_equery_record")
@@index([assignedTo], map: "idx_iit_equery_assigned")
@@map("equery")
@@schema("iit_schema")
}
/// 重大事件归档表 - SAE、重大方案偏离等长期临床资产
model IitCriticalEvent {
id String @id @default(uuid())
projectId String @map("project_id")
recordId String @map("record_id")
// 事件分类
eventType String @map("event_type")
severity String @default("critical")
// 事件内容
title String
description String @db.Text
detectedAt DateTime @map("detected_at")
detectedBy String @default("ai") @map("detected_by")
// 来源追溯
sourceQcLogId String? @map("source_qc_log_id")
sourceEqueryId String? @map("source_equery_id")
sourceData Json? @map("source_data") @db.JsonB
// 处理状态
status String @default("open")
handledBy String? @map("handled_by")
handledAt DateTime? @map("handled_at")
handlingNote String? @map("handling_note") @db.Text
// 上报追踪
reportedToEc Boolean @default(false) @map("reported_to_ec")
reportedAt DateTime? @map("reported_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([projectId], map: "idx_iit_critical_event_project")
@@index([projectId, eventType], map: "idx_iit_critical_event_type")
@@index([projectId, status], map: "idx_iit_critical_event_status")
@@map("critical_events")
@@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")
}
/// 运营日志表 (MVP V3.1)
/// 用于记录用户行为,支持 DAU/DAT 统计和用户360画像
model simple_logs {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
created_at DateTime @default(now())
// 租户和用户信息
tenant_id String @db.VarChar(50)
tenant_name String? @db.VarChar(100) // 冗余字段避免JOIN
user_id String @db.Uuid
user_name String? @db.VarChar(50)
// 行为记录
module String @db.VarChar(20) // AIA, PKB, ASL, DC, RVW, IIT, SSA, ST, SYSTEM
feature String @db.VarChar(50) // 细分功能
action String @db.VarChar(20) // LOGIN, USE, EXPORT, CREATE, DELETE, ERROR
// 详情信息
info String? @db.Text // JSON或文本详情
@@index([created_at], map: "idx_simple_logs_created_at")
@@index([tenant_id], map: "idx_simple_logs_tenant_id")
@@index([user_id], map: "idx_simple_logs_user_id")
@@index([module, feature], map: "idx_simple_logs_module_feature")
@@index([action], map: "idx_simple_logs_action")
@@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"]
knowledge_config Json? @map("knowledge_config") /// 知识库增强配置
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")
}
/// 系统知识库 - 运营管理的公共知识库,供 Prompt 引用
model system_knowledge_bases {
id String @id @default(uuid())
code String @unique @db.VarChar(50) /// 唯一编码,如 'CONSORT_2010'
name String @db.VarChar(100) /// 名称,如 'CONSORT 2010 声明'
description String? @db.Text /// 描述
category String? @db.VarChar(50) /// 分类: methodology, statistics, crf
document_count Int @default(0) @map("document_count") /// 文档数量
total_tokens Int @default(0) @map("total_tokens") /// 总 Token 数
status String @default("active") @db.VarChar(20) /// 状态: active, archived
documents system_kb_documents[]
created_at DateTime @default(now()) @map("created_at")
updated_at DateTime @updatedAt @map("updated_at")
@@index([category], map: "idx_system_kb_category")
@@index([status], map: "idx_system_kb_status")
@@map("system_knowledge_bases")
@@schema("capability_schema")
}
/// 系统知识库文档 - 知识库中的文档
model system_kb_documents {
id String @id @default(uuid())
kb_id String @map("kb_id") /// 所属知识库ID
filename String @db.VarChar(255) /// 原始文件名
file_path String? @db.VarChar(500) @map("file_path") /// OSS 存储路径
file_size Int? @map("file_size") /// 文件大小(字节)
file_type String? @db.VarChar(50) @map("file_type") /// 文件类型: pdf, docx, md, txt
content String? @db.Text /// 解析后的文本内容
token_count Int @default(0) @map("token_count") /// Token 数量
status String @default("pending") @db.VarChar(20) /// 状态: pending, processing, ready, failed
error_message String? @db.Text @map("error_message") /// 错误信息
knowledge_base system_knowledge_bases @relation(fields: [kb_id], references: [id], onDelete: Cascade)
created_at DateTime @default(now()) @map("created_at")
updated_at DateTime @updatedAt @map("updated_at")
@@index([kb_id], map: "idx_system_kb_docs_kb_id")
@@index([status], map: "idx_system_kb_docs_status")
@@map("system_kb_documents")
@@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")
}
// ============================================================
// SSA Schema (ssa_schema)
// 智能统计分析模块
// ============================================================
/// SSA 分析会话
model SsaSession {
id String @id @default(uuid())
userId String @map("user_id")
title String?
dataSchema Json? @map("data_schema") /// 数据结构LLM可见
dataPayload Json? @map("data_payload") /// 真实数据仅R可见
dataOssKey String? @map("data_oss_key") /// OSS 存储 key大数据
dataProfile Json? @map("data_profile") /// 🆕 Python 生成的 DataProfilePhase 2A
status String @default("active") /// active | consult | completed | error
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
messages SsaMessage[]
executionLogs SsaExecutionLog[]
workflows SsaWorkflow[] /// 🆕 多步骤流程Phase 2A
@@index([userId], map: "idx_ssa_session_user")
@@index([status], map: "idx_ssa_session_status")
@@map("ssa_sessions")
@@schema("ssa_schema")
}
/// SSA 消息记录
model SsaMessage {
id String @id @default(uuid())
sessionId String @map("session_id")
role String /// user | assistant | system
contentType String @map("content_type") /// text | plan | result
content Json
createdAt DateTime @default(now()) @map("created_at")
session SsaSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
@@index([sessionId], map: "idx_ssa_message_session")
@@map("ssa_messages")
@@schema("ssa_schema")
}
/// SSA 工具库
model SsaTool {
id String @id @default(uuid())
toolCode String @unique @map("tool_code")
name String
version String @default("1.0.0")
description String
usageContext String? @map("usage_context")
paramsSchema Json @map("params_schema")
guardrails Json?
searchText String @map("search_text")
embedding Unsupported("vector(1024)")?
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("ssa_tools_library")
@@schema("ssa_schema")
}
/// SSA 执行日志
model SsaExecutionLog {
id String @id @default(uuid())
sessionId String @map("session_id")
messageId String? @map("message_id")
toolCode String @map("tool_code")
inputParams Json @map("input_params")
outputStatus String @map("output_status")
outputResult Json? @map("output_result")
traceLog String[] @map("trace_log")
executionMs Int? @map("execution_ms")
createdAt DateTime @default(now()) @map("created_at")
session SsaSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
@@index([sessionId], map: "idx_ssa_exec_log_session")
@@index([toolCode], map: "idx_ssa_exec_log_tool")
@@map("ssa_execution_logs")
@@schema("ssa_schema")
}
/// SSA 统计决策表
model SsaDecisionTable {
id String @id @default(uuid())
goalType String @map("goal_type") /// 分析目标
yType String @map("y_type") /// 因变量类型
xType String? @map("x_type") /// 自变量类型
designType String @map("design_type") /// 设计类型
toolCode String @map("tool_code") /// 推荐工具
altToolCode String? @map("alt_tool_code") /// 备选工具(降级)
priority Int @default(0) /// 优先级
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
@@unique([goalType, yType, xType, designType], map: "uq_ssa_decision_table")
@@map("ssa_decision_table")
@@schema("ssa_schema")
}
/// SSA R 代码库
model SsaRCodeLibrary {
id String @id @default(uuid())
toolCode String @map("tool_code") /// 关联工具代码
version String @default("1.0.0")
fileName String @map("file_name") /// R 脚本文件名
codeContent String @map("code_content") @db.Text /// R 代码内容
entryFunc String @default("run_analysis") @map("entry_func")
description String?
dependencies String[] @default([]) /// 依赖包列表
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([toolCode], map: "idx_ssa_rcode_tool")
@@map("ssa_r_code_library")
@@schema("ssa_schema")
}
/// SSA 参数映射配置
model SsaParamMapping {
id String @id @default(uuid())
toolCode String @map("tool_code")
jsonKey String @map("json_key") /// 前端传入的 JSON Key
rParamName String @map("r_param_name") /// R 函数参数名
dataType String @map("data_type") /// string | number | boolean
isRequired Boolean @default(false) @map("is_required")
defaultValue String? @map("default_value")
validationRule String? @map("validation_rule")
description String?
@@unique([toolCode, jsonKey], map: "uq_ssa_param_mapping")
@@map("ssa_param_mapping")
@@schema("ssa_schema")
}
/// SSA 护栏规则配置
model SsaGuardrailConfig {
id String @id @default(uuid())
toolCode String @map("tool_code")
checkName String @map("check_name") /// 检查名称
checkOrder Int @default(0) @map("check_order")
checkCode String @map("check_code") /// R 函数名
threshold String? /// 阈值条件
actionType String @map("action_type") /// Block | Warn | Switch
actionTarget String? @map("action_target") /// Switch 时的目标工具
isEnabled Boolean @default(true) @map("is_enabled")
@@index([toolCode], map: "idx_ssa_guardrail_tool")
@@map("ssa_guardrail_config")
@@schema("ssa_schema")
}
/// SSA 结果解读模板
model SsaInterpretation {
id String @id @default(uuid())
toolCode String @map("tool_code")
scenarioKey String @map("scenario_key") /// significant | not_significant
template String @db.Text /// 解读模板(含占位符)
placeholders String[] @default([]) /// 占位符列表
@@unique([toolCode, scenarioKey], map: "uq_ssa_interpretation")
@@map("ssa_interpretation_templates")
@@schema("ssa_schema")
}
// ============================================================
// 🆕 Phase 2A 新增:多步骤流程管理
// ============================================================
/// SSA 多步骤流程
model SsaWorkflow {
id String @id @default(uuid())
sessionId String @map("session_id")
messageId String? @map("message_id") /// 关联的计划消息
status String @default("pending") /// pending | running | completed | partial | error
totalSteps Int @map("total_steps")
completedSteps Int @default(0) @map("completed_steps")
workflowPlan Json @map("workflow_plan") /// 原始计划 JSON
reasoning String? @db.Text /// LLM 规划理由
createdAt DateTime @default(now()) @map("created_at")
startedAt DateTime? @map("started_at")
completedAt DateTime? @map("completed_at")
session SsaSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
steps SsaWorkflowStep[]
@@index([sessionId], map: "idx_ssa_workflow_session")
@@index([status], map: "idx_ssa_workflow_status")
@@map("ssa_workflows")
@@schema("ssa_schema")
}
/// SSA 流程步骤
model SsaWorkflowStep {
id String @id @default(uuid())
workflowId String @map("workflow_id")
stepOrder Int @map("step_order") /// 步骤顺序1, 2, 3...
toolCode String @map("tool_code")
toolName String @map("tool_name")
status String @default("pending") /// pending | running | success | warning | error | skipped
inputParams Json? @map("input_params") /// 输入参数
guardrailChecks Json? @map("guardrail_checks") /// JIT 护栏检验结果
outputResult Json? @map("output_result") /// 执行结果
errorInfo Json? @map("error_info") /// 错误信息
executionMs Int? @map("execution_ms") /// 执行耗时(毫秒)
startedAt DateTime? @map("started_at")
completedAt DateTime? @map("completed_at")
workflow SsaWorkflow @relation(fields: [workflowId], references: [id], onDelete: Cascade)
@@index([workflowId], map: "idx_ssa_workflow_step_workflow")
@@index([status], map: "idx_ssa_workflow_step_status")
@@map("ssa_workflow_steps")
@@schema("ssa_schema")
}