Skip to content

Commit 76c5893

Browse files
committed
fix: calculate attendance for all days up to today const declaration
1 parent 5ad9623 commit 76c5893

File tree

4 files changed

+53
-5
lines changed

4 files changed

+53
-5
lines changed

backend/src/database/events_attendance.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,3 +414,35 @@ func (db *DB) CreateAttendanceAuditTrail(ctx context.Context, att *models.Progra
414414

415415
return nil
416416
}
417+
418+
func (db *DB) GetCumulativeAttendanceRateForClass(ctx context.Context, classID int) (float64, error) {
419+
var attendanceRate float64
420+
today := time.Now().Format("2006-01-02")
421+
sql := `
422+
WITH attendance_credits AS (
423+
SELECT
424+
pcea.id,
425+
CASE
426+
WHEN pcea.attendance_status = 'present' THEN 1.0
427+
WHEN pcea.attendance_status = 'partial' THEN LEAST(
428+
COALESCE(pcea.minutes_attended, pcea.scheduled_minutes, 0)::numeric /
429+
NULLIF(COALESCE(pcea.scheduled_minutes, pcea.minutes_attended, 0), 0),
430+
1
431+
)
432+
ELSE 0
433+
END as credit
434+
FROM program_class_event_attendance pcea
435+
INNER JOIN program_class_events pce ON pce.id = pcea.event_id
436+
WHERE pce.class_id = ? AND pcea.date <= ?
437+
)
438+
SELECT COALESCE(
439+
(SELECT SUM(credit) FROM attendance_credits) * 100.0 /
440+
NULLIF((SELECT COUNT(*) FROM attendance_credits), 0),
441+
0
442+
) as attendance_percentage`
443+
444+
if err := db.WithContext(ctx).Raw(sql, classID, today).Scan(&attendanceRate).Error; err != nil {
445+
return 0, newNotFoundDBError(err, "program_class_event_attendance")
446+
}
447+
return attendanceRate, nil
448+
}

backend/src/handlers/classes_handler.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func (srv *Server) registerClassesRoutes() []routeDef {
3737
adminValidatedFeatureRoute("GET /api/programs/{program_id}/classes/outcomes", srv.handleGetProgramClassOutcomes, axx, validateFacility("")),
3838
adminValidatedFeatureRoute("GET /api/program-classes/{class_id}/attendance-flags", srv.handleGetAttendanceFlagsForClass, axx, resolver),
3939
adminValidatedFeatureRoute("GET /api/program-classes/{class_id}/missing-attendance", srv.handleGetMissingAttendance, axx, resolver),
40+
adminValidatedFeatureRoute("GET /api/program-classes/{class_id}/attendance-rate", srv.handleGetCumulativeAttendanceRate, axx, resolver),
4041
adminValidatedFeatureRoute("GET /api/program-classes/{class_id}/history", srv.handleGetClassHistory, axx, resolver),
4142
adminValidatedFeatureRoute("PATCH /api/program-classes", srv.handleUpdateClasses, axx, func(tx *database.DB, r *http.Request) bool {
4243
var programClass models.ProgramClass
@@ -267,3 +268,18 @@ func (srv *Server) handleGetMissingAttendance(w http.ResponseWriter, r *http.Req
267268
}
268269
return writeJsonResponse(w, http.StatusOK, totalMissing)
269270
}
271+
272+
func (srv *Server) handleGetCumulativeAttendanceRate(w http.ResponseWriter, r *http.Request, log sLog) error {
273+
classID, err := strconv.Atoi(r.PathValue("class_id"))
274+
if err != nil {
275+
return newInvalidIdServiceError(err, "class ID")
276+
}
277+
attendanceRate, err := srv.Db.GetCumulativeAttendanceRateForClass(r.Context(), classID)
278+
if err != nil {
279+
return newDatabaseServiceError(err)
280+
}
281+
response := map[string]float64{
282+
"attendance_rate": attendanceRate,
283+
}
284+
return writeJsonResponse(w, http.StatusOK, response)
285+
}

frontend/src/Pages/EventAttendance.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@ export default function EventAttendance() {
157157
return { check_in_at: start, check_out_at: end };
158158
};
159159

160+
const isPresentLike = (status?: Attendance) =>
161+
status === Attendance.Present || status === Attendance.Partial;
162+
160163
const getAttendedMinutes = (row: LocalRowData) => {
161164
if (!isPresentLike(row.attendance_status)) {
162165
return null;
@@ -255,9 +258,6 @@ export default function EventAttendance() {
255258
);
256259
}
257260

258-
const isPresentLike = (status?: Attendance) =>
259-
status === Attendance.Present || status === Attendance.Partial;
260-
261261
const handleSearch = (search: string) => {
262262
startTransition(() => {
263263
setSearchTerm(search);

frontend/src/routeLoaders.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,9 @@ export const getClassMgmtData: LoaderFunction = async ({
226226
if (classResp.data.status === SelectedClassStatus.Scheduled) {
227227
attendanceRate = 0;
228228
missingAttendance = 0;
229-
} else if (cls.events && cls.events.length > 0) {
229+
} else {
230230
const resp2 = (await API.get(
231-
`program-classes/${class_id}/events/${cls.events[0].id}/attendance-rate`
231+
`program-classes/${class_id}/attendance-rate`
232232
)) as ServerResponseOne<{ attendance_rate: number }>;
233233
attendanceRate = resp2.success ? resp2.data.attendance_rate : 0;
234234
const resp3 = (await API.get(

0 commit comments

Comments
 (0)