diff --git a/crates/erp-server/migration/src/lib.rs b/crates/erp-server/migration/src/lib.rs index 3d2fb45..aef19de 100644 --- a/crates/erp-server/migration/src/lib.rs +++ b/crates/erp-server/migration/src/lib.rs @@ -144,6 +144,7 @@ mod m20260512_000139_create_copilot_insights; mod m20260512_000140_create_copilot_risk_snapshots; mod m20260512_000141_create_copilot_chat_logs; mod m20260512_000142_seed_copilot_rules; +mod m20260512_000143_seed_copilot_alert_rules; pub struct Migrator; @@ -295,6 +296,7 @@ impl MigratorTrait for Migrator { Box::new(m20260512_000140_create_copilot_risk_snapshots::Migration), Box::new(m20260512_000141_create_copilot_chat_logs::Migration), Box::new(m20260512_000142_seed_copilot_rules::Migration), + Box::new(m20260512_000143_seed_copilot_alert_rules::Migration), ] } } diff --git a/crates/erp-server/migration/src/m20260512_000143_seed_copilot_alert_rules.rs b/crates/erp-server/migration/src/m20260512_000143_seed_copilot_alert_rules.rs new file mode 100644 index 0000000..0590a44 --- /dev/null +++ b/crates/erp-server/migration/src/m20260512_000143_seed_copilot_alert_rules.rs @@ -0,0 +1,141 @@ +//! 8 条 Copilot 趋势/复合类告警规则种子数据 +//! +//! 覆盖 spec §3.2 告警分级场景: +//! - 趋势类(4): 收缩压快速上升、肌酐连续上升、体重连续上升、血压趋势上升 +//! - 复合类(4): eGFR+血钾双重危急、透析间期+血压、失约+依从性、Kt/V+血压 + +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 nil_tenant = "00000000-0000-0000-0000-000000000000"; + + // 8 条趋势/复合类告警规则 + // (idx, name, category, condition_expr, score, severity, suggestion, sort_order) + let rules: &[(i32, &str, &str, &str, i16, &str, &str, i32)] = &[ + // 趋势类规则 + ( + 20, + "收缩压快速上升", + "vital_signs", + r#"{"and":[{"=": [{"var":"vital_signs.systolic.trend_rising"}, true]},{">": [{"var":"vital_signs.systolic.change_pct"}, 15]}]}"#, + 3, + "warning", + "血压短时间内快速上升,需排除急性因素,建议加测并通知主治医生", + 20, + ), + ( + 21, + "肌酐连续上升", + "lab", + r#"{"and":[{"=": [{"var":"lab_reports.creatinine.trend_rising"}, true]},{">": [{"var":"lab_reports.creatinine.change_pct"}, 10]}]}"#, + 3, + "warning", + "肌酐连续上升且环比增幅>10%,需排除急性肾损伤可能", + 21, + ), + ( + 22, + "体重连续上升", + "vital_signs", + r#"{"=": [{"var":"vital_signs.weight.trend_rising"}, true]}"#, + 2, + "info", + "体重连续上升,需评估水钠潴留情况", + 22, + ), + ( + 23, + "血压趋势整体上升", + "vital_signs", + r#"{"=": [{"var":"vital_signs.systolic.trend_rising"}, true]}"#, + 2, + "info", + "血压呈持续上升趋势,建议关注并调整降压方案", + 23, + ), + // 复合类规则 + ( + 24, + "eGFR<45且血钾>5.0", + "composite", + r#"{"and":[{"<": [{"var":"lab_reports.egfr.latest"}, 45]},{">": [{"var":"lab_reports.potassium.latest"}, 5.0]}]}"#, + 5, + "critical", + "eGFR严重下降且血钾升高,需紧急评估并考虑紧急透析", + 24, + ), + ( + 25, + "透析间期体重增长+血压极高", + "composite", + r#"{"and":[{">": [{"var":"dialysis.interdialytic_weight_gain_pct"}, 5]},{">": [{"var":"vital_signs.systolic_bp_morning.latest"}, 160]}]}"#, + 4, + "critical", + "透析间期体重增长过多且血压极高,需紧急评估液体管理和降压方案", + 25, + ), + ( + 26, + "失约+药物依从性不足", + "composite", + r#"{"and":[{">": [{"var":"follow_up.missed_count"}, 2]},{"<": [{"var":"medication.adherence_rate"}, 70]}]}"#, + 3, + "warning", + "随访失约且药物依从性不足,建议加强患者教育和主动联系", + 26, + ), + ( + 27, + "透析质量危急", + "composite", + r#"{"and":[{"<": [{"var":"dialysis.kt_v.latest"}, 1.0]},{">": [{"var":"vital_signs.systolic_bp_morning.latest"}, 180]}]}"#, + 5, + "critical", + "透析充分性严重不足且血压极高,需紧急评估透析方案", + 27, + ), + ]; + + for &(idx, name, category, cond, score, severity, suggestion, sort) in rules { + let id = format!("c0000000-0000-0000-0000-{:012}", idx); + let sql = format!( + r#"INSERT INTO copilot_rules (id, tenant_id, name, category, condition_expr, score, severity, suggestion, enabled, sort_order, created_at, updated_at, version_lock) + VALUES ('{}', '{}', $1, $2, $3::jsonb, $4, $5, $6, true, $7, NOW(), NOW(), 1)"#, + id, nil_tenant + ); + db.execute(sea_orm::Statement::from_sql_and_values( + sea_orm::DatabaseBackend::Postgres, + sql, + [ + name.into(), + category.into(), + cond.into(), + score.into(), + severity.into(), + suggestion.into(), + sort.into(), + ], + )) + .await?; + } + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let db = manager.get_connection(); + db.execute(sea_orm::Statement::from_sql_and_values( + sea_orm::DatabaseBackend::Postgres, + "DELETE FROM copilot_rules WHERE id LIKE 'c0000000-0000-0000-0000-0000000000%' AND sort_order >= 20", + [], + )) + .await?; + Ok(()) + } +} diff --git a/docs/superpowers/plans/2026-05-11-copilot-gene-plan.md b/docs/superpowers/plans/2026-05-11-copilot-gene-plan.md index ffaee50..bfa0fea 100644 --- a/docs/superpowers/plans/2026-05-11-copilot-gene-plan.md +++ b/docs/superpowers/plans/2026-05-11-copilot-gene-plan.md @@ -31,7 +31,7 @@ - Create: `crates/erp-server/migration/src/m20260512_000138_create_copilot_rules.rs` - Modify: `crates/erp-server/migration/src/lib.rs` -- [ ] **Step 1: 创建迁移文件** +- [x] **Step 1: 创建迁移文件** 文件 `m20260512_000138_create_copilot_rules.rs`,参照 `m20260510_000136_create_banner.rs` 模式: @@ -106,18 +106,18 @@ enum CopilotRules { } ``` -- [ ] **Step 2: 注册迁移** +- [x] **Step 2: 注册迁移** 在 `migration/src/lib.rs` 中: - 顶部添加 `mod m20260512_000138_create_copilot_rules;` - `migrations()` vec 中添加 `Box::new(m20260512_000138_create_copilot_rules::Migration)` -- [ ] **Step 3: 编译验证** +- [x] **Step 3: 编译验证** Run: `cargo check -p erp-server` Expected: 编译通过 -- [ ] **Step 4: 提交** +- [x] **Step 4: 提交** ```bash git add crates/erp-server/migration/src/m20260512_000138_create_copilot_rules.rs crates/erp-server/migration/src/lib.rs @@ -132,7 +132,7 @@ git commit -m "feat(db): copilot_rules 表迁移" - Create: `crates/erp-server/migration/src/m20260512_000141_create_copilot_chat_logs.rs` - Modify: `crates/erp-server/migration/src/lib.rs` -- [ ] **Step 1: 创建 copilot_insights 迁移(m20260512_000139)** +- [x] **Step 1: 创建 copilot_insights 迁移(m20260512_000139)** 参照 Task 1 模式,字段按 spec §6.2 DDL。关键列: - `patient_id UUID NOT NULL`(无 REFERENCES,逻辑关联) @@ -149,7 +149,7 @@ git commit -m "feat(db): copilot_rules 表迁移" - 索引:`idx_copilot_insights_tenant_patient` on (tenant_id, patient_id) - 索引:`idx_copilot_insights_expires` on (expires_at) -- [ ] **Step 2: 创建 copilot_risk_snapshots 迁移(m20260512_000140)** +- [x] **Step 2: 创建 copilot_risk_snapshots 迁移(m20260512_000140)** 关键列: - `patient_id UUID NOT NULL`(无 REFERENCES) @@ -161,7 +161,7 @@ git commit -m "feat(db): copilot_rules 表迁移" - `data_freshness JSONB` - 唯一索引:`idx_copilot_risk_snapshots_tenant_patient` UNIQUE on (tenant_id, patient_id) -- [ ] **Step 3: 创建 copilot_chat_logs 迁移(m20260512_000141)** +- [x] **Step 3: 创建 copilot_chat_logs 迁移(m20260512_000141)** 关键列: - `patient_id UUID NOT NULL`(无 REFERENCES) @@ -177,16 +177,16 @@ git commit -m "feat(db): copilot_rules 表迁移" - 索引:`idx_copilot_chat_logs_session` on (tenant_id, session_id) - 索引:`idx_copilot_chat_logs_patient` on (tenant_id, patient_id) -- [ ] **Step 4: 注册 3 个迁移** +- [x] **Step 4: 注册 3 个迁移** 在 `migration/src/lib.rs` 中注册 m139、m140、m141。 -- [ ] **Step 5: 编译验证** +- [x] **Step 5: 编译验证** Run: `cargo check -p erp-server` Expected: 编译通过 -- [ ] **Step 6: 提交** +- [x] **Step 6: 提交** ```bash git add crates/erp-server/migration/src/ @@ -202,7 +202,7 @@ git commit -m "feat(db): copilot_insights/risk_snapshots/chat_logs 表迁移" - Create: `crates/erp-ai/src/entity/copilot_chat_logs.rs` - Modify: `crates/erp-ai/src/entity/mod.rs` -- [ ] **Step 1: 创建 copilot_rules entity** +- [x] **Step 1: 创建 copilot_rules entity** 参照 `crates/erp-ai/src/entity/ai_suggestion.rs` 模式: @@ -238,11 +238,11 @@ pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} ``` -- [ ] **Step 2: 创建其余 3 个 entity** +- [x] **Step 2: 创建其余 3 个 entity** 同样模式,字段对应 spec §6.2 DDL。 -- [ ] **Step 3: 注册 entity 模块** +- [x] **Step 3: 注册 entity 模块** 在 `crates/erp-ai/src/entity/mod.rs` 中添加: ```rust @@ -252,12 +252,12 @@ pub mod copilot_risk_snapshots; pub mod copilot_chat_logs; ``` -- [ ] **Step 4: 编译验证** +- [x] **Step 4: 编译验证** Run: `cargo check -p erp-ai` Expected: 编译通过 -- [ ] **Step 5: 提交** +- [x] **Step 5: 提交** ```bash git add crates/erp-ai/src/entity/ @@ -270,7 +270,7 @@ git commit -m "feat(ai): copilot 4 个 SeaORM entity" - Create: `crates/erp-ai/src/copilot/mod.rs` - Create: `crates/erp-ai/src/copilot/rules.rs` -- [ ] **Step 1: 创建 copilot 模块入口(仅 rules,其他 Task 5 再加)** +- [x] **Step 1: 创建 copilot 模块入口(仅 rules,其他 Task 5 再加)** `crates/erp-ai/src/copilot/mod.rs`: ```rust @@ -278,7 +278,7 @@ pub mod rules; // scoring 和 engine 将在 Task 5 中添加 ``` -- [ ] **Step 2: 编写规则引擎失败的测试** +- [x] **Step 2: 编写规则引擎失败的测试** `crates/erp-ai/src/copilot/rules.rs` 底部: @@ -329,12 +329,12 @@ mod tests { } ``` -- [ ] **Step 3: 运行测试确认失败** +- [x] **Step 3: 运行测试确认失败** Run: `cargo test -p erp-ai -- copilot::rules::tests` Expected: 编译失败(函数不存在) -- [ ] **Step 4: 实现 JSONLogic 解释器** +- [x] **Step 4: 实现 JSONLogic 解释器** ```rust use serde_json::Value; @@ -447,7 +447,7 @@ pub fn evaluate_rules( } ``` -- [ ] **Step 5: 运行测试确认通过** +- [x] **Step 5: 运行测试确认通过** Run: `cargo test -p erp-ai -- copilot::rules::tests` Expected: 5 tests PASS @@ -824,14 +824,14 @@ Expected: 返回 15 条预置规则 - Modify: `crates/erp-ai/src/lib.rs`(添加 `pub mod event;`) - Modify: `crates/erp-ai/src/module.rs`(on_startup 中启动消费者) -- [ ] **Step 1: 创建 event 模块入口** +- [x] **Step 1: 创建 event 模块入口** `crates/erp-ai/src/event/mod.rs`: ```rust pub mod copilot_consumer; ``` -- [ ] **Step 2: 编写消费者失败的测试** +- [x] **Step 2: 编写消费者失败的测试** `crates/erp-ai/src/event/copilot_consumer.rs` 底部: @@ -858,12 +858,12 @@ mod tests { } ``` -- [ ] **Step 3: 运行测试确认失败** +- [x] **Step 3: 运行测试确认失败** Run: `cargo test -p erp-ai -- copilot_consumer::tests` Expected: 编译失败 -- [ ] **Step 4: 实现事件消费者** +- [x] **Step 4: 实现事件消费者** `crates/erp-ai/src/event/copilot_consumer.rs`: @@ -941,7 +941,7 @@ async fn process_event(db: &sea_orm::DatabaseConnection, event: &DomainEvent) { } ``` -- [ ] **Step 5: 在 module.rs on_startup 中启动消费者** +- [x] **Step 5: 在 module.rs on_startup 中启动消费者** ```rust // 在 on_startup 方法中添加 @@ -949,21 +949,21 @@ let copilot_handles = crate::event::copilot_consumer::spawn(&ctx.db, &ctx.event_ std::mem::forget(copilot_handles); ``` -- [ ] **Step 6: 在 lib.rs 注册 event 模块** +- [x] **Step 6: 在 lib.rs 注册 event 模块** 添加 `pub mod event;` -- [ ] **Step 7: 运行测试** +- [x] **Step 7: 运行测试** Run: `cargo test -p erp-ai -- copilot_consumer::tests` Expected: 2 tests PASS -- [ ] **Step 8: 编译验证** +- [x] **Step 8: 编译验证** Run: `cargo check -p erp-ai` Expected: 编译通过 -- [ ] **Step 9: 提交** +- [x] **Step 9: 提交** ```bash git add crates/erp-ai/src/event/ crates/erp-ai/src/lib.rs crates/erp-ai/src/module.rs @@ -975,7 +975,7 @@ git commit -m "feat(ai): Copilot 事件消费者(订阅 health 事件)" **Files:** - Modify: `crates/erp-ai/src/copilot/scoring.rs` -- [ ] **Step 1: 编写 LLM 补充分析失败的测试** +- [x] **Step 1: 编写 LLM 补充分析失败的测试** 在 `scoring.rs` 底部添加测试: @@ -1019,7 +1019,7 @@ mod tests { } ``` -- [ ] **Step 2: 运行测试** +- [x] **Step 2: 运行测试** Run: `cargo test -p erp-ai -- copilot::scoring::tests` Expected: 3 tests PASS