diff --git a/crates/erp-health/src/entity/critical_alert.rs b/crates/erp-health/src/entity/critical_alert.rs new file mode 100644 index 0000000..715d9d4 --- /dev/null +++ b/crates/erp-health/src/entity/critical_alert.rs @@ -0,0 +1,49 @@ +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] +#[sea_orm(table_name = "critical_alerts")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + pub tenant_id: Uuid, + pub patient_id: Uuid, + pub alert_type: String, + pub metric_name: String, + pub metric_value: String, + pub threshold_value: String, + pub severity: String, + pub status: String, + #[sea_orm(skip_serializing_if = "Option::is_none")] + pub acknowledged_by: Option, + #[sea_orm(skip_serializing_if = "Option::is_none")] + pub acknowledged_at: Option, + pub escalation_level: i32, + pub created_at: DateTimeUtc, + pub updated_at: DateTimeUtc, + #[sea_orm(skip_serializing_if = "Option::is_none")] + pub created_by: Option, + #[sea_orm(skip_serializing_if = "Option::is_none")] + pub updated_by: Option, + #[sea_orm(skip_serializing_if = "Option::is_none")] + pub deleted_at: Option, + pub version: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::patient::Entity", + from = "Column::PatientId", + to = "super::patient::Column::Id" + )] + Patient, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Patient.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/erp-health/src/entity/critical_alert_response.rs b/crates/erp-health/src/entity/critical_alert_response.rs new file mode 100644 index 0000000..0cd7180 --- /dev/null +++ b/crates/erp-health/src/entity/critical_alert_response.rs @@ -0,0 +1,42 @@ +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] +#[sea_orm(table_name = "critical_alert_responses")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: Uuid, + pub tenant_id: Uuid, + pub alert_id: Uuid, + pub responder_id: Uuid, + pub response_type: String, + #[sea_orm(skip_serializing_if = "Option::is_none")] + pub notes: Option, + pub created_at: DateTimeUtc, + pub updated_at: DateTimeUtc, + #[sea_orm(skip_serializing_if = "Option::is_none")] + pub created_by: Option, + #[sea_orm(skip_serializing_if = "Option::is_none")] + pub updated_by: Option, + #[sea_orm(skip_serializing_if = "Option::is_none")] + pub deleted_at: Option, + pub version: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::critical_alert::Entity", + from = "Column::AlertId", + to = "super::critical_alert::Column::Id" + )] + CriticalAlert, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::CriticalAlert.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/erp-health/src/entity/mod.rs b/crates/erp-health/src/entity/mod.rs index 1b9f154..93def26 100644 --- a/crates/erp-health/src/entity/mod.rs +++ b/crates/erp-health/src/entity/mod.rs @@ -11,6 +11,8 @@ pub mod critical_value_threshold; pub mod consent; pub mod consultation_message; pub mod consultation_session; +pub mod critical_alert; +pub mod critical_alert_response; pub mod daily_monitoring; pub mod device_readings; pub mod diagnosis; diff --git a/crates/erp-server/migration/src/lib.rs b/crates/erp-server/migration/src/lib.rs index 9b30f54..a2b71f7 100644 --- a/crates/erp-server/migration/src/lib.rs +++ b/crates/erp-server/migration/src/lib.rs @@ -89,6 +89,7 @@ mod m20260427_000086_enable_rls_all_tables; mod m20260427_000087_audit_logs_hash_chain; mod m20260428_000088_rls_policy_strict; mod m20260428_000089_blind_indexes; +mod m20260428_000090_critical_alerts; pub struct Migrator; @@ -185,6 +186,7 @@ impl MigratorTrait for Migrator { Box::new(m20260427_000087_audit_logs_hash_chain::Migration), Box::new(m20260428_000088_rls_policy_strict::Migration), Box::new(m20260428_000089_blind_indexes::Migration), + Box::new(m20260428_000090_critical_alerts::Migration), ] } } diff --git a/crates/erp-server/migration/src/m20260428_000090_critical_alerts.rs b/crates/erp-server/migration/src/m20260428_000090_critical_alerts.rs new file mode 100644 index 0000000..179928c --- /dev/null +++ b/crates/erp-server/migration/src/m20260428_000090_critical_alerts.rs @@ -0,0 +1,219 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[derive(Iden)] +enum CriticalAlert { + Table, + Id, + TenantId, + PatientId, + AlertType, + MetricName, + MetricValue, + ThresholdValue, + Severity, + Status, + AcknowledgedBy, + AcknowledgedAt, + EscalationLevel, + CreatedAt, + UpdatedAt, + CreatedBy, + UpdatedBy, + DeletedAt, + Version, +} + +#[derive(Iden)] +enum CriticalAlertResponse { + Table, + Id, + TenantId, + AlertId, + ResponderId, + ResponseType, + Notes, + CreatedAt, + UpdatedAt, + CreatedBy, + UpdatedBy, + DeletedAt, + Version, +} + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(CriticalAlert::Table) + .col( + ColumnDef::new(CriticalAlert::Id) + .uuid() + .not_null() + .primary_key() + .default(PgFunc::gen_random_uuid()), + ) + .col(ColumnDef::new(CriticalAlert::TenantId).uuid().not_null()) + .col(ColumnDef::new(CriticalAlert::PatientId).uuid().not_null()) + .col( + ColumnDef::new(CriticalAlert::AlertType) + .string_len(64) + .not_null(), + ) + .col( + ColumnDef::new(CriticalAlert::MetricName) + .string_len(64) + .not_null(), + ) + .col(ColumnDef::new(CriticalAlert::MetricValue).text().not_null()) + .col( + ColumnDef::new(CriticalAlert::ThresholdValue) + .text() + .not_null(), + ) + .col( + ColumnDef::new(CriticalAlert::Severity) + .string_len(16) + .not_null() + .default("critical"), + ) + .col( + ColumnDef::new(CriticalAlert::Status) + .string_len(16) + .not_null() + .default("pending"), + ) + .col(ColumnDef::new(CriticalAlert::AcknowledgedBy).uuid()) + .col( + ColumnDef::new(CriticalAlert::AcknowledgedAt) + .timestamp_with_time_zone(), + ) + .col( + ColumnDef::new(CriticalAlert::EscalationLevel) + .small_integer() + .not_null() + .default(0), + ) + .col( + ColumnDef::new(CriticalAlert::CreatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(CriticalAlert::UpdatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col(ColumnDef::new(CriticalAlert::CreatedBy).uuid()) + .col(ColumnDef::new(CriticalAlert::UpdatedBy).uuid()) + .col(ColumnDef::new(CriticalAlert::DeletedAt).timestamp_with_time_zone()) + .col( + ColumnDef::new(CriticalAlert::Version) + .big_integer() + .not_null() + .default(1), + ) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_critical_alerts_pending") + .table(CriticalAlert::Table) + .col(CriticalAlert::TenantId) + .col(CriticalAlert::Status) + .col(CriticalAlert::CreatedAt) + .to_owned(), + ) + .await?; + + manager + .create_table( + Table::create() + .table(CriticalAlertResponse::Table) + .col( + ColumnDef::new(CriticalAlertResponse::Id) + .uuid() + .not_null() + .primary_key() + .default(PgFunc::gen_random_uuid()), + ) + .col( + ColumnDef::new(CriticalAlertResponse::TenantId) + .uuid() + .not_null(), + ) + .col( + ColumnDef::new(CriticalAlertResponse::AlertId) + .uuid() + .not_null(), + ) + .col( + ColumnDef::new(CriticalAlertResponse::ResponderId) + .uuid() + .not_null(), + ) + .col( + ColumnDef::new(CriticalAlertResponse::ResponseType) + .string_len(16) + .not_null(), + ) + .col(ColumnDef::new(CriticalAlertResponse::Notes).text()) + .col( + ColumnDef::new(CriticalAlertResponse::CreatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col( + ColumnDef::new(CriticalAlertResponse::UpdatedAt) + .timestamp_with_time_zone() + .not_null() + .default(Expr::current_timestamp()), + ) + .col(ColumnDef::new(CriticalAlertResponse::CreatedBy).uuid()) + .col(ColumnDef::new(CriticalAlertResponse::UpdatedBy).uuid()) + .col( + ColumnDef::new(CriticalAlertResponse::DeletedAt) + .timestamp_with_time_zone(), + ) + .col( + ColumnDef::new(CriticalAlertResponse::Version) + .big_integer() + .not_null() + .default(1), + ) + .foreign_key( + ForeignKey::create() + .from( + CriticalAlertResponse::Table, + CriticalAlertResponse::AlertId, + ) + .to(CriticalAlert::Table, CriticalAlert::Id), + ) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table( + Table::drop() + .table(CriticalAlertResponse::Table) + .to_owned(), + ) + .await?; + manager + .drop_table(Table::drop().table(CriticalAlert::Table).to_owned()) + .await + } +}