Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 38 additions & 7 deletions app/logic/volunteerSpreadsheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,19 @@ def getBaseQuery(academicYear):
def getUniqueVolunteers(academicYear):
base = getBaseQuery(academicYear)

columns = ["Full Name", "Email", "B-Number"]
subquery = (base.select(fn.DISTINCT(EventParticipant.user_id).alias('user_id'), fn.CONCAT(User.firstName, ' ', User.lastName).alias("fullname"), User.bnumber)
.where(Event.isService == True)).alias('subq')
query = Select().from_(subquery).select(subquery.c.fullname, fn.CONCAT(subquery.c.user_id,'@berea.edu'), subquery.c.bnumber)
columns = ["Full Name", "Email", "B-Number", "Term"]
subquery = (base.select(fn.DISTINCT(EventParticipant.user_id).alias('user_id'),
fn.CONCAT(User.firstName, ' ', User.lastName).alias("fullname"),
User.bnumber,
Term.description.alias("term"))
.where(Event.isService == True)).alias('subq')

query = Select().from_(subquery).select(subquery.c.fullname,
fn.CONCAT(subquery.c.user_id,'@berea.edu'),
subquery.c.bnumber,
subquery.c.term)

return (columns,query.tuples().execute(mainDB))
return (columns, query.tuples().execute(mainDB))


def volunteerProgramHours(academicYear):
Expand Down Expand Up @@ -207,6 +214,29 @@ def termParticipation(term):

return dict(programParticipationDict)

def graduatingSeniorsVolunteerHours(academicYear):
columns = ["Full Name", "Email", "B-Number", "Unique Volunteer Semesters", "Total Volunteer Hours"]

currentSeniors = (User.select().where(User.rawClassLevel.in_(["Senior", "Graduating"])))

query = (EventParticipant
.select(fn.CONCAT(User.firstName, ' ', User.lastName),
fn.CONCAT(User.username, '@berea.edu'),
User.bnumber,
fn.COUNT(fn.DISTINCT(Event.term)).alias("semester_count"),
fn.SUM(EventParticipant.hoursEarned).alias("total_hours"))
.join(User).switch(EventParticipant)
.join(Event)
.where(Event.isService == True,
Event.deletionDate == None,
Event.isCanceled == False,
EventParticipant.user_id.in_(currentSeniors))
.group_by(User.bnumber)
.having(fn.COUNT(fn.DISTINCT(Event.term)) >= 4)
.order_by(SQL("semester_count").desc()))

return (columns, query.tuples())


def removeNullParticipants(participantList):
return list(filter(lambda participant: participant, participantList))
Expand Down Expand Up @@ -314,10 +344,11 @@ def createSpreadsheet(academicYear):
makeDataXls("Volunteers By Major", volunteerMajorAndClass(academicYear, User.major), workbook, sheetDesc="All volunteers who participated in service events, by major.")
makeDataXls("Volunteers By Class Level", volunteerMajorAndClass(academicYear, User.rawClassLevel, classLevel=True), workbook, sheetDesc="All volunteers who participated in service events, by class level. Our source for this data does not seem to be particularly accurate.")
makeDataXls("Repeat Participants", repeatParticipants(academicYear), workbook, sheetDesc="Students who participated in multiple events, whether earning service hours or not.")
makeDataXls("Unique Volunteers", getUniqueVolunteers(academicYear), workbook, sheetDesc=f"All students who participated in at least one service event during {academicYear}.")
makeDataXls("Unique Volunteers", getUniqueVolunteers(academicYear), workbook, sheetDesc=f"All students who participated in at least one service event per term 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("Graduating Seniors", graduatingSeniorsVolunteerHours(academicYear), workbook, sheetDesc="Graduating seniors who have earned any number of service hours for at least 4 unique semesters.")

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).")
Expand Down
93 changes: 46 additions & 47 deletions database/prod-backup.sql

Large diffs are not rendered by default.

114 changes: 111 additions & 3 deletions tests/code/test_spreadsheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,115 @@ def test_laborAttendanceByTerm(fixture_info):

EventParticipant.create(event=fixture_info['event3'], user=fixture_info['user1'], hoursEarned=2)

columns, results = laborAttendanceByTerm(fixture_info['term1'])
results = list(results)
@pytest.mark.integration
def test_graduatingSeniorsVolunteerHours(fixture_info):
columns, rows = graduatingSeniorsVolunteerHours("2024-2025-test")
assert columns == ["Full Name", "Email", "B-Number", "Unique Volunteer Semesters", "Total Volunteer Hours"]
assert list(rows) == []

term5 = Term.create(description='Fall 2021 Test', academicYear='2021-2022-test')
term6 = Term.create(description='Spring 2022 Test', academicYear='2021-2022-test')
term7 = Term.create(description='Fall 2022 Test', academicYear='2022-2023-test')

program5 = Program.create(programName='Program5')

event5 = Event.create(name='Event5', term=term5, program=program5, startDate=date(2021, 9, 1),
isCanceled=False, deletionDate=None, isService=True)
event6 = Event.create(name='Event6', term=term6, program=program5, startDate=date(2022, 2, 1),
isCanceled=False, deletionDate=None, isService=True)
event7 = Event.create(name='Event7', term=term7, program=program5, startDate=date(2022, 9, 1),
isCanceled=False, deletionDate=None, isService=True)

# Bob is a Senior with 3 unique service semesters, which means he should NOT appear in the list
EventParticipant.create(user=fixture_info['user3'], event=event5, hoursEarned=2)
EventParticipant.create(user=fixture_info['user3'], event=event6, hoursEarned=3)
EventParticipant.create(user=fixture_info['user3'], event=event7, hoursEarned=4)

# Bob now has 4 unique semesters total, and is a Senior in 2024-2025-test
columns, rows = graduatingSeniorsVolunteerHours("2024-2025-test")
assert list(rows) == []

assert ("John Doe", "B774377", "doej@berea.edu", 2) in results
# Add a 4th unique semester for Bob. So now he should appear regardless of academic year queried
termBobUnique = Term.create(description='Spring 2023 Test', academicYear='2022-2023-test')
eventBobUnique = Event.create(name='EventBob4th', term=termBobUnique, program=program5,
startDate=date(2023, 2, 1), isCanceled=False, deletionDate=None, isService=True)
EventParticipant.create(user=fixture_info['user3'], event=eventBobUnique, hoursEarned=2)

# Bob now appears for ANY academic year since we only check rawClassLevel
columns, rows = graduatingSeniorsVolunteerHours("2024-2025-test")
result = list(rows)
assert len(result) == 1
assert result[0] == ("Bob Builder", "builderb@berea.edu", "B00700932", 4, 11.0)

columns, rows = graduatingSeniorsVolunteerHours("2023-2024-test")
result = list(rows)
assert len(result) == 1
assert result[0] == ("Bob Builder", "builderb@berea.edu", "B00700932", 4, 11.0)

# Non-senior students should never appear even with enough semesters
extraTerm = Term.create(description='Spring 2021 Test', academicYear='2020-2021-test')
extraTerm2 = Term.create(description='Fall 2020 Test', academicYear='2020-2021-test')
extraTerm3 = Term.create(description='Spring 2020 Test', academicYear='2019-2020-test')

event8 = Event.create(name='Event8', term=extraTerm, program=program5, startDate=date(2021, 2, 1),
isCanceled=False, deletionDate=None, isService=True)
event9 = Event.create(name='Event9', term=extraTerm2, program=program5, startDate=date(2020, 9, 1),
isCanceled=False, deletionDate=None, isService=True)
event10 = Event.create(name='Event10', term=extraTerm3, program=program5, startDate=date(2020, 2, 1),
isCanceled=False, deletionDate=None, isService=True)

# Give John (Sophomore) 4 unique semesters. He should never appear because he is not a senior
EventParticipant.create(user=fixture_info['user1'], event=event8, hoursEarned=1)
EventParticipant.create(user=fixture_info['user1'], event=event9, hoursEarned=1)
EventParticipant.create(user=fixture_info['user1'], event=event10, hoursEarned=1)
# John already has term5/term6/term7 plus one more = 6, but is Sophomore so should not appear
columns, rows = graduatingSeniorsVolunteerHours("2023-2024-test")
result = list(rows)
assert len(result) == 1
assert result[0][0] == "Bob Builder"

# Test "Graduating" class level works the same as "Senior"
graduatingUser = User.create(username="smithj", firstName="James", lastName="Smith",
bnumber="B999999", major="Math", rawClassLevel="Graduating")

gradTerm1 = Term.create(description='Fall 2023 Grad', academicYear='2023-2024-test')
gradTerm2 = Term.create(description='Spring 2023 Grad', academicYear='2022-2023-test')
gradTerm3 = Term.create(description='Fall 2022 Grad', academicYear='2022-2023-test')
gradTerm4 = Term.create(description='Spring 2022 Grad', academicYear='2021-2022-test')

gevent1 = Event.create(name='GEvent1', term=gradTerm1, program=program5, startDate=date(2023, 9, 5),
isCanceled=False, deletionDate=None, isService=True)
gevent2 = Event.create(name='GEvent2', term=gradTerm2, program=program5, startDate=date(2023, 2, 5),
isCanceled=False, deletionDate=None, isService=True)
gevent3 = Event.create(name='GEvent3', term=gradTerm3, program=program5, startDate=date(2022, 9, 5),
isCanceled=False, deletionDate=None, isService=True)
gevent4 = Event.create(name='GEvent4', term=gradTerm4, program=program5, startDate=date(2022, 2, 5),
isCanceled=False, deletionDate=None, isService=True)

EventParticipant.create(user=graduatingUser, event=gevent1, hoursEarned=5)
EventParticipant.create(user=graduatingUser, event=gevent2, hoursEarned=5)
EventParticipant.create(user=graduatingUser, event=gevent3, hoursEarned=5)
EventParticipant.create(user=graduatingUser, event=gevent4, hoursEarned=5)

# Both Bob and James should appear now
columns, rows = graduatingSeniorsVolunteerHours("2023-2024-test")
result = list(rows)
assert len(result) == 2
names = [r[0] for r in result]
assert "Bob Builder" in names
assert "James Smith" in names

james = next(r for r in result if r[0] == "James Smith")
assert james == ("James Smith", "smithj@berea.edu", "B999999", 4, 20.0)

# non-service events should not be counted towards semester count
nonServiceTerm = Term.create(description='Fall 2019 Test', academicYear='2019-2020-test')
nonServiceEvent = Event.create(name='NonServiceEvent', term=nonServiceTerm, program=program5,
startDate=date(2019, 9, 1), isCanceled=False, deletionDate=None, isService=False)
EventParticipant.create(user=fixture_info['user3'], event=nonServiceEvent, hoursEarned=5)

# Bob still has exactly 4 service semesters, the non-service event should not push the count up
columns, rows = graduatingSeniorsVolunteerHours("2024-2025-test")
result = list(rows)
bob = next(r for r in result if r[0] == "Bob Builder")
assert bob[3] == 4
Loading