feat(ai): 数据脱敏服务 + Prompt 模板渲染引擎

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
iven
2026-04-25 13:55:40 +08:00
parent 06f3d08c61
commit e0e4a7f9a1
3 changed files with 94 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
use erp_core::health_provider::{
HealthReportDto, LabReportDto, PatientSummaryDto, VitalSignDto,
};
use serde_json::Value;
use crate::error::{AiError, AiResult};
/// 数据脱敏服务 — 确保发送给 AI 的数据不含 PII
/// HealthDataProvider 返回的 DTO 已经是脱敏的(只有年龄/性别/医疗数据)
/// 此服务做二次检查和安全约束注入
pub struct SanitizationService;
impl SanitizationService {
pub fn new() -> Self {
Self
}
pub fn sanitize_lab_report(&self, report: &LabReportDto) -> AiResult<Value> {
let sanitized = serde_json::to_value(report)
.map_err(|e| AiError::SanitizationError(format!("序列化失败: {e}")))?;
self.verify_no_pii(&sanitized)?;
Ok(sanitized)
}
pub fn sanitize_vital_signs(&self, signs: &[VitalSignDto]) -> AiResult<Value> {
let sanitized = serde_json::to_value(signs)
.map_err(|e| AiError::SanitizationError(format!("序列化失败: {e}")))?;
self.verify_no_pii(&sanitized)?;
Ok(sanitized)
}
pub fn sanitize_patient_summary(&self, summary: &PatientSummaryDto) -> AiResult<Value> {
let sanitized = serde_json::to_value(summary)
.map_err(|e| AiError::SanitizationError(format!("序列化失败: {e}")))?;
self.verify_no_pii(&sanitized)?;
Ok(sanitized)
}
pub fn sanitize_health_report(&self, report: &HealthReportDto) -> AiResult<Value> {
let sanitized = serde_json::to_value(report)
.map_err(|e| AiError::SanitizationError(format!("序列化失败: {e}")))?;
self.verify_no_pii(&sanitized)?;
Ok(sanitized)
}
/// 二次验证: 确保没有意外泄漏的 PII
fn verify_no_pii(&self, value: &Value) -> AiResult<()> {
let pii_keys = [
"name",
"phone",
"id_number",
"address",
"birth_date",
"email",
];
if let Value::Object(map) = value {
for key in pii_keys {
if map.contains_key(key) {
return Err(AiError::SanitizationError(format!(
"检测到疑似 PII 字段: {key}"
)));
}
}
}
Ok(())
}
}