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());
|
.and_then(|s| Uuid::parse_str(s).ok());
|
||||||
if let Some(pid) = patient_id {
|
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(
|
if let Err(e) = crate::service::alert_engine::evaluate_rules(
|
||||||
&eval_state, event.tenant_id, pid, device_type,
|
&eval_state, event.tenant_id, pid, device_type,
|
||||||
).await {
|
).await {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use serde_json::json;
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use uuid::Uuid;
|
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::error::{HealthError, HealthResult};
|
||||||
use crate::state::HealthState;
|
use crate::state::HealthState;
|
||||||
|
|
||||||
@@ -62,6 +62,9 @@ pub async fn evaluate_rules(
|
|||||||
let condition_type = rule.condition_type.as_str();
|
let condition_type = rule.condition_type.as_str();
|
||||||
|
|
||||||
let is_triggered = match condition_type {
|
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),
|
"single_threshold" => evaluate_single_threshold_in_memory(&hourly_records, params),
|
||||||
"consecutive" => evaluate_consecutive_in_memory(&hourly_records, params),
|
"consecutive" => evaluate_consecutive_in_memory(&hourly_records, params),
|
||||||
"trend" => evaluate_trend_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)
|
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",
|
"urgent",
|
||||||
120,
|
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
|
for (name, description, device_type, condition_type, condition_params, severity, cooldown) in
|
||||||
|
|||||||
Reference in New Issue
Block a user