use crate::copilot::rules::MatchedRuleData; /// 风险评分结果 #[derive(Debug, Clone, serde::Serialize)] pub struct RiskScore { pub score: i16, pub level: String, pub matched_rules: Vec, } #[derive(Debug, Clone, serde::Serialize)] pub struct MatchedRule { pub rule_id: uuid::Uuid, pub name: String, pub score: i16, pub severity: String, pub suggestion: Option, } /// 根据匹配规则计算风险评分 pub fn calculate_risk(matched: Vec) -> RiskScore { let total: i16 = matched.iter().map(|(_, _, s, _, _)| *s).sum(); let clamped = total.clamp(0, 10); let level = match clamped { 0..=2 => "low".to_string(), 3..=5 => "medium".to_string(), 6..=8 => "high".to_string(), _ => "critical".to_string(), }; let matched_rules = matched .into_iter() .map(|(id, name, score, severity, suggestion)| MatchedRule { rule_id: id, name, score, severity, suggestion, }) .collect(); RiskScore { score: clamped, level, matched_rules, } } /// LLM 补充分析:基于规则评分结果和患者数据,生成自然语言的补充洞察 /// 失败时返回 None(降级为纯规则模式) pub async fn llm_supplement( provider_registry: &crate::provider::registry::ProviderRegistry, preferred_provider: &str, risk_score: &RiskScore, patient_data: &serde_json::Value, ) -> Option { let prompt = format!( "基于以下患者风险评分和匹配规则,是否存在规则未覆盖的风险因素?\ 风险评分:{}/10,等级:{}\n\ 匹配规则:{}\n\ 患者近期数据摘要:{}\n\ 请给出简洁的补充分析(100字以内),如无补充请回复\"无补充\"。", risk_score.score, risk_score.level, risk_score .matched_rules .iter() .map(|r| format!("- {}(+{}分)", r.name, r.score)) .collect::>() .join("\n"), serde_json::to_string(&serde_json::json!({ "latest_bp": patient_data.get("vital_signs"), "latest_lab": patient_data.get("lab_reports"), })) .unwrap_or_default(), ); let resolved: crate::error::AiResult<_> = provider_registry.resolve(preferred_provider).await; let resolved = resolved.ok()?; let req = crate::dto::GenerateRequest { system_prompt: "你是健康管理AI助手,负责对患者的风险评分进行补充分析。".into(), user_prompt: prompt, model: String::new(), temperature: 0.3, max_tokens: 256, }; let resp: crate::error::AiResult<_> = resolved.provider().generate(req).await; Some(resp.ok()?.content) } #[cfg(test)] mod tests { use super::*; #[test] fn test_calculate_risk_low() { let matched = vec![]; let result = calculate_risk(matched); assert_eq!(result.score, 0); assert_eq!(result.level, "low"); } #[test] fn test_calculate_risk_high() { let matched = vec![ ( uuid::Uuid::now_v7(), "eGFR下降".into(), 3, "warning".into(), Some("建议调整".into()), ), ( uuid::Uuid::now_v7(), "血压偏高".into(), 2, "warning".into(), None, ), (uuid::Uuid::now_v7(), "失约".into(), 1, "info".into(), None), ]; let result = calculate_risk(matched); assert_eq!(result.score, 6); assert_eq!(result.level, "high"); assert_eq!(result.matched_rules.len(), 3); } #[test] fn test_calculate_risk_clamp_at_10() { let matched = vec![ ( uuid::Uuid::now_v7(), "危急".into(), 5, "critical".into(), None, ), ( uuid::Uuid::now_v7(), "严重".into(), 4, "critical".into(), None, ), ( uuid::Uuid::now_v7(), "异常".into(), 3, "warning".into(), None, ), ]; let result = calculate_risk(matched); assert_eq!(result.score, 10); assert_eq!(result.level, "critical"); } }