package main import ( "encoding/json" "fmt" "log" "math" "os" "strings" "time" ) const dataFile = "/data/chu_stats.json" type CHUStats struct { GeneratedAt time.Time `json:"generated_at"` HospitalName string `json:"hospital_name"` Date string `json:"date"` BedsTotal int `json:"beds_total"` BedsOccupied int `json:"beds_occupied"` OccupancyRate float64 `json:"occupancy_rate"` PatientsAdmitted int `json:"patients_admitted"` PatientsDischarge int `json:"patients_discharged"` EmergencyVisits int `json:"emergency_visits"` AvgERWaitMinutes int `json:"avg_er_wait_minutes"` SurgeriesPerformed int `json:"surgeries_performed"` ICUBeds int `json:"icu_beds"` ICUOccupied int `json:"icu_occupied"` MortalityRate float64 `json:"mortality_rate"` InfectionRate float64 `json:"infection_rate"` StaffPresent int `json:"staff_present"` StaffRequired int `json:"staff_required"` PatientSatisfaction float64 `json:"patient_satisfaction"` } type Check struct { Indicator string `json:"indicator"` Value float64 `json:"value"` Unit string `json:"unit"` Threshold string `json:"threshold"` Conformant bool `json:"conformant"` Severity string `json:"severity"` Message string `json:"message"` } type Analysis struct { AnalyzedAt time.Time `json:"analyzed_at"` HospitalName string `json:"hospital_name"` StatsDate string `json:"stats_date"` Conformant bool `json:"conformant"` Score int `json:"score"` PassingChecks int `json:"passing_checks"` TotalChecks int `json:"total_checks"` CriticalAlerts int `json:"critical_alerts"` WarningAlerts int `json:"warning_alerts"` Checks []Check `json:"checks"` Summary string `json:"summary"` } func round2(v float64) float64 { return math.Round(v*100) / 100 } func analyze(s CHUStats) Analysis { var checks []Check occ := Check{Indicator: "Taux d'occupation des lits", Value: s.OccupancyRate, Unit: "%", Threshold: "< 85%"} switch { case s.OccupancyRate < 85: occ.Conformant, occ.Severity, occ.Message = true, "ok", "Taux d'occupation normal" case s.OccupancyRate < 95: occ.Conformant, occ.Severity, occ.Message = false, "warning", "Taux d'occupation élevé, risque de saturation" default: occ.Conformant, occ.Severity, occ.Message = false, "critical", "Saturation critique des lits hospitaliers" } checks = append(checks, occ) er := Check{Indicator: "Temps d'attente aux urgences", Value: float64(s.AvgERWaitMinutes), Unit: "min", Threshold: "< 60 min"} switch { case s.AvgERWaitMinutes < 60: er.Conformant, er.Severity, er.Message = true, "ok", "Temps d'attente aux urgences acceptable" case s.AvgERWaitMinutes < 120: er.Conformant, er.Severity, er.Message = false, "warning", "Temps d'attente aux urgences trop long" default: er.Conformant, er.Severity, er.Message = false, "critical", "Saturation critique des urgences" } checks = append(checks, er) icuRate := round2(float64(s.ICUOccupied) / float64(s.ICUBeds) * 100) icu := Check{Indicator: "Taux d'occupation ICU", Value: icuRate, Unit: "%", Threshold: "< 85%"} switch { case icuRate < 85: icu.Conformant, icu.Severity, icu.Message = true, "ok", "Capacité de réanimation suffisante" case icuRate < 95: icu.Conformant, icu.Severity, icu.Message = false, "warning", "Taux d'occupation ICU élevé" default: icu.Conformant, icu.Severity, icu.Message = false, "critical", "Réanimation en situation critique" } checks = append(checks, icu) mort := Check{Indicator: "Taux de mortalité hospitalière", Value: s.MortalityRate, Unit: "%", Threshold: "< 2%"} switch { case s.MortalityRate < 2.0: mort.Conformant, mort.Severity, mort.Message = true, "ok", "Taux de mortalité dans les normes" case s.MortalityRate < 4.0: mort.Conformant, mort.Severity, mort.Message = false, "warning", "Taux de mortalité supérieur au seuil recommandé" default: mort.Conformant, mort.Severity, mort.Message = false, "critical", "Taux de mortalité critique — investigation requise" } checks = append(checks, mort) infect := Check{Indicator: "Taux d'infection nosocomiale", Value: s.InfectionRate, Unit: "%", Threshold: "< 5%"} switch { case s.InfectionRate < 5.0: infect.Conformant, infect.Severity, infect.Message = true, "ok", "Taux d'infection nosocomiale acceptable" case s.InfectionRate < 8.0: infect.Conformant, infect.Severity, infect.Message = false, "warning", "Taux d'infection nosocomiale préoccupant" default: infect.Conformant, infect.Severity, infect.Message = false, "critical", "Risque infectieux critique — mesures d'urgence requises" } checks = append(checks, infect) staffRate := round2(float64(s.StaffPresent) / float64(s.StaffRequired) * 100) staff := Check{Indicator: "Taux de couverture du personnel", Value: staffRate, Unit: "%", Threshold: ">= 80%"} switch { case staffRate >= 80: staff.Conformant, staff.Severity, staff.Message = true, "ok", "Personnel suffisant" case staffRate >= 70: staff.Conformant, staff.Severity, staff.Message = false, "warning", "Manque de personnel, vigilance requise" default: staff.Conformant, staff.Severity, staff.Message = false, "critical", "Sous-effectif critique" } checks = append(checks, staff) sat := Check{Indicator: "Satisfaction des patients", Value: s.PatientSatisfaction, Unit: "/ 5", Threshold: ">= 3.5 / 5"} switch { case s.PatientSatisfaction >= 3.5: sat.Conformant, sat.Severity, sat.Message = true, "ok", "Satisfaction des patients satisfaisante" case s.PatientSatisfaction >= 3.0: sat.Conformant, sat.Severity, sat.Message = false, "warning", "Satisfaction des patients insuffisante" default: sat.Conformant, sat.Severity, sat.Message = false, "critical", "Satisfaction des patients très mauvaise" } checks = append(checks, sat) passing, critical, warning := 0, 0, 0 for _, c := range checks { if c.Conformant { passing++ } switch c.Severity { case "critical": critical++ case "warning": warning++ } } total := len(checks) score := passing * 100 / total return Analysis{ AnalyzedAt: time.Now().UTC(), HospitalName: s.HospitalName, StatsDate: s.Date, Conformant: passing == total, Score: score, PassingChecks: passing, TotalChecks: total, CriticalAlerts: critical, WarningAlerts: warning, Checks: checks, Summary: fmt.Sprintf( "%d/%d indicateurs conformes (score: %d%%) — %d alerte(s) critique(s), %d avertissement(s)", passing, total, score, critical, warning, ), } } func main() { for i := range []int{0, 1} { data, err := os.ReadFile(strings.ReplaceAll(dataFile, "stats", "stats"+fmt.Sprintf("%v", i))) if err != nil { if os.IsNotExist(err) { log.Fatalf("stats file not found at %s — run chu-stats-generator first", dataFile) } log.Fatalf("failed to read stats file: %v", err) } var stats CHUStats if err := json.Unmarshal(data, &stats); err != nil { log.Fatalf("failed to parse stats file: %v", err) } analysis := analyze(stats) result, err := json.MarshalIndent(analysis, "", " ") if err != nil { log.Fatalf("failed to marshal analysis: %v", err) } log.Printf("Analysis complete: %s", analysis.Summary) fmt.Println(string(result)) } }