feat(health): 新增血压/血糖临床阈值告警规则 + alert engine 直接查 device_readings

This commit is contained in:
iven
2026-04-28 19:40:25 +08:00
parent 8a61ae3f8e
commit 00f615d8e5
3 changed files with 87 additions and 2 deletions

View File

@@ -137,7 +137,7 @@ pub fn register_handlers_with_state(state: crate::state::HealthState) {
.and_then(|s| Uuid::parse_str(s).ok());
if let Some(pid) = patient_id {
// 对所有设备类型触发评估
for device_type in &["heart_rate", "blood_oxygen", "temperature"] {
for device_type in &["heart_rate", "blood_oxygen", "temperature", "blood_pressure", "blood_glucose"] {
if let Err(e) = crate::service::alert_engine::evaluate_rules(
&eval_state, event.tenant_id, pid, device_type,
).await {

View File

@@ -5,7 +5,7 @@ use serde_json::json;
use std::collections::HashSet;
use uuid::Uuid;
use crate::entity::{alert_rules, alerts, vital_signs_hourly};
use crate::entity::{alert_rules, alerts, device_readings, vital_signs_hourly};
use crate::error::{HealthError, HealthResult};
use crate::state::HealthState;
@@ -62,6 +62,9 @@ pub async fn evaluate_rules(
let condition_type = rule.condition_type.as_str();
let is_triggered = match condition_type {
"single_threshold" if matches!(device_type, "blood_pressure" | "blood_glucose") => {
evaluate_bp_glucose_threshold(&state.db, tenant_id, patient_id, device_type, params).await
}
"single_threshold" => evaluate_single_threshold_in_memory(&hourly_records, params),
"consecutive" => evaluate_consecutive_in_memory(&hourly_records, params),
"trend" => evaluate_trend_in_memory(&hourly_records, params),
@@ -213,3 +216,38 @@ async fn create_alert_and_notify(
Ok(alert)
}
/// 血压/血糖告警:直接查 device_readings 最新值,支持 metric 过滤
async fn evaluate_bp_glucose_threshold(
db: &DatabaseConnection,
tenant_id: Uuid,
patient_id: Uuid,
device_type: &str,
params: &serde_json::Value,
) -> bool {
let direction = params["direction"].as_str().unwrap_or("above");
let threshold = params["value"].as_f64().unwrap_or(f64::MAX);
let metric = params["metric"].as_str();
let mut query = device_readings::Entity::find()
.filter(device_readings::Column::TenantId.eq(tenant_id))
.filter(device_readings::Column::PatientId.eq(patient_id))
.filter(device_readings::Column::DeviceType.eq(device_type))
.filter(device_readings::Column::DeletedAt.is_null())
.order_by_desc(device_readings::Column::MeasuredAt);
if let Some(m) = metric {
query = query.filter(device_readings::Column::Metric.eq(m));
}
let latest = query.one(db).await.ok().flatten();
let Some(record) = latest else { return false };
let val = record.raw_value.get("value").and_then(|v| v.as_f64()).unwrap_or(f64::MAX);
match direction {
"above" => val > threshold,
"below" => val < threshold,
_ => false,
}
}

View File

@@ -91,6 +91,53 @@ pub async fn seed_tenant_health(
"urgent",
120,
),
// 血压告警规则
(
"血压收缩压偏高",
Some("收缩压 ≥ 140 mmHg"),
"blood_pressure",
"single_threshold",
json!({"direction": "above", "value": 140.0, "metric": "systolic"}),
"warning",
60,
),
(
"血压收缩压危急",
Some("收缩压 ≥ 180 mmHg"),
"blood_pressure",
"single_threshold",
json!({"direction": "above", "value": 180.0, "metric": "systolic"}),
"critical",
30,
),
(
"血压舒张压偏低",
Some("舒张压 < 60 mmHg"),
"blood_pressure",
"single_threshold",
json!({"direction": "below", "value": 60.0, "metric": "diastolic"}),
"critical",
30,
),
// 血糖告警规则
(
"空腹血糖偏高",
Some("空腹血糖 ≥ 7.0 mmol/L"),
"blood_glucose",
"single_threshold",
json!({"direction": "above", "value": 7.0}),
"warning",
60,
),
(
"低血糖",
Some("血糖 < 3.9 mmol/L"),
"blood_glucose",
"single_threshold",
json!({"direction": "below", "value": 3.9}),
"critical",
30,
),
];
for (name, description, device_type, condition_type, condition_params, severity, cooldown) in