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") tokenVersion Int @default(0) @map("token_version") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") tenant_members tenant_members[] user_modules user_modules[] iitUserMappings IitUserMapping[] 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 // 深度思考内容 ... 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 执行上下文数据 errorDetails Json? @map("error_details") /// 结构化错误详情(记录各 Skill 成功/失败状态) 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") isDemo Boolean @default(false) @map("is_demo") status String @default("active") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") deletedAt DateTime? @map("deleted_at") tenantId String? @map("tenant_id") auditLogs IitAuditLog[] pendingActions IitPendingAction[] taskRuns IitTaskRun[] userMappings IitUserMapping[] tenant tenants? @relation(fields: [tenantId], references: [id]) @@index([status, deletedAt]) @@index([knowledgeBaseId], map: "idx_iit_project_kb") @@index([tenantId]) @@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") userId String? @map("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]) user User? @relation(fields: [userId], references: [id]) @@unique([projectId, systemUserId]) @@unique([projectId, redcapUsername]) @@index([wecomUserId]) @@index([miniProgramOpenId]) @@index([userId]) @@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 // 核心配置 JSON(SOP 流程图 / 规则定义) 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 } semanticLabel String? @map("semantic_label") // V3.1: 中文语义标签(LLM 输出方向),如"谷丙转氨酶(ALT)" formName String? @map("form_name") // V3.1: 所属表单名 ruleCategory String? @map("rule_category") // V3.1: 所属维度 D1-D7 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") // 单表质控时记录表单名 instanceId Int @default(1) @map("instance_id") // V3.1: 重复表单实例编号 // 核心结果 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") // V3.1: 事件级聚合 eventsTotal Int @default(0) @map("events_total") eventsPassed Int @default(0) @map("events_passed") eventsFailed Int @default(0) @map("events_failed") eventsWarning Int @default(0) @map("events_warning") // V3.1: 字段级聚合 fieldsTotal Int @default(0) @map("fields_total") fieldsPassed Int @default(0) @map("fields_passed") fieldsFailed Int @default(0) @map("fields_failed") // V3.1: 维度计数(D1-D7) d1Issues Int @default(0) @map("d1_issues") d2Issues Int @default(0) @map("d2_issues") d3Issues Int @default(0) @map("d3_issues") d5Issues Int @default(0) @map("d5_issues") d6Issues Int @default(0) @map("d6_issues") d7Issues Int @default(0) @map("d7_issues") // V3.1: 关键问题摘要 topIssues Json @default("[]") @db.JsonB @map("top_issues") // 时间戳 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") // V3.1: D1-D7 维度统计 d1PassRate Float @default(0) @map("d1_pass_rate") d2PassRate Float @default(0) @map("d2_pass_rate") d3PassRate Float @default(0) @map("d3_pass_rate") d5PassRate Float @default(0) @map("d5_pass_rate") d6PassRate Float @default(0) @map("d6_pass_rate") d7PassRate Float @default(0) @map("d7_pass_rate") // V3.1: 综合健康度(0-100 加权评分) healthScore Float @default(0) @map("health_score") healthGrade String? @map("health_grade") // 'A' | 'B' | 'C' | 'D' | 'F' dimensionDetail Json @default("{}") @db.JsonB @map("dimension_detail") // 更新时间 updatedAt DateTime @updatedAt @map("updated_at") @@map("qc_project_stats") @@schema("iit_schema") } /// V3.1 字段级质控状态表 — 五级坐标(Record → Event → Form → Instance → Field) /// 设计原则:每个字段最新一次 QC 结果,UPSERT 语义 model IitQcFieldStatus { 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") instanceId Int @default(1) @map("instance_id") fieldName String @map("field_name") status String // 'PASS' | 'FAIL' | 'WARNING' ruleId String? @map("rule_id") ruleName String? @map("rule_name") ruleCategory String? @map("rule_category") // 'D1' | 'D2' | 'D3' | 'D5' | 'D6' | 'D7' severity String? // 'critical' | 'warning' | 'info' message String? @db.Text actualValue String? @map("actual_value") expectedValue String? @map("expected_value") sourceQcLogId String? @map("source_qc_log_id") triggeredBy String @map("triggered_by") // 'webhook' | 'cron' | 'manual' lastQcAt DateTime @default(now()) @map("last_qc_at") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") @@unique([projectId, recordId, eventId, formName, instanceId, fieldName], map: "uq_field_status") @@index([projectId, recordId], map: "idx_fs_record") @@index([projectId, recordId, eventId], map: "idx_fs_event") @@index([projectId, status], map: "idx_fs_status") @@index([projectId, ruleCategory], map: "idx_fs_category") @@map("qc_field_status") @@schema("iit_schema") } /// V3.1 事件级质控状态表 — 由 qc_field_status 聚合而来 /// 设计原则:每个 project × record × event 唯一一行,UPSERT 语义 model IitQcEventStatus { id String @id @default(uuid()) projectId String @map("project_id") recordId String @map("record_id") eventId String @map("event_id") eventLabel String? @map("event_label") status String // 最严重的子级状态 fieldsTotal Int @default(0) @map("fields_total") fieldsPassed Int @default(0) @map("fields_passed") fieldsFailed Int @default(0) @map("fields_failed") fieldsWarning Int @default(0) @map("fields_warning") d1Issues Int @default(0) @map("d1_issues") d2Issues Int @default(0) @map("d2_issues") d3Issues Int @default(0) @map("d3_issues") d5Issues Int @default(0) @map("d5_issues") d6Issues Int @default(0) @map("d6_issues") d7Issues Int @default(0) @map("d7_issues") formsChecked String[] @default([]) @map("forms_checked") topIssues Json @default("[]") @db.JsonB @map("top_issues") triggeredBy String @map("triggered_by") lastQcAt DateTime @default(now()) @map("last_qc_at") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") @@unique([projectId, recordId, eventId], map: "uq_event_status") @@index([projectId, recordId], map: "idx_es_record") @@index([projectId, status], map: "idx_es_status") @@map("qc_event_status") @@schema("iit_schema") } /// eQuery 表 - AI 自动生成的电子质疑,具有完整生命周期 model IitEquery { // TODO: Tech Debt - DB 由 prisma db push 创建为 UUID 类型,未来大版本重构时统一为 String/TEXT id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid projectId String @map("project_id") // 来源 recordId String @map("record_id") eventId String? @map("event_id") formName String? @map("form_name") instanceId Int @default(1) @map("instance_id") // V3.1: 重复表单实例编号 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") // TODO: Tech Debt - DB 由 prisma db push 创建为 TIMESTAMPTZ(6),与 Prisma 默认的 TIMESTAMP(3) 不同 respondedAt DateTime? @map("responded_at") @db.Timestamptz(6) 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") @db.Timestamptz(6) // 关闭 closedAt DateTime? @map("closed_at") @db.Timestamptz(6) closedBy String? @map("closed_by") resolution String? @db.Text // 时间线 createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6) @@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 { // TODO: Tech Debt - DB 由 prisma db push 创建为 UUID 类型,未来大版本重构时统一为 String/TEXT id String @id @default(dbgenerated("gen_random_uuid()")) @db.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 // TODO: Tech Debt - DB 由 prisma db push 创建为 TIMESTAMPTZ(6) detectedAt DateTime @map("detected_at") @db.Timestamptz(6) 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") @db.Timestamptz(6) handlingNote String? @map("handling_note") @db.Text // 上报追踪 reportedToEc Boolean @default(false) @map("reported_to_ec") reportedAt DateTime? @map("reported_at") @db.Timestamptz(6) createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6) @@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[] iitProjects IitProject[] @@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 IIT_OPERATOR 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") /// 关联的对话ID(aia_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 生成的 DataProfile(Phase 2A) executionMode String @default("agent") @map("execution_mode") /// qper | agent 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) agentExecutions SsaAgentExecution[] /// Agent 模式执行记录 @@index([userId], map: "idx_ssa_session_user") @@index([status], map: "idx_ssa_session_status") @@map("ssa_sessions") @@schema("ssa_schema") } /// SSA Agent 执行记录(LLM 代码生成通道) model SsaAgentExecution { id String @id @default(uuid()) sessionId String @map("session_id") query String planText String? @map("plan_text") generatedCode String? @map("generated_code") reviewResult Json? @map("review_result") executionResult Json? @map("execution_result") reportBlocks Json? @map("report_blocks") retryCount Int @default(0) @map("retry_count") status String @default("pending") /// pending | planning | coding | reviewing | executing | completed | error errorMessage String? @map("error_message") durationMs Int? @map("duration_ms") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") session SsaSession @relation(fields: [sessionId], references: [id], onDelete: Cascade) @@index([sessionId], map: "idx_ssa_agent_exec_session") @@map("ssa_agent_executions") @@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 { // TODO: Tech Debt - DB 由 prisma db push 创建,类型与 Prisma 默认不同,已手动对齐 id String @id @default(dbgenerated("(gen_random_uuid())::text")) sessionId String @map("session_id") messageId String? @map("message_id") /// 关联的计划消息 status String @default("pending") @db.VarChar(20) /// 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") @db.Timestamp(6) startedAt DateTime? @map("started_at") @db.Timestamp(6) completedAt DateTime? @map("completed_at") @db.Timestamp(6) 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 { // TODO: Tech Debt - DB 由 prisma db push 创建,类型与 Prisma 默认不同,已手动对齐 id String @id @default(dbgenerated("(gen_random_uuid())::text")) workflowId String @map("workflow_id") stepOrder Int @map("step_order") /// 步骤顺序(1, 2, 3...) toolCode String @map("tool_code") @db.VarChar(50) toolName String @map("tool_name") @db.VarChar(100) status String @default("pending") @db.VarChar(20) /// 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") @db.Timestamp(6) completedAt DateTime? @map("completed_at") @db.Timestamp(6) 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") }