feat(health): 新增小程序专用今日体征摘要端点 GET /health/vital-signs/today
This commit is contained in:
@@ -171,3 +171,31 @@ pub struct MiniTrendResp {
|
|||||||
/// 数据点列表(按日期升序)
|
/// 数据点列表(按日期升序)
|
||||||
pub data_points: Vec<DataPoint>,
|
pub data_points: Vec<DataPoint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 小程序今日体征摘要
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 单个指标摘要
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
|
pub struct IndicatorSummary {
|
||||||
|
/// 指标数值
|
||||||
|
pub value: f64,
|
||||||
|
/// 状态: "normal" | "high" | "low"
|
||||||
|
pub status: String,
|
||||||
|
/// 参考范围,如 "60-100"
|
||||||
|
pub reference_range: Option<String>,
|
||||||
|
/// 收缩压(仅血压指标)
|
||||||
|
pub systolic: Option<f64>,
|
||||||
|
/// 舒张压(仅血压指标)
|
||||||
|
pub diastolic: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 小程序今日体征摘要响应
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
|
pub struct MiniTodayResp {
|
||||||
|
pub blood_pressure: Option<IndicatorSummary>,
|
||||||
|
pub heart_rate: Option<IndicatorSummary>,
|
||||||
|
pub blood_sugar: Option<IndicatorSummary>,
|
||||||
|
pub weight: Option<IndicatorSummary>,
|
||||||
|
}
|
||||||
|
|||||||
@@ -351,6 +351,26 @@ where
|
|||||||
Ok(Json(ApiResponse::ok(result)))
|
Ok(Json(ApiResponse::ok(result)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 小程序今日体征摘要(通过当前用户关联 patient,无需传 patient_id)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub async fn get_mini_today<S>(
|
||||||
|
State(state): State<HealthState>,
|
||||||
|
Extension(ctx): Extension<TenantContext>,
|
||||||
|
) -> Result<Json<ApiResponse<MiniTodayResp>>, AppError>
|
||||||
|
where
|
||||||
|
HealthState: FromRef<S>,
|
||||||
|
S: Clone + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
require_permission(&ctx, "health.health-data.list")?;
|
||||||
|
let result = health_data_service::get_mini_today(
|
||||||
|
&state, ctx.tenant_id, ctx.user_id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(Json(ApiResponse::ok(result)))
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// 带版本号的更新请求包装
|
// 带版本号的更新请求包装
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -117,6 +117,11 @@ impl HealthModule {
|
|||||||
"/health/vital-signs/trend",
|
"/health/vital-signs/trend",
|
||||||
axum::routing::get(health_data_handler::get_mini_trend),
|
axum::routing::get(health_data_handler::get_mini_trend),
|
||||||
)
|
)
|
||||||
|
// 小程序今日体征摘要
|
||||||
|
.route(
|
||||||
|
"/health/vital-signs/today",
|
||||||
|
axum::routing::get(health_data_handler::get_mini_today),
|
||||||
|
)
|
||||||
// 预约排班
|
// 预约排班
|
||||||
.route(
|
.route(
|
||||||
"/health/appointments",
|
"/health/appointments",
|
||||||
|
|||||||
@@ -722,3 +722,112 @@ pub async fn get_mini_trend(
|
|||||||
data_points,
|
data_points,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 小程序今日体征摘要
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 根据参考范围计算指标状态
|
||||||
|
fn compute_status(value: f64, low: f64, high: f64) -> &'static str {
|
||||||
|
if value < low {
|
||||||
|
"low"
|
||||||
|
} else if value > high {
|
||||||
|
"high"
|
||||||
|
} else {
|
||||||
|
"normal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 查询今日最新体征记录并生成摘要
|
||||||
|
pub async fn get_mini_today(
|
||||||
|
state: &HealthState,
|
||||||
|
tenant_id: Uuid,
|
||||||
|
user_id: Uuid,
|
||||||
|
) -> HealthResult<MiniTodayResp> {
|
||||||
|
let patient_id = find_patient_by_user_id(state, tenant_id, user_id).await?;
|
||||||
|
|
||||||
|
let Some(patient_id) = patient_id else {
|
||||||
|
return Ok(MiniTodayResp {
|
||||||
|
blood_pressure: None,
|
||||||
|
heart_rate: None,
|
||||||
|
blood_sugar: None,
|
||||||
|
weight: None,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let today = chrono::Local::now().date_naive();
|
||||||
|
|
||||||
|
// 查询今日最新体征记录
|
||||||
|
let vital = vital_signs::Entity::find()
|
||||||
|
.filter(vital_signs::Column::TenantId.eq(tenant_id))
|
||||||
|
.filter(vital_signs::Column::PatientId.eq(patient_id))
|
||||||
|
.filter(vital_signs::Column::DeletedAt.is_null())
|
||||||
|
.filter(vital_signs::Column::RecordDate.eq(today))
|
||||||
|
.order_by_desc(vital_signs::Column::CreatedAt)
|
||||||
|
.one(&state.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let Some(v) = vital else {
|
||||||
|
return Ok(MiniTodayResp {
|
||||||
|
blood_pressure: None,
|
||||||
|
heart_rate: None,
|
||||||
|
blood_sugar: None,
|
||||||
|
weight: None,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 构建各指标摘要,优先使用晨间数据
|
||||||
|
let blood_pressure = v.systolic_bp_morning.and_then(|sys| {
|
||||||
|
v.diastolic_bp_morning.map(|dia| {
|
||||||
|
let status = compute_status(sys as f64, 90.0, 140.0);
|
||||||
|
IndicatorSummary {
|
||||||
|
value: sys as f64,
|
||||||
|
status: status.to_string(),
|
||||||
|
reference_range: Some("90-140/60-90".to_string()),
|
||||||
|
systolic: Some(sys as f64),
|
||||||
|
diastolic: Some(dia as f64),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let heart_rate = v.heart_rate.map(|hr| {
|
||||||
|
let status = compute_status(hr as f64, 60.0, 100.0);
|
||||||
|
IndicatorSummary {
|
||||||
|
value: hr as f64,
|
||||||
|
status: status.to_string(),
|
||||||
|
reference_range: Some("60-100".to_string()),
|
||||||
|
systolic: None,
|
||||||
|
diastolic: None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let blood_sugar = v.blood_sugar.map(|bs| {
|
||||||
|
let val = bs.to_f64().unwrap_or(0.0);
|
||||||
|
let status = compute_status(val, 3.9, 6.1);
|
||||||
|
IndicatorSummary {
|
||||||
|
value: val,
|
||||||
|
status: status.to_string(),
|
||||||
|
reference_range: Some("3.9-6.1".to_string()),
|
||||||
|
systolic: None,
|
||||||
|
diastolic: None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let weight = v.weight.map(|w| {
|
||||||
|
let val = w.to_f64().unwrap_or(0.0);
|
||||||
|
IndicatorSummary {
|
||||||
|
value: val,
|
||||||
|
status: "normal".to_string(), // 体重无通用参考范围
|
||||||
|
reference_range: None,
|
||||||
|
systolic: None,
|
||||||
|
diastolic: None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(MiniTodayResp {
|
||||||
|
blood_pressure,
|
||||||
|
heart_rate,
|
||||||
|
blood_sugar,
|
||||||
|
weight,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user