Skip to content

Commit b7f4104

Browse files
committed
fix: add logic changes per pr comments
1 parent bca5d58 commit b7f4104

File tree

2 files changed

+36
-53
lines changed

2 files changed

+36
-53
lines changed

backend/src/database/programs.go

Lines changed: 33 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ func (db *DB) FetchEnrollmentMetrics(programID int, facilityId uint) (*models.Pr
3434
COUNT(CASE WHEN pce.enrollment_status = 'Completed' THEN 1 END) AS completions,
3535
COUNT(CASE WHEN pce.enrolled_at IS NOT NULL THEN 1 END) AS total_enrollments,
3636
COUNT(DISTINCT CASE WHEN pce.enrollment_status = 'Enrolled' AND pce.enrolled_at IS NOT NULL AND (pce.enrollment_ended_at IS NULL OR pce.enrollment_ended_at > CURRENT_TIMESTAMP) THEN pce.user_id END) as active_residents,
37-
CASE
37+
CASE
3838
WHEN COUNT(CASE WHEN pce.enrolled_at IS NOT NULL THEN 1 END) = 0 THEN 0
3939
WHEN COUNT(CASE WHEN pce.enrollment_status = 'Enrolled' AND pce.enrolled_at IS NOT NULL AND (pce.enrollment_ended_at IS NULL OR pce.enrollment_ended_at > CURRENT_TIMESTAMP) THEN 1 END) = 0
40-
AND COUNT(CASE WHEN pce.enrollment_status = 'Completed' THEN 1 END) = COUNT(CASE WHEN pce.enrolled_at IS NOT NULL THEN 1 END)
40+
AND COUNT(CASE WHEN pce.enrollment_status = 'Completed' THEN 1 END) = COUNT(CASE WHEN pce.enrolled_at IS NOT NULL THEN 1 END)
4141
THEN 100
4242
ELSE COUNT(CASE WHEN pce.enrollment_status = 'Completed' THEN 1 END) * 1.0 / COUNT(CASE WHEN pce.enrolled_at IS NOT NULL THEN 1 END) * 100
4343
END AS completion_rate
@@ -363,20 +363,14 @@ func (db *DB) GetProgramsFacilitiesStats(args *models.QueryContext, timeFilter i
363363
attendanceTimeFilter = completionTimeFilter
364364
}
365365

366-
castMinutes := "COALESCE(pcea.minutes_attended, pcea.scheduled_minutes, 0)::numeric"
367-
if db.Name() == "sqlite" {
368-
castMinutes = "CAST(COALESCE(pcea.minutes_attended, pcea.scheduled_minutes, 0) AS REAL)"
369-
}
370-
ratioExpr := fmt.Sprintf("%s / NULLIF(COALESCE(pcea.scheduled_minutes, pcea.minutes_attended, 0), 0)", castMinutes)
371-
ratioCapped := fmt.Sprintf("CASE WHEN %s < 1 THEN %s ELSE 1 END", ratioExpr, ratioExpr)
372-
366+
partialAttendanceSQL := buildPartialAttendanceSQL(db.Name(), "pcea")
373367
var avgResult struct {
374368
TotalAssociations int64 `json:"total_associations"`
375369
TotalFacilities int64 `json:"total_facilities"`
376370
}
377371

378372
if err := db.WithContext(args.Ctx).Raw(`
379-
SELECT
373+
SELECT
380374
COUNT(DISTINCT fp.id) as total_associations,
381375
COUNT(DISTINCT f.id) as total_facilities
382376
FROM facilities f
@@ -396,8 +390,8 @@ func (db *DB) GetProgramsFacilitiesStats(args *models.QueryContext, timeFilter i
396390
}
397391

398392
query := fmt.Sprintf(`
399-
SELECT
400-
COALESCE(COUNT(CASE WHEN pce.enrollment_status = 'Completed' AND pce.enrolled_at IS NOT NULL %s THEN 1 END) * 100.0 /
393+
SELECT
394+
COALESCE(COUNT(CASE WHEN pce.enrollment_status = 'Completed' AND pce.enrolled_at IS NOT NULL %s THEN 1 END) * 100.0 /
401395
NULLIF(COUNT(CASE WHEN pce.enrolled_at IS NOT NULL THEN 1 END), 0), 0) AS completion_rate,
402396
COALESCE(SUM(
403397
CASE
@@ -412,7 +406,7 @@ func (db *DB) GetProgramsFacilitiesStats(args *models.QueryContext, timeFilter i
412406
LEFT JOIN program_class_events pcev ON pcev.class_id = pc.id
413407
LEFT JOIN program_class_event_attendance pcea ON pcea.event_id = pcev.id AND pcea.user_id = pce.user_id
414408
WHERE pce.enrolled_at IS NOT NULL %s
415-
`, completionTimeFilter, attendanceTimeFilter, attendanceTimeFilter, ratioCapped, attendanceTimeFilter, completionTimeFilter)
409+
`, completionTimeFilter, attendanceTimeFilter, attendanceTimeFilter, partialAttendanceSQL, attendanceTimeFilter, completionTimeFilter)
416410

417411
var completionAttendanceRates struct {
418412
AttendanceRate float64 `json:"attendance_rate"`
@@ -477,16 +471,10 @@ func (db *DB) GetProgramsFacilityStats(args *models.QueryContext, timeFilter int
477471
attendanceTimeFilter = completionTimeFilter
478472
}
479473

480-
castMinutes := "COALESCE(pcea.minutes_attended, pcea.scheduled_minutes, 0)::numeric"
481-
if db.Name() == "sqlite" {
482-
castMinutes = "CAST(COALESCE(pcea.minutes_attended, pcea.scheduled_minutes, 0) AS REAL)"
483-
}
484-
ratioExpr := fmt.Sprintf("%s / NULLIF(COALESCE(pcea.scheduled_minutes, pcea.minutes_attended, 0), 0)", castMinutes)
485-
ratioCapped := fmt.Sprintf("CASE WHEN %s < 1 THEN %s ELSE 1 END", ratioExpr, ratioExpr)
486-
474+
partialAttendanceSQL := buildPartialAttendanceSQL(db.Name(), "pcea")
487475
query := fmt.Sprintf(`
488-
SELECT
489-
COALESCE(COUNT(CASE WHEN pce.enrollment_status = 'Completed' AND pce.enrolled_at IS NOT NULL %s THEN 1 END) * 100.0 /
476+
SELECT
477+
COALESCE(COUNT(CASE WHEN pce.enrollment_status = 'Completed' AND pce.enrolled_at IS NOT NULL %s THEN 1 END) * 100.0 /
490478
NULLIF(COUNT(CASE WHEN pce.enrolled_at IS NOT NULL THEN 1 END), 0), 0) AS completion_rate,
491479
COALESCE(SUM(
492480
CASE
@@ -501,7 +489,7 @@ func (db *DB) GetProgramsFacilityStats(args *models.QueryContext, timeFilter int
501489
LEFT JOIN program_class_events pcev ON pcev.class_id = pc.id
502490
LEFT JOIN program_class_event_attendance pcea ON pcea.event_id = pcev.id AND pcea.user_id = pce.user_id
503491
WHERE pc.facility_id = ? AND pce.enrolled_at IS NOT NULL %s
504-
`, completionTimeFilter, attendanceTimeFilter, attendanceTimeFilter, ratioCapped, attendanceTimeFilter, completionTimeFilter)
492+
`, completionTimeFilter, attendanceTimeFilter, attendanceTimeFilter, partialAttendanceSQL, attendanceTimeFilter, completionTimeFilter)
505493

506494
if err := db.WithContext(args.Ctx).Raw(query, args.FacilityID).Scan(&rateStats).Error; err != nil {
507495
return programsFacilityStats, newGetRecordsDBError(err, "facility completion and attendance rates")
@@ -596,7 +584,7 @@ func (db *DB) GetProgramsOverviewTable(args *models.QueryContext, timeFilter int
596584
if adminRole == models.FacilityAdmin {
597585
currentMetricsSubquery = fmt.Sprintf(`
598586
LEFT JOIN (
599-
SELECT
587+
SELECT
600588
p.id as program_id,
601589
COUNT(DISTINCT pce.id) AS total_enrollments,
602590
COUNT(DISTINCT CASE WHEN pce.enrollment_status = 'Enrolled' AND pce.enrolled_at IS NOT NULL AND (pce.enrollment_ended_at IS NULL OR pce.enrollment_ended_at > CURRENT_TIMESTAMP) THEN pce.id END) AS total_active_enrollments,
@@ -612,7 +600,7 @@ func (db *DB) GetProgramsOverviewTable(args *models.QueryContext, timeFilter int
612600
} else {
613601
currentMetricsSubquery = `
614602
LEFT JOIN (
615-
SELECT
603+
SELECT
616604
p.id as program_id,
617605
COUNT(DISTINCT CASE WHEN p.is_active = true AND p.archived_at IS NULL THEN fp.facility_id END) AS total_active_facilities,
618606
COUNT(DISTINCT pce.id) AS total_enrollments,
@@ -640,13 +628,7 @@ func (db *DB) GetProgramsOverviewTable(args *models.QueryContext, timeFilter int
640628
facilityFilterForRates = fmt.Sprintf("AND pc.facility_id = %d", args.FacilityID)
641629
}
642630

643-
rateCast := "COALESCE(pcea.minutes_attended, pcea.scheduled_minutes, 0)::numeric"
644-
if db.Name() == "sqlite" {
645-
rateCast = "CAST(COALESCE(pcea.minutes_attended, pcea.scheduled_minutes, 0) AS REAL)"
646-
}
647-
rateRatio := fmt.Sprintf("%s / NULLIF(COALESCE(pcea.scheduled_minutes, pcea.minutes_attended, 0), 0)", rateCast)
648-
rateCapped := fmt.Sprintf("CASE WHEN %s < 1 THEN %s ELSE 1 END", rateRatio, rateRatio)
649-
631+
partialAttendanceSQL := buildPartialAttendanceSQL(db.Name(), "pcea")
650632
timeFilteredRatesSubquery := `
651633
LEFT JOIN (
652634
SELECT
@@ -656,7 +638,7 @@ func (db *DB) GetProgramsOverviewTable(args *models.QueryContext, timeFilter int
656638
COALESCE(SUM(
657639
CASE
658640
WHEN pcea.attendance_status = 'present' ` + timeFilterCondition + ` THEN 1
659-
WHEN pcea.attendance_status = 'partial' ` + timeFilterCondition + ` THEN ` + rateCapped + `
641+
WHEN pcea.attendance_status = 'partial' ` + timeFilterCondition + ` THEN ` + partialAttendanceSQL + `
660642
ELSE 0
661643
END
662644
) * 100.0 /
@@ -790,13 +772,7 @@ func (db *DB) GetProgramsCSVData(args *models.QueryContext) ([]models.ProgramCSV
790772
models.EnrollmentIncompleteTransfered,
791773
}
792774

793-
csvRateCast := "COALESCE(pca.minutes_attended, pca.scheduled_minutes, 0)::numeric"
794-
if db.Name() == "sqlite" {
795-
csvRateCast = "CAST(COALESCE(pca.minutes_attended, pca.scheduled_minutes, 0) AS REAL)"
796-
}
797-
csvRateRatio := fmt.Sprintf("%s / NULLIF(COALESCE(pca.scheduled_minutes, pca.minutes_attended, 0), 0)", csvRateCast)
798-
csvRateCapped := fmt.Sprintf("CASE WHEN %s < 1 THEN %s ELSE 1 END", csvRateRatio, csvRateRatio)
799-
775+
partialAttendanceSQL := buildPartialAttendanceSQL(db.Name(), "pca")
800776
tx := db.WithContext(args.Ctx).Table("programs p").
801777
Joins("JOIN program_classes pc ON pc.program_id = p.id").
802778
Joins("JOIN program_class_enrollments pe ON pe.class_id = pc.id").
@@ -805,7 +781,7 @@ func (db *DB) GetProgramsCSVData(args *models.QueryContext) ([]models.ProgramCSV
805781
Joins("LEFT JOIN program_completions c on c.program_class_id = pc.id and c.user_id = u.id").
806782
Joins("LEFT JOIN program_class_events pce on pce.class_id = pc.id").
807783
Joins("LEFT JOIN program_class_event_attendance pca ON pca.event_id = pce.id AND pca.user_id = u.id").
808-
Select(`
784+
Select(`
809785
f.name AS facility_name,
810786
p.name AS program_name,
811787
pc.name AS class_name,
@@ -819,18 +795,18 @@ func (db *DB) GetProgramsCSVData(args *models.QueryContext) ([]models.ProgramCSV
819795
CASE
820796
WHEN c.id IS NOT NULL THEN c.created_at
821797
WHEN pe.enrollment_status IN (?) THEN pe.updated_at
822-
END,
798+
END,
823799
pc.end_dt
824800
) as end_date,
825801
pe.enrollment_status AS end_status,
826-
CASE
802+
CASE
827803
WHEN COUNT(CASE WHEN pca.attendance_status IS NOT NULL AND pca.attendance_status != '' THEN 1 END) = 0 THEN 0
828-
ELSE
804+
ELSE
829805
COALESCE(
830806
SUM(
831807
CASE
832808
WHEN pca.attendance_status = 'present' THEN 1
833-
WHEN pca.attendance_status = 'partial' THEN `+csvRateCapped+`
809+
WHEN pca.attendance_status = 'partial' THEN `+partialAttendanceSQL+`
834810
ELSE 0
835811
END
836812
) * 100.0 /
@@ -840,7 +816,7 @@ func (db *DB) GetProgramsCSVData(args *models.QueryContext) ([]models.ProgramCSV
840816
END AS attendance_percentage`, statuses).
841817
Where(`
842818
(
843-
c.id IS NOT NULL
819+
c.id IS NOT NULL
844820
OR pe.enrollment_status IN (?)
845821
)
846822
`, statuses).
@@ -856,3 +832,14 @@ func (db *DB) GetProgramsCSVData(args *models.QueryContext) ([]models.ProgramCSV
856832
}
857833
return programCSVData, nil
858834
}
835+
836+
func buildPartialAttendanceSQL(dialect, alias string) string {
837+
minutesCast := "COALESCE(%s.minutes_attended, %s.scheduled_minutes, 0)::numeric"
838+
if dialect == "sqlite" {
839+
minutesCast = "CAST(COALESCE(%s.minutes_attended, %s.scheduled_minutes, 0) AS REAL)"
840+
}
841+
minutesCast = fmt.Sprintf(minutesCast, alias, alias)
842+
ratioExpr := fmt.Sprintf("%s / NULLIF(COALESCE(%s.scheduled_minutes, %s.minutes_attended, 0), 0)", minutesCast, alias, alias)
843+
partialAttendanceSQL := fmt.Sprintf("CASE WHEN %s < 1 THEN %s ELSE 1 END", ratioExpr, ratioExpr)
844+
return partialAttendanceSQL
845+
}

backend/src/handlers/events_attendance_handler.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -333,11 +333,7 @@ func applyTimeTracking(attendance *models.ProgramClassEventAttendance, scheduled
333333
attendance.CheckOutAt = &checkOut
334334
}
335335

336-
if minutesAttended != nil {
337-
attendance.MinutesAttended = minutesAttended
338-
} else {
339-
attendance.MinutesAttended = nil
340-
}
336+
attendance.MinutesAttended = minutesAttended
341337
attendance.ScheduledMinutes = &scheduledMinutes
342338

343339
if minutesAttended == nil {
@@ -379,11 +375,11 @@ func applyTimeTracking(attendance *models.ProgramClassEventAttendance, scheduled
379375
}
380376

381377
func (srv *Server) handleGetAttendanceRateForEvent(w http.ResponseWriter, r *http.Request, log sLog) error {
382-
eventID, err := strconv.Atoi(r.PathValue("class_id"))
378+
classID, err := strconv.Atoi(r.PathValue("class_id"))
383379
if err != nil {
384380
return newInvalidIdServiceError(err, "class ID")
385381
}
386-
classID, err := strconv.Atoi(r.PathValue("event_id"))
382+
eventID, err := strconv.Atoi(r.PathValue("event_id"))
387383
if err != nil {
388384
return newInvalidQueryParamServiceError(err, "event ID")
389385
}

0 commit comments

Comments
 (0)