Files
hms/crates/erp-health/src/service/critical_value_threshold_service.rs
iven 212c08b7ae feat(health,ai): 后端服务优化 + 媒体文件处理
- erp-health: article/banner/consultation/media 服务层优化
- erp-ai: analysis/insight/prompt 服务增强
- erp-auth: auth/role/token 服务改进
- erp-workflow: executor 执行引擎修复
- erp-plugin: 服务层改进
- 新增媒体上传文件样例
2026-05-13 23:28:57 +08:00

211 lines
6.4 KiB
Rust
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.
use sea_orm::ActiveValue::Set;
use sea_orm::entity::prelude::*;
use uuid::Uuid;
use crate::entity::critical_value_threshold;
use crate::error::{HealthError, HealthResult};
/// 阈值查询条件:指标名 + 方向
pub struct ThresholdQuery {
pub indicator: String,
pub direction: String,
}
/// 查询指定租户的活跃危急值阈值。
///
/// 如果指定了 department 和 age优先匹配精确条件回退到通用阈值。
pub async fn find_thresholds(
db: &DatabaseConnection,
tenant_id: Uuid,
) -> HealthResult<Vec<critical_value_threshold::Model>> {
let list = critical_value_threshold::Entity::find()
.filter(critical_value_threshold::Column::TenantId.eq(tenant_id))
.filter(critical_value_threshold::Column::IsActive.eq(true))
.filter(critical_value_threshold::Column::DeletedAt.is_null())
.all(db)
.await?;
Ok(list)
}
/// 查找匹配的阈值配置(按 indicator + direction 匹配)。
///
/// 优先匹配有科室/年龄限制的精确规则,否则返回通用规则。
pub async fn find_threshold(
db: &DatabaseConnection,
tenant_id: Uuid,
indicator: &str,
direction: &str,
department: Option<&str>,
age: Option<i32>,
) -> HealthResult<Option<critical_value_threshold::Model>> {
let all = find_thresholds(db, tenant_id).await?;
let mut exact_match: Option<&critical_value_threshold::Model> = None;
let mut generic_match: Option<&critical_value_threshold::Model> = None;
for t in &all {
if t.indicator != indicator || t.direction != direction {
continue;
}
let dept_match = match (t.department.as_deref(), department) {
(Some(td), Some(d)) => td == d,
(None, _) => true, // 通用规则匹配任意科室
(Some(_), None) => false, // 有科室限制但没传科室
};
let age_match = match (t.age_min, t.age_max, age) {
(Some(min), Some(max), Some(a)) => a >= min && a <= max,
(None, None, _) => true, // 通用规则匹配任意年龄
_ => false, // 有年龄限制但没传年龄
};
if dept_match && age_match {
if t.department.is_some() || t.age_min.is_some() {
// 精确规则
if exact_match.is_none() {
exact_match = Some(t);
}
} else {
// 通用规则
if generic_match.is_none() {
generic_match = Some(t);
}
}
}
}
Ok(exact_match.or(generic_match).cloned())
}
/// 创建新的危急值阈值。
#[allow(clippy::too_many_arguments)]
pub async fn create_threshold(
db: &DatabaseConnection,
tenant_id: Uuid,
operator_id: Option<Uuid>,
indicator: String,
direction: String,
threshold_value: f64,
level: String,
department: Option<String>,
age_min: Option<i32>,
age_max: Option<i32>,
) -> HealthResult<critical_value_threshold::Model> {
validate_direction(&direction)?;
validate_indicator(&indicator)?;
let now = chrono::Utc::now();
let model = critical_value_threshold::ActiveModel {
id: Set(Uuid::now_v7()),
tenant_id: Set(tenant_id),
indicator: Set(indicator),
direction: Set(direction),
threshold_value: Set(threshold_value),
level: Set(level),
department: Set(department),
age_min: Set(age_min),
age_max: Set(age_max),
is_active: Set(true),
created_at: Set(now),
updated_at: Set(now),
created_by: Set(operator_id),
updated_by: Set(operator_id),
deleted_at: Set(None),
version: Set(1),
};
let result = model.insert(db).await?;
Ok(result)
}
/// 更新危急值阈值。
#[allow(clippy::too_many_arguments)]
pub async fn update_threshold(
db: &DatabaseConnection,
tenant_id: Uuid,
threshold_id: Uuid,
operator_id: Option<Uuid>,
threshold_value: f64,
level: String,
department: Option<String>,
age_min: Option<i32>,
age_max: Option<i32>,
expected_version: i32,
) -> HealthResult<critical_value_threshold::Model> {
let existing = critical_value_threshold::Entity::find_by_id(threshold_id)
.one(db)
.await?
.ok_or(HealthError::ThresholdNotFound)?;
if existing.tenant_id != tenant_id {
return Err(HealthError::ThresholdNotFound);
}
erp_core::error::check_version(existing.version, expected_version)?;
let now = chrono::Utc::now();
let mut active: critical_value_threshold::ActiveModel = existing.into();
active.threshold_value = Set(threshold_value);
active.level = Set(level);
active.department = Set(department);
active.age_min = Set(age_min);
active.age_max = Set(age_max);
active.updated_at = Set(now);
active.updated_by = Set(operator_id);
active.version = Set(expected_version + 1);
let result = active.update(db).await?;
Ok(result)
}
/// 软删除危急值阈值。
pub async fn delete_threshold(
db: &DatabaseConnection,
tenant_id: Uuid,
threshold_id: Uuid,
operator_id: Option<Uuid>,
) -> HealthResult<()> {
let existing = critical_value_threshold::Entity::find_by_id(threshold_id)
.one(db)
.await?
.ok_or(HealthError::ThresholdNotFound)?;
if existing.tenant_id != tenant_id {
return Err(HealthError::ThresholdNotFound);
}
let mut active: critical_value_threshold::ActiveModel = existing.into();
active.deleted_at = Set(Some(chrono::Utc::now()));
active.updated_by = Set(operator_id);
active.version = Set(active.version.unwrap() + 1);
active.update(db).await?;
Ok(())
}
fn validate_direction(direction: &str) -> HealthResult<()> {
if matches!(direction, "high" | "low") {
Ok(())
} else {
Err(HealthError::Validation(
"direction 必须为 'high' 或 'low'".to_string(),
))
}
}
fn validate_indicator(indicator: &str) -> HealthResult<()> {
let valid = [
"systolic_bp",
"diastolic_bp",
"heart_rate",
"blood_sugar",
"blood_sugar_fasting",
"blood_sugar_postprandial",
"blood_oxygen",
"temperature",
];
if valid.contains(&indicator) {
Ok(())
} else {
Err(HealthError::Validation(format!(
"indicator 必须为以下之一: {}",
valid.join(", ")
)))
}
}