fix(ai): AI 对话全链路修复 + 菜单配置 + 会话消息持久化
- 修复 ai_tenant_config Entity 表名错误(复数→单数)导致 budget_status 500
- 修复 ai_usage 表 SQL 引用不存在的 deleted_at 列
- 修复 risk_service SQL 列名/表名与实际数据库 schema 不匹配
- chat_handler provider 选择改为配置优先(default_provider→fallback chain)
- 新增 Ollama 非 FC provider 的 generate() 降级路径
- 新增 GET /ai/chat/sessions/{id}/messages 端点
- 前端 ChatPage 切换会话时从后端加载历史消息
- AiConfigPage 新增 default_provider 和 system_prompt 配置字段
- 迁移 000155-000156:AI 菜单调整 + AI 客服菜单 + 角色绑定
- 配额检查错误处理区分配额耗尽和 DB 异常
This commit is contained in:
@@ -138,7 +138,6 @@ impl CostService {
|
||||
SELECT COALESCE(SUM(input_tokens + output_tokens), 0) AS total
|
||||
FROM ai_usage
|
||||
WHERE tenant_id = $1
|
||||
AND deleted_at IS NULL
|
||||
AND created_at >= DATE_TRUNC('month', CURRENT_DATE)
|
||||
"#;
|
||||
|
||||
|
||||
@@ -175,66 +175,52 @@ impl RiskService {
|
||||
) -> AppResult<serde_json::Value> {
|
||||
use sea_orm::FromQueryResult;
|
||||
|
||||
// 最新一条体征数据(最近 30 天)
|
||||
// 体征数据:按 device_type 获取最近 30 天最新均值
|
||||
// vital_signs_daily 实际列: device_type, date_bucket, avg_val, min_val, max_val, sample_count
|
||||
#[derive(FromQueryResult)]
|
||||
struct VitalRow {
|
||||
systolic_bp_morning: Option<i32>,
|
||||
diastolic_bp_morning: Option<i32>,
|
||||
heart_rate: Option<i32>,
|
||||
blood_sugar: Option<f64>,
|
||||
weight: Option<f64>,
|
||||
spo2: Option<i32>,
|
||||
body_temperature: Option<f64>,
|
||||
device_type: String,
|
||||
avg_val: Option<f64>,
|
||||
}
|
||||
let vital: Option<VitalRow> = VitalRow::find_by_statement(
|
||||
sea_orm::Statement::from_sql_and_values(
|
||||
let vitals: Vec<VitalRow> =
|
||||
VitalRow::find_by_statement(sea_orm::Statement::from_sql_and_values(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"SELECT systolic_bp_morning, diastolic_bp_morning, heart_rate, blood_sugar, weight, spo2, body_temperature FROM vital_signs_daily WHERE tenant_id = $1 AND patient_id = $2 AND deleted_at IS NULL ORDER BY record_date DESC LIMIT 1",
|
||||
r#"SELECT device_type, avg_val FROM vital_signs_daily
|
||||
WHERE tenant_id = $1 AND patient_id = $2
|
||||
AND date_bucket >= CURRENT_DATE - INTERVAL '30 days'
|
||||
ORDER BY date_bucket DESC"#,
|
||||
[tenant_id.into(), patient_id.into()],
|
||||
),
|
||||
)
|
||||
.one(db)
|
||||
.await?;
|
||||
))
|
||||
.all(db)
|
||||
.await?;
|
||||
|
||||
// 最新化验报告异常计数(最近 90 天)
|
||||
// 化验报告异常项计数(最近 90 天)
|
||||
// lab_report 表: is_abnormal 字段在 indicators JSON 内,通过 JSON 查询统计
|
||||
#[derive(FromQueryResult)]
|
||||
struct LabAbnormal {
|
||||
report_type: String,
|
||||
abnormal_count: i64,
|
||||
}
|
||||
let lab_abnormals: Vec<LabAbnormal> = LabAbnormal::find_by_statement(
|
||||
sea_orm::Statement::from_sql_and_values(
|
||||
let lab_abnormals: Vec<LabAbnormal> =
|
||||
LabAbnormal::find_by_statement(sea_orm::Statement::from_sql_and_values(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"SELECT report_type, COUNT(*) as abnormal_count FROM lab_reports WHERE tenant_id = $1 AND patient_id = $2 AND deleted_at IS NULL AND is_abnormal = true AND report_date >= NOW() - INTERVAL '90 days' GROUP BY report_type",
|
||||
r#"SELECT report_type, COUNT(*) as abnormal_count FROM lab_report
|
||||
WHERE tenant_id = $1 AND patient_id = $2
|
||||
AND deleted_at IS NULL
|
||||
AND report_date >= CURRENT_DATE - INTERVAL '90 days'
|
||||
AND indicators @> '[{"is_abnormal":true}]'::jsonb
|
||||
GROUP BY report_type"#,
|
||||
[tenant_id.into(), patient_id.into()],
|
||||
),
|
||||
)
|
||||
.all(db)
|
||||
.await?;
|
||||
))
|
||||
.all(db)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut data = serde_json::Map::new();
|
||||
|
||||
if let Some(v) = vital {
|
||||
if let Some(bp_sys) = v.systolic_bp_morning {
|
||||
data.insert("systolic_bp_morning".into(), serde_json::json!(bp_sys));
|
||||
}
|
||||
if let Some(bp_dia) = v.diastolic_bp_morning {
|
||||
data.insert("diastolic_bp_morning".into(), serde_json::json!(bp_dia));
|
||||
}
|
||||
if let Some(hr) = v.heart_rate {
|
||||
data.insert("heart_rate".into(), serde_json::json!(hr));
|
||||
}
|
||||
if let Some(bs) = v.blood_sugar {
|
||||
data.insert("blood_sugar".into(), serde_json::json!(bs));
|
||||
}
|
||||
if let Some(w) = v.weight {
|
||||
data.insert("weight".into(), serde_json::json!(w));
|
||||
}
|
||||
if let Some(spo2) = v.spo2 {
|
||||
data.insert("spo2".into(), serde_json::json!(spo2));
|
||||
}
|
||||
if let Some(temp) = v.body_temperature {
|
||||
data.insert("body_temperature".into(), serde_json::json!(temp));
|
||||
for v in &vitals {
|
||||
if let Some(avg) = v.avg_val {
|
||||
data.insert(format!("vital_{}", v.device_type), serde_json::json!(avg));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user