package abnormal import ( "cpone-dashboard/db" ) type AbnormalSummary struct { Total int Abnormal int Normal int AbnormalRate int // percentage, 0-100 } type AgeChartData struct { Labels []string Abnormal []int } type GenderChartData struct { Labels []string Abnormal []int } type DeptChartData struct { Labels []string Abnormal []int } // GetTotalPatients returns total active patients for the project. func GetTotalPatients(mcuID int) (int, error) { var total int err := db.DB.QueryRow(` SELECT COUNT(*) FROM mcu_patient WHERE Mcu_PatientMcuID = ? AND Mcu_PatientIsActive = 'Y' `, mcuID).Scan(&total) return total, err } // GetAbnormalCount returns distinct patients with at least one kelainan. // If group is non-empty, counts only patients with kelainan in that group. func GetAbnormalCount(mcuID int, group string) (int, error) { var count int var err error if group == "" { err = db.DB.QueryRow(` SELECT COUNT(DISTINCT M_PatientID) FROM kelainan_details WHERE Mgm_McuID = ? `, mcuID).Scan(&count) } else { err = db.DB.QueryRow(` SELECT COUNT(DISTINCT M_PatientID) FROM kelainan_details WHERE Mgm_McuID = ? AND Mcu_KelainanGroupSummaryName = ? `, mcuID, group).Scan(&count) } return count, err } // GetAbnormalGroups returns distinct kelainan group names for a project. func GetAbnormalGroups(mcuID int) ([]string, error) { rows, err := db.DB.Query(` SELECT DISTINCT Mcu_KelainanGroupSummaryName FROM kelainan_details WHERE Mgm_McuID = ? AND Mcu_KelainanGroupSummaryName IS NOT NULL AND Mcu_KelainanGroupSummaryName != '' ORDER BY Mcu_KelainanGroupSummaryName `, mcuID) if err != nil { return nil, err } defer rows.Close() var groups []string for rows.Next() { var g string if err := rows.Scan(&g); err != nil { continue } groups = append(groups, g) } return groups, rows.Err() } // GetAgeChartData returns abnormal patient counts by age bucket. func GetAgeChartData(mcuID int, group string) (AgeChartData, error) { buckets := []string{"<30", "30-39", "40-49", "50+"} counts := make(map[string]int) var query string var args []any if group == "" { query = ` SELECT CASE WHEN AgePatient < 30 THEN '<30' WHEN AgePatient < 40 THEN '30-39' WHEN AgePatient < 50 THEN '40-49' ELSE '50+' END AS age_group, COUNT(DISTINCT M_PatientID) AS cnt FROM kelainan_details WHERE Mgm_McuID = ? AND AgePatient IS NOT NULL GROUP BY age_group` args = []any{mcuID} } else { query = ` SELECT CASE WHEN AgePatient < 30 THEN '<30' WHEN AgePatient < 40 THEN '30-39' WHEN AgePatient < 50 THEN '40-49' ELSE '50+' END AS age_group, COUNT(DISTINCT M_PatientID) AS cnt FROM kelainan_details WHERE Mgm_McuID = ? AND Mcu_KelainanGroupSummaryName = ? AND AgePatient IS NOT NULL GROUP BY age_group` args = []any{mcuID, group} } rows, err := db.DB.Query(query, args...) if err != nil { return AgeChartData{}, err } defer rows.Close() for rows.Next() { var label string var cnt int if err := rows.Scan(&label, &cnt); err != nil { continue } counts[label] = cnt } data := AgeChartData{Labels: buckets, Abnormal: make([]int, len(buckets))} for i, b := range buckets { data.Abnormal[i] = counts[b] } return data, rows.Err() } // GetGenderChartData returns abnormal patient counts by gender. func GetGenderChartData(mcuID int, group string) (GenderChartData, error) { labels := []string{"Male", "Female"} counts := make(map[string]int) var query string var args []any if group == "" { query = ` SELECT CASE WHEN LOWER(M_PatientGender) = 'male' THEN 'Male' ELSE 'Female' END AS gender, COUNT(DISTINCT M_PatientID) AS cnt FROM kelainan_details WHERE Mgm_McuID = ? AND M_PatientGender IS NOT NULL GROUP BY gender` args = []any{mcuID} } else { query = ` SELECT CASE WHEN LOWER(M_PatientGender) = 'male' THEN 'Male' ELSE 'Female' END AS gender, COUNT(DISTINCT M_PatientID) AS cnt FROM kelainan_details WHERE Mgm_McuID = ? AND Mcu_KelainanGroupSummaryName = ? AND M_PatientGender IS NOT NULL GROUP BY gender` args = []any{mcuID, group} } rows, err := db.DB.Query(query, args...) if err != nil { return GenderChartData{}, err } defer rows.Close() for rows.Next() { var label string var cnt int if err := rows.Scan(&label, &cnt); err != nil { continue } counts[label] = cnt } data := GenderChartData{Labels: labels, Abnormal: make([]int, len(labels))} for i, l := range labels { data.Abnormal[i] = counts[l] } return data, rows.Err() } // GetDeptChartData returns top-10 departments by abnormal patient count. func GetDeptChartData(mcuID int, group string) (DeptChartData, error) { var query string var args []any if group == "" { query = ` SELECT COALESCE( NULLIF(TRIM(M_PatientDepartement), ''), NULLIF(TRIM(M_PatientDivisi), ''), NULLIF(TRIM(M_PatientPosisi), ''), '-' ) AS dept, COUNT(DISTINCT M_PatientID) AS cnt FROM kelainan_details WHERE Mgm_McuID = ? GROUP BY dept ORDER BY cnt DESC LIMIT 10` args = []any{mcuID} } else { query = ` SELECT COALESCE( NULLIF(TRIM(M_PatientDepartement), ''), NULLIF(TRIM(M_PatientDivisi), ''), NULLIF(TRIM(M_PatientPosisi), ''), '-' ) AS dept, COUNT(DISTINCT M_PatientID) AS cnt FROM kelainan_details WHERE Mgm_McuID = ? AND Mcu_KelainanGroupSummaryName = ? GROUP BY dept ORDER BY cnt DESC LIMIT 10` args = []any{mcuID, group} } rows, err := db.DB.Query(query, args...) if err != nil { return DeptChartData{}, err } defer rows.Close() var data DeptChartData for rows.Next() { var label string var cnt int if err := rows.Scan(&label, &cnt); err != nil { continue } data.Labels = append(data.Labels, label) data.Abnormal = append(data.Abnormal, cnt) } return data, rows.Err() }