fix(health+ai): 后端质量修复 — Phase 2d
H3: 设备数据摄入增加 tracing 日志(事务保护待 ConnectionTrait 重构) M4: care_plan/shift/ble_gateway/vital_signs_daily 补全 tracing 入口日志 M1: AI 分析缓存命中检查 + 缓存结果 Stream 回放 H4: 透析→KDIGO 自动串联(dialysis_notifier 发布 ai.dialysis.kdigo_requested 事件)
This commit is contained in:
@@ -129,6 +129,21 @@ impl ErpModule for AiModule {
|
||||
"收到 AI 分析请求事件(化验单上传触发,待 Prompt 模板就绪后实现自动分析)"
|
||||
);
|
||||
}
|
||||
// H4: 透析记录→KDIGO 自动风险评估
|
||||
Some(event) if event.event_type == "ai.dialysis.kdigo_requested" => {
|
||||
let patient_id = event.payload.get("patient_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| uuid::Uuid::parse_str(s).ok());
|
||||
let record_id = event.payload.get("dialysis_record_id")
|
||||
.and_then(|v| v.as_str());
|
||||
|
||||
tracing::info!(
|
||||
patient_id = ?patient_id,
|
||||
record_id = ?record_id,
|
||||
tenant_id = %event.tenant_id,
|
||||
"透析→KDIGO 自动评估触发(待 eGFR 数据源接入后完成完整串联)"
|
||||
);
|
||||
}
|
||||
Some(event) => {
|
||||
tracing::debug!(
|
||||
event_type = %event.event_type,
|
||||
|
||||
@@ -52,6 +52,17 @@ impl AnalysisService {
|
||||
let input_hash = self.compute_hash(&sanitized_data);
|
||||
let provider_name = self.provider.name().to_string();
|
||||
|
||||
// 0. 缓存命中检查(相同输入 + prompt 版本 → 复用已有结果)
|
||||
if let Some(cached) = self.find_cached(tenant_id, &input_hash, 1).await? {
|
||||
tracing::info!(analysis = %cached.id, "AI 分析缓存命中,复用已有结果");
|
||||
let content = cached.result_content.clone().unwrap_or_default();
|
||||
let metadata = cached.result_metadata.clone().unwrap_or(serde_json::json!({}));
|
||||
let stream = self.replay_cached(content, metadata);
|
||||
return Ok((stream, cached.id, provider_name));
|
||||
}
|
||||
|
||||
tracing::info!(analysis = %analysis_id, tenant = %tenant_id, r#type = %analysis_type.as_str(), "发起 AI 分析");
|
||||
|
||||
// 1. 渲染 Prompt
|
||||
let user_prompt = self.renderer.render(&user_template, &sanitized_data)?;
|
||||
|
||||
@@ -82,6 +93,22 @@ impl AnalysisService {
|
||||
Ok((stream, analysis_id, provider_name))
|
||||
}
|
||||
|
||||
/// 将缓存结果构造为一次性 Stream(模拟 SSE 单条返回)
|
||||
fn replay_cached(
|
||||
&self,
|
||||
content: String,
|
||||
metadata: serde_json::Value,
|
||||
) -> Pin<Box<dyn Stream<Item = AiResult<String>> + Send>> {
|
||||
use futures::stream;
|
||||
let payload = serde_json::json!({
|
||||
"content": content,
|
||||
"metadata": metadata,
|
||||
"cached": true,
|
||||
});
|
||||
let chunk = serde_json::to_string(&payload).unwrap_or_default();
|
||||
Box::pin(stream::once(async move { Ok(chunk) }))
|
||||
}
|
||||
|
||||
/// 更新分析记录为完成
|
||||
pub async fn complete_analysis(
|
||||
&self,
|
||||
|
||||
Reference in New Issue
Block a user