use crate::dto::suggestion::{ParsedOutput, StructuredOutput}; use crate::error::AiResult; const TEXT_MARKER: &str = "===PATIENT_TEXT==="; const JSON_MARKER: &str = "===STRUCTURED_JSON==="; /// 解析 AI 双通道输出。JSON 解析失败时降级为纯文本。 pub fn parse_dual_channel(raw: &str) -> AiResult { let text_content = extract_section(raw, TEXT_MARKER, JSON_MARKER) .unwrap_or(raw) .trim() .to_string(); let structured = extract_section(raw, JSON_MARKER, TEXT_MARKER).and_then(|json_str| { let parsed: Result = serde_json::from_str(json_str.trim()); parsed.ok() }); Ok(ParsedOutput { text_content, structured, }) } fn extract_section<'a>(raw: &'a str, start: &str, end: &str) -> Option<&'a str> { let start_idx = raw.find(start)?; let content_start = start_idx + start.len(); let content_end = raw[content_start..] .find(end) .map(|i| content_start + i) .unwrap_or(raw.len()); Some(&raw[content_start..content_end]) } #[cfg(test)] mod tests { use super::*; use crate::dto::suggestion::RiskLevel; #[test] fn parse_dual_channel_output_success() { let raw = "===PATIENT_TEXT===\n张三的收缩压呈上升趋势\n===STRUCTURED_JSON===\n{\"risk_level\":\"medium\",\"risk_factors\":[\"收缩压偏高\"],\"suggestions\":[{\"type\":\"followup\",\"priority\":1,\"timing\":\"14天内\",\"reason\":\"血压异常\",\"params\":{},\"auto_executable\":false}],\"baseline_summary\":{}}"; let result = parse_dual_channel(raw).unwrap(); assert_eq!(result.text_content, "张三的收缩压呈上升趋势"); assert!(result.structured.is_some()); let s = result.structured.unwrap(); assert_eq!(s.risk_level, RiskLevel::Medium); assert_eq!(s.suggestions.len(), 1); } #[test] fn parse_text_only_fallback() { let raw = "纯文本分析结果,没有结构化部分"; let result = parse_dual_channel(raw).unwrap(); assert_eq!(result.text_content, "纯文本分析结果,没有结构化部分"); assert!(result.structured.is_none()); } #[test] fn parse_invalid_json_falls_back() { let raw = "===PATIENT_TEXT===\n分析内容\n===STRUCTURED_JSON===\n{invalid json}"; let result = parse_dual_channel(raw).unwrap(); assert_eq!(result.text_content, "分析内容"); assert!(result.structured.is_none()); } #[test] fn empty_suggestions_is_valid() { let raw = "===PATIENT_TEXT===\n指标正常\n===STRUCTURED_JSON===\n{\"risk_level\":\"low\",\"risk_factors\":[],\"suggestions\":[],\"baseline_summary\":{}}"; let result = parse_dual_channel(raw).unwrap(); let s = result.structured.unwrap(); assert!(s.suggestions.is_empty()); } #[test] fn risk_level_auto_executable() { assert!(RiskLevel::Low.is_auto_executable()); assert!(!RiskLevel::Medium.is_auto_executable()); assert!(!RiskLevel::High.is_auto_executable()); } }