feat(health): 新增血压/血糖临床阈值告警规则 + alert engine 直接查 device_readings
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user