From 590554976b12c31d3c4e98bbe31766f73f22e0a0 Mon Sep 17 00:00:00 2001 From: Arohasina Date: Wed, 25 Feb 2026 16:05:36 -0500 Subject: [PATCH 01/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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) From 4354374d5e4087575bdbc9aafdaedb4f059f8133 Mon Sep 17 00:00:00 2001 From: Arohasina Date: Wed, 18 Mar 2026 16:31:10 -0400 Subject: [PATCH 12/22] switched FULL JOIN to Union to fix SQL syntax bug --- app/logic/volunteerSpreadsheet.py | 78 +++++++++++++++++-------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/app/logic/volunteerSpreadsheet.py b/app/logic/volunteerSpreadsheet.py index 15d7df71e..64edb32da 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -226,28 +226,8 @@ 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).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 = ( +def laborAttendanceByTerm(academicYear): + laborQuery = ( CeltsLabor .select( fn.CONCAT(User.firstName, ' ', User.lastName).alias('fullName'), @@ -260,32 +240,62 @@ def laborAttendanceByTerm(academicYear): .switch(CeltsLabor) .join( EventParticipant, - JOIN.FULL, + JOIN.LEFT_OUTER, on=(CeltsLabor.user == EventParticipant.user) ) .join( Event, JOIN.LEFT_OUTER, - on=(EventParticipant.event_id == Event.id) + on=( + (EventParticipant.event == Event.id) & + (Event.isLaborOnly == True) & + (Event.deletionDate.is_null()) & + (Event.isCanceled == False) + ) ) .join( Term, - on=(Event.term == Term.id) + JOIN.LEFT_OUTER, + on=( + (Event.term == Term.id) & + (Term.academicYear == academicYear) + ) ) + .group_by(CeltsLabor.user, Term.description) + ) + + nonLaborQuery = ( + EventParticipant + .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(EventParticipant) + .join( + Event, + on=( + (EventParticipant.event == Event.id) & + (Event.isLaborOnly == True) & + (Event.deletionDate.is_null()) & + (Event.isCanceled == False) + ) + ) + .join(Term, on=(Event.term == Term.id)) .where( Term.academicYear == academicYear, - Event.isLaborOnly == True, - Event.deletionDate.is_null(True), - Event.isCanceled == False + User.username.not_in(CeltsLabor.select(CeltsLabor.user_id)) ) - .group_by(User.id, Term.description) - .order_by(User.lastName, User.firstName, Term.description) + .group_by(EventParticipant.user, Term.description) ) - + + query = laborQuery.union(nonLaborQuery) columns = ("Full Name", "B-Number", "Email", "Term", "Meetings Attended") - results = list(query.tuples()) - return (columns, results) + return (columns, query.tuples()) def makeDataXls(sheetName, sheetData, workbook, sheetDesc=None): @@ -333,7 +343,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 events attended for each term in the academic year.") + makeDataXls("Labor Attendance By Term", laborAttendanceByTerm(academicYear), workbook, sheetDesc="Reports the number of labor-only events attended per term for each labor student, including those with zero attendance, and non-labor attendees.") fallTerm = getFallTerm(academicYear) springTerm = getSpringTerm(academicYear) From 240e8028befd8765c1d4237fc42479db2377a648 Mon Sep 17 00:00:00 2001 From: Arohasina Date: Tue, 24 Mar 2026 15:19:45 -0400 Subject: [PATCH 13/22] Split labor attendance report into separate sheets by term and fix blank term/duplicate row bugs --- app/logic/volunteerSpreadsheet.py | 46 +++++++++++++++---------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/app/logic/volunteerSpreadsheet.py b/app/logic/volunteerSpreadsheet.py index 64edb32da..1053a916e 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -1,4 +1,5 @@ from os import major +from openpyxl import workbook import xlsxwriter from peewee import fn, Case, JOIN, SQL, Select from collections import defaultdict @@ -226,14 +227,13 @@ def calculateRetentionRate(fallDict, springDict): return retentionDict -def laborAttendanceByTerm(academicYear): - laborQuery = ( +def laborAttendanceByTerm(term): + laborQuery = ( #so that all Celts Labor students appear even if they didn't attend anything 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) @@ -248,29 +248,21 @@ def laborAttendanceByTerm(academicYear): JOIN.LEFT_OUTER, on=( (EventParticipant.event == Event.id) & + (Event.term == term) & (Event.isLaborOnly == True) & (Event.deletionDate.is_null()) & (Event.isCanceled == False) ) ) - .join( - Term, - JOIN.LEFT_OUTER, - on=( - (Event.term == Term.id) & - (Term.academicYear == academicYear) - ) - ) - .group_by(CeltsLabor.user, Term.description) + .group_by(CeltsLabor.user) ) - nonLaborQuery = ( + nonLaborQuery = ( #so that non-labor attendees who are not in CeltsLabor also appear EventParticipant .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) @@ -279,23 +271,25 @@ def laborAttendanceByTerm(academicYear): Event, on=( (EventParticipant.event == Event.id) & + (Event.term == term) & (Event.isLaborOnly == True) & (Event.deletionDate.is_null()) & (Event.isCanceled == False) ) ) - .join(Term, on=(Event.term == Term.id)) - .where( - Term.academicYear == academicYear, - User.username.not_in(CeltsLabor.select(CeltsLabor.user_id)) + .join( + CeltsLabor, + JOIN.LEFT_OUTER, + on=(EventParticipant.user == CeltsLabor.user) ) - .group_by(EventParticipant.user, Term.description) + .where(CeltsLabor.user.is_null()) + .group_by(EventParticipant.user) ) - query = laborQuery.union(nonLaborQuery) - columns = ("Full Name", "B-Number", "Email", "Term", "Meetings Attended") + query = laborQuery.union(nonLaborQuery).order_by(SQL('fullName')) + columns = ("Full Name", "B-Number", "Email", "Meetings Attended") - return (columns, query.tuples()) + return (columns, query.tuples()) def makeDataXls(sheetName, sheetData, workbook, sheetDesc=None): @@ -343,8 +337,12 @@ 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="Reports the number of labor-only events attended per term for each labor student, including those with zero attendance, and non-labor attendees.") - + + fallTerm = getFallTerm(academicYear) + springTerm = getSpringTerm(academicYear) + makeDataXls(f"Labor Attendance {fallTerm.description}", laborAttendanceByTerm(fallTerm), workbook,sheetDesc=f"Number of labor-only events attended in {fallTerm.description} for each labor student and non-labor attendees, including zero attendance (for labor students).") + makeDataXls(f"Labor Attendance {springTerm.description}", laborAttendanceByTerm(springTerm), workbook, sheetDesc=f"Number of labor-only events attended in {springTerm.description} for each labor student and non-labor attendees, including zero attendance (for labor students).") + fallTerm = getFallTerm(academicYear) springTerm = getSpringTerm(academicYear) makeDataXls(fallTerm.description, getAllTermData(fallTerm), workbook, sheetDesc= "All event participation for the term, excluding deleted or canceled events.") From 5cd48599d219fcc15a7ffd95623dfd6e0be5a7e6 Mon Sep 17 00:00:00 2001 From: Arohasina Date: Tue, 24 Mar 2026 15:46:40 -0400 Subject: [PATCH 14/22] for laborQuery changed EventParticipant.event_id to Event.id to fix count error --- app/logic/volunteerSpreadsheet.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/logic/volunteerSpreadsheet.py b/app/logic/volunteerSpreadsheet.py index 1053a916e..8b8af5073 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -1,5 +1,4 @@ from os import major -from openpyxl import workbook import xlsxwriter from peewee import fn, Case, JOIN, SQL, Select from collections import defaultdict @@ -234,7 +233,7 @@ def laborAttendanceByTerm(term): fn.CONCAT(User.firstName, ' ', User.lastName).alias('fullName'), User.bnumber, fn.CONCAT(User.username, '@berea.edu').alias('email'), - fn.COUNT(EventParticipant.event_id).alias('meetingsAttended') + fn.COUNT(Event.id).alias('meetingsAttended') ) .join(User) .switch(CeltsLabor) From ef2e93d3ed273df8d789bcc8f54f4888a195d60d Mon Sep 17 00:00:00 2001 From: Arohasina Ravoahanginiaina Date: Wed, 25 Mar 2026 16:42:30 -0400 Subject: [PATCH 15/22] removed redundant 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 8b8af5073..e27242c9d 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -342,8 +342,6 @@ def createSpreadsheet(academicYear): makeDataXls(f"Labor Attendance {fallTerm.description}", laborAttendanceByTerm(fallTerm), workbook,sheetDesc=f"Number of labor-only events attended in {fallTerm.description} for each labor student and non-labor attendees, including zero attendance (for labor students).") makeDataXls(f"Labor Attendance {springTerm.description}", laborAttendanceByTerm(springTerm), workbook, sheetDesc=f"Number of labor-only events attended in {springTerm.description} for each labor student and non-labor attendees, including zero attendance (for labor students).") - fallTerm = getFallTerm(academicYear) - springTerm = getSpringTerm(academicYear) makeDataXls(fallTerm.description, getAllTermData(fallTerm), workbook, sheetDesc= "All event participation for the term, excluding deleted or canceled events.") makeDataXls(springTerm.description, getAllTermData(springTerm), workbook, sheetDesc="All event participation for the term, excluding deleted or canceled events.") From 80081a2f7b93c71f3a2dbab02462da0676004740 Mon Sep 17 00:00:00 2001 From: Arohasina Date: Thu, 26 Mar 2026 16:03:19 -0400 Subject: [PATCH 16/22] fix filter term --- app/logic/volunteerSpreadsheet.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/logic/volunteerSpreadsheet.py b/app/logic/volunteerSpreadsheet.py index e27242c9d..361e4ccf0 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -233,7 +233,7 @@ def laborAttendanceByTerm(term): fn.CONCAT(User.firstName, ' ', User.lastName).alias('fullName'), User.bnumber, fn.CONCAT(User.username, '@berea.edu').alias('email'), - fn.COUNT(Event.id).alias('meetingsAttended') + fn.COUNT(fn.DISTINCT(Event.id)).alias('meetingsAttended') ) .join(User) .switch(CeltsLabor) @@ -253,6 +253,9 @@ def laborAttendanceByTerm(term): (Event.isCanceled == False) ) ) + .where( + (CeltsLabor.term == term) + ) .group_by(CeltsLabor.user) ) @@ -279,7 +282,8 @@ def laborAttendanceByTerm(term): .join( CeltsLabor, JOIN.LEFT_OUTER, - on=(EventParticipant.user == CeltsLabor.user) + on=(EventParticipant.user == CeltsLabor.user) & + (CeltsLabor.term == term) ) .where(CeltsLabor.user.is_null()) .group_by(EventParticipant.user) From f9749ffcea8c9d887bba64c2188c24e20272b2f9 Mon Sep 17 00:00:00 2001 From: Arohasina Date: Thu, 26 Mar 2026 16:04:30 -0400 Subject: [PATCH 17/22] fix test --- tests/code/test_spreadsheet.py | 59 ++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/tests/code/test_spreadsheet.py b/tests/code/test_spreadsheet.py index 36fd99142..a7d5ff5ab 100644 --- a/tests/code/test_spreadsheet.py +++ b/tests/code/test_spreadsheet.py @@ -4,6 +4,7 @@ from app.models.user import User from app.models.term import Term from app.models.eventParticipant import EventParticipant +from app.models.celtsLabor import CeltsLabor from app.logic.volunteerSpreadsheet import * from app.models.program import Program from app.models.event import Event @@ -32,7 +33,7 @@ def fixture_info(): startDate=date(2023, 9, 1), isCanceled=False, deletionDate=None, - isService=True, + isService=True, isLaborOnly=True ) event2 = Event.create( @@ -42,7 +43,7 @@ def fixture_info(): startDate=date(2023, 9, 10), isCanceled=False, deletionDate=None, - isService=True, + isService=True, isLaborOnly=True ) event3 = Event.create( @@ -64,6 +65,10 @@ def fixture_info(): isService=True ) + labor1 = CeltsLabor.create(user=user1, term=term1, positionTitle="test position 1") + labor2 = CeltsLabor.create(user=user2, term=term1, positionTitle="test position 2") + labor3 = CeltsLabor.create(user=user1, term=term2, positionTitle="test position 3") + labor4 = CeltsLabor.create(user=user2, term=term2, positionTitle="test position 4") eventparticipant1 = EventParticipant.create(event=event1, user=user1, hoursEarned=5) eventparticipant2 = EventParticipant.create(event=event1, user=user2, hoursEarned=3) @@ -88,6 +93,10 @@ def fixture_info(): 'eventparticipant1': eventparticipant1, 'eventparticipant2': eventparticipant2, 'eventparticipant4': eventparticipant4, + 'labor1': labor1, + 'labor2': labor2, + 'labor3': labor3, + 'labor4': labor4 } transaction.rollback() @@ -687,14 +696,44 @@ def test_getUniqueVolunteers(fixture_info): @pytest.mark.integration def test_laborAttendanceByTerm(fixture_info): - EventParticipant.create(event=fixture_info["event2"], user=fixture_info['user1'], 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(fixture_info['term1']) + results = list(results) - columns, results = laborAttendanceByTerm("2023-2024-test") - assert columns == ("Full Name", "B-Number", "Email", "Term", "Meetings Attended") + assert columns == ("Full Name", "B-Number", "Email", "Meetings Attended") + + assert len(results) == 2 + assert ("John Doe", "B774377", "doej@berea.edu", 1) in results + assert ("Jane Doe", "B888828", "doej2@berea.edu", 1) in results + + columns, results = laborAttendanceByTerm(fixture_info['term2']) + results = list(results) + + assert len(results) == 2 + assert ("John Doe", "B774377", "doej@berea.edu", 0) in results + assert ("Jane Doe", "B888828", "doej2@berea.edu", 0) in results + + EventParticipant.create(event=fixture_info['event2'], user=fixture_info['user1'], hoursEarned=1) + + columns, results = laborAttendanceByTerm(fixture_info['term1']) + results = list(results) + + assert len(results) == 2 + assert ("John Doe", "B774377", "doej@berea.edu", 2) in results + assert ("Jane Doe", "B888828", "doej2@berea.edu", 1) in results + + EventParticipant.create(event=fixture_info['event1'], user=fixture_info['user3'], hoursEarned=1) + + columns, results = laborAttendanceByTerm(fixture_info['term1']) + results = list(results) 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 + assert ("John Doe", "B774377", "doej@berea.edu", 2) in results + assert ("Jane Doe", "B888828", "doej2@berea.edu", 1) in results + assert ("Bob Builder", "B00700932", "builderb@berea.edu", 1) in results + + EventParticipant.create(event=fixture_info['event3'], user=fixture_info['user1'], hoursEarned=2) + + columns, results = laborAttendanceByTerm(fixture_info['term1']) + results = list(results) + + assert ("John Doe", "B774377", "doej@berea.edu", 2) in results \ No newline at end of file From 68ca9efeb7ad24fe1506bf97e7898f9e55f20d74 Mon Sep 17 00:00:00 2001 From: Arohasina Ravoahanginiaina Date: Sat, 28 Mar 2026 22:40:30 -0400 Subject: [PATCH 18/22] 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 361e4ccf0..abae1cd76 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -265,7 +265,7 @@ def laborAttendanceByTerm(term): fn.CONCAT(User.firstName, ' ', User.lastName).alias('fullName'), User.bnumber, fn.CONCAT(User.username, '@berea.edu').alias('email'), - fn.COUNT(EventParticipant.event_id).alias('meetingsAttended') + fn.COUNT(fn.DISTINCT(EventParticipant.event_id)).alias('meetingsAttended') ) .join(User) .switch(EventParticipant) From 08b756e897523f537452eb2407ce1a647c45e2cb Mon Sep 17 00:00:00 2001 From: Arohasina Ravoahanginiaina Date: Tue, 31 Mar 2026 17:49:09 -0400 Subject: [PATCH 19/22] Update tests/code/test_spreadsheet.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/code/test_spreadsheet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/code/test_spreadsheet.py b/tests/code/test_spreadsheet.py index a7d5ff5ab..21e58efd2 100644 --- a/tests/code/test_spreadsheet.py +++ b/tests/code/test_spreadsheet.py @@ -699,7 +699,7 @@ def test_laborAttendanceByTerm(fixture_info): columns, results = laborAttendanceByTerm(fixture_info['term1']) results = list(results) - assert columns == ("Full Name", "B-Number", "Email", "Meetings Attended") + assert columns == ["Full Name", "B-Number", "Email", "Meetings Attended"] assert len(results) == 2 assert ("John Doe", "B774377", "doej@berea.edu", 1) in results From 8e06bec3340c2d2aef0cb020ecacd10a01f6abc3 Mon Sep 17 00:00:00 2001 From: Arohasina Date: Tue, 31 Mar 2026 18:33:37 -0400 Subject: [PATCH 20/22] fix spring report not including 0 attendance for labor students --- app/logic/volunteerSpreadsheet.py | 89 ++++++++++++------------------- 1 file changed, 33 insertions(+), 56 deletions(-) diff --git a/app/logic/volunteerSpreadsheet.py b/app/logic/volunteerSpreadsheet.py index abae1cd76..c0121f7e7 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -227,73 +227,50 @@ def calculateRetentionRate(fallDict, springDict): return retentionDict def laborAttendanceByTerm(term): - laborQuery = ( #so that all Celts Labor students appear even if they didn't attend anything + fullName = fn.CONCAT(User.firstName, ' ', User.lastName).alias('fullName') + email = fn.CONCAT(User.username, '@berea.edu').alias('email') + meetingsAttended = fn.COUNT(fn.DISTINCT(Event.id)).alias('meetingsAttended') + + validEvent = ( + (EventParticipant.event == Event.id) & + (Event.term == term) & + (Event.isLaborOnly == True) & + (Event.deletionDate.is_null()) & + (Event.isCanceled == False)) + + CLTerm = Term.alias() + laborMembers = ( CeltsLabor - .select( - fn.CONCAT(User.firstName, ' ', User.lastName).alias('fullName'), - User.bnumber, - fn.CONCAT(User.username, '@berea.edu').alias('email'), - fn.COUNT(fn.DISTINCT(Event.id)).alias('meetingsAttended') - ) + .select(CeltsLabor.user_id) + .join(CLTerm, on=(CeltsLabor.term == CLTerm.id)) + .where( + (CeltsLabor.term == term) | + ((CLTerm.academicYear == term.academicYear) & (CeltsLabor.isAcademicYear == True)) + )) + + laborQuery = ( + CeltsLabor + .select(fullName, User.bnumber, email, meetingsAttended) .join(User) .switch(CeltsLabor) - .join( - EventParticipant, - JOIN.LEFT_OUTER, - on=(CeltsLabor.user == EventParticipant.user) - ) - .join( - Event, - JOIN.LEFT_OUTER, - on=( - (EventParticipant.event == Event.id) & - (Event.term == term) & - (Event.isLaborOnly == True) & - (Event.deletionDate.is_null()) & - (Event.isCanceled == False) - ) - ) - .where( - (CeltsLabor.term == term) - ) - .group_by(CeltsLabor.user) - ) + .join(EventParticipant, JOIN.LEFT_OUTER, on=(CeltsLabor.user == EventParticipant.user)) + .join(Event, JOIN.LEFT_OUTER,on=validEvent) + .where(CeltsLabor.user.in_(laborMembers)) + .group_by(CeltsLabor.user)) - nonLaborQuery = ( #so that non-labor attendees who are not in CeltsLabor also appear + nonLaborQuery = ( EventParticipant - .select( - fn.CONCAT(User.firstName, ' ', User.lastName).alias('fullName'), - User.bnumber, - fn.CONCAT(User.username, '@berea.edu').alias('email'), - fn.COUNT(fn.DISTINCT(EventParticipant.event_id)).alias('meetingsAttended') - ) + .select(fullName, User.bnumber, email, meetingsAttended) .join(User) .switch(EventParticipant) - .join( - Event, - on=( - (EventParticipant.event == Event.id) & - (Event.term == term) & - (Event.isLaborOnly == True) & - (Event.deletionDate.is_null()) & - (Event.isCanceled == False) - ) - ) - .join( - CeltsLabor, - JOIN.LEFT_OUTER, - on=(EventParticipant.user == CeltsLabor.user) & - (CeltsLabor.term == term) - ) - .where(CeltsLabor.user.is_null()) - .group_by(EventParticipant.user) - ) + .join(Event,on=validEvent) + .where(EventParticipant.user.not_in(laborMembers)) + .group_by(EventParticipant.user)) query = laborQuery.union(nonLaborQuery).order_by(SQL('fullName')) columns = ("Full Name", "B-Number", "Email", "Meetings Attended") - return (columns, query.tuples()) - + return (columns, query.tuples()) def makeDataXls(sheetName, sheetData, workbook, sheetDesc=None): # assumes the length of the column titles matches the length of the data From 3baaf0c7ac4fd4899e57994ae7e2f4d7aa2db41d Mon Sep 17 00:00:00 2001 From: Arohasina Date: Tue, 31 Mar 2026 18:35:25 -0400 Subject: [PATCH 21/22] updated test --- tests/code/test_spreadsheet.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/code/test_spreadsheet.py b/tests/code/test_spreadsheet.py index 21e58efd2..7b183667a 100644 --- a/tests/code/test_spreadsheet.py +++ b/tests/code/test_spreadsheet.py @@ -65,10 +65,10 @@ def fixture_info(): isService=True ) - labor1 = CeltsLabor.create(user=user1, term=term1, positionTitle="test position 1") - labor2 = CeltsLabor.create(user=user2, term=term1, positionTitle="test position 2") - labor3 = CeltsLabor.create(user=user1, term=term2, positionTitle="test position 3") - labor4 = CeltsLabor.create(user=user2, term=term2, positionTitle="test position 4") + labor1 = CeltsLabor.create(user=user1, term=term1, positionTitle="test position 1", isAcademicYear=True) + labor2 = CeltsLabor.create(user=user2, term=term1, positionTitle="test position 2", isAcademicYear=True) + labor3 = CeltsLabor.create(user=user1, term=term2, positionTitle="test position 3", isAcademicYear=True) + labor4 = CeltsLabor.create(user=user2, term=term2, positionTitle="test position 4", isAcademicYear=True) eventparticipant1 = EventParticipant.create(event=event1, user=user1, hoursEarned=5) eventparticipant2 = EventParticipant.create(event=event1, user=user2, hoursEarned=3) @@ -699,7 +699,7 @@ def test_laborAttendanceByTerm(fixture_info): columns, results = laborAttendanceByTerm(fixture_info['term1']) results = list(results) - assert columns == ["Full Name", "B-Number", "Email", "Meetings Attended"] + assert columns == ("Full Name", "B-Number", "Email", "Meetings Attended") assert len(results) == 2 assert ("John Doe", "B774377", "doej@berea.edu", 1) in results @@ -712,6 +712,13 @@ def test_laborAttendanceByTerm(fixture_info): assert ("John Doe", "B774377", "doej@berea.edu", 0) in results assert ("Jane Doe", "B888828", "doej2@berea.edu", 0) in results + columns, results = laborAttendanceByTerm(fixture_info['term3']) + results = list(results) + + assert len(results) == 2 + assert ("John Doe", "B774377", "doej@berea.edu", 0) in results + assert ("Jane Doe", "B888828", "doej2@berea.edu", 0) in results + EventParticipant.create(event=fixture_info['event2'], user=fixture_info['user1'], hoursEarned=1) columns, results = laborAttendanceByTerm(fixture_info['term1']) From c305246283e730c19e77049c7e16b21e0a6212c8 Mon Sep 17 00:00:00 2001 From: Arohasina Date: Tue, 31 Mar 2026 18:47:42 -0400 Subject: [PATCH 22/22] add distinct --- 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 c0121f7e7..997002b0d 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -241,7 +241,7 @@ def laborAttendanceByTerm(term): CLTerm = Term.alias() laborMembers = ( CeltsLabor - .select(CeltsLabor.user_id) + .select(fn.DISTINCT(CeltsLabor.user_id)) .join(CLTerm, on=(CeltsLabor.term == CLTerm.id)) .where( (CeltsLabor.term == term) |