feat(health): 新增小程序专用今日体征摘要端点 GET /health/vital-signs/today
This commit is contained in:
@@ -171,3 +171,31 @@ pub struct MiniTrendResp {
|
||||
/// 数据点列表(按日期升序)
|
||||
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)))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 小程序今日体征摘要(通过当前用户关联 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",
|
||||
axum::routing::get(health_data_handler::get_mini_trend),
|
||||
)
|
||||
// 小程序今日体征摘要
|
||||
.route(
|
||||
"/health/vital-signs/today",
|
||||
axum::routing::get(health_data_handler::get_mini_today),
|
||||
)
|
||||
// 预约排班
|
||||
.route(
|
||||
"/health/appointments",
|
||||
|
||||
@@ -722,3 +722,112 @@ pub async fn get_mini_trend(
|
||||
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