use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, sea_query::Expr, FromQueryResult}; use erp_core::error::AppResult; use crate::dto::stats_dto::*; use crate::entity::{ patient, consultation_session, follow_up_task, points_transaction, dialysis_record, lab_report, appointment, vital_signs, patient_doctor_relation, doctor_profile, }; use crate::state::HealthState; // --------------------------------------------------------------------------- // 基础运营统计 // --------------------------------------------------------------------------- pub async fn get_patient_statistics( state: &HealthState, tenant_id: uuid::Uuid, ) -> AppResult { let db = &state.db; let total = patient::Entity::find() .filter(patient::Column::TenantId.eq(tenant_id)) .filter(patient::Column::DeletedAt.is_null()) .count(db) .await?; let new_this_month = patient::Entity::find() .filter(patient::Column::TenantId.eq(tenant_id)) .filter(patient::Column::DeletedAt.is_null()) .filter(Expr::col(patient::Column::CreatedAt).gte(Expr::cust("date_trunc('month', NOW())"))) .count(db) .await?; let new_this_week = patient::Entity::find() .filter(patient::Column::TenantId.eq(tenant_id)) .filter(patient::Column::DeletedAt.is_null()) .filter(Expr::col(patient::Column::CreatedAt).gte(Expr::cust("date_trunc('week', NOW())"))) .count(db) .await?; let active_this_month = points_transaction::Entity::find() .filter(points_transaction::Column::TenantId.eq(tenant_id)) .filter(Expr::col(points_transaction::Column::CreatedAt).gte(Expr::cust("date_trunc('month', NOW())"))) .count(db) .await?; Ok(PatientStatisticsResp { total_patients: total as i64, new_this_month: new_this_month as i64, new_this_week: new_this_week as i64, active_this_month: active_this_month as i64, }) } pub async fn get_consultation_statistics( state: &HealthState, tenant_id: uuid::Uuid, ) -> AppResult { let db = &state.db; let total_sessions = consultation_session::Entity::find() .filter(consultation_session::Column::TenantId.eq(tenant_id)) .filter(consultation_session::Column::DeletedAt.is_null()) .count(db) .await?; let pending_reply = consultation_session::Entity::find() .filter(consultation_session::Column::TenantId.eq(tenant_id)) .filter(consultation_session::Column::DeletedAt.is_null()) .filter(consultation_session::Column::Status.eq("waiting")) .count(db) .await?; let this_month = consultation_session::Entity::find() .filter(consultation_session::Column::TenantId.eq(tenant_id)) .filter(consultation_session::Column::DeletedAt.is_null()) .filter(Expr::col(consultation_session::Column::CreatedAt).gte(Expr::cust("date_trunc('month', NOW())"))) .count(db) .await?; let avg_response_time_minutes = compute_avg_response_time(db, tenant_id).await?; Ok(ConsultationStatisticsResp { total_sessions: total_sessions as i64, pending_reply: pending_reply as i64, avg_response_time_minutes, this_month: this_month as i64, }) } pub async fn get_follow_up_statistics( state: &HealthState, tenant_id: uuid::Uuid, ) -> AppResult { let db = &state.db; // 单次 GROUP BY 查询替代 4 次独立 COUNT let sql = r#" SELECT COALESCE(status, '__total') AS status, COUNT(*) AS cnt FROM follow_up_task WHERE tenant_id = $1 AND deleted_at IS NULL GROUP BY GROUPING SETS ((status), ()) "#; #[derive(Debug, sea_orm::FromQueryResult)] struct StatusCount { status: String, cnt: i64, } let rows: Vec = sea_orm::FromQueryResult::find_by_statement( sea_orm::Statement::from_sql_and_values( sea_orm::DatabaseBackend::Postgres, sql, [tenant_id.into()], ), ) .all(db) .await?; let mut total_tasks: i64 = 0; let mut completed: i64 = 0; let mut pending: i64 = 0; let mut overdue: i64 = 0; for row in &rows { match row.status.as_str() { "__total" => total_tasks = row.cnt, "completed" => completed = row.cnt, "pending" => pending = row.cnt, "overdue" => overdue = row.cnt, _ => {} } } let completion_rate = if completed + pending + overdue > 0 { (completed as f64 / (completed + pending + overdue) as f64) * 100.0 } else { 0.0 }; Ok(FollowUpStatisticsResp { total_tasks, completed, pending, overdue, completion_rate, }) } #[derive(Debug, FromQueryResult)] struct AvgResponseTime { avg_minutes: Option, } async fn compute_avg_response_time( db: &sea_orm::DatabaseConnection, tenant_id: uuid::Uuid, ) -> AppResult> { let sql = r#" SELECT AVG(EXTRACT(EPOCH FROM (m.created_at - s.created_at)) / 60) AS avg_minutes FROM consultation_session s INNER JOIN consultation_message m ON m.session_id = s.id AND m.tenant_id = $1 AND m.deleted_at IS NULL WHERE s.tenant_id = $1 AND s.deleted_at IS NULL AND m.sender_role = 'doctor' "#; let result: Option = sea_orm::FromQueryResult::find_by_statement( sea_orm::Statement::from_sql_and_values( sea_orm::DatabaseBackend::Postgres, sql, [tenant_id.into()], ), ) .one(db) .await?; Ok(result.and_then(|r| r.avg_minutes)) } // --------------------------------------------------------------------------- // 健康数据统计 // --------------------------------------------------------------------------- pub async fn get_dialysis_statistics( state: &HealthState, tenant_id: uuid::Uuid, ) -> AppResult { let db = &state.db; let total_records = dialysis_record::Entity::find() .filter(dialysis_record::Column::TenantId.eq(tenant_id)) .filter(dialysis_record::Column::DeletedAt.is_null()) .count(db) .await?; let this_month = dialysis_record::Entity::find() .filter(dialysis_record::Column::TenantId.eq(tenant_id)) .filter(dialysis_record::Column::DeletedAt.is_null()) .filter(Expr::col(dialysis_record::Column::CreatedAt).gte(Expr::cust("date_trunc('month', NOW())"))) .count(db) .await?; let pending_review = dialysis_record::Entity::find() .filter(dialysis_record::Column::TenantId.eq(tenant_id)) .filter(dialysis_record::Column::DeletedAt.is_null()) .filter(dialysis_record::Column::Status.eq("draft")) .count(db) .await?; let type_distribution = count_by_field( db, tenant_id, "SELECT dialysis_type AS name, COUNT(*) AS value FROM dialysis_record \ WHERE tenant_id = $1 AND deleted_at IS NULL \ AND created_at >= date_trunc('month', NOW()) \ GROUP BY dialysis_type ORDER BY value DESC", ).await?; let complication_rate = compute_complication_rate(db, tenant_id).await?; let avg_ultrafiltration = compute_avg_field(db, tenant_id, "ultrafiltration_volume").await?; let avg_duration = compute_avg_field(db, tenant_id, "dialysis_duration").await?; Ok(DialysisStatisticsResp { total_records: total_records as i64, this_month: this_month as i64, type_distribution, complication_rate, avg_ultrafiltration, avg_duration, pending_review: pending_review as i64, }) } pub async fn get_lab_report_statistics( state: &HealthState, tenant_id: uuid::Uuid, ) -> AppResult { let db = &state.db; let total_reports = lab_report::Entity::find() .filter(lab_report::Column::TenantId.eq(tenant_id)) .filter(lab_report::Column::DeletedAt.is_null()) .count(db) .await?; let this_month = lab_report::Entity::find() .filter(lab_report::Column::TenantId.eq(tenant_id)) .filter(lab_report::Column::DeletedAt.is_null()) .filter(Expr::col(lab_report::Column::CreatedAt).gte(Expr::cust("date_trunc('month', NOW())"))) .count(db) .await?; let pending_review = lab_report::Entity::find() .filter(lab_report::Column::TenantId.eq(tenant_id)) .filter(lab_report::Column::DeletedAt.is_null()) .filter(lab_report::Column::Status.eq("pending")) .count(db) .await?; let reviewed = lab_report::Entity::find() .filter(lab_report::Column::TenantId.eq(tenant_id)) .filter(lab_report::Column::DeletedAt.is_null()) .filter(lab_report::Column::Status.eq("reviewed")) .count(db) .await?; let type_distribution = count_by_field( db, tenant_id, "SELECT report_type AS name, COUNT(*) AS value FROM lab_report \ WHERE tenant_id = $1 AND deleted_at IS NULL \ AND created_at >= date_trunc('month', NOW()) \ GROUP BY report_type ORDER BY value DESC", ).await?; let abnormal_items = count_abnormal_lab_items(db, tenant_id).await?; Ok(LabReportStatisticsResp { total_reports: total_reports as i64, this_month: this_month as i64, type_distribution, abnormal_items, pending_review: pending_review as i64, reviewed: reviewed as i64, }) } pub async fn get_appointment_statistics( state: &HealthState, tenant_id: uuid::Uuid, ) -> AppResult { let db = &state.db; let total_appointments = appointment::Entity::find() .filter(appointment::Column::TenantId.eq(tenant_id)) .filter(appointment::Column::DeletedAt.is_null()) .count(db) .await?; let this_month = appointment::Entity::find() .filter(appointment::Column::TenantId.eq(tenant_id)) .filter(appointment::Column::DeletedAt.is_null()) .filter(Expr::col(appointment::Column::CreatedAt).gte(Expr::cust("date_trunc('month', NOW())"))) .count(db) .await?; let status_distribution = count_by_field( db, tenant_id, "SELECT status AS name, COUNT(*) AS value FROM appointment \ WHERE tenant_id = $1 AND deleted_at IS NULL \ AND created_at >= date_trunc('month', NOW()) \ GROUP BY status ORDER BY value DESC", ).await?; let type_distribution = count_by_field( db, tenant_id, "SELECT appointment_type AS name, COUNT(*) AS value FROM appointment \ WHERE tenant_id = $1 AND deleted_at IS NULL \ AND created_at >= date_trunc('month', NOW()) \ GROUP BY appointment_type ORDER BY value DESC", ).await?; let cancelled = appointment::Entity::find() .filter(appointment::Column::TenantId.eq(tenant_id)) .filter(appointment::Column::DeletedAt.is_null()) .filter(Expr::col(appointment::Column::CreatedAt).gte(Expr::cust("date_trunc('month', NOW())"))) .filter(appointment::Column::Status.eq("cancelled")) .count(db) .await?; let cancel_rate = if this_month > 0 { (cancelled as f64 / this_month as f64) * 100.0 } else { 0.0 }; Ok(AppointmentStatisticsResp { total_appointments: total_appointments as i64, this_month: this_month as i64, status_distribution, type_distribution, cancel_rate, }) } pub async fn get_vital_signs_report_rate( state: &HealthState, tenant_id: uuid::Uuid, ) -> AppResult { let db = &state.db; let total_patients = patient::Entity::find() .filter(patient::Column::TenantId.eq(tenant_id)) .filter(patient::Column::DeletedAt.is_null()) .count(db) .await?; let total_records = vital_signs::Entity::find() .filter(vital_signs::Column::TenantId.eq(tenant_id)) .filter(vital_signs::Column::DeletedAt.is_null()) .filter(Expr::col(vital_signs::Column::CreatedAt).gte(Expr::cust("date_trunc('month', NOW())"))) .count(db) .await?; let reported_patients = count_distinct_patients_vital_signs(db, tenant_id).await?; let report_rate = if total_patients > 0 { (reported_patients as f64 / total_patients as f64) * 100.0 } else { 0.0 }; let daily_trend = compute_daily_report_rate(db, tenant_id).await?; Ok(VitalSignsReportRateResp { total_patients: total_patients as i64, reported_patients: reported_patients as i64, report_rate, total_records: total_records as i64, daily_trend, }) } pub async fn get_health_data_stats( state: &HealthState, tenant_id: uuid::Uuid, ) -> AppResult { let dialysis = get_dialysis_statistics(state, tenant_id).await?; let lab_reports = get_lab_report_statistics(state, tenant_id).await?; let appointments = get_appointment_statistics(state, tenant_id).await?; let vital_signs_report_rate = get_vital_signs_report_rate(state, tenant_id).await?; Ok(HealthDataStatsResp { dialysis, lab_reports, appointments, vital_signs_report_rate, }) } // --------------------------------------------------------------------------- // 辅助查询 // --------------------------------------------------------------------------- #[derive(Debug, FromQueryResult)] struct NameValueRow { name: String, value: i64, } async fn count_by_field( db: &sea_orm::DatabaseConnection, tenant_id: uuid::Uuid, sql: &str, ) -> AppResult> { let rows: Vec = sea_orm::FromQueryResult::find_by_statement( sea_orm::Statement::from_sql_and_values( sea_orm::DatabaseBackend::Postgres, sql, [tenant_id.into()], ), ) .all(db) .await?; Ok(rows.into_iter().map(|r| NameValue { name: r.name, value: r.value }).collect()) } #[derive(Debug, FromQueryResult)] struct AvgFieldResult { avg_val: Option, } macro_rules! avg_field_sql { ($field:literal) => { concat!( "SELECT AVG(", $field, ")::FLOAT8 AS avg_val FROM dialysis_record ", "WHERE tenant_id = $1 AND deleted_at IS NULL AND ", $field, " IS NOT NULL ", "AND created_at >= date_trunc('month', NOW())" ) }; } async fn compute_avg_field( db: &sea_orm::DatabaseConnection, tenant_id: uuid::Uuid, field: &str, ) -> AppResult> { let sql = match field { "ultrafiltration_volume" => avg_field_sql!("ultrafiltration_volume"), "dialysis_duration" => avg_field_sql!("dialysis_duration"), "uf_volume" => avg_field_sql!("uf_volume"), "uf_rate" => avg_field_sql!("uf_rate"), "blood_flow_rate" => avg_field_sql!("blood_flow_rate"), "dialysate_flow_rate" => avg_field_sql!("dialysate_flow_rate"), "pre_weight" => avg_field_sql!("pre_weight"), "post_weight" => avg_field_sql!("post_weight"), "pre_bp_systolic" => avg_field_sql!("pre_bp_systolic"), "pre_bp_diastolic" => avg_field_sql!("pre_bp_diastolic"), "post_bp_systolic" => avg_field_sql!("post_bp_systolic"), "post_bp_diastolic" => avg_field_sql!("post_bp_diastolic"), _ => return Err(erp_core::error::AppError::Validation(format!("不允许的字段名: {field}"))), }; let result: Option = sea_orm::FromQueryResult::find_by_statement( sea_orm::Statement::from_sql_and_values( sea_orm::DatabaseBackend::Postgres, sql, [tenant_id.into()], ), ) .one(db) .await?; Ok(result.and_then(|r| r.avg_val)) } async fn compute_complication_rate( db: &sea_orm::DatabaseConnection, tenant_id: uuid::Uuid, ) -> AppResult { let sql = r#" SELECT COUNT(*) FILTER (WHERE complication_notes IS NOT NULL AND complication_notes != '') AS with_comp, COUNT(*) AS total FROM dialysis_record WHERE tenant_id = $1 AND deleted_at IS NULL AND created_at >= date_trunc('month', NOW()) "#; #[derive(Debug, FromQueryResult)] struct CompResult { with_comp: i64, total: i64, } let result: Option = sea_orm::FromQueryResult::find_by_statement( sea_orm::Statement::from_sql_and_values( sea_orm::DatabaseBackend::Postgres, sql, [tenant_id.into()], ), ) .one(db) .await?; Ok(match result { Some(r) if r.total > 0 => (r.with_comp as f64 / r.total as f64) * 100.0, _ => 0.0, }) } async fn count_abnormal_lab_items( db: &sea_orm::DatabaseConnection, tenant_id: uuid::Uuid, ) -> AppResult { let sql = r#" SELECT COALESCE(SUM(jsonb_array_length( COALESCE( (SELECT jsonb_agg(elem) FROM jsonb_array_elements(items) elem WHERE elem->>'is_abnormal' = 'true'), '[]'::jsonb ) )), 0) AS total FROM lab_report WHERE tenant_id = $1 AND deleted_at IS NULL AND items IS NOT NULL AND created_at >= date_trunc('month', NOW()) "#; #[derive(Debug, FromQueryResult)] struct AbnormalCount { total: Option, } let result: Option = sea_orm::FromQueryResult::find_by_statement( sea_orm::Statement::from_sql_and_values( sea_orm::DatabaseBackend::Postgres, sql, [tenant_id.into()], ), ) .one(db) .await?; Ok(result.and_then(|r| r.total).unwrap_or(0)) } async fn count_distinct_patients_vital_signs( db: &sea_orm::DatabaseConnection, tenant_id: uuid::Uuid, ) -> AppResult { let sql = r#" SELECT COUNT(DISTINCT patient_id) AS cnt FROM vital_signs WHERE tenant_id = $1 AND deleted_at IS NULL AND created_at >= date_trunc('month', NOW()) "#; #[derive(Debug, FromQueryResult)] struct DistinctCount { cnt: i64, } let result: Option = sea_orm::FromQueryResult::find_by_statement( sea_orm::Statement::from_sql_and_values( sea_orm::DatabaseBackend::Postgres, sql, [tenant_id.into()], ), ) .one(db) .await?; Ok(result.map(|r| r.cnt as u64).unwrap_or(0)) } async fn compute_daily_report_rate( db: &sea_orm::DatabaseConnection, tenant_id: uuid::Uuid, ) -> AppResult> { let sql = r#" SELECT d::date::text AS date, COUNT(DISTINCT vs.patient_id) AS reported, 0 AS total FROM generate_series( CURRENT_DATE - INTERVAL '6 days', CURRENT_DATE, INTERVAL '1 day' ) d LEFT JOIN vital_signs vs ON vs.record_date = d::date AND vs.tenant_id = $1 AND vs.deleted_at IS NULL GROUP BY d::date ORDER BY d::date "#; #[derive(Debug, FromQueryResult)] struct DailyRow { date: String, reported: i64, total: i64, } let rows: Vec = sea_orm::FromQueryResult::find_by_statement( sea_orm::Statement::from_sql_and_values( sea_orm::DatabaseBackend::Postgres, sql, [tenant_id.into()], ), ) .all(db) .await?; let total_patients = patient::Entity::find() .filter(patient::Column::TenantId.eq(tenant_id)) .filter(patient::Column::DeletedAt.is_null()) .count(db) .await?; Ok(rows.into_iter().map(|r| { let total = total_patients as i64; let rate = if total > 0 { (r.reported as f64 / total as f64) * 100.0 } else { 0.0 }; DailyReportRate { date: r.date, reported: r.reported, total, rate } }).collect()) } // --------------------------------------------------------------------------- // 个人维度统计 // --------------------------------------------------------------------------- pub async fn get_personal_stats( state: &HealthState, user_id: uuid::Uuid, tenant_id: uuid::Uuid, ) -> AppResult { let db = &state.db; // 通过 user_id 查找 doctor_profile 以获得 doctor_id let doctor_profile = doctor_profile::Entity::find() .filter(doctor_profile::Column::TenantId.eq(tenant_id)) .filter(doctor_profile::Column::DeletedAt.is_null()) .filter(doctor_profile::Column::UserId.eq(user_id)) .one(db) .await?; let doctor_id = doctor_profile.map(|p| p.id); // my_patients: 通过 patient_doctor_relation 统计 let my_patients = if let Some(did) = doctor_id { patient_doctor_relation::Entity::find() .filter(patient_doctor_relation::Column::TenantId.eq(tenant_id)) .filter(patient_doctor_relation::Column::DeletedAt.is_null()) .filter(patient_doctor_relation::Column::DoctorId.eq(did)) .count(db) .await? as i64 } else { 0 }; // new_patients_this_month: 本月新增关联患者 let new_patients_this_month = if let Some(did) = doctor_id { let sql = r#" SELECT COUNT(*) AS cnt FROM patient_doctor_relation pdr INNER JOIN patient p ON p.id = pdr.patient_id AND p.deleted_at IS NULL AND p.tenant_id = $1 WHERE pdr.tenant_id = $1 AND pdr.deleted_at IS NULL AND pdr.doctor_id = $2 AND p.created_at >= date_trunc('month', NOW()) "#; #[derive(Debug, FromQueryResult)] struct Cnt { cnt: i64, } let result: Option = FromQueryResult::find_by_statement( sea_orm::Statement::from_sql_and_values( sea_orm::DatabaseBackend::Postgres, sql, [tenant_id.into(), did.into()], ), ) .one(db) .await?; result.map(|r| r.cnt).unwrap_or(0) } else { 0 }; // follow_up_rate: 分配给当前用户的随访完成率 let sql = r#" SELECT COALESCE(status, '__total') AS status, COUNT(*) AS cnt FROM follow_up_task WHERE tenant_id = $1 AND deleted_at IS NULL AND assigned_to = $2 GROUP BY GROUPING SETS ((status), ()) "#; #[derive(Debug, FromQueryResult)] struct StatusCount { status: String, cnt: i64, } let fu_rows: Vec = FromQueryResult::find_by_statement( sea_orm::Statement::from_sql_and_values( sea_orm::DatabaseBackend::Postgres, sql, [tenant_id.into(), user_id.into()], ), ) .all(db) .await?; let mut fu_total: i64 = 0; let mut fu_completed: i64 = 0; let mut overdue_follow_ups: i64 = 0; for row in &fu_rows { match row.status.as_str() { "__total" => fu_total = row.cnt, "completed" => fu_completed = row.cnt, "overdue" => overdue_follow_ups = row.cnt, _ => {} } } let follow_up_rate = if fu_total > 0 { (fu_completed as f64 / fu_total as f64) * 100.0 } else { 0.0 }; // consultations_this_month / pending_consultations: 咨询统计 let consultations_this_month = if let Some(did) = doctor_id { consultation_session::Entity::find() .filter(consultation_session::Column::TenantId.eq(tenant_id)) .filter(consultation_session::Column::DeletedAt.is_null()) .filter(consultation_session::Column::DoctorId.eq(did)) .filter(Expr::col(consultation_session::Column::CreatedAt).gte(Expr::cust("date_trunc('month', NOW())"))) .count(db) .await? as i64 } else { 0 }; let pending_consultations = if let Some(did) = doctor_id { consultation_session::Entity::find() .filter(consultation_session::Column::TenantId.eq(tenant_id)) .filter(consultation_session::Column::DeletedAt.is_null()) .filter(consultation_session::Column::DoctorId.eq(did)) .filter(consultation_session::Column::Status.eq("active")) .count(db) .await? as i64 } else { 0 }; // today_appointments: 今日预约 let today_appointments = if let Some(did) = doctor_id { appointment::Entity::find() .filter(appointment::Column::TenantId.eq(tenant_id)) .filter(appointment::Column::DeletedAt.is_null()) .filter(appointment::Column::DoctorId.eq(did)) .filter(Expr::col(appointment::Column::AppointmentDate).eq(Expr::cust("CURRENT_DATE"))) .count(db) .await? as i64 } else { 0 }; // today_follow_ups: 今日随访任务 let today_follow_ups = follow_up_task::Entity::find() .filter(follow_up_task::Column::TenantId.eq(tenant_id)) .filter(follow_up_task::Column::DeletedAt.is_null()) .filter(follow_up_task::Column::AssignedTo.eq(user_id)) .filter(Expr::col(follow_up_task::Column::PlannedDate).eq(Expr::cust("CURRENT_DATE"))) .count(db) .await? as i64; // vital_signs_report_rate: 当前医生的患者体征上报率 let (vital_signs_reported, vital_signs_total, vital_signs_report_rate) = if my_patients > 0 { let vs_sql = r#" SELECT COUNT(DISTINCT vs.patient_id) AS reported, $3::bigint AS total FROM vital_signs vs WHERE vs.tenant_id = $1 AND vs.deleted_at IS NULL AND vs.created_at >= date_trunc('month', NOW()) AND vs.patient_id IN ( SELECT patient_id FROM patient_doctor_relation WHERE doctor_id = $2 AND tenant_id = $1 AND deleted_at IS NULL ) "#; #[derive(Debug, FromQueryResult)] struct VsCount { reported: i64, total: i64, } let result: Option = FromQueryResult::find_by_statement( sea_orm::Statement::from_sql_and_values( sea_orm::DatabaseBackend::Postgres, vs_sql, [tenant_id.into(), doctor_id.unwrap_or_default().into(), my_patients.into()], ), ) .one(db) .await?; match result { Some(r) => { let rate = if r.total > 0 { (r.reported as f64 / r.total as f64) * 100.0 } else { 0.0 }; (r.reported, r.total, rate) } None => (0, my_patients, 0.0), } } else { (0, 0, 0.0) }; // pending_lab_reviews: 待审核化验报告(与当前医生的患者关联) let pending_lab_reviews = if doctor_id.is_some() { let lr_sql = r#" SELECT COUNT(*) AS cnt FROM lab_report lr WHERE lr.tenant_id = $1 AND lr.deleted_at IS NULL AND lr.status = 'pending' AND lr.patient_id IN ( SELECT patient_id FROM patient_doctor_relation WHERE doctor_id = $2 AND tenant_id = $1 AND deleted_at IS NULL ) "#; #[derive(Debug, FromQueryResult)] struct LrCnt { cnt: i64, } let result: Option = FromQueryResult::find_by_statement( sea_orm::Statement::from_sql_and_values( sea_orm::DatabaseBackend::Postgres, lr_sql, [tenant_id.into(), doctor_id.unwrap_or_default().into()], ), ) .one(db) .await?; result.map(|r| r.cnt).unwrap_or(0) } else { 0 }; // abnormal_vital_signs: 简化实现,返回 0(完整实现需要关联危急值阈值配置) let abnormal_vital_signs: i64 = 0; Ok(PersonalStatsResp { my_patients, new_patients_this_month, follow_up_rate, consultations_this_month, pending_consultations, vital_signs_report_rate, today_appointments, overdue_follow_ups, today_follow_ups, abnormal_vital_signs, vital_signs_reported, vital_signs_total, pending_lab_reviews, }) }