Files
AIclinicalresearch/backend/prisma/schema.prisma
HaHafeng 1ece9a4ae8 feat(asl): Add DeepSearch smart literature retrieval MVP
Features:
- Integrate unifuncs DeepSearch API (OpenAI compatible protocol)
- SSE real-time streaming for AI thinking process display
- Natural language input, auto-generate PubMed search strategy
- Extract and display PubMed literature links
- Database storage for task records (asl_research_tasks)

Backend:
- researchService.ts - Core business logic with SSE streaming
- researchController.ts - SSE stream endpoint
- researchWorker.ts - Async task worker (backup mode)
- schema.prisma - AslResearchTask model

Frontend:
- ResearchSearch.tsx - Search page with unified content stream
- ResearchSearch.css - Styling (unifuncs-inspired simple design)
- ASLLayout.tsx - Enable menu item
- api/index.ts - Add research API functions

API Endpoints:
- POST /api/v1/asl/research/stream - SSE streaming search
- POST /api/v1/asl/research/tasks - Async task creation
- GET /api/v1/asl/research/tasks/:taskId/status - Task status

Documentation:
- Development record for DeepSearch integration
- Update ASL module status (v1.5)
- Update system status (v3.7)

Known limitations:
- SSE mode, task interrupts when leaving page
- Cost ~0.3 RMB per search (unifuncs API)
2026-01-18 19:15:55 +08:00

1286 lines
54 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", "aia_schema", "asl_schema", "capability_schema", "common_schema", "dc_schema", "iit_schema", "pkb_schema", "platform_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")
difyDocumentId String @map("dify_document_id")
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([difyDocumentId], map: "idx_pkb_documents_dify_document_id")
@@index([extractionMethod], map: "idx_pkb_documents_extraction_method")
@@index([kbId], map: "idx_pkb_documents_kb_id")
@@index([status], map: "idx_pkb_documents_status")
@@index([userId], map: "idx_pkb_documents_user_id")
@@map("documents")
@@schema("pkb_schema")
}
model BatchTask {
id String @id @default(uuid())
userId String @map("user_id")
kbId String @map("kb_id")
name String
templateType String @map("template_type")
templateId String? @map("template_id")
prompt String
status String
totalDocuments Int @map("total_documents")
completedCount Int @default(0) @map("completed_count")
failedCount Int @default(0) @map("failed_count")
modelType String @map("model_type")
concurrency Int @default(3)
startedAt DateTime? @map("started_at")
completedAt DateTime? @map("completed_at")
durationSeconds Int? @map("duration_seconds")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
results BatchResult[] @relation("TaskBatchResults")
knowledgeBase KnowledgeBase @relation("KnowledgeBaseBatchTasks", fields: [kbId], references: [id], onDelete: Cascade)
@@index([createdAt], map: "idx_pkb_batch_tasks_created_at")
@@index([kbId], map: "idx_pkb_batch_tasks_kb_id")
@@index([status], map: "idx_pkb_batch_tasks_status")
@@index([userId], map: "idx_pkb_batch_tasks_user_id")
@@map("batch_tasks")
@@schema("pkb_schema")
}
model BatchResult {
id String @id @default(uuid())
taskId String @map("task_id")
documentId String @map("document_id")
status String
data Json?
rawOutput String? @map("raw_output")
errorMessage String? @map("error_message")
processingTimeMs Int? @map("processing_time_ms")
tokensUsed Int? @map("tokens_used")
createdAt DateTime @default(now()) @map("created_at")
document Document @relation("DocumentBatchResults", fields: [documentId], references: [id], onDelete: Cascade)
task BatchTask @relation("TaskBatchResults", fields: [taskId], references: [id], onDelete: Cascade)
@@index([documentId], map: "idx_pkb_batch_results_document_id")
@@index([status], map: "idx_pkb_batch_results_status")
@@index([taskId], map: "idx_pkb_batch_results_task_id")
@@map("batch_results")
@@schema("pkb_schema")
}
model TaskTemplate {
id String @id @default(uuid())
userId String @map("user_id")
name String
description String?
prompt String
isPublic Boolean @default(false) @map("is_public")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
outputFields Json
@@index([userId], map: "idx_pkb_task_templates_user_id")
@@map("task_templates")
@@schema("pkb_schema")
}
model AdminLog {
id Int @id @default(autoincrement())
adminId String @map("admin_id")
action String
resourceType String? @map("resource_type")
resourceId String? @map("resource_id")
details Json?
ipAddress String? @map("ip_address")
userAgent String? @map("user_agent")
createdAt DateTime @default(now()) @map("created_at")
admin users @relation(fields: [adminId], references: [id], onDelete: Cascade)
@@index([adminId])
@@index([createdAt])
@@index([action])
@@map("admin_logs")
@@schema("public")
}
// GeneralConversation 和 GeneralMessage 已删除2026-01-11
// 原因:与 Conversation/Message 功能重叠,使用 conversations.project_id = NULL 表示无项目对话
model ReviewTask {
id String @id @default(uuid())
userId String @map("user_id")
fileName String @map("file_name")
fileSize Int @map("file_size")
filePath String? @map("file_path")
extractedText String @map("extracted_text")
wordCount Int? @map("word_count")
status String @default("pending")
selectedAgents String[] @default(["editorial", "methodology"]) @map("selected_agents")
editorialReview Json? @map("editorial_review")
methodologyReview Json? @map("methodology_review")
overallScore Float? @map("overall_score")
editorialScore Float? @map("editorial_score")
methodologyScore Float? @map("methodology_score")
methodologyStatus String? @map("methodology_status")
picoExtract Json? @map("pico_extract")
isArchived Boolean @default(false) @map("is_archived")
archivedAt DateTime? @map("archived_at")
modelUsed String? @map("model_used")
startedAt DateTime? @map("started_at")
completedAt DateTime? @map("completed_at")
durationSeconds Int? @map("duration_seconds")
errorMessage String? @map("error_message")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 注意userId 暂不添加外键约束,因为用户来自不同 schema (platform_schema.users)
// 跨 schema 外键在 PostgreSQL 中需要特殊处理
@@index([userId])
@@index([status])
@@index([createdAt])
@@index([isArchived])
@@map("review_tasks")
@@schema("rvw_schema")
}
model AslScreeningProject {
id String @id @default(uuid())
userId String @map("user_id")
projectName String @map("project_name")
picoCriteria Json @map("pico_criteria")
inclusionCriteria String @map("inclusion_criteria")
exclusionCriteria String @map("exclusion_criteria")
status String @default("draft")
screeningConfig Json? @map("screening_config")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
fulltextResults AslFulltextScreeningResult[] @relation("ProjectFulltextResults")
fulltextTasks AslFulltextScreeningTask[] @relation("ProjectFulltextTasks")
literatures AslLiterature[] @relation("ProjectLiteratures")
screeningResults AslScreeningResult[] @relation("ProjectScreeningResults")
screeningTasks AslScreeningTask[] @relation("ProjectScreeningTasks")
@@index([status], map: "idx_screening_projects_status")
@@index([userId], map: "idx_screening_projects_user_id")
@@map("screening_projects")
@@schema("asl_schema")
}
model AslLiterature {
id String @id @default(uuid())
projectId String @map("project_id")
pmid String?
title String
abstract String
authors String?
journal String?
publicationYear Int? @map("publication_year")
doi String?
pdfUrl String? @map("pdf_url")
pdfOssKey String? @map("pdf_oss_key")
pdfFileSize Int? @map("pdf_file_size")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
stage String @default("imported") @map("stage")
hasPdf Boolean @default(false) @map("has_pdf")
pdfStorageType String? @map("pdf_storage_type")
pdfStorageRef String? @map("pdf_storage_ref")
pdfStatus String? @map("pdf_status")
pdfUploadedAt DateTime? @map("pdf_uploaded_at")
fullTextStorageType String? @map("full_text_storage_type")
fullTextStorageRef String? @map("full_text_storage_ref")
fullTextUrl String? @map("full_text_url")
fullTextFormat String? @map("full_text_format")
fullTextSource String? @map("full_text_source")
fullTextTokenCount Int? @map("full_text_token_count")
fullTextExtractedAt DateTime? @map("full_text_extracted_at")
fulltextResults AslFulltextScreeningResult[] @relation("LiteratureFulltextResults")
project AslScreeningProject @relation("ProjectLiteratures", fields: [projectId], references: [id], onDelete: Cascade)
screeningResults AslScreeningResult[] @relation("LiteratureScreeningResults")
@@unique([projectId, pmid], map: "unique_project_pmid")
@@index([doi], map: "idx_literatures_doi")
@@index([hasPdf], map: "idx_literatures_has_pdf")
@@index([pdfStatus], map: "idx_literatures_pdf_status")
@@index([projectId], map: "idx_literatures_project_id")
@@index([stage], map: "idx_literatures_stage")
@@map("literatures")
@@schema("asl_schema")
}
model AslScreeningResult {
id String @id @default(uuid())
projectId String @map("project_id")
literatureId String @map("literature_id")
dsModelName String @map("ds_model_name")
dsPJudgment String? @map("ds_p_judgment")
dsIJudgment String? @map("ds_i_judgment")
dsCJudgment String? @map("ds_c_judgment")
dsSJudgment String? @map("ds_s_judgment")
dsConclusion String? @map("ds_conclusion")
dsConfidence Float? @map("ds_confidence")
dsPEvidence String? @map("ds_p_evidence")
dsIEvidence String? @map("ds_i_evidence")
dsCEvidence String? @map("ds_c_evidence")
dsSEvidence String? @map("ds_s_evidence")
dsReason String? @map("ds_reason")
qwenModelName String @map("qwen_model_name")
qwenPJudgment String? @map("qwen_p_judgment")
qwenIJudgment String? @map("qwen_i_judgment")
qwenCJudgment String? @map("qwen_c_judgment")
qwenSJudgment String? @map("qwen_s_judgment")
qwenConclusion String? @map("qwen_conclusion")
qwenConfidence Float? @map("qwen_confidence")
qwenPEvidence String? @map("qwen_p_evidence")
qwenIEvidence String? @map("qwen_i_evidence")
qwenCEvidence String? @map("qwen_c_evidence")
qwenSEvidence String? @map("qwen_s_evidence")
qwenReason String? @map("qwen_reason")
conflictStatus String @default("none") @map("conflict_status")
conflictFields Json? @map("conflict_fields")
finalDecision String? @map("final_decision")
finalDecisionBy String? @map("final_decision_by")
finalDecisionAt DateTime? @map("final_decision_at")
exclusionReason String? @map("exclusion_reason")
aiProcessingStatus String @default("pending") @map("ai_processing_status")
aiProcessedAt DateTime? @map("ai_processed_at")
aiErrorMessage String? @map("ai_error_message")
promptVersion String @default("v1.0.0") @map("prompt_version")
rawOutput Json? @map("raw_output")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
literature AslLiterature @relation("LiteratureScreeningResults", fields: [literatureId], references: [id], onDelete: Cascade)
project AslScreeningProject @relation("ProjectScreeningResults", fields: [projectId], references: [id], onDelete: Cascade)
@@unique([projectId, literatureId], map: "unique_project_literature")
@@index([conflictStatus], map: "idx_screening_results_conflict_status")
@@index([finalDecision], map: "idx_screening_results_final_decision")
@@index([literatureId], map: "idx_screening_results_literature_id")
@@index([projectId], map: "idx_screening_results_project_id")
@@map("screening_results")
@@schema("asl_schema")
}
model AslScreeningTask {
id String @id @default(uuid())
projectId String @map("project_id")
taskType String @map("task_type")
status String @default("pending")
totalItems Int @map("total_items")
processedItems Int @default(0) @map("processed_items")
successItems Int @default(0) @map("success_items")
failedItems Int @default(0) @map("failed_items")
conflictItems Int @default(0) @map("conflict_items")
startedAt DateTime? @map("started_at")
completedAt DateTime? @map("completed_at")
estimatedEndAt DateTime? @map("estimated_end_at")
errorMessage String? @map("error_message")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
project AslScreeningProject @relation("ProjectScreeningTasks", fields: [projectId], references: [id], onDelete: Cascade)
@@index([projectId], map: "idx_screening_tasks_project_id")
@@index([status], map: "idx_screening_tasks_status")
@@map("screening_tasks")
@@schema("asl_schema")
}
model AslFulltextScreeningTask {
id String @id @default(uuid())
projectId String @map("project_id")
modelA String @map("model_a")
modelB String @map("model_b")
promptVersion String @default("v1.0.0") @map("prompt_version")
status String @default("pending")
totalCount Int @map("total_count")
processedCount Int @default(0) @map("processed_count")
successCount Int @default(0) @map("success_count")
failedCount Int @default(0) @map("failed_count")
degradedCount Int @default(0) @map("degraded_count")
totalTokens Int @default(0) @map("total_tokens")
totalCost Float @default(0) @map("total_cost")
startedAt DateTime? @map("started_at")
completedAt DateTime? @map("completed_at")
estimatedEndAt DateTime? @map("estimated_end_at")
errorMessage String? @map("error_message")
errorStack String? @map("error_stack")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
results AslFulltextScreeningResult[] @relation("TaskFulltextResults")
project AslScreeningProject @relation("ProjectFulltextTasks", fields: [projectId], references: [id], onDelete: Cascade)
@@index([createdAt], map: "idx_fulltext_tasks_created_at")
@@index([projectId], map: "idx_fulltext_tasks_project_id")
@@index([status], map: "idx_fulltext_tasks_status")
@@map("fulltext_screening_tasks")
@@schema("asl_schema")
}
/// 智能文献检索任务DeepSearch
model AslResearchTask {
id String @id @default(uuid())
// 关联
projectId String @map("project_id")
userId String @map("user_id")
// 检索输入
query String // 用户的自然语言查询
filters Json? // 🔜 后续:高级筛选 { yearFrom, yearTo, articleTypes }
// unifuncs 任务
externalTaskId String? @map("external_task_id")
// 状态
status String @default("pending") // pending/processing/completed/failed
errorMessage String? @map("error_message")
// 结果
resultCount Int? @map("result_count")
rawResult String? @map("raw_result") @db.Text
reasoningContent String? @map("reasoning_content") @db.Text // AI思考过程
literatures Json? // 解析后的文献列表
// 统计(🔜 后续展示)
tokenUsage Json? @map("token_usage")
searchCount Int? @map("search_count")
readCount Int? @map("read_count")
iterations Int?
// 时间
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
completedAt DateTime? @map("completed_at")
@@index([projectId], map: "idx_research_tasks_project_id")
@@index([userId], map: "idx_research_tasks_user_id")
@@index([status], map: "idx_research_tasks_status")
@@index([createdAt], map: "idx_research_tasks_created_at")
@@map("research_tasks")
@@schema("asl_schema")
}
model AslFulltextScreeningResult {
id String @id @default(uuid())
taskId String @map("task_id")
projectId String @map("project_id")
literatureId String @map("literature_id")
modelAName String @map("model_a_name")
modelAStatus String @map("model_a_status")
modelAFields Json @map("model_a_fields")
modelAOverall Json @map("model_a_overall")
modelAProcessingLog Json? @map("model_a_processing_log")
modelAVerification Json? @map("model_a_verification")
modelATokens Int? @map("model_a_tokens")
modelACost Float? @map("model_a_cost")
modelAError String? @map("model_a_error")
modelBName String @map("model_b_name")
modelBStatus String @map("model_b_status")
modelBFields Json @map("model_b_fields")
modelBOverall Json @map("model_b_overall")
modelBProcessingLog Json? @map("model_b_processing_log")
modelBVerification Json? @map("model_b_verification")
modelBTokens Int? @map("model_b_tokens")
modelBCost Float? @map("model_b_cost")
modelBError String? @map("model_b_error")
medicalLogicIssues Json? @map("medical_logic_issues")
evidenceChainIssues Json? @map("evidence_chain_issues")
isConflict Boolean @default(false) @map("is_conflict")
conflictSeverity String? @map("conflict_severity")
conflictFields String[] @map("conflict_fields")
conflictDetails Json? @map("conflict_details")
reviewPriority Int? @map("review_priority")
reviewDeadline DateTime? @map("review_deadline")
finalDecision String? @map("final_decision")
finalDecisionBy String? @map("final_decision_by")
finalDecisionAt DateTime? @map("final_decision_at")
exclusionReason String? @map("exclusion_reason")
reviewNotes String? @map("review_notes")
processingStatus String @default("pending") @map("processing_status")
isDegraded Boolean @default(false) @map("is_degraded")
degradedModel String? @map("degraded_model")
processedAt DateTime? @map("processed_at")
promptVersion String @default("v1.0.0") @map("prompt_version")
rawOutputA Json? @map("raw_output_a")
rawOutputB Json? @map("raw_output_b")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
literature AslLiterature @relation("LiteratureFulltextResults", fields: [literatureId], references: [id], onDelete: Cascade)
project AslScreeningProject @relation("ProjectFulltextResults", fields: [projectId], references: [id], onDelete: Cascade)
task AslFulltextScreeningTask @relation("TaskFulltextResults", fields: [taskId], references: [id], onDelete: Cascade)
@@unique([projectId, literatureId], map: "unique_project_literature_fulltext")
@@index([finalDecision], map: "idx_fulltext_results_final_decision")
@@index([isConflict], map: "idx_fulltext_results_is_conflict")
@@index([literatureId], map: "idx_fulltext_results_literature_id")
@@index([projectId], map: "idx_fulltext_results_project_id")
@@index([reviewPriority], map: "idx_fulltext_results_review_priority")
@@index([taskId], map: "idx_fulltext_results_task_id")
@@map("fulltext_screening_results")
@@schema("asl_schema")
}
model DCHealthCheck {
id String @id @default(uuid())
userId String @map("user_id")
fileName String @map("file_name")
columnName String @map("column_name")
emptyRate Float @map("empty_rate")
avgLength Float @map("avg_length")
totalRows Int @map("total_rows")
estimatedTokens Int @map("estimated_tokens")
status String @map("status")
message String @map("message")
createdAt DateTime @default(now()) @map("created_at")
@@index([userId, fileName])
@@map("dc_health_checks")
@@schema("dc_schema")
}
model DCTemplate {
id String @id @default(uuid())
diseaseType String @map("disease_type")
reportType String @map("report_type")
displayName String @map("display_name")
fields Json @map("fields")
promptTemplate String @map("prompt_template")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@unique([diseaseType, reportType])
@@map("dc_templates")
@@schema("dc_schema")
}
model DCExtractionTask {
id String @id @default(uuid())
userId String @map("user_id")
projectName String @map("project_name")
sourceFileKey String @map("source_file_key")
textColumn String @map("text_column")
diseaseType String @map("disease_type")
reportType String @map("report_type")
targetFields Json @map("target_fields")
modelA String @default("deepseek-v3") @map("model_a")
modelB String @default("qwen-max") @map("model_b")
status String @default("pending") @map("status")
totalCount Int @default(0) @map("total_count")
processedCount Int @default(0) @map("processed_count")
cleanCount Int @default(0) @map("clean_count")
conflictCount Int @default(0) @map("conflict_count")
failedCount Int @default(0) @map("failed_count")
totalTokens Int @default(0) @map("total_tokens")
totalCost Float @default(0) @map("total_cost")
error String? @map("error")
createdAt DateTime @default(now()) @map("created_at")
startedAt DateTime? @map("started_at")
completedAt DateTime? @map("completed_at")
items DCExtractionItem[]
@@index([userId, status])
@@map("dc_extraction_tasks")
@@schema("dc_schema")
}
model DCExtractionItem {
id String @id @default(uuid())
taskId String @map("task_id")
rowIndex Int @map("row_index")
originalText String @map("original_text")
resultA Json? @map("result_a")
resultB Json? @map("result_b")
status String @default("pending") @map("status")
conflictFields String[] @default([]) @map("conflict_fields")
finalResult Json? @map("final_result")
tokensA Int @default(0) @map("tokens_a")
tokensB Int @default(0) @map("tokens_b")
error String? @map("error")
createdAt DateTime @default(now()) @map("created_at")
resolvedAt DateTime? @map("resolved_at")
task DCExtractionTask @relation(fields: [taskId], references: [id], onDelete: Cascade)
@@index([taskId, status])
@@map("dc_extraction_items")
@@schema("dc_schema")
}
model DcToolCSession {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
userId String @map("user_id") @db.VarChar(255)
fileName String @map("file_name") @db.VarChar(500)
fileKey String @map("file_key") @db.VarChar(500)
totalRows Int? @map("total_rows")
totalCols Int? @map("total_cols")
columns Json? @map("columns")
encoding String? @map("encoding") @db.VarChar(50)
fileSize Int @map("file_size")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(6)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamp(6)
expiresAt DateTime @map("expires_at") @db.Timestamp(6)
dataStats Json? @map("data_stats")
columnMapping Json? @map("column_mapping")
cleanDataKey String? @map("clean_data_key") @db.VarChar(1000)
@@index([expiresAt], map: "idx_dc_tool_c_sessions_expires_at")
@@index([userId], map: "idx_dc_tool_c_sessions_user_id")
@@map("dc_tool_c_sessions")
@@schema("dc_schema")
}
model DcToolCAiHistory {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
sessionId String @map("session_id") @db.VarChar(255)
userId String @map("user_id") @db.VarChar(255)
role String @map("role") @db.VarChar(50)
content String
generatedCode String? @map("generated_code")
codeExplanation String? @map("code_explanation")
executeStatus String? @map("execute_status") @db.VarChar(50)
executeResult Json? @map("execute_result")
executeError String? @map("execute_error")
retryCount Int? @default(0) @map("retry_count")
model String? @map("model") @db.VarChar(100)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(6)
@@index([createdAt], map: "idx_dc_tool_c_ai_history_created_at")
@@index([sessionId], map: "idx_dc_tool_c_ai_history_session_id")
@@index([userId], map: "idx_dc_tool_c_ai_history_user_id")
@@map("dc_tool_c_ai_history")
@@schema("dc_schema")
}
/// This table is a partition table and requires additional setup for migrations. Visit https://pris.ly/d/partition-tables for more info.
model job {
id String @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String
priority Int @default(0)
data Json?
state job_state @default(created)
retry_limit Int @default(2)
retry_count Int @default(0)
retry_delay Int @default(0)
retry_backoff Boolean @default(false)
retry_delay_max Int?
expire_seconds Int @default(900)
deletion_seconds Int @default(604800)
singleton_key String?
singleton_on DateTime? @db.Timestamp(6)
start_after DateTime @default(now()) @db.Timestamptz(6)
created_on DateTime @default(now()) @db.Timestamptz(6)
started_on DateTime? @db.Timestamptz(6)
completed_on DateTime? @db.Timestamptz(6)
keep_until DateTime @default(dbgenerated("(now() + '336:00:00'::interval)")) @db.Timestamptz(6)
output Json?
dead_letter String?
policy String?
@@id([name, id])
@@schema("platform_schema")
}
/// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info.
model queue {
name String @id
policy String
retry_limit Int
retry_delay Int
retry_backoff Boolean
retry_delay_max Int?
expire_seconds Int
retention_seconds Int
deletion_seconds Int
dead_letter String?
partition Boolean
table_name String
deferred_count Int @default(0)
queued_count Int @default(0)
warning_queued Int @default(0)
active_count Int @default(0)
total_count Int @default(0)
singletons_active String[]
monitor_on DateTime? @db.Timestamptz(6)
maintain_on DateTime? @db.Timestamptz(6)
created_on DateTime @default(now()) @db.Timestamptz(6)
updated_on DateTime @default(now()) @db.Timestamptz(6)
queue queue? @relation("queueToqueue", fields: [dead_letter], references: [name], onDelete: NoAction, onUpdate: NoAction)
other_queue queue[] @relation("queueToqueue")
schedule schedule[]
subscription subscription[]
@@schema("platform_schema")
}
model schedule {
name String
key String @default("")
cron String
timezone String?
data Json?
options Json?
created_on DateTime @default(now()) @db.Timestamptz(6)
updated_on DateTime @default(now()) @db.Timestamptz(6)
queue queue @relation(fields: [name], references: [name], onDelete: Cascade, onUpdate: NoAction)
@@id([name, key])
@@schema("platform_schema")
}
model subscription {
event String
name String
created_on DateTime @default(now()) @db.Timestamptz(6)
updated_on DateTime @default(now()) @db.Timestamptz(6)
queue queue @relation(fields: [name], references: [name], onDelete: Cascade, onUpdate: NoAction)
@@id([event, name])
@@schema("platform_schema")
}
model version {
version Int @id
cron_on DateTime? @db.Timestamptz(6)
@@schema("platform_schema")
}
model users {
id String @id
email String @unique
password String
name String?
avatar_url String?
role String @default("user")
status String @default("active")
kb_quota Int @default(3)
kb_used Int @default(0)
trial_ends_at DateTime?
is_trial Boolean @default(true)
last_login_at DateTime?
created_at DateTime @default(now())
updated_at DateTime
adminLogs AdminLog[]
// reviewTasks 已移除,因为 ReviewTask.userId 现在不再引用此表
@@index([created_at])
@@index([email])
@@index([status])
@@schema("public")
}
/// IIT项目表
model IitProject {
id String @id @default(uuid())
name String
description String?
difyDatasetId String? @unique @map("dify_dataset_id")
protocolFileKey String? @map("protocol_file_key")
cachedRules Json? @map("cached_rules")
fieldMappings Json @map("field_mappings")
redcapProjectId String @map("redcap_project_id")
redcapApiToken String @map("redcap_api_token")
redcapUrl String @map("redcap_url")
lastSyncAt DateTime? @map("last_sync_at")
status String @default("active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
auditLogs IitAuditLog[]
pendingActions IitPendingAction[]
taskRuns IitTaskRun[]
userMappings IitUserMapping[]
@@index([status, deletedAt])
@@map("projects")
@@schema("iit_schema")
}
/// 影子状态表(核心)
model IitPendingAction {
id String @id @default(uuid())
projectId String @map("project_id")
recordId String @map("record_id")
fieldName String @map("field_name")
currentValue Json? @map("current_value")
suggestedValue Json? @map("suggested_value")
status String
agentType String @map("agent_type")
reasoning String
evidence Json
approvedBy String? @map("approved_by")
approvedAt DateTime? @map("approved_at")
rejectionReason String? @map("rejection_reason")
executedAt DateTime? @map("executed_at")
errorMessage String? @map("error_message")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
project IitProject @relation(fields: [projectId], references: [id])
@@index([projectId, status])
@@index([projectId, recordId])
@@index([status, createdAt])
@@map("pending_actions")
@@schema("iit_schema")
}
/// 任务运行记录(与 pg-boss 关联)
model IitTaskRun {
id String @id @default(uuid())
projectId String @map("project_id")
taskType String @map("task_type")
jobId String? @unique @map("job_id")
status String
totalItems Int @map("total_items")
processedItems Int @default(0) @map("processed_items")
successItems Int @default(0) @map("success_items")
failedItems Int @default(0) @map("failed_items")
startedAt DateTime? @map("started_at")
completedAt DateTime? @map("completed_at")
duration Int?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
project IitProject @relation(fields: [projectId], references: [id])
@@index([projectId, taskType, status])
@@index([jobId])
@@map("task_runs")
@@schema("iit_schema")
}
/// 用户映射表(异构系统身份关联)
model IitUserMapping {
id String @id @default(uuid())
projectId String @map("project_id")
systemUserId String @map("system_user_id")
redcapUsername String @map("redcap_username")
wecomUserId String? @map("wecom_user_id")
miniProgramOpenId String? @unique @map("mini_program_open_id")
sessionKey String? @map("session_key")
role String
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
project IitProject @relation(fields: [projectId], references: [id])
@@unique([projectId, systemUserId])
@@unique([projectId, redcapUsername])
@@index([wecomUserId])
@@index([miniProgramOpenId])
@@map("user_mappings")
@@schema("iit_schema")
}
/// 审计日志表
model IitAuditLog {
id String @id @default(uuid())
projectId String @map("project_id")
userId String @map("user_id")
actionType String @map("action_type")
entityType String @map("entity_type")
entityId String @map("entity_id")
details Json?
traceId String @map("trace_id")
createdAt DateTime @default(now()) @map("created_at")
project IitProject @relation(fields: [projectId], references: [id])
@@index([projectId, createdAt])
@@index([userId, createdAt])
@@index([actionType, createdAt])
@@index([traceId])
@@map("audit_logs")
@@schema("iit_schema")
}
model admin_operation_logs {
id Int @id @default(autoincrement())
admin_id String
operation_type String
target_type String
target_id String
module String?
before_data Json?
after_data Json?
ip_address String?
user_agent String?
created_at DateTime @default(now())
@@index([admin_id], map: "idx_admin_logs_admin_id")
@@index([created_at], map: "idx_admin_logs_created_at")
@@index([module], map: "idx_admin_logs_module")
@@index([operation_type], map: "idx_admin_logs_operation_type")
@@schema("admin_schema")
}
model departments {
id String @id
tenant_id String
name String
parent_id String?
description String?
created_at DateTime @default(now())
updated_at DateTime
departments departments? @relation("departmentsTodepartments", fields: [parent_id], references: [id])
other_departments departments[] @relation("departmentsTodepartments")
tenants tenants @relation(fields: [tenant_id], references: [id], onDelete: Cascade)
users User[]
@@index([parent_id], map: "idx_departments_parent_id")
@@index([tenant_id], map: "idx_departments_tenant_id")
@@schema("platform_schema")
}
/// The underlying table does not contain a valid unique identifier and can therefore currently not be handled by Prisma Client.
model job_common {
id String @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String
priority Int @default(0)
data Json?
state job_state @default(created)
retry_limit Int @default(2)
retry_count Int @default(0)
retry_delay Int @default(0)
retry_backoff Boolean @default(false)
retry_delay_max Int?
expire_seconds Int @default(900)
deletion_seconds Int @default(604800)
singleton_key String?
singleton_on DateTime? @db.Timestamp(6)
start_after DateTime @default(now()) @db.Timestamptz(6)
created_on DateTime @default(now()) @db.Timestamptz(6)
started_on DateTime? @db.Timestamptz(6)
completed_on DateTime? @db.Timestamptz(6)
keep_until DateTime @default(dbgenerated("(now() + '336:00:00'::interval)")) @db.Timestamptz(6)
output Json?
dead_letter String?
policy String?
@@ignore
@@schema("platform_schema")
}
model permissions {
id Int @id @default(autoincrement())
code String @unique
name String
description String?
module String?
created_at DateTime @default(now())
role_permissions role_permissions[]
@@schema("platform_schema")
}
model role_permissions {
id Int @id @default(autoincrement())
role UserRole
permission_id Int
created_at DateTime @default(now())
permissions permissions @relation(fields: [permission_id], references: [id], onDelete: Cascade)
@@unique([role, permission_id])
@@schema("platform_schema")
}
model tenant_members {
id String @id
tenant_id String
user_id String
role UserRole
joined_at DateTime @default(now())
tenants tenants @relation(fields: [tenant_id], references: [id], onDelete: Cascade)
users User @relation(fields: [user_id], references: [id], onDelete: Cascade)
@@unique([tenant_id, user_id])
@@index([tenant_id], map: "idx_tenant_members_tenant_id")
@@index([user_id], map: "idx_tenant_members_user_id")
@@schema("platform_schema")
}
/// 系统模块配置表(动态管理可用模块)
model modules {
code String @id // 模块代码: RVW, PKB, ASL, DC, IIT, AIA
name String // 显示名称
description String? // 模块描述
icon String? // 图标(可选)
is_active Boolean @default(true) // 是否上线
sort_order Int @default(0) // 排序
created_at DateTime @default(now())
updated_at DateTime @updatedAt
@@index([is_active])
@@index([sort_order])
@@schema("platform_schema")
}
/// 租户模块订阅表
model tenant_modules {
id String @id @default(uuid())
tenant_id String
module_code String
is_enabled Boolean @default(true)
expires_at DateTime?
created_at DateTime @default(now())
tenants tenants @relation(fields: [tenant_id], references: [id], onDelete: Cascade)
@@unique([tenant_id, module_code])
@@index([tenant_id])
@@index([module_code])
@@schema("platform_schema")
}
/// 用户模块权限表(精细控制用户可访问的模块)
model user_modules {
id String @id @default(uuid())
user_id String
tenant_id String /// 在哪个租户内的权限
module_code String /// 模块代码: RVW, PKB, ASL, DC, IIT, AIA
is_enabled Boolean @default(true) /// 是否启用
created_at DateTime @default(now())
updated_at DateTime @updatedAt
user User @relation(fields: [user_id], references: [id], onDelete: Cascade)
tenant tenants @relation(fields: [tenant_id], references: [id], onDelete: Cascade)
@@unique([user_id, tenant_id, module_code])
@@index([user_id])
@@index([tenant_id])
@@index([module_code])
@@map("user_modules")
@@schema("platform_schema")
}
model tenant_quota_allocations {
id Int @id @default(autoincrement())
tenant_id String
target_type String
target_key String
limit_amount BigInt
used_amount BigInt @default(0)
created_at DateTime @default(now())
updated_at DateTime
tenants tenants @relation(fields: [tenant_id], references: [id], onDelete: Cascade)
@@unique([tenant_id, target_type, target_key])
@@index([tenant_id], map: "idx_quota_allocations_tenant_id")
@@schema("platform_schema")
}
model tenant_quotas {
id String @id
tenant_id String
quota_type String
total_amount BigInt
used_amount BigInt @default(0)
reset_period String?
last_reset_at DateTime?
created_at DateTime @default(now())
updated_at DateTime
tenants tenants @relation(fields: [tenant_id], references: [id], onDelete: Cascade)
@@unique([tenant_id, quota_type])
@@schema("platform_schema")
}
model tenants {
id String @id
code String @unique
name String
type TenantType
status TenantStatus @default(ACTIVE)
config Json? @default("{}")
total_quota BigInt @default(0)
used_quota BigInt @default(0)
contact_name String?
contact_phone String?
contact_email String?
created_at DateTime @default(now())
updated_at DateTime
expires_at DateTime?
departments departments[]
tenant_members tenant_members[]
tenant_modules tenant_modules[]
tenant_quota_allocations tenant_quota_allocations[]
tenant_quotas tenant_quotas[]
users User[]
user_modules user_modules[]
@@index([code], map: "idx_tenants_code")
@@index([status], map: "idx_tenants_status")
@@index([type], map: "idx_tenants_type")
@@schema("platform_schema")
}
model verification_codes {
id Int @id @default(autoincrement())
phone String
code String @db.VarChar(6)
type VerificationType
expires_at DateTime
is_used Boolean @default(false)
attempts Int @default(0)
created_at DateTime @default(now())
@@index([expires_at], map: "idx_verification_codes_expires")
@@index([phone, type, is_used], map: "idx_verification_codes_phone_type")
@@schema("platform_schema")
}
enum job_state {
created
retry
active
completed
cancelled
failed
@@schema("platform_schema")
}
enum TenantStatus {
ACTIVE
SUSPENDED
EXPIRED
@@schema("platform_schema")
}
enum TenantType {
HOSPITAL // 医院
PHARMA // 药企
INTERNAL // 内部(公司自己)
PUBLIC // 个人用户公共池
@@schema("platform_schema")
}
enum UserRole {
SUPER_ADMIN
PROMPT_ENGINEER
HOSPITAL_ADMIN
PHARMA_ADMIN
DEPARTMENT_ADMIN
USER
@@schema("platform_schema")
}
enum VerificationType {
LOGIN
RESET_PASSWORD
BIND_PHONE
@@schema("platform_schema")
}
// ============================================
// Prompt Management System (capability_schema)
// ============================================
/// Prompt模板 - 存储Prompt的元信息
model prompt_templates {
id Int @id @default(autoincrement())
code String @unique /// 唯一标识符,如 'RVW_EDITORIAL'
name String /// 人类可读名称,如 "稿约规范性评估"
module String /// 所属模块: RVW, ASL, DC, IIT, PKB, AIA
description String? /// 描述
variables Json? /// 预期变量列表,如 ["title", "abstract"]
versions prompt_versions[]
created_at DateTime @default(now())
updated_at DateTime @updatedAt
@@index([module], map: "idx_prompt_templates_module")
@@map("prompt_templates")
@@schema("capability_schema")
}
/// Prompt版本 - 存储Prompt的具体内容和版本历史
model prompt_versions {
id Int @id @default(autoincrement())
template_id Int /// 关联的模板ID
version Int /// 版本号: 1, 2, 3...
content String @db.Text /// Prompt内容支持Handlebars模板
model_config Json? /// 模型参数: {"temperature": 0.3, "model": "deepseek-v3"}
status PromptStatus @default(DRAFT)
changelog String? /// 变更说明
created_by String? /// 修改人userId审计用
template prompt_templates @relation(fields: [template_id], references: [id])
created_at DateTime @default(now())
@@index([template_id, status], map: "idx_prompt_versions_template_status")
@@index([status], map: "idx_prompt_versions_status")
@@map("prompt_versions")
@@schema("capability_schema")
}
/// Prompt状态枚举
enum PromptStatus {
DRAFT /// 草稿(仅调试者可见)
ACTIVE /// 线上生效(默认可见)
ARCHIVED /// 归档
@@schema("capability_schema")
}