feat(ai): Phase 2A-3 随访页 AI 辅助生成小结 — SSE 端点 + 前端集成
- AnalysisType 新增 FollowUpSummary 变体(as_str/prompt_name) - HealthDataProvider 新增 get_follow_up_summary_data() + FollowUpSummaryDataDto - erp-health 实现随访数据查询(task + records + PII 解密) - 新增 /ai/analyze/follow-up-summary SSE 端点 - SanitizationService 新增 sanitize_follow_up_data() - 前端 analysisSse.ts/AiAnalysisCard 支持 follow-up-summary 类型 - FollowUpTaskList 操作列新增「AI 小结」按钮
This commit is contained in:
@@ -52,6 +52,7 @@ pub struct AnalyzeBody {
|
||||
pub report_id: Option<uuid::Uuid>,
|
||||
pub patient_id: Option<uuid::Uuid>,
|
||||
pub metrics: Option<Vec<String>>,
|
||||
pub source_id: Option<uuid::Uuid>,
|
||||
}
|
||||
|
||||
// === SSE 分析端点 ===
|
||||
@@ -378,6 +379,83 @@ where
|
||||
Ok(Sse::new(sse_stream).keep_alive(KeepAlive::default()))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/analyze/follow-up-summary",
|
||||
request_body = AnalyzeBody,
|
||||
responses((status = 200, description = "SSE 随访小结生成流")),
|
||||
tag = "AI 分析",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn stream_follow_up_summary<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Json(body): Json<AnalyzeBody>,
|
||||
) -> Result<Sse<impl futures::Stream<Item = Result<Event, Infallible>>>, erp_core::error::AppError>
|
||||
where
|
||||
AiState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "ai.analysis.manage")?;
|
||||
let task_id = body
|
||||
.source_id
|
||||
.ok_or_else(|| erp_core::error::AppError::Validation("source_id (task_id) 必填".into()))?;
|
||||
|
||||
let data = state
|
||||
.health_provider
|
||||
.get_follow_up_summary_data(ctx.tenant_id, task_id)
|
||||
.await?;
|
||||
|
||||
if data.records.is_empty() {
|
||||
return Err(erp_core::error::AppError::Validation(
|
||||
"该随访任务尚无随访记录,无法生成小结。请先填写至少一条随访记录。".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let sanitized_data = state.analysis.sanitizer.sanitize_follow_up_data(&data)?;
|
||||
|
||||
let prompt = state
|
||||
.prompt
|
||||
.get_active_prompt(ctx.tenant_id, "follow_up_summary_generation")
|
||||
.await?;
|
||||
|
||||
let model_config = &prompt.model_config;
|
||||
let (model, temperature, max_tokens) =
|
||||
resolve_model_config(model_config, ctx.tenant_id, &state.db).await;
|
||||
|
||||
let (stream, analysis_id, _) = state
|
||||
.analysis
|
||||
.stream_analyze(
|
||||
ctx.tenant_id,
|
||||
ctx.user_id,
|
||||
data.patient_id,
|
||||
AnalysisType::FollowUpSummary,
|
||||
task_id.to_string(),
|
||||
prompt.system_prompt,
|
||||
prompt.user_prompt_template,
|
||||
sanitized_data,
|
||||
model,
|
||||
temperature,
|
||||
max_tokens,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let analysis_id_clone = analysis_id;
|
||||
let state_clone = state.clone();
|
||||
let patient_id_clone = data.patient_id;
|
||||
|
||||
let sse_stream = build_sse_stream(
|
||||
stream,
|
||||
analysis_id_clone,
|
||||
state_clone,
|
||||
"follow_up_summary",
|
||||
ctx.tenant_id,
|
||||
patient_id_clone,
|
||||
ctx.user_id,
|
||||
);
|
||||
Ok(Sse::new(sse_stream).keep_alive(KeepAlive::default()))
|
||||
}
|
||||
|
||||
// === 分析历史 ===
|
||||
|
||||
#[derive(Debug, Deserialize, utoipa::IntoParams)]
|
||||
|
||||
Reference in New Issue
Block a user