Coverage for app/logic/spreadsheet.py: 100%
113 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-10-02 17:57 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-10-02 17:57 +0000
1from importlib.abc import ResourceReader
2from os import major
3import xlsxwriter
4from peewee import fn, Case, JOIN
5from collections import defaultdict
7from app import app
8from app.models.eventParticipant import EventParticipant
9from app.models.user import User
10from app.models.program import Program
11from app.models.event import Event
12from app.models.term import Term
15def getUniqueVolunteers(academicYear):
16 uniqueVolunteers = (EventParticipant.select(fn.DISTINCT(EventParticipant.user_id), fn.CONCAT(User.firstName, ' ', User.lastName), User.bnumber)
17 .join(User).switch(EventParticipant)
18 .join(Event)
19 .join(Term)
20 .where(Term.academicYear == academicYear)
21 .order_by(EventParticipant.user_id))
23 return uniqueVolunteers.tuples()
26def getVolunteerProgramEventByTerm(term):
27 volunteersByTerm = (EventParticipant.select(fn.CONCAT(User.firstName, ' ', User.lastName), EventParticipant.user_id, Program.programName, Event.name)
28 .join(User).switch(EventParticipant)
29 .join(Event)
30 .join(Program)
31 .where(Event.term_id == term)
32 .order_by(EventParticipant.user_id))
34 return volunteersByTerm.tuples()
37def totalVolunteerHours(academicYear):
38 query = (EventParticipant.select(fn.SUM(EventParticipant.hoursEarned))
39 .join(Event, on=(EventParticipant.event == Event.id))
40 .join(Term, on=(Event.term == Term.id))
41 .where(Term.academicYear == academicYear)
42 )
44 return query.tuples()
47def volunteerProgramHours(academicYear):
48 volunteerProgramHours = (EventParticipant.select(Program.programName, EventParticipant.user_id, fn.SUM(EventParticipant.hoursEarned))
49 .join(Event, on=(EventParticipant.event_id == Event.id))
50 .join(Program, on=(Event.program_id == Program.id))
51 .join(Term, on=(Event.term == Term.id))
52 .where(Term.academicYear == academicYear)
53 .group_by(Program.programName, EventParticipant.user_id))
55 return volunteerProgramHours.tuples()
58def onlyCompletedAllVolunteer(academicYear):
59 subQuery = (EventParticipant.select(EventParticipant.user_id)
60 .join(Event)
61 .join(Term)
62 .where(Event.name != "All Volunteer Training", Term.academicYear == academicYear))
64 onlyAllVolunteer = (EventParticipant.select(EventParticipant.user_id, fn.CONCAT(User.firstName, " ", User.lastName))
65 .join(User).switch(EventParticipant)
66 .join(Event)
67 .join(Term)
68 .where(Event.name == "All Volunteer Training", Term.academicYear == academicYear, EventParticipant.user_id.not_in(subQuery)))
70 return onlyAllVolunteer.tuples()
73def volunteerHoursByProgram(academicYear):
74 query = (Program.select(Program.programName, fn.SUM(EventParticipant.hoursEarned).alias('sum'))
75 .join(Event)
76 .join(EventParticipant, on=(Event.id == EventParticipant.event_id))
77 .join(Term, on=(Term.id == Event.term))
78 .where(Term.academicYear == academicYear)
79 .group_by(Program.programName)
80 .order_by(Program.programName))
82 return query.tuples()
85def volunteerMajorAndClass(academicYear, column, reorderClassLevel=False):
86 majorAndClass = (User.select(Case(None, ((column.is_null(), "Unknown"),), column), fn.COUNT(fn.DISTINCT(EventParticipant.user_id)).alias('count'))
87 .join(EventParticipant, on=(User.username == EventParticipant.user_id))
88 .join(Event, on=(EventParticipant.event_id == Event.id))
89 .join(Term, on=(Event.term == Term.id))
90 .where(Term.academicYear == academicYear)
91 .group_by(column))
93 if reorderClassLevel:
94 majorAndClass = majorAndClass.order_by(Case(None, ((column == "Freshman", 1),
95 (column == "Sophomore", 2),
96 (column == "Junior", 3),
97 (column == "Senior", 4),
98 (column == "Graduating", 5),
99 (column == "Non-Degree", 6),
100 (column.is_null(), 7)),
101 8))
102 else:
103 majorAndClass = majorAndClass.order_by(column.asc(nulls='LAST'))
105 return majorAndClass.tuples()
108def repeatVolunteersPerProgram(academicYear):
109 repeatPerProgramQuery = (EventParticipant.select(fn.CONCAT(User.firstName, " ", User.lastName).alias('fullName'),
110 Program.programName.alias("programName"),
111 fn.COUNT(EventParticipant.event_id).alias('event_count'))
112 .join(Event, on=(EventParticipant.event_id == Event.id))
113 .join(Program, on=(Event.program == Program.id))
114 .join(User, on=(User.username == EventParticipant.user_id))
115 .join(Term, on=(Event.term == Term.id))
116 .where(Term.academicYear == academicYear)
117 .group_by(User.firstName, User.lastName, Event.program)
118 .having(fn.COUNT(EventParticipant.event_id) > 1)
119 .order_by(Event.program, User.lastName))
121 return repeatPerProgramQuery.tuples()
124def repeatVolunteers(academicYear):
125 repeatAllProgramQuery = (EventParticipant.select(fn.CONCAT(User.firstName, " ", User.lastName), fn.COUNT(EventParticipant.user_id).alias('count'))
126 .join(User, on=(User.username == EventParticipant.user_id))
127 .join(Event, on=(EventParticipant.event == Event.id))
128 .join(Term, on=(Event.term == Term.id))
129 .where(Term.academicYear == academicYear)
130 .group_by(User.firstName, User.lastName)
131 .having(fn.COUNT(EventParticipant.user_id) > 1))
133 return repeatAllProgramQuery.tuples()
136def getRetentionRate(academicYear):
137 retentionList = []
138 fall, spring = academicYear.split("-")
139 fallParticipationDict = termParticipation(f"Fall {fall}")
140 springParticipationDict = termParticipation(f"Spring {spring}")
142 retentionRateDict = calculateRetentionRate(fallParticipationDict, springParticipationDict)
143 for program, retentionRate in retentionRateDict.items():
144 retentionList.append((program, str(round(retentionRate * 100, 2)) + "%"))
146 return retentionList
149def termParticipation(termDescription):
150 participationQuery = (Event.select(Event.program, EventParticipant.user_id.alias('participant'), Program.programName.alias("programName"))
151 .join(EventParticipant, JOIN.LEFT_OUTER, on=(Event.id == EventParticipant.event))
152 .join(Program, on=(Event.program == Program.id))
153 .join(Term, on=(Event.term_id == Term.id))
154 .where(Term.description == termDescription)
155 .order_by(EventParticipant.user))
157 programParticipationDict = defaultdict(list)
158 for result in participationQuery.dicts():
159 programName = result['programName']
160 participant = result['participant']
161 programParticipationDict[programName].append(participant)
163 return dict(programParticipationDict)
166def removeNullParticipants(participantList):
167 return list(filter(lambda participant: participant, participantList))
170def calculateRetentionRate(fallDict, springDict):
171 retentionDict = {}
172 for program in fallDict:
173 fallParticipants = set(removeNullParticipants(fallDict[program]))
174 springParticipants = set(removeNullParticipants(springDict.get(program, [])))
175 retentionRate = 0.0
176 try:
177 retentionRate = len(fallParticipants & springParticipants) / len(fallParticipants)
178 except ZeroDivisionError:
179 pass
180 retentionDict[program] = retentionRate
182 return retentionDict
185def makeDataXls(getData, columnTitles, sheetName, workbook):
186 worksheet = workbook.add_worksheet(sheetName)
187 bold = workbook.add_format({'bold': True})
189 worksheet.write_string(0, 0, sheetName)
191 for column, title in enumerate(columnTitles):
192 worksheet.write(1, column, title, bold)
194 for column, rowData in enumerate(getData):
195 for data, value in enumerate(rowData):
196 worksheet.write(column + 2, data, value)
198 for column, title in enumerate(columnTitles):
199 columnData = [title] + [rowData[column] for rowData in getData]
200 setColumnWidth = max(len(str(x)) for x in columnData)
201 worksheet.set_column(column, column, setColumnWidth + 3)
204def createSpreadsheet(academicYear):
205 filepath = f"{app.config['files']['base_path']}/volunteer_data_{academicYear}.xlsx"
206 workbook = xlsxwriter.Workbook(filepath, {'in_memory': True})
208 hoursByProgramColumns = ["Program", "Hours"]
209 volunteerMajorColumns = ["Major", "Count"]
210 volunteerClassColumns = ["Class Level", "Count"]
211 repeatProgramEventVolunteerColumns = ["Volunteer", "Program Name", "Event Count"]
212 repeatAllProgramVolunteerColumns = ["Volunteer", "Number of Events"]
213 volunteerProgramRetentionRateAcrossTermColumns = ["Program", "Retention Rate"]
214 uniqueVolunteersColumns = ["Username", "Full Name", "B-Number"]
215 totalVolunteerHoursColumns = ["Total Volunteer Hours"]
216 volunteerProgramHoursColumns = ["Program Name", "Volunteer Username", "Volunteer Hours"]
217 onlyCompletedAllVolunteerColumns = ["Username", "Full Name"]
218 volunteerProgramEventByTerm = ["Full Name", "Username", "Program Name", "Event Name"]
220 makeDataXls(volunteerHoursByProgram(academicYear), hoursByProgramColumns, "Total Hours By Program", workbook)
221 makeDataXls(volunteerMajorAndClass(academicYear, User.major), volunteerMajorColumns, "Volunteers By Major", workbook)
222 makeDataXls(volunteerMajorAndClass(academicYear, User.classLevel, reorderClassLevel=True), volunteerClassColumns, "Volunteers By Class Level", workbook)
223 makeDataXls(repeatVolunteersPerProgram(academicYear), repeatProgramEventVolunteerColumns, "Repeat Volunteers Per Program", workbook)
224 makeDataXls(repeatVolunteers(academicYear), repeatAllProgramVolunteerColumns, "Repeat Volunteers All Programs", workbook)
225 makeDataXls(getRetentionRate(academicYear), volunteerProgramRetentionRateAcrossTermColumns, "Retention Rate By Semester", workbook)
226 makeDataXls(getUniqueVolunteers(academicYear), uniqueVolunteersColumns, "Unique Volunteers", workbook)
227 makeDataXls(totalVolunteerHours(academicYear), totalVolunteerHoursColumns, "Total Hours", workbook)
228 makeDataXls(volunteerProgramHours(academicYear), volunteerProgramHoursColumns, "Volunteer Hours By Program", workbook)
229 makeDataXls(onlyCompletedAllVolunteer(academicYear), onlyCompletedAllVolunteerColumns, "Only All Volunteer Training", workbook)
230 makeDataXls(getVolunteerProgramEventByTerm(Term.get_or_none(Term.description == f"Fall {academicYear.split('-')[0]}")), volunteerProgramEventByTerm, f"Fall {academicYear.split('-')[0]}", workbook)
232 workbook.close()
234 return filepath