PyKubeGrader 0.3.7__py3-none-any.whl → 0.3.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {PyKubeGrader-0.3.7.dist-info → PyKubeGrader-0.3.9.dist-info}/METADATA +2 -1
- {PyKubeGrader-0.3.7.dist-info → PyKubeGrader-0.3.9.dist-info}/RECORD +22 -16
- pykubegrader/build/api_notebook_builder.py +43 -27
- pykubegrader/build/build_folder.py +29 -15
- pykubegrader/grade_reports/assignments.py +184 -0
- pykubegrader/grade_reports/class_grade_report.py +137 -0
- pykubegrader/grade_reports/grade_report.py +353 -0
- pykubegrader/grade_reports/grading_config.py +70 -0
- pykubegrader/grade_reports/test.ipynb +43 -0
- pykubegrader/grading_tester.ipynb +53 -2
- pykubegrader/submit/submit_assignment.py +4 -3
- pykubegrader/telemetry.py +88 -84
- pykubegrader/tokens/generator.py +75 -0
- pykubegrader/tokens/token_panel.py +2 -2
- pykubegrader/tokens/tokens.py +0 -1
- pykubegrader/widgets_base/multi_select.py +1 -1
- pykubegrader/widgets_base/select.py +1 -1
- {PyKubeGrader-0.3.7.dist-info → PyKubeGrader-0.3.9.dist-info}/LICENSE.txt +0 -0
- {PyKubeGrader-0.3.7.dist-info → PyKubeGrader-0.3.9.dist-info}/WHEEL +0 -0
- {PyKubeGrader-0.3.7.dist-info → PyKubeGrader-0.3.9.dist-info}/entry_points.txt +0 -0
- {PyKubeGrader-0.3.7.dist-info → PyKubeGrader-0.3.9.dist-info}/top_level.txt +0 -0
- /pykubegrader/grade_reports/{grade_reports.py → __grade_reports.py} +0 -0
@@ -0,0 +1,137 @@
|
|
1
|
+
# # import the password and user from the build folder
|
2
|
+
# try:
|
3
|
+
# from pykubegrader.build.passwords import password, user
|
4
|
+
# except: # noqa: E722
|
5
|
+
# print("Passwords not found, cannot access database")
|
6
|
+
|
7
|
+
from pykubegrader.grade_reports.grading_config import assignment_type_list, skipped_users
|
8
|
+
from pykubegrader.grade_reports.grade_report import GradeReport
|
9
|
+
# from ..build.passwords import password, user
|
10
|
+
from pykubegrader.telemetry import get_all_students
|
11
|
+
|
12
|
+
|
13
|
+
import os
|
14
|
+
import requests
|
15
|
+
from requests.auth import HTTPBasicAuth
|
16
|
+
import socket
|
17
|
+
import pandas as pd
|
18
|
+
import numpy as np
|
19
|
+
import tqdm
|
20
|
+
|
21
|
+
# user = user()
|
22
|
+
# password = password()
|
23
|
+
|
24
|
+
# Set the environment variables for the database
|
25
|
+
# os.environ["JUPYTERHUB_USER"] = "jca92"
|
26
|
+
# os.environ["TOKEN"] = "token"
|
27
|
+
# os.environ["DB_URL"] = "https://engr-131-api.eastus.cloudapp.azure.com/"
|
28
|
+
# os.environ["keys_student"] = "capture"
|
29
|
+
# os.environ["user_name_student"] = "student"
|
30
|
+
|
31
|
+
api_base_url = os.getenv("DB_URL")
|
32
|
+
student_user = os.getenv("user_name_student")
|
33
|
+
student_pw = os.getenv("keys_student")
|
34
|
+
|
35
|
+
|
36
|
+
class ClassGradeReport:
|
37
|
+
"""Generates and manages a class-wide grade report.
|
38
|
+
|
39
|
+
This class retrieves a list of students, initializes a structured grade report,
|
40
|
+
and populates it with individual student grade data. The final report includes
|
41
|
+
both assignment-specific grades and a weighted average grade.
|
42
|
+
|
43
|
+
Attributes:
|
44
|
+
student_list (list): A sorted list of all students in the class.
|
45
|
+
all_student_grades_df (pd.DataFrame): A DataFrame storing grades for each student,
|
46
|
+
including assignment scores and a weighted average.
|
47
|
+
|
48
|
+
Methods:
|
49
|
+
setup_class_grades():
|
50
|
+
Initializes an empty DataFrame with assignment names and weighted average columns.
|
51
|
+
update_student_grade(student):
|
52
|
+
Fetches and updates an individual student’s weighted average grades in the DataFrame.
|
53
|
+
fill_class_grades():
|
54
|
+
Iterates through all students to populate the class-wide grade report.
|
55
|
+
"""
|
56
|
+
|
57
|
+
def __init__(self, user, password):
|
58
|
+
"""Initializes the class grade report.
|
59
|
+
|
60
|
+
Retrieves the student list using authentication, sorts it, and sets up
|
61
|
+
the class-wide grade report by initializing and populating a DataFrame.
|
62
|
+
"""
|
63
|
+
self.user = user
|
64
|
+
self.password = password
|
65
|
+
|
66
|
+
self.student_list = get_all_students(self.user, self.password)
|
67
|
+
self.student_list = list( set(self.student_list)-set(skipped_users) )
|
68
|
+
self.student_list.sort()
|
69
|
+
|
70
|
+
self.setup_class_grades()
|
71
|
+
self.fill_class_grades()
|
72
|
+
self.get_class_stats()
|
73
|
+
|
74
|
+
|
75
|
+
def setup_class_grades(self):
|
76
|
+
"""Creates an empty DataFrame to store grades for all students.
|
77
|
+
|
78
|
+
The DataFrame contains assignment columns and a "Weighted Average Grade" column,
|
79
|
+
with students as index labels.
|
80
|
+
"""
|
81
|
+
self.all_student_grades_df = pd.DataFrame(
|
82
|
+
np.nan,
|
83
|
+
dtype=float,
|
84
|
+
index=self.student_list,
|
85
|
+
columns=[a.name for a in assignment_type_list] + ["Weighted Average Grade"],
|
86
|
+
)
|
87
|
+
|
88
|
+
def update_student_grade(self, student):
|
89
|
+
"""Fetches and updates the grade report for an individual student.
|
90
|
+
|
91
|
+
Args:
|
92
|
+
student (str): The username or identifier of the student.
|
93
|
+
|
94
|
+
Updates:
|
95
|
+
The student's row in `all_student_grades_df` with their weighted average grades.
|
96
|
+
"""
|
97
|
+
report = GradeReport(params={"username": student}, display_=False)
|
98
|
+
row_series = report.weighted_average_grades.transpose().iloc[
|
99
|
+
0
|
100
|
+
] # Example transformation
|
101
|
+
row_series = row_series.reindex(self.all_student_grades_df.columns)
|
102
|
+
self.all_student_grades_df.loc[student] = row_series
|
103
|
+
|
104
|
+
def fill_class_grades(self):
|
105
|
+
"""Populates the class-wide grade report with data from all students.
|
106
|
+
|
107
|
+
Iterates through the `student_list` and updates the DataFrame by fetching
|
108
|
+
and storing each student's weighted average grades.
|
109
|
+
"""
|
110
|
+
for student in tqdm.tqdm(self.student_list):
|
111
|
+
self.update_student_grade(student)
|
112
|
+
|
113
|
+
def get_class_stats(self):
|
114
|
+
"""Calculates and stores descriptive statistics for the class-wide grade report.
|
115
|
+
Requires filling class grades first
|
116
|
+
"""
|
117
|
+
# Calculate descriptive statistics
|
118
|
+
self.stats_df = self.all_student_grades_df.describe(include='all')
|
119
|
+
|
120
|
+
def write_excel_spreadsheet(self, out_name='output.xlsx'):
|
121
|
+
"""Exports the class-wide grade report to an Excel spreadsheet.
|
122
|
+
|
123
|
+
Args:
|
124
|
+
out_name (str, optional): Name of output file. Defaults to 'output.xlsx'.
|
125
|
+
"""
|
126
|
+
# Export to Excel with different sheets
|
127
|
+
with pd.ExcelWriter('output.xlsx') as writer:
|
128
|
+
self.all_student_grades_df.to_excel(writer, sheet_name='all_student_grades')
|
129
|
+
self.stats_df.to_excel(writer, sheet_name='performance_statistics')
|
130
|
+
|
131
|
+
|
132
|
+
def main():
|
133
|
+
class_grades = ClassGradeReport()
|
134
|
+
print(class_grades.all_student_grades_df)
|
135
|
+
|
136
|
+
if __name__ == "__main__":
|
137
|
+
main()
|
@@ -0,0 +1,353 @@
|
|
1
|
+
# TODO: if not due yet and score is 0, make NAN, fix the rendering
|
2
|
+
|
3
|
+
from pykubegrader.telemetry import get_assignments_submissions
|
4
|
+
from pykubegrader.grade_reports.assignments import Assignment
|
5
|
+
from pykubegrader.grade_reports.grading_config import (
|
6
|
+
assignment_type_list,
|
7
|
+
aliases,
|
8
|
+
globally_exempted_assignments,
|
9
|
+
dropped_assignments,
|
10
|
+
optional_drop_week,
|
11
|
+
exclude_from_running_avg,
|
12
|
+
custom_grade_adjustments,
|
13
|
+
)
|
14
|
+
|
15
|
+
import pandas as pd
|
16
|
+
from datetime import datetime
|
17
|
+
from IPython.display import display
|
18
|
+
import numpy as np
|
19
|
+
|
20
|
+
from ..telemetry import get_assignments_submissions
|
21
|
+
|
22
|
+
|
23
|
+
class GradeReport:
|
24
|
+
"""Class to generate a grade report for a course and perform grade calculations for each student."""
|
25
|
+
|
26
|
+
def __init__(self, start_date="2025-01-06", verbose=True, params=None, display_ = True):
|
27
|
+
"""Initializes an instance of the GradeReport class.
|
28
|
+
Args:
|
29
|
+
start_date (str, optional): The start date of the course. Defaults to "2025-01-06".
|
30
|
+
verbose (bool, optional): Indicates if verbose output should be displayed. Defaults to True.
|
31
|
+
"""
|
32
|
+
self.assignments, self.student_subs = get_assignments_submissions(params=params)
|
33
|
+
|
34
|
+
self.start_date = start_date
|
35
|
+
self.verbose = verbose
|
36
|
+
self.assignment_type_list = assignment_type_list
|
37
|
+
self.aliases = aliases
|
38
|
+
self.globally_exempted_assignments = globally_exempted_assignments
|
39
|
+
self.dropped_assignments = dropped_assignments
|
40
|
+
self.optional_drop_week = optional_drop_week
|
41
|
+
|
42
|
+
# assignments that have been dropped for a given students.
|
43
|
+
self.student_assignments_dropped = []
|
44
|
+
|
45
|
+
self.setup_grades_df()
|
46
|
+
self.build_assignments()
|
47
|
+
self.update_global_exempted_assignments()
|
48
|
+
self.calculate_grades()
|
49
|
+
self.update_assignments_not_due_yet()
|
50
|
+
self.calculate_grades()
|
51
|
+
self.drop_lowest_n_for_types(1)
|
52
|
+
self.update_weekly_table()
|
53
|
+
self._build_running_avg()
|
54
|
+
self._calculate_final_average()
|
55
|
+
if display_:
|
56
|
+
try:
|
57
|
+
display(self.weekly_grades_df)
|
58
|
+
display(self.weighted_average_grades)
|
59
|
+
except: # noqa: E722
|
60
|
+
pass
|
61
|
+
|
62
|
+
def update_assignments_not_due_yet(self):
|
63
|
+
"""
|
64
|
+
Updates the score of assignments that are not due yet to NaN.
|
65
|
+
"""
|
66
|
+
for assignment in self.graded_assignments:
|
67
|
+
if assignment.due_date:
|
68
|
+
# Convert due date to datetime object
|
69
|
+
due_date = datetime.fromisoformat(assignment.due_date.replace("Z", "+00:00"))
|
70
|
+
if due_date > datetime.now(due_date.tzinfo) and assignment.score == 0:
|
71
|
+
assignment.score = np.nan
|
72
|
+
assignment.exempted = True
|
73
|
+
|
74
|
+
def _calculate_final_average(self):
|
75
|
+
total_percentage = 1
|
76
|
+
df_ = self.compute_final_average()
|
77
|
+
score_earned = 0
|
78
|
+
|
79
|
+
for assignment_type in self.assignment_type_list:
|
80
|
+
if assignment_type.name in exclude_from_running_avg:
|
81
|
+
total_percentage -= assignment_type.weight
|
82
|
+
|
83
|
+
score_earned += assignment_type.weight * df_[assignment_type.name]
|
84
|
+
|
85
|
+
self.final_grade = score_earned / total_percentage
|
86
|
+
self.weighted_average_grades = pd.concat(
|
87
|
+
[
|
88
|
+
pd.DataFrame(self.final_grades),
|
89
|
+
pd.DataFrame(
|
90
|
+
{"Running Avg": [self.final_grade]},
|
91
|
+
index=["Weighted Average Grade"],
|
92
|
+
),
|
93
|
+
]
|
94
|
+
)
|
95
|
+
|
96
|
+
def grade_report(self):
|
97
|
+
"""Generates a grade report for the course.
|
98
|
+
Returns:
|
99
|
+
pd.DataFrame: A DataFrame containing the grade report or weekly grades only.
|
100
|
+
"""
|
101
|
+
self._update_running_avg()
|
102
|
+
return self.weekly_grades_df
|
103
|
+
|
104
|
+
def update_weekly_table(self):
|
105
|
+
"""Updates the weekly grades table with the calculated scores."""
|
106
|
+
for assignment in self.graded_assignments:
|
107
|
+
if assignment.weekly:
|
108
|
+
self.weekly_grades_df.loc[f"week{assignment.week}", assignment.name] = (
|
109
|
+
assignment.score
|
110
|
+
)
|
111
|
+
|
112
|
+
def update_global_exempted_assignments(self):
|
113
|
+
"""Updates the graded assignments with the globally exempted assignments. If assignment doesn't exist, pass."""
|
114
|
+
for assignment_type, week in self.globally_exempted_assignments:
|
115
|
+
try:
|
116
|
+
self.get_graded_assignment(week, assignment_type)[0].exempted = True
|
117
|
+
except: # noqa: E722
|
118
|
+
pass
|
119
|
+
|
120
|
+
def build_assignments(self):
|
121
|
+
"""Generates a list of Assignment objects for each week, applying custom adjustments where needed."""
|
122
|
+
self.graded_assignments = []
|
123
|
+
weekly_assignments = self.get_weekly_assignments()
|
124
|
+
|
125
|
+
for assignment_type in weekly_assignments:
|
126
|
+
for week in range(1, self.get_num_weeks() + 1): # Weeks start at 1
|
127
|
+
self.graded_assignment_constructor(assignment_type, week=week)
|
128
|
+
|
129
|
+
non_weekly_assignments = self.get_non_weekly_assignments()
|
130
|
+
|
131
|
+
for assignment_type in non_weekly_assignments:
|
132
|
+
self.graded_assignment_constructor(assignment_type)
|
133
|
+
|
134
|
+
def graded_assignment_constructor(self, assignment_type: str, **kwargs):
|
135
|
+
"""Constructs a graded assignment object and appends it to the graded_assignments list.
|
136
|
+
|
137
|
+
Args:
|
138
|
+
assignment_type (str): Type of assignment. Options: readings, lecture, practicequiz, quiz, homework, lab, labattendance, practicemidterm, midterm, practicefinal, final.
|
139
|
+
"""
|
140
|
+
custom_func = custom_grade_adjustments.get(
|
141
|
+
(assignment_type.name, kwargs.get("week", None)), None
|
142
|
+
)
|
143
|
+
|
144
|
+
filtered_assignments = self.get_assignment(
|
145
|
+
kwargs.get("week", None), assignment_type.name
|
146
|
+
)
|
147
|
+
|
148
|
+
new_assignment = Assignment(
|
149
|
+
name=assignment_type.name,
|
150
|
+
weekly=assignment_type.weekly,
|
151
|
+
weight=assignment_type.weight,
|
152
|
+
score=0,
|
153
|
+
grade_adjustment_func=custom_func,
|
154
|
+
# filters the submissions for an assignment and gets the last due date
|
155
|
+
due_date=self.determine_due_date(filtered_assignments),
|
156
|
+
max_score=self.get_max_score(filtered_assignments),
|
157
|
+
**kwargs,
|
158
|
+
)
|
159
|
+
self.graded_assignments.append(new_assignment)
|
160
|
+
|
161
|
+
def calculate_grades(self):
|
162
|
+
"""Calculates the grades for each student based on the graded assignments.
|
163
|
+
If there are filtered assignments, the score is updated based on the submission.
|
164
|
+
Otherwise,
|
165
|
+
"""
|
166
|
+
for assignment in self.graded_assignments:
|
167
|
+
filtered_submission = self.filter_submissions(
|
168
|
+
assignment.week, assignment.name
|
169
|
+
)
|
170
|
+
|
171
|
+
if filtered_submission:
|
172
|
+
for submission in filtered_submission:
|
173
|
+
assignment.update_score(submission)
|
174
|
+
|
175
|
+
# runs if there are no filtered submissions
|
176
|
+
else:
|
177
|
+
assignment.update_score()
|
178
|
+
|
179
|
+
def compute_final_average(self):
|
180
|
+
"""
|
181
|
+
Computes the final average by combining the running average from weekly assignments
|
182
|
+
and the midterm/final exam scores.
|
183
|
+
"""
|
184
|
+
|
185
|
+
# Extract running average from the weekly table
|
186
|
+
self.final_grades = self.weekly_grades_df.loc["Running Avg"]
|
187
|
+
|
188
|
+
for assignment in self.graded_assignments:
|
189
|
+
if not assignment.weekly:
|
190
|
+
self.final_grades[f"{assignment.name}"] = assignment.score
|
191
|
+
|
192
|
+
return self.final_grades
|
193
|
+
|
194
|
+
def filter_submissions(self, week_number, assignment_type):
|
195
|
+
# Normalize the assignment type using aliases
|
196
|
+
normalized_type = self.aliases.get(
|
197
|
+
assignment_type.lower(), [assignment_type.lower()]
|
198
|
+
)
|
199
|
+
|
200
|
+
if week_number:
|
201
|
+
# Filter the assignments based on the week number and normalized assignment type
|
202
|
+
filtered = [
|
203
|
+
assignment
|
204
|
+
for assignment in self.student_subs
|
205
|
+
if assignment["week_number"] == week_number
|
206
|
+
and assignment["assignment_type"].lower() in normalized_type
|
207
|
+
]
|
208
|
+
|
209
|
+
# If week_number is None, filter based on the normalized assignment type only
|
210
|
+
else:
|
211
|
+
# Filter the assignments based on the normalized assignment type
|
212
|
+
filtered = [
|
213
|
+
assignment
|
214
|
+
for assignment in self.student_subs
|
215
|
+
if assignment["assignment_type"].lower() in normalized_type
|
216
|
+
]
|
217
|
+
|
218
|
+
return filtered
|
219
|
+
|
220
|
+
def get_assignment(self, week_number, assignment_type):
|
221
|
+
# Normalize the assignment type using aliases
|
222
|
+
normalized_type = self.aliases.get(
|
223
|
+
assignment_type.lower(), [assignment_type.lower()]
|
224
|
+
)
|
225
|
+
|
226
|
+
# Filter the assignments based on the week number and normalized assignment type
|
227
|
+
filtered = [
|
228
|
+
assignment
|
229
|
+
for assignment in self.assignments
|
230
|
+
if (assignment["week_number"] == week_number or week_number is None)
|
231
|
+
and assignment["assignment_type"].lower() in normalized_type
|
232
|
+
]
|
233
|
+
|
234
|
+
return filtered
|
235
|
+
|
236
|
+
def get_graded_assignment(self, week_number, assignment_type):
|
237
|
+
return list(
|
238
|
+
filter(
|
239
|
+
lambda a: isinstance(a, Assignment)
|
240
|
+
and a.name == assignment_type
|
241
|
+
and (week_number is None or a.week == week_number),
|
242
|
+
self.graded_assignments,
|
243
|
+
)
|
244
|
+
)
|
245
|
+
|
246
|
+
def get_max_score(self, filtered_assignments):
|
247
|
+
if not filtered_assignments:
|
248
|
+
return None
|
249
|
+
|
250
|
+
return max(filtered_assignments, key=lambda x: x["id"])["max_score"]
|
251
|
+
|
252
|
+
def determine_due_date(self, filtered_assignments):
|
253
|
+
if not filtered_assignments:
|
254
|
+
return None # Return None if the list is empty
|
255
|
+
|
256
|
+
# Convert due_date strings to datetime objects and find the max
|
257
|
+
max_due = max(
|
258
|
+
filtered_assignments,
|
259
|
+
key=lambda x: datetime.fromisoformat(x["due_date"].replace("Z", "+00:00")),
|
260
|
+
)
|
261
|
+
|
262
|
+
return max_due["due_date"] # Return the max due date as a string
|
263
|
+
|
264
|
+
def get_non_weekly_assignments(self):
|
265
|
+
"""Get all weekly assignments from the assignment list configuration"""
|
266
|
+
non_weekly_assignments = [
|
267
|
+
assignment
|
268
|
+
for assignment in self.assignment_type_list
|
269
|
+
if not assignment.weekly
|
270
|
+
]
|
271
|
+
return non_weekly_assignments
|
272
|
+
|
273
|
+
def get_weekly_assignments(self):
|
274
|
+
"""Get all weekly assignments from the assignment list configuration"""
|
275
|
+
weekly_assignments = [
|
276
|
+
assignment for assignment in self.assignment_type_list if assignment.weekly
|
277
|
+
]
|
278
|
+
return weekly_assignments
|
279
|
+
|
280
|
+
def get_num_weeks(self):
|
281
|
+
"""Get the number of weeks in the course"""
|
282
|
+
max_week_number = max(item["week_number"] for item in self.assignments)
|
283
|
+
return max_week_number
|
284
|
+
|
285
|
+
def setup_grades_df(self):
|
286
|
+
weekly_assignments = self.get_weekly_assignments()
|
287
|
+
max_week_number = self.get_num_weeks()
|
288
|
+
inds = [f"week{i + 1}" for i in range(max_week_number)] + ["Running Avg"]
|
289
|
+
restruct_grades = {
|
290
|
+
k.name: [0 for i in range(len(inds))] for k in weekly_assignments
|
291
|
+
}
|
292
|
+
new_weekly_grades = pd.DataFrame(restruct_grades, dtype=float)
|
293
|
+
new_weekly_grades["inds"] = inds
|
294
|
+
new_weekly_grades.set_index("inds", inplace=True)
|
295
|
+
self.weekly_grades_df = new_weekly_grades
|
296
|
+
|
297
|
+
def _build_running_avg(self):
|
298
|
+
"""
|
299
|
+
Subfunction to compute and update the Running Avg row, handling NaNs.
|
300
|
+
"""
|
301
|
+
|
302
|
+
self.weekly_grades_df.loc["Running Avg"] = self.weekly_grades_df.drop(
|
303
|
+
"Running Avg", errors="ignore"
|
304
|
+
).mean(axis=0, skipna=True)
|
305
|
+
|
306
|
+
def drop_lowest_n_for_types(self, n, assignments_=None):
|
307
|
+
"""
|
308
|
+
Exempts the lowest n assignments for each specified assignment type.
|
309
|
+
If the lowest dropped score is from week 1, an additional lowest score is dropped.
|
310
|
+
|
311
|
+
:param assignments_: List of assignment types (names) to process.
|
312
|
+
:param n: Number of lowest scores to exempt per type.
|
313
|
+
"""
|
314
|
+
from collections import defaultdict
|
315
|
+
import numpy as np
|
316
|
+
|
317
|
+
# Group assignments by name
|
318
|
+
assignment_groups = defaultdict(list)
|
319
|
+
for assignment in self.graded_assignments:
|
320
|
+
if assignments_ is None:
|
321
|
+
if (
|
322
|
+
assignment.name in self.dropped_assignments
|
323
|
+
and not assignment.exempted
|
324
|
+
):
|
325
|
+
assignment_groups[assignment.name].append(assignment)
|
326
|
+
else:
|
327
|
+
if assignment.name in assignments_ and not assignment.exempted:
|
328
|
+
assignment_groups[assignment.name].append(assignment)
|
329
|
+
|
330
|
+
# Iterate over each specified assignment type and drop the lowest n scores
|
331
|
+
for name, assignments in assignment_groups.items():
|
332
|
+
# Filter assignments that are not already exempted (NaN scores should not count)
|
333
|
+
valid_assignments = [a for a in assignments if not np.isnan(a.score)]
|
334
|
+
|
335
|
+
# Sort assignments by score in ascending order
|
336
|
+
valid_assignments.sort(key=lambda a: a.score)
|
337
|
+
|
338
|
+
# Exempt the lowest `n` assignments
|
339
|
+
dropped = []
|
340
|
+
for i in range(min(n, len(valid_assignments))):
|
341
|
+
valid_assignments[i].exempted = True
|
342
|
+
dropped.append(valid_assignments[i])
|
343
|
+
self.student_assignments_dropped.append(valid_assignments[i])
|
344
|
+
|
345
|
+
# Check if the lowest dropped assignment is from week 1
|
346
|
+
if dropped and any(a.week in self.optional_drop_week for a in dropped):
|
347
|
+
# Find the next lowest non-exempted assignment and drop it
|
348
|
+
for a in valid_assignments:
|
349
|
+
if not a.exempted:
|
350
|
+
a.exempted = True
|
351
|
+
break
|
352
|
+
|
353
|
+
self.calculate_grades()
|
@@ -0,0 +1,70 @@
|
|
1
|
+
from pykubegrader.grade_reports.assignments import assignment_type
|
2
|
+
|
3
|
+
# Assignment types, weekly, and weight
|
4
|
+
# Total should add up to 1.0
|
5
|
+
assignment_type_list = [
|
6
|
+
assignment_type("readings", True, 0.15),
|
7
|
+
assignment_type("lecture", True, 0.15),
|
8
|
+
assignment_type("practicequiz", True, 0.015),
|
9
|
+
assignment_type("quiz", True, 0.15),
|
10
|
+
assignment_type("homework", True, 0.15),
|
11
|
+
assignment_type("lab", True, 0.15),
|
12
|
+
assignment_type("labattendance", True, 0.05),
|
13
|
+
assignment_type("practicemidterm", False, 0.015),
|
14
|
+
assignment_type("midterm", False, 0.15),
|
15
|
+
assignment_type("practicefinal", False, 0.02),
|
16
|
+
assignment_type("final", False, 0.2),
|
17
|
+
]
|
18
|
+
|
19
|
+
# Custom grade adjustments, key is a tuple of assignment type and week, value is a lambda function that takes the score and returns the adjusted score
|
20
|
+
custom_grade_adjustments = {
|
21
|
+
("lecture", 3): lambda score: 100.0 if score > 0 else 0.0,
|
22
|
+
("lecture", 4): lambda score: 100.0 if score > 0 else 0.0,
|
23
|
+
("lecture", 5): lambda score: 100.0 if score > 0 else 0.0,
|
24
|
+
}
|
25
|
+
|
26
|
+
# Exempted assignments, key is a tuple of assignment type and week
|
27
|
+
globally_exempted_assignments = [
|
28
|
+
("readings", 6),
|
29
|
+
("quiz", 6),
|
30
|
+
("practicequiz", 6),
|
31
|
+
("lecture", 6),
|
32
|
+
("homework", 5),
|
33
|
+
("lab", 5),
|
34
|
+
("labattendance", 5),
|
35
|
+
]
|
36
|
+
|
37
|
+
# Common Assignment Aliases, these are other names used for the same assignment category
|
38
|
+
aliases = {
|
39
|
+
"practicequiz": [
|
40
|
+
"practice quiz",
|
41
|
+
"practice-quiz",
|
42
|
+
"practice quiz",
|
43
|
+
"practice_quiz",
|
44
|
+
"practicequiz",
|
45
|
+
],
|
46
|
+
"labattendance": ["labattendance", "attendance", "attend"],
|
47
|
+
}
|
48
|
+
|
49
|
+
# Skipped assignments, key is a tuple of assignment type and week
|
50
|
+
skipped_assignments = {}
|
51
|
+
|
52
|
+
# Dropped assignments a list of assignments which lowest score will be dropped
|
53
|
+
dropped_assignments = [
|
54
|
+
"readings",
|
55
|
+
"lecture",
|
56
|
+
"practicequiz",
|
57
|
+
"quiz",
|
58
|
+
"homework",
|
59
|
+
"lab",
|
60
|
+
"labattendance",
|
61
|
+
]
|
62
|
+
|
63
|
+
# TAs and other users to skip in class grade report
|
64
|
+
skipped_users = ['JCA', 'jca92', 'cnp68', 'dak329', 'xz498', 'ag4328', 'rg897', 'jce63', 'qt49']
|
65
|
+
|
66
|
+
# Optional drop week, a list of weeks where the lowest score will be dropped
|
67
|
+
optional_drop_week = [1]
|
68
|
+
|
69
|
+
# Excluded from running average, a list of assignment types that will be excluded from the running average calculation
|
70
|
+
exclude_from_running_avg = ["final"]
|
@@ -0,0 +1,43 @@
|
|
1
|
+
{
|
2
|
+
"cells": [
|
3
|
+
{
|
4
|
+
"cell_type": "code",
|
5
|
+
"execution_count": null,
|
6
|
+
"metadata": {},
|
7
|
+
"outputs": [],
|
8
|
+
"source": [
|
9
|
+
"from pykubegrader.grade_reports.class_grade_report import ClassGradeReport\n",
|
10
|
+
"\n",
|
11
|
+
"out = ClassGradeReport().run()"
|
12
|
+
]
|
13
|
+
},
|
14
|
+
{
|
15
|
+
"cell_type": "code",
|
16
|
+
"execution_count": null,
|
17
|
+
"metadata": {},
|
18
|
+
"outputs": [],
|
19
|
+
"source": []
|
20
|
+
}
|
21
|
+
],
|
22
|
+
"metadata": {
|
23
|
+
"kernelspec": {
|
24
|
+
"display_name": "engr131_dev",
|
25
|
+
"language": "python",
|
26
|
+
"name": "python3"
|
27
|
+
},
|
28
|
+
"language_info": {
|
29
|
+
"codemirror_mode": {
|
30
|
+
"name": "ipython",
|
31
|
+
"version": 3
|
32
|
+
},
|
33
|
+
"file_extension": ".py",
|
34
|
+
"mimetype": "text/x-python",
|
35
|
+
"name": "python",
|
36
|
+
"nbconvert_exporter": "python",
|
37
|
+
"pygments_lexer": "ipython3",
|
38
|
+
"version": "3.12.7"
|
39
|
+
}
|
40
|
+
},
|
41
|
+
"nbformat": 4,
|
42
|
+
"nbformat_minor": 2
|
43
|
+
}
|
@@ -1,5 +1,56 @@
|
|
1
1
|
{
|
2
2
|
"cells": [
|
3
|
+
{
|
4
|
+
"cell_type": "code",
|
5
|
+
"execution_count": null,
|
6
|
+
"metadata": {},
|
7
|
+
"outputs": [],
|
8
|
+
"source": [
|
9
|
+
"from datetime import datetime\n",
|
10
|
+
"\n",
|
11
|
+
"import pandas as pd\n",
|
12
|
+
"import requests\n",
|
13
|
+
"from build.passwords import password, user\n",
|
14
|
+
"from grade_reports.grade_reports import filter_assignments, get_student_grades\n",
|
15
|
+
"from requests.auth import HTTPBasicAuth\n",
|
16
|
+
"\n",
|
17
|
+
"from pykubegrader.graders.late_assignments import calculate_late_submission\n",
|
18
|
+
"\n",
|
19
|
+
"api_base_url = \"https://engr-131-api.eastus.cloudapp.azure.com/\" "
|
20
|
+
]
|
21
|
+
},
|
22
|
+
{
|
23
|
+
"cell_type": "code",
|
24
|
+
"execution_count": null,
|
25
|
+
"metadata": {},
|
26
|
+
"outputs": [],
|
27
|
+
"source": [
|
28
|
+
"from telemetry import get_assignments_submissions\n"
|
29
|
+
]
|
30
|
+
},
|
31
|
+
{
|
32
|
+
"cell_type": "code",
|
33
|
+
"execution_count": null,
|
34
|
+
"metadata": {},
|
35
|
+
"outputs": [],
|
36
|
+
"source": [
|
37
|
+
"self.assignments, self.student_subs = get_assignments_submissions()"
|
38
|
+
]
|
39
|
+
},
|
40
|
+
{
|
41
|
+
"cell_type": "code",
|
42
|
+
"execution_count": null,
|
43
|
+
"metadata": {},
|
44
|
+
"outputs": [],
|
45
|
+
"source": []
|
46
|
+
},
|
47
|
+
{
|
48
|
+
"cell_type": "code",
|
49
|
+
"execution_count": null,
|
50
|
+
"metadata": {},
|
51
|
+
"outputs": [],
|
52
|
+
"source": []
|
53
|
+
},
|
3
54
|
{
|
4
55
|
"cell_type": "code",
|
5
56
|
"execution_count": null,
|
@@ -466,7 +517,7 @@
|
|
466
517
|
],
|
467
518
|
"metadata": {
|
468
519
|
"kernelspec": {
|
469
|
-
"display_name": "
|
520
|
+
"display_name": "engr131_dev",
|
470
521
|
"language": "python",
|
471
522
|
"name": "python3"
|
472
523
|
},
|
@@ -480,7 +531,7 @@
|
|
480
531
|
"name": "python",
|
481
532
|
"nbconvert_exporter": "python",
|
482
533
|
"pygments_lexer": "ipython3",
|
483
|
-
"version": "3.
|
534
|
+
"version": "3.12.7"
|
484
535
|
}
|
485
536
|
},
|
486
537
|
"nbformat": 4,
|
@@ -42,9 +42,10 @@ def call_score_assignment(
|
|
42
42
|
"assignment_title": assignment_title,
|
43
43
|
"notebook_title": notebook_title,
|
44
44
|
}
|
45
|
-
|
46
|
-
|
47
|
-
|
45
|
+
|
46
|
+
token = os.getenv("TOKEN")
|
47
|
+
if token:
|
48
|
+
params["key_used"] = token
|
48
49
|
|
49
50
|
username, password = get_credentials().values()
|
50
51
|
|