Skip to content
22 changes: 22 additions & 0 deletions app/logic/volunteerSpreadsheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,27 @@ 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'),
)
.where(Event.isLaborOnly == True)
.group_by(EventParticipant.user_id, Term.description)
.order_by(User.lastName, User.firstName, Term.description)
)
Comment on lines +228 to +242
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

laborAttendanceByTerm is currently counting attendees of labor-only events, but it doesn’t restrict the population to CELTS labor students (e.g., records in CeltsLabor) and it will omit labor students who have 0 meeting attendance. This doesn’t match the issue requirement of listing labor students for the academic year with per-term attendance counts. Consider driving the report off CeltsLabor (per term / academic year) and LEFT JOINing labor-only EventParticipant rows so labor students with zero attendance still appear with a 0 count, and non-labor attendees are excluded.

Copilot uses AI. Check for mistakes.

columns = ("Full Name", "B-Number", "Email", "Term", "Meetings Attended")
results = list(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
Expand Down Expand Up @@ -271,6 +292,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)
Expand Down
16 changes: 14 additions & 2 deletions tests/code/test_spreadsheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ def fixture_info():
startDate=date(2023, 9, 1),
isCanceled=False,
deletionDate=None,
isService=True
isService=True,
isLaborOnly=True
Comment on lines +35 to +36
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is trailing whitespace after the comma on these lines. Consider removing it for consistency with the rest of the codebase.

Copilot uses AI. Check for mistakes.
)
event2 = Event.create(
name='Event2',
Expand All @@ -41,7 +42,8 @@ def fixture_info():
startDate=date(2023, 9, 10),
isCanceled=False,
deletionDate=None,
isService=True
isService=True,
isLaborOnly=True
Comment on lines +45 to +46
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is trailing whitespace after the comma on these lines. Consider removing it for consistency with the rest of the codebase.

Copilot uses AI. Check for mistakes.
)
event3 = Event.create(
name='Event3',
Expand Down Expand Up @@ -683,5 +685,15 @@ def test_getUniqueVolunteers(fixture_info):
("Test Tester", "testt@berea.edu", "B55555"),
])

@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)

Comment on lines +690 to 693
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test creates duplicate EventParticipant records for the same user-event combinations (user1 on event1 already exists from fixture line 68, and user2 on event1 already exists from fixture line 69). However, the production code in app/logic/participants.py (lines 52, 69-70) and app/logic/volunteers.py (lines 38-48) prevents such duplicates by checking if a participant already exists before creating a new record. This test should either remove the existing participants from the fixture for these events, or create participations for different events to avoid testing an impossible scenario.

Suggested change
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)
# Create separate labor events for this test to avoid duplicating existing participations
labor_event1 = Event.create(
name="Labor Attendance Test Event 1",
term=fixture_info["term1"],
program=fixture_info["program1"],
startDate=date(2023, 9, 1),
isCanceled=False,
deletionDate=None,
isService=False,
)
labor_event2 = Event.create(
name="Labor Attendance Test Event 2",
term=fixture_info["term1"],
program=fixture_info["program1"],
startDate=date(2023, 10, 1),
isCanceled=False,
deletionDate=None,
isService=False,
)
EventParticipant.create(event=labor_event1, user=fixture_info["user1"], hoursEarned=1)
EventParticipant.create(event=labor_event2, user=fixture_info["user1"], hoursEarned=1)
EventParticipant.create(event=labor_event1, user=fixture_info["user2"], hoursEarned=1)

Copilot uses AI. Check for mistakes.
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
Comment on lines +698 to +699
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test assertions expect user1 to have attended 3 meetings and user2 to have attended 2 meetings. However, if the duplicate EventParticipant creation issue (lines 690-692) is fixed, the expected values should be 2 meetings for user1 (event1 and event2) and 1 meeting for user2 (event1), assuming the fixture participants are removed or the test creates participations for different events.

Suggested change
assert ("John Doe", "B774377", "doej@berea.edu", "Fall 2023", 3) in results
assert ("Jane Doe", "B888828", "doej2@berea.edu", "Fall 2023", 2) in results
assert ("John Doe", "B774377", "doej@berea.edu", "Fall 2023", 2) in results
assert ("Jane Doe", "B888828", "doej2@berea.edu", "Fall 2023", 1) in results

Copilot uses AI. Check for mistakes.
Comment on lines +689 to +699
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test only covers participation in Fall 2023 labor events, but the academic year '2023-2024-test' also includes Spring 2024 (term3). Consider adding a labor event in Spring 2024 and creating participations for it to verify that the function correctly groups attendance by multiple terms and that students appear in multiple rows (one per term they participated in).

Copilot uses AI. Check for mistakes.
Loading