feat(ai+db): 趋势分析 prompt 升级为结构化统计摘要
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

- 新增迁移 000093:更新 health_trend_analysis prompt,使用统计字段
  替代原始数据点遍历,引导 AI 专注 slope/R²/异常点分析
- erp-ai handler: stream_trends 改用 get_trend_analysis_data()
  替代 get_vital_signs(),传递预计算趋势特征
- sanitizer: 新增 sanitize_trend_analysis() 方法
This commit is contained in:
iven
2026-04-28 19:57:51 +08:00
parent 1c9e7ccf1d
commit cf844a561f
4 changed files with 107 additions and 4 deletions

View File

@@ -104,11 +104,11 @@ where
end: chrono::Utc::now(),
};
let vital_dtos = state
let trend_data = state
.health_provider
.get_vital_signs(ctx.tenant_id, patient_id, &metrics, &range)
.get_trend_analysis_data(ctx.tenant_id, patient_id, &metrics, &range)
.await?;
let sanitized_data = state.analysis.sanitizer.sanitize_vital_signs(&vital_dtos)?;
let sanitized_data = state.analysis.sanitizer.sanitize_trend_analysis(&trend_data)?;
let prompt = state
.prompt

View File

@@ -1,5 +1,5 @@
use erp_core::health_provider::{
HealthReportDto, LabReportDto, PatientSummaryDto, VitalSignDto,
HealthReportDto, LabReportDto, PatientSummaryDto, TrendAnalysisDto, VitalSignDto,
};
use serde_json::Value;
@@ -43,6 +43,13 @@ impl SanitizationService {
Ok(sanitized)
}
pub fn sanitize_trend_analysis(&self, data: &TrendAnalysisDto) -> AiResult<Value> {
let sanitized = serde_json::to_value(data)
.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 = [

View File

@@ -92,6 +92,7 @@ mod m20260428_000089_blind_indexes;
mod m20260428_000090_critical_alerts;
mod m20260428_000091_dead_letter_events;
mod m20260429_000092_device_readings_metric;
mod m20260429_000093_trend_analysis_prompt_v2;
pub struct Migrator;
@@ -191,6 +192,7 @@ impl MigratorTrait for Migrator {
Box::new(m20260428_000090_critical_alerts::Migration),
Box::new(m20260428_000091_dead_letter_events::Migration),
Box::new(m20260429_000092_device_readings_metric::Migration),
Box::new(m20260429_000093_trend_analysis_prompt_v2::Migration),
]
}
}

View File

@@ -0,0 +1,94 @@
//! 更新 health_trend_analysis prompt 模板为结构化统计摘要版本
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let db = manager.get_connection();
// 更新系统提示词 — 强调使用统计摘要而非原始数据
let system_prompt = r#"你是一名健康数据分析专家。你将收到经过预处理的结构化统计摘要数据,包括线性回归趋势、异常检测结果等。
要求:
1. **趋势判断** — 基于回归斜率(slope)和R²判断指标趋势上升/下降/稳定注意R²较低时趋势不确定性大
2. **异常预警** — 重点分析被检测为异常的数据点,说明偏离程度和可能的原因
3. **综合分析** — 考虑各指标间的关联性(如血压和体重、血糖和心率)
4. **临床建议** — 给出切实可行的健康管理建议,不替代医生诊断
5. **风险评级** — 对整体健康风险给出低/中/高评估并说明理由
6. **关注重点** — 用简洁的语言总结最需要关注的 2-3 个问题"#;
// 更新用户提示词模板 — 使用结构化统计字段
let user_template = r#"请分析以下患者的健康趋势数据:
分析周期:{{period_start}} 至 {{period_end}}
## 各指标趋势分析
{{#each metrics}}
### {{metric}}{{unit}}
- 数据点数:{{data_point_count}}
{{#if regression}}
- **趋势方向**{{regression.direction}}(斜率:{{regression.slope}}{{regression.r_squared}}
- **日变化量**{{regression.daily_change}} {{unit}}/天
- **周期变化**{{regression.period_change}} {{unit}}{{regression.daily_change}} × 天数)
{{else}}
- 数据不足,无法计算回归趋势
{{/if}}
{{#if anomalies.length}}
- **异常数据点**
{{#each anomalies}}
- {{date}}:值 {{value}} {{../unit}}(均值 {{mean}},偏离 {{deviation}} 个标准差)
{{/each}}
{{else}}
- 未检测到显著异常
{{/if}}
{{/each}}
请基于以上统计摘要,给出详细的趋势分析报告。"#;
let esc_sys = esc(system_prompt);
let esc_tpl = esc(user_template);
// 更新已有的 health_trend_analysis promptversion 升级到 2
db.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
format!(
"UPDATE ai_prompt SET system_prompt = '{esc_sys}', user_prompt_template = '{esc_tpl}', version = version + 1, updated_at = NOW() WHERE name = 'health_trend_analysis' AND is_active = true"
),
))
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// 回滚到原始 prompt保持与 000082 seed 一致)
let db = manager.get_connection();
let system_prompt = "你是一名健康数据分析专家。请根据提供的生命体征趋势数据,分析患者的健康变化趋势。\n\n要求:\n1. 识别数据中的关键趋势\n2. 对异常趋势提出预警\n3. 结合各指标间的关联性进行综合分析\n4. 给出健康管理建议";
let user_template = "以下是患者的生命体征趋势数据:\n\n患者年龄段:{{age_group}},性别:{{sex}}\n\n监测指标:\n{{#each metrics}}\n### {{name}}{{unit}}\n数据点:{{#each values}}{{this.[0]}}: {{this.[1]}}{{#unless @last}}, {{/unless}}{{/each}}\n{{/each}}\n\n请分析以上健康趋势数据。";
let esc_sys = esc(system_prompt);
let esc_tpl = esc(user_template);
db.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
format!(
"UPDATE ai_prompt SET system_prompt = '{esc_sys}', user_prompt_template = '{esc_tpl}', version = version + 1, updated_at = NOW() WHERE name = 'health_trend_analysis' AND is_active = true"
),
))
.await?;
Ok(())
}
}
fn esc(s: &str) -> String {
s.replace('\'', "''")
}