diff --git a/crates/erp-ai/src/entity/ai_analysis_queue.rs b/crates/erp-ai/src/entity/ai_analysis_queue.rs new file mode 100644 index 0000000..25935a8 --- /dev/null +++ b/crates/erp-ai/src/entity/ai_analysis_queue.rs @@ -0,0 +1,34 @@ +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] +#[sea_orm(table_name = "ai_analysis_queue")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + pub tenant_id: Uuid, + pub patient_id: Uuid, + pub analysis_type: String, + pub priority: i32, + pub status: String, + pub source_event: Option, + pub source_ref: String, + pub scheduled_at: DateTimeUtc, + pub started_at: Option, + pub completed_at: Option, + pub result_analysis_id: Option, + pub error_message: Option, + pub retry_count: i32, + pub max_retries: i32, + pub created_at: DateTimeUtc, + pub updated_at: DateTimeUtc, + pub created_by: Option, + pub updated_by: Option, + pub deleted_at: Option, + pub version_lock: i32, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/erp-ai/src/entity/mod.rs b/crates/erp-ai/src/entity/mod.rs index f31e3ac..ffca218 100644 --- a/crates/erp-ai/src/entity/mod.rs +++ b/crates/erp-ai/src/entity/mod.rs @@ -1,4 +1,5 @@ pub mod ai_analysis; +pub mod ai_analysis_queue; pub mod ai_prompt; pub mod ai_risk_threshold; pub mod ai_suggestion; diff --git a/crates/erp-server/migration/src/lib.rs b/crates/erp-server/migration/src/lib.rs index bd6d000..abac88c 100644 --- a/crates/erp-server/migration/src/lib.rs +++ b/crates/erp-server/migration/src/lib.rs @@ -117,6 +117,7 @@ mod m20260505_000114_dialysis_record_add_workflow_instance; mod m20260505_000115_family_member_health_proxy; mod m20260505_000116_seed_missing_health_menus; mod m20260505_000117_create_ai_tenant_configs; +mod m20260505_000118_create_ai_analysis_queue; pub struct Migrator; @@ -241,6 +242,7 @@ impl MigratorTrait for Migrator { Box::new(m20260505_000115_family_member_health_proxy::Migration), Box::new(m20260505_000116_seed_missing_health_menus::Migration), Box::new(m20260505_000117_create_ai_tenant_configs::Migration), + Box::new(m20260505_000118_create_ai_analysis_queue::Migration), ] } } diff --git a/crates/erp-server/migration/src/m20260505_000118_create_ai_analysis_queue.rs b/crates/erp-server/migration/src/m20260505_000118_create_ai_analysis_queue.rs new file mode 100644 index 0000000..5d14b03 --- /dev/null +++ b/crates/erp-server/migration/src/m20260505_000118_create_ai_analysis_queue.rs @@ -0,0 +1,147 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(AiAnalysisQueue::Table) + .if_not_exists() + .col(ColumnDef::new(AiAnalysisQueue::Id).uuid().not_null().primary_key()) + .col(ColumnDef::new(AiAnalysisQueue::TenantId).uuid().not_null()) + .col(ColumnDef::new(AiAnalysisQueue::PatientId).uuid().not_null()) + .col( + ColumnDef::new(AiAnalysisQueue::AnalysisType) + .string_len(50) + .not_null(), + ) + .col( + ColumnDef::new(AiAnalysisQueue::Priority) + .integer() + .not_null() + .default(0), + ) + .col( + ColumnDef::new(AiAnalysisQueue::Status) + .string_len(20) + .not_null() + .default("pending"), + ) + .col(ColumnDef::new(AiAnalysisQueue::SourceEvent).string_len(100).null()) + .col( + ColumnDef::new(AiAnalysisQueue::SourceRef) + .string_len(200) + .not_null() + .default(""), + ) + .col( + ColumnDef::new(AiAnalysisQueue::ScheduledAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col(ColumnDef::new(AiAnalysisQueue::StartedAt).timestamp_with_time_zone().null()) + .col(ColumnDef::new(AiAnalysisQueue::CompletedAt).timestamp_with_time_zone().null()) + .col(ColumnDef::new(AiAnalysisQueue::ResultAnalysisId).uuid().null()) + .col(ColumnDef::new(AiAnalysisQueue::ErrorMessage).text().null()) + .col( + ColumnDef::new(AiAnalysisQueue::RetryCount) + .integer() + .not_null() + .default(0), + ) + .col( + ColumnDef::new(AiAnalysisQueue::MaxRetries) + .integer() + .not_null() + .default(3), + ) + .col( + ColumnDef::new(AiAnalysisQueue::CreatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(AiAnalysisQueue::UpdatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col(ColumnDef::new(AiAnalysisQueue::CreatedBy).uuid().null()) + .col(ColumnDef::new(AiAnalysisQueue::UpdatedBy).uuid().null()) + .col(ColumnDef::new(AiAnalysisQueue::DeletedAt).timestamp_with_time_zone().null()) + .col( + ColumnDef::new(AiAnalysisQueue::VersionLock) + .integer() + .not_null() + .default(1), + ) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .if_not_exists() + .name("idx_ai_analysis_queue_tenant_status") + .table(AiAnalysisQueue::Table) + .col(AiAnalysisQueue::TenantId) + .col(AiAnalysisQueue::Status) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .if_not_exists() + .name("idx_ai_analysis_queue_scheduled") + .table(AiAnalysisQueue::Table) + .col(AiAnalysisQueue::Status) + .col(AiAnalysisQueue::ScheduledAt) + .to_owned(), + ) + .await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(AiAnalysisQueue::Table).to_owned()) + .await?; + Ok(()) + } +} + +#[derive(DeriveIden)] +enum AiAnalysisQueue { + Table, + Id, + TenantId, + PatientId, + AnalysisType, + Priority, + Status, + SourceEvent, + SourceRef, + ScheduledAt, + StartedAt, + CompletedAt, + ResultAnalysisId, + ErrorMessage, + RetryCount, + MaxRetries, + CreatedAt, + UpdatedAt, + CreatedBy, + UpdatedBy, + DeletedAt, + VersionLock, +}