From 590554976b12c31d3c4e98bbe31766f73f22e0a0 Mon Sep 17 00:00:00 2001 From: Arohasina Date: Wed, 25 Feb 2026 16:05:36 -0500 Subject: [PATCH 01/11] added the function laborAttendanceByTerm --- app/logic/volunteerSpreadsheet.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/app/logic/volunteerSpreadsheet.py b/app/logic/volunteerSpreadsheet.py index 255cbc97b..4c1c802ed 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -225,6 +225,30 @@ def calculateRetentionRate(fallDict, springDict): return retentionDict +def laborAttendanceByTerm(academicYear): + """Get labor students and their meeting attendance count for each term""" + base = getBaseQuery(academicYear) + + query = (base.select( + fn.CONCAT(User.firstName, ' ', User.lastName).alias('fullName'), + User.bnumber, + fn.CONCAT(User.username, '@berea.edu').alias('email'), + Term.description, + fn.COUNT(EventParticipant.event_id).alias('meetingsAttended'), + ) + .where(Event.isLaborOnly == True) + .group_by(User.username, Term.description) + .order_by(User.lastName, Term.description) + ) + + columns = ("Full Name", "B-Number", "Email", "Term", "Meetings Attended") + results = list(query.tuples()) + print("Row count:", len(results)) + print("Results:", results) + # return (columns,query.tuples()) + return (columns, results) + + def makeDataXls(sheetName, sheetData, workbook, sheetDesc=None): # assumes the length of the column titles matches the length of the data From 97fe1834c05e71f88661e6cdb2074694b574acae Mon Sep 17 00:00:00 2001 From: Arohasina Date: Wed, 25 Feb 2026 16:07:39 -0500 Subject: [PATCH 02/11] added test function test_laborAttendanceByTerm --- tests/code/test_spreadsheet.py | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/code/test_spreadsheet.py b/tests/code/test_spreadsheet.py index d66139c33..85f8733ec 100644 --- a/tests/code/test_spreadsheet.py +++ b/tests/code/test_spreadsheet.py @@ -683,5 +683,42 @@ def test_getUniqueVolunteers(fixture_info): ("Test Tester", "testt@berea.edu", "B55555"), ]) +@pytest.mark.integration +def test_laborAttendanceByTerm(fixture_info): + # No labor events yet so should return empty results + columns, results = laborAttendanceByTerm("2023-2024-test") + assert columns == ("Full Name", "B-Number", "Email", "Term", "Meetings Attended") + assert results == [] + + # Create a labor-only program and events + laborProgram = Program.create(programName='Labor') + laborEvent1 = Event.create( + name='Labor Meeting 1', + term=fixture_info['term1'], + program=laborProgram, + startDate=date(2023, 9, 5), + isCanceled=False, + deletionDate=None, + isLaborOnly=True, + ) + laborEvent2 = Event.create( + name='Labor Meeting 2', + term=fixture_info['term1'], + program=laborProgram, + startDate=date(2023, 9, 12), + isCanceled=False, + deletionDate=None, + isLaborOnly=True, + ) + + EventParticipant.create(event=laborEvent1, user=fixture_info['user1'], hoursEarned=1) + EventParticipant.create(event=laborEvent2, user=fixture_info['user1'], hoursEarned=1) + EventParticipant.create(event=laborEvent1, user=fixture_info['user2'], hoursEarned=1) + columns, results = laborAttendanceByTerm("2023-2024-test") + assert columns == ("Full Name", "B-Number", "Email", "Term", "Meetings Attended") + # user1 (Doe, John) attended 2 meetings, user2 (Doe, Jane) attended 1 + assert len(results) == 2 + assert ("John Doe", "B774377", "doej@berea.edu", "Fall 2023", 2) in results + assert ("Jane Doe", "B888828", "doej2@berea.edu", "Fall 2023", 1) in results \ No newline at end of file From 4c89d93f8159f44cb757e455459d758e5f051cd3 Mon Sep 17 00:00:00 2001 From: Arohasina Date: Wed, 25 Feb 2026 17:08:00 -0500 Subject: [PATCH 03/11] added laborAttendanceByTerm to the spreadsheet --- app/logic/volunteerSpreadsheet.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/logic/volunteerSpreadsheet.py b/app/logic/volunteerSpreadsheet.py index 4c1c802ed..7bafdd4c4 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -295,6 +295,7 @@ def createSpreadsheet(academicYear): makeDataXls("Unique Volunteers", getUniqueVolunteers(academicYear), workbook, sheetDesc=f"All students who participated in at least one service event during {academicYear}.") makeDataXls("Only All Volunteer Training", onlyCompletedAllVolunteer(academicYear), workbook, sheetDesc="Students who participated in an All Volunteer Training, but did not participate in any service events.") makeDataXls("Retention Rate By Semester", getRetentionRate(academicYear), workbook, sheetDesc="The percentage of students who participated in service events in the fall semester who also participated in a service event in the spring semester. Does not currently account for fall graduations.") + makeDataXls("Labor Attendance By Term", laborAttendanceByTerm(academicYear), workbook, sheetDesc="Labor students and the number of labor meetings attended for each term in the academic year.") fallTerm = getFallTerm(academicYear) springTerm = getSpringTerm(academicYear) From be0b9af689221e5f80386ee28cdeb94a3c25a35c Mon Sep 17 00:00:00 2001 From: Arohasina Ravoahanginiaina Date: Thu, 26 Feb 2026 15:27:30 -0500 Subject: [PATCH 04/11] removed print statements Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- app/logic/volunteerSpreadsheet.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/logic/volunteerSpreadsheet.py b/app/logic/volunteerSpreadsheet.py index 7bafdd4c4..8bbbb5489 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -243,8 +243,6 @@ def laborAttendanceByTerm(academicYear): columns = ("Full Name", "B-Number", "Email", "Term", "Meetings Attended") results = list(query.tuples()) - print("Row count:", len(results)) - print("Results:", results) # return (columns,query.tuples()) return (columns, results) From eeb4674a20e2add61479f08dbce813190da3116e Mon Sep 17 00:00:00 2001 From: Arohasina Ravoahanginiaina Date: Thu, 26 Feb 2026 15:27:59 -0500 Subject: [PATCH 05/11] removed commented code Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- app/logic/volunteerSpreadsheet.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/logic/volunteerSpreadsheet.py b/app/logic/volunteerSpreadsheet.py index 8bbbb5489..c4b67acd3 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -243,7 +243,6 @@ def laborAttendanceByTerm(academicYear): columns = ("Full Name", "B-Number", "Email", "Term", "Meetings Attended") results = list(query.tuples()) - # return (columns,query.tuples()) return (columns, results) From 07e2deda895fc956a174a70d41c98d2d444e60d5 Mon Sep 17 00:00:00 2001 From: Arohasina Ravoahanginiaina Date: Thu, 26 Feb 2026 15:28:27 -0500 Subject: [PATCH 06/11] Update app/logic/volunteerSpreadsheet.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- app/logic/volunteerSpreadsheet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/logic/volunteerSpreadsheet.py b/app/logic/volunteerSpreadsheet.py index c4b67acd3..1d9d21e8e 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -232,12 +232,12 @@ def laborAttendanceByTerm(academicYear): query = (base.select( fn.CONCAT(User.firstName, ' ', User.lastName).alias('fullName'), User.bnumber, - fn.CONCAT(User.username, '@berea.edu').alias('email'), + fn.CONCAT(EventParticipant.user_id, '@berea.edu').alias('email'), Term.description, fn.COUNT(EventParticipant.event_id).alias('meetingsAttended'), ) .where(Event.isLaborOnly == True) - .group_by(User.username, Term.description) + .group_by(EventParticipant.user_id, Term.description) .order_by(User.lastName, Term.description) ) From ee8672915b421cfc86a99217e2b36d709167f082 Mon Sep 17 00:00:00 2001 From: Arohasina Ravoahanginiaina Date: Thu, 26 Feb 2026 15:29:12 -0500 Subject: [PATCH 07/11] Update app/logic/volunteerSpreadsheet.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- app/logic/volunteerSpreadsheet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/logic/volunteerSpreadsheet.py b/app/logic/volunteerSpreadsheet.py index 1d9d21e8e..1fbd14c72 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -238,7 +238,7 @@ def laborAttendanceByTerm(academicYear): ) .where(Event.isLaborOnly == True) .group_by(EventParticipant.user_id, Term.description) - .order_by(User.lastName, Term.description) + .order_by(User.lastName, User.firstName, Term.description) ) columns = ("Full Name", "B-Number", "Email", "Term", "Meetings Attended") From 6209cba5099431b68add62ea76489d4bf884f1ae Mon Sep 17 00:00:00 2001 From: Arohasina Date: Thu, 26 Feb 2026 15:34:11 -0500 Subject: [PATCH 08/11] removed redundant event and reused existing ones --- tests/code/test_spreadsheet.py | 43 +++++++--------------------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/tests/code/test_spreadsheet.py b/tests/code/test_spreadsheet.py index 85f8733ec..7df67758c 100644 --- a/tests/code/test_spreadsheet.py +++ b/tests/code/test_spreadsheet.py @@ -32,7 +32,8 @@ def fixture_info(): startDate=date(2023, 9, 1), isCanceled=False, deletionDate=None, - isService=True + isService=True, + isLaborOnly=True ) event2 = Event.create( name='Event2', @@ -41,7 +42,8 @@ def fixture_info(): startDate=date(2023, 9, 10), isCanceled=False, deletionDate=None, - isService=True + isService=True, + isLaborOnly=True ) event3 = Event.create( name='Event3', @@ -685,40 +687,13 @@ def test_getUniqueVolunteers(fixture_info): @pytest.mark.integration def test_laborAttendanceByTerm(fixture_info): - # No labor events yet so should return empty results - columns, results = laborAttendanceByTerm("2023-2024-test") - assert columns == ("Full Name", "B-Number", "Email", "Term", "Meetings Attended") - assert results == [] - - # Create a labor-only program and events - laborProgram = Program.create(programName='Labor') - laborEvent1 = Event.create( - name='Labor Meeting 1', - term=fixture_info['term1'], - program=laborProgram, - startDate=date(2023, 9, 5), - isCanceled=False, - deletionDate=None, - isLaborOnly=True, - ) - laborEvent2 = Event.create( - name='Labor Meeting 2', - term=fixture_info['term1'], - program=laborProgram, - startDate=date(2023, 9, 12), - isCanceled=False, - deletionDate=None, - isLaborOnly=True, - ) - - EventParticipant.create(event=laborEvent1, user=fixture_info['user1'], hoursEarned=1) - EventParticipant.create(event=laborEvent2, user=fixture_info['user1'], hoursEarned=1) - EventParticipant.create(event=laborEvent1, user=fixture_info['user2'], hoursEarned=1) + EventParticipant.create(event=fixture_info["event1"], user=fixture_info['user1'], hoursEarned=1) + EventParticipant.create(event=fixture_info["event2"], user=fixture_info['user1'], hoursEarned=1) + EventParticipant.create(event=fixture_info["event1"], user=fixture_info['user2'], hoursEarned=1) columns, results = laborAttendanceByTerm("2023-2024-test") assert columns == ("Full Name", "B-Number", "Email", "Term", "Meetings Attended") - # user1 (Doe, John) attended 2 meetings, user2 (Doe, Jane) attended 1 assert len(results) == 2 - assert ("John Doe", "B774377", "doej@berea.edu", "Fall 2023", 2) in results - assert ("Jane Doe", "B888828", "doej2@berea.edu", "Fall 2023", 1) in results \ No newline at end of file + assert ("John Doe", "B774377", "doej@berea.edu", "Fall 2023", 3) in results + assert ("Jane Doe", "B888828", "doej2@berea.edu", "Fall 2023", 2) in results \ No newline at end of file From 4a863e30ba419b50e4fe02267deb2d1e06644e7c Mon Sep 17 00:00:00 2001 From: Arohasina Ravoahanginiaina Date: Thu, 26 Feb 2026 15:55:33 -0500 Subject: [PATCH 09/11] added distinct Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- app/logic/volunteerSpreadsheet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/logic/volunteerSpreadsheet.py b/app/logic/volunteerSpreadsheet.py index 1fbd14c72..ca8e925bf 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -234,7 +234,7 @@ def laborAttendanceByTerm(academicYear): User.bnumber, fn.CONCAT(EventParticipant.user_id, '@berea.edu').alias('email'), Term.description, - fn.COUNT(EventParticipant.event_id).alias('meetingsAttended'), + fn.COUNT(EventParticipant.event_id.distinct()).alias('meetingsAttended'), ) .where(Event.isLaborOnly == True) .group_by(EventParticipant.user_id, Term.description) From d37e2ae8aedc97f8446493a2490e912b72d62425 Mon Sep 17 00:00:00 2001 From: Arohasina Date: Wed, 18 Mar 2026 14:47:21 -0400 Subject: [PATCH 10/11] include all labor students even with zero attendance and non-labor attendees --- app/logic/volunteerSpreadsheet.py | 73 +++++++++++++++++++++++-------- tests/code/test_spreadsheet.py | 11 ++--- 2 files changed, 61 insertions(+), 23 deletions(-) diff --git a/app/logic/volunteerSpreadsheet.py b/app/logic/volunteerSpreadsheet.py index ca8e925bf..d98dca246 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -5,6 +5,7 @@ from datetime import date, datetime,time from app import app from app.models import mainDB +from app.models.celtsLabor import CeltsLabor from app.models.eventParticipant import EventParticipant from app.models.user import User from app.models.program import Program @@ -226,24 +227,60 @@ def calculateRetentionRate(fallDict, springDict): return retentionDict def laborAttendanceByTerm(academicYear): - """Get labor students and their meeting attendance count for each term""" - base = getBaseQuery(academicYear) - - query = (base.select( - fn.CONCAT(User.firstName, ' ', User.lastName).alias('fullName'), - User.bnumber, - fn.CONCAT(EventParticipant.user_id, '@berea.edu').alias('email'), - Term.description, - fn.COUNT(EventParticipant.event_id.distinct()).alias('meetingsAttended'), +# """Get labor students and their meeting attendance count for each term""" +# base = getBaseQuery(academicYear) + +# query = (base.select( +# fn.CONCAT(User.firstName, ' ', User.lastName).alias('fullName'), +# User.bnumber, +# fn.CONCAT(EventParticipant.user_id, '@berea.edu').alias('email'), +# Term.description, +# fn.COUNT(EventParticipant.event_id).alias('meetingsAttended'), +# ) +# .where(Event.isLaborOnly == True) +# .group_by(EventParticipant.user_id, Term.description) +# .order_by(User.lastName, User.firstName, Term.description) +# ) + +# columns = ("Full Name", "B-Number", "Email", "Term", "Meetings Attended") +# results = list(query.tuples()) +# return (columns, results) + + + query = ( + CeltsLabor + .select( + fn.CONCAT(User.firstName, ' ', User.lastName).alias('fullName'), + User.bnumber, + fn.CONCAT(User.username, '@berea.edu').alias('email'), + Term.description, + fn.COUNT(EventParticipant.event_id).alias('meetingsAttended') + ) + .join(User) + .switch(CeltsLabor) + .join( + EventParticipant, + JOIN.FULL, + on=(CeltsLabor.user == EventParticipant.user) + ) + .join( + Event, + JOIN.LEFT_OUTER, + on=(EventParticipant.event_id == Event.id) + ) + .join( + Term, + on=(Event.term == Term.id) + ) + .where( + Term.academicYear == academicYear, + Event.isLaborOnly == True, + Event.deletionDate.is_null(True), + Event.isCanceled == False + ) + .group_by(User.id, Term.description) + .order_by(User.lastName, User.firstName, Term.description) ) - .where(Event.isLaborOnly == True) - .group_by(EventParticipant.user_id, Term.description) - .order_by(User.lastName, User.firstName, Term.description) - ) - - columns = ("Full Name", "B-Number", "Email", "Term", "Meetings Attended") - results = list(query.tuples()) - return (columns, results) @@ -292,7 +329,7 @@ def createSpreadsheet(academicYear): makeDataXls("Unique Volunteers", getUniqueVolunteers(academicYear), workbook, sheetDesc=f"All students who participated in at least one service event during {academicYear}.") makeDataXls("Only All Volunteer Training", onlyCompletedAllVolunteer(academicYear), workbook, sheetDesc="Students who participated in an All Volunteer Training, but did not participate in any service events.") makeDataXls("Retention Rate By Semester", getRetentionRate(academicYear), workbook, sheetDesc="The percentage of students who participated in service events in the fall semester who also participated in a service event in the spring semester. Does not currently account for fall graduations.") - makeDataXls("Labor Attendance By Term", laborAttendanceByTerm(academicYear), workbook, sheetDesc="Labor students and the number of labor meetings attended for each term in the academic year.") + makeDataXls("Labor Attendance By Term", laborAttendanceByTerm(academicYear), workbook, sheetDesc="Labor students and the number of labor events attended for each term in the academic year.") fallTerm = getFallTerm(academicYear) springTerm = getSpringTerm(academicYear) diff --git a/tests/code/test_spreadsheet.py b/tests/code/test_spreadsheet.py index 7df67758c..36fd99142 100644 --- a/tests/code/test_spreadsheet.py +++ b/tests/code/test_spreadsheet.py @@ -687,13 +687,14 @@ def test_getUniqueVolunteers(fixture_info): @pytest.mark.integration def test_laborAttendanceByTerm(fixture_info): - EventParticipant.create(event=fixture_info["event1"], user=fixture_info['user1'], hoursEarned=1) EventParticipant.create(event=fixture_info["event2"], user=fixture_info['user1'], hoursEarned=1) - EventParticipant.create(event=fixture_info["event1"], user=fixture_info['user2'], hoursEarned=1) + EventParticipant.create(event=fixture_info["event2"], user=fixture_info['user2'], hoursEarned=1) + EventParticipant.create(event=fixture_info["event1"], user=fixture_info['user3'], hoursEarned=1) columns, results = laborAttendanceByTerm("2023-2024-test") assert columns == ("Full Name", "B-Number", "Email", "Term", "Meetings Attended") - assert len(results) == 2 - assert ("John Doe", "B774377", "doej@berea.edu", "Fall 2023", 3) in results - assert ("Jane Doe", "B888828", "doej2@berea.edu", "Fall 2023", 2) in results \ No newline at end of file + assert len(results) == 3 + assert ("John Doe", "B774377", "doej@berea.edu", "Fall 2023", 2) in results + assert ("Jane Doe", "B888828", "doej2@berea.edu", "Fall 2023", 2) in results + assert ("Bob Builder", "B00700932", "builderb@berea.edu", "Fall 2023", 1) in results \ No newline at end of file From 7936d626fc6913c1579af922bb34c14273b8066f Mon Sep 17 00:00:00 2001 From: Arohasina Date: Wed, 18 Mar 2026 14:49:04 -0400 Subject: [PATCH 11/11] added the return --- app/logic/volunteerSpreadsheet.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/logic/volunteerSpreadsheet.py b/app/logic/volunteerSpreadsheet.py index d98dca246..15d7df71e 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -281,6 +281,10 @@ def laborAttendanceByTerm(academicYear): .group_by(User.id, Term.description) .order_by(User.lastName, User.firstName, Term.description) ) + + columns = ("Full Name", "B-Number", "Email", "Term", "Meetings Attended") + results = list(query.tuples()) + return (columns, results)