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:
@@ -3,17 +3,18 @@ use chrono::Datelike;
|
||||
use erp_core::crypto::{self as pii, PiiCrypto};
|
||||
use erp_core::error::{AppError, AppResult};
|
||||
use erp_core::health_provider::{
|
||||
AnomalyInfo, AppointmentSummaryDto, HealthDataProvider, HealthReportDto, LabItemDto,
|
||||
LabReportDto, LabReportListItemDto, MedicationSummaryDto, MetricTrendAnalysis,
|
||||
PatientSummaryDto, RegressionStats, ReportSectionDto, TimeRange, TrendAnalysisDto,
|
||||
TrendDirection, VitalSignDto,
|
||||
AnomalyInfo, AppointmentSummaryDto, FollowUpRecordDto, FollowUpSummaryDataDto,
|
||||
HealthDataProvider, HealthReportDto, LabItemDto, LabReportDto, LabReportListItemDto,
|
||||
MedicationSummaryDto, MetricTrendAnalysis, PatientSummaryDto, RegressionStats,
|
||||
ReportSectionDto, TimeRange, TrendAnalysisDto, TrendDirection, VitalSignDto,
|
||||
};
|
||||
use num_traits::ToPrimitive;
|
||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::entity::{
|
||||
appointment, diagnosis, health_record, lab_report, medication_record, patient, vital_signs,
|
||||
appointment, diagnosis, follow_up_record, follow_up_task, health_record, lab_report,
|
||||
medication_record, patient, vital_signs,
|
||||
};
|
||||
|
||||
pub struct HealthDataProviderImpl {
|
||||
@@ -668,4 +669,50 @@ impl HealthDataProvider for HealthDataProviderImpl {
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
async fn get_follow_up_summary_data(
|
||||
&self,
|
||||
tenant_id: Uuid,
|
||||
task_id: Uuid,
|
||||
) -> AppResult<FollowUpSummaryDataDto> {
|
||||
let task = follow_up_task::Entity::find_by_id(task_id)
|
||||
.filter(follow_up_task::Column::TenantId.eq(tenant_id))
|
||||
.filter(follow_up_task::Column::DeletedAt.is_null())
|
||||
.one(&self.db)
|
||||
.await?
|
||||
.ok_or_else(|| AppError::NotFound(format!("随访任务 {task_id} 不存在")))?;
|
||||
|
||||
let records = follow_up_record::Entity::find()
|
||||
.filter(follow_up_record::Column::TaskId.eq(task_id))
|
||||
.filter(follow_up_record::Column::DeletedAt.is_null())
|
||||
.order_by_asc(follow_up_record::Column::ExecutedDate)
|
||||
.all(&self.db)
|
||||
.await?;
|
||||
|
||||
let kek = self.crypto.kek();
|
||||
let record_dtos: Vec<FollowUpRecordDto> = records
|
||||
.into_iter()
|
||||
.map(|r| {
|
||||
let decrypt_text = |enc: &str| -> String {
|
||||
pii::decrypt(kek, enc).unwrap_or_else(|_| enc.to_string())
|
||||
};
|
||||
FollowUpRecordDto {
|
||||
executed_date: r.executed_date.to_string(),
|
||||
result: pii::decrypt(kek, &r.result).unwrap_or_else(|_| r.result.clone()),
|
||||
patient_condition: r.patient_condition.as_deref().map(decrypt_text),
|
||||
medical_advice: r.medical_advice.as_deref().map(decrypt_text),
|
||||
next_follow_up_date: r.next_follow_up_date.map(|d| d.to_string()),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(FollowUpSummaryDataDto {
|
||||
task_id: task.id,
|
||||
patient_id: task.patient_id,
|
||||
follow_up_type: task.follow_up_type,
|
||||
planned_date: task.planned_date.to_string(),
|
||||
task_status: task.status,
|
||||
records: record_dtos,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user