feat(health): 新增小程序专用今日体征摘要端点 GET /health/vital-signs/today

This commit is contained in:
iven
2026-04-24 12:17:17 +08:00
parent 19be2a08c7
commit e7b6bdfcac
4 changed files with 162 additions and 0 deletions

View File

@@ -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>,
}

View File

@@ -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)))
}
// ---------------------------------------------------------------------------
// 带版本号的更新请求包装
// ---------------------------------------------------------------------------

View File

@@ -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",

View File

@@ -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,
})
}