feat(db): 注册透析处方迁移 + AI Prompt 种子数据(4 个默认模板)
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

- 注册遗漏的 m20260427_000081_create_dialysis_prescription 迁移
- 新增 000082 种子迁移:插入 4 个 AI Prompt 模板
  (化验单解读/趋势分析/体检方案/报告摘要)
This commit is contained in:
iven
2026-04-27 12:50:16 +08:00
parent a1bc62cd5e
commit a2c1b5ece8
3 changed files with 221 additions and 0 deletions

View File

@@ -80,6 +80,8 @@ mod m20260426_000077_create_alerts;
mod m20260427_000078_normalize_follow_up_types;
mod m20260427_000079_add_vital_signs_fields;
mod m20260427_000080_create_medication_record;
mod m20260427_000081_create_dialysis_prescription;
mod m20260427_000082_seed_ai_prompts;
pub struct Migrator;
@@ -167,6 +169,8 @@ impl MigratorTrait for Migrator {
Box::new(m20260427_000078_normalize_follow_up_types::Migration),
Box::new(m20260427_000079_add_vital_signs_fields::Migration),
Box::new(m20260427_000080_create_medication_record::Migration),
Box::new(m20260427_000081_create_dialysis_prescription::Migration),
Box::new(m20260427_000082_seed_ai_prompts::Migration),
]
}
}

View File

@@ -0,0 +1,133 @@
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> {
manager
.create_table(
Table::create()
.table(Alias::new("dialysis_prescription"))
.if_not_exists()
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key())
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
.col(ColumnDef::new(Alias::new("patient_id")).uuid().not_null())
// 透析器型号
.col(ColumnDef::new(Alias::new("dialyzer_model")).string_len(100).null())
// 膜面积 (m²)
.col(ColumnDef::new(Alias::new("membrane_area")).decimal_len(5, 2).null())
// 透析液钾浓度 (mmol/L)
.col(ColumnDef::new(Alias::new("dialysate_potassium")).decimal_len(5, 2).null())
// 透析液钙浓度 (mmol/L)
.col(ColumnDef::new(Alias::new("dialysate_calcium")).decimal_len(5, 2).null())
// 透析液碳酸氢盐浓度 (mmol/L)
.col(ColumnDef::new(Alias::new("dialysate_bicarbonate")).decimal_len(5, 2).null())
// 抗凝方式: heparin/lmwh/heparin_free
.col(ColumnDef::new(Alias::new("anticoagulation_type")).string_len(20).null())
// 抗凝剂剂量
.col(ColumnDef::new(Alias::new("anticoagulation_dose")).string_len(50).null())
// 目标超滤量 (ml)
.col(ColumnDef::new(Alias::new("target_ultrafiltration_ml")).integer().null())
// 目标干体重 (kg)
.col(ColumnDef::new(Alias::new("target_dry_weight")).decimal_len(5, 2).null())
// 血流量 (ml/min)
.col(ColumnDef::new(Alias::new("blood_flow_rate")).integer().null())
// 透析液流量 (ml/min)
.col(ColumnDef::new(Alias::new("dialysate_flow_rate")).integer().null())
// 每周透析频次
.col(ColumnDef::new(Alias::new("frequency_per_week")).integer().null())
// 每次透析时长 (分钟)
.col(ColumnDef::new(Alias::new("duration_minutes")).integer().null())
// 血管通路类型: avf/avg/cvc
.col(ColumnDef::new(Alias::new("vascular_access_type")).string_len(20).null())
// 血管通路位置
.col(ColumnDef::new(Alias::new("vascular_access_location")).string_len(100).null())
// 生效日期
.col(ColumnDef::new(Alias::new("effective_from")).date().null())
// 失效日期
.col(ColumnDef::new(Alias::new("effective_to")).date().null())
// 状态: active/discontinued
.col(
ColumnDef::new(Alias::new("status"))
.string_len(20)
.not_null()
.default("active"),
)
// 开方医生 ID
.col(ColumnDef::new(Alias::new("prescribed_by")).uuid().null())
// 备注
.col(ColumnDef::new(Alias::new("notes")).text().null())
// 标准审计字段
.col(
ColumnDef::new(Alias::new("created_at"))
.timestamp_with_time_zone()
.not_null()
.default(Expr::current_timestamp()),
)
.col(
ColumnDef::new(Alias::new("updated_at"))
.timestamp_with_time_zone()
.not_null()
.default(Expr::current_timestamp()),
)
.col(ColumnDef::new(Alias::new("created_by")).uuid().null())
.col(ColumnDef::new(Alias::new("updated_by")).uuid().null())
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone().null())
.col(
ColumnDef::new(Alias::new("version"))
.integer()
.not_null()
.default(1),
)
.to_owned(),
)
.await?;
// 租户索引
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_dialysis_prescription_tenant_id")
.table(Alias::new("dialysis_prescription"))
.col(Alias::new("tenant_id"))
.to_owned(),
)
.await?;
// 患者索引
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_dialysis_prescription_patient_id")
.table(Alias::new("dialysis_prescription"))
.col(Alias::new("patient_id"))
.to_owned(),
)
.await?;
// 患者+状态复合索引(仅未删除记录)— 查询当前生效处方
manager
.create_index(
Index::create()
.if_not_exists()
.name("idx_dialysis_prescription_patient_status")
.table(Alias::new("dialysis_prescription"))
.col(Alias::new("patient_id"))
.col(Alias::new("status"))
.to_owned(),
)
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Alias::new("dialysis_prescription")).to_owned())
.await
}
}

View File

@@ -0,0 +1,84 @@
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 result = db.query_one(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
"SELECT id::text FROM tenant LIMIT 1".to_string(),
))
.await?;
let tid = match result {
Some(row) => row.try_get_by_index::<String>(0).unwrap_or_default(),
None => return Ok(()),
};
let sys = "00000000-0000-0000-0000-000000000000";
let mc = r#"{"model":"claude-sonnet-4-6","temperature":0.3,"max_tokens":2048}"#;
insert_prompt(db, &tid, sys, "lab_report_interpretation", "化验单解读", "analysis", mc,
"你是一名专业的医学检验解读助手。请根据提供的化验指标数据,为患者提供清晰、准确的解读。\n\n要求:\n1. 逐项分析每个指标的含义和是否在正常范围\n2. 对异常指标提供可能的原因说明(不作为诊断)\n3. 给出建议的后续行动\n4. 使用通俗易懂的语言\n5. 最后给出总体健康提示",
"以下是患者的化验报告数据:\n\n报告日期:{{report_date}}\n科室:{{department}}\n患者年龄段:{{age_group}},性别:{{sex}}\n\n检查项目:\n{{#each items}}\n- {{name}}{{value}} {{unit}}(参考范围:{{reference_range}}{{#if is_abnormal}}异常{{else}}正常{{/if}}\n{{/each}}\n\n请对以上化验结果进行详细解读。",
).await?;
insert_prompt(db, &tid, sys, "health_trend_analysis", "健康趋势分析", "analysis", mc,
"你是一名健康数据分析专家。请根据提供的生命体征趋势数据,分析患者的健康变化趋势。\n\n要求:\n1. 识别数据中的关键趋势\n2. 对异常趋势提出预警\n3. 结合各指标间的关联性进行综合分析\n4. 给出健康管理建议",
"以下是患者的生命体征趋势数据:\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请分析以上健康趋势数据。",
).await?;
insert_prompt(db, &tid, sys, "personalized_checkup_plan", "个性化体检方案", "planning", mc,
"你是一名健康管理顾问。请根据患者的健康状况信息,为其制定个性化的体检方案。\n\n要求:\n1. 基于患者年龄、性别、既往病史推荐检查项目\n2. 按优先级排序\n3. 给出建议的检查频率\n4. 说明每项检查的目的和意义",
"请为以下患者制定个性化体检方案:\n\n年龄段:{{age_group}}\n性别:{{sex}}\n慢性疾病:{{#each chronic_conditions}}{{this}}{{#unless @last}}、{{/unless}}{{/each}}\n当前用药:{{#each medications}}{{this}}{{#unless @last}}、{{/unless}}{{/each}}\n家族病史:{{#each family_history}}{{this}}{{#unless @last}}、{{/unless}}{{/each}}\n\n请给出详细的体检方案推荐。",
).await?;
insert_prompt(db, &tid, sys, "report_summary_generation", "报告摘要生成", "summary", mc,
"你是一名医疗报告摘要撰写专家。请根据提供的健康报告内容,生成结构清晰的摘要。\n\n要求:\n1. 提取报告中的关键发现\n2. 标注异常项目及其严重程度\n3. 给出简明的结论\n4. 提供行动建议\n5. 控制在 500 字以内",
"请为以下健康报告生成摘要:\n\n报告日期:{{report_date}}\n科室:{{department}}\n患者年龄段:{{age_group}},性别:{{sex}}\n\n报告内容:\n{{#each sections}}\n## {{title}}\n发现:{{#each findings}}{{this}}{{#unless @last}}{{/unless}}{{/each}}\n{{#if abnormal_items.length}}\n异常项:{{#each abnormal_items}}{{this}}{{#unless @last}}、{{/unless}}{{/each}}\n{{/if}}\n{{/each}}\n\n请生成结构化的报告摘要。",
).await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let db = manager.get_connection();
db.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
"DELETE FROM ai_prompts WHERE name IN ('lab_report_interpretation','health_trend_analysis','personalized_checkup_plan','report_summary_generation')".to_string(),
))
.await?;
Ok(())
}
}
fn esc(s: &str) -> String {
s.replace('\'', "''")
}
async fn insert_prompt(
db: &sea_orm_migration::SchemaManagerConnection<'_>,
tenant_id: &str,
sys: &str,
name: &str,
description: &str,
category: &str,
model_config: &str,
system_prompt: &str,
user_template: &str,
) -> Result<(), DbErr> {
db.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Postgres,
format!(
"INSERT INTO ai_prompts (id, tenant_id, name, description, system_prompt, user_prompt_template, model_config, version, is_active, category, tags, created_at, updated_at, created_by, updated_by, version_lock) \
VALUES (gen_random_uuid(), '{}', '{}', '{}', '{}', '{}', '{}', 1, true, '{}', NULL, NOW(), NOW(), '{}', '{}', 1) ON CONFLICT DO NOTHING",
tenant_id, esc(name), esc(description), esc(system_prompt), esc(user_template), esc(model_config), category, sys, sys
),
))
.await?;
Ok(())
}