PyKubeGrader 0.3.11__py3-none-any.whl → 0.3.13__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: PyKubeGrader
3
- Version: 0.3.11
3
+ Version: 0.3.13
4
4
  Summary: Add a short description here!
5
5
  Home-page: https://github.com/pyscaffold/pyscaffold/
6
6
  Author: jagar2
@@ -11,10 +11,10 @@ pykubegrader/build/clean_folder.py,sha256=8N0KyL4eXRs0DCw-V_2jR9igtFs_mOFMQufdL6
11
11
  pykubegrader/build/collate.py,sha256=cVvF7tf2U3iiH4R_dbghTcieedIx5w3Fyw9L_llInM8,6754
12
12
  pykubegrader/build/markdown_questions.py,sha256=cSh8mkHK3hh-etJdgrZu9UQi1WPrKQtofkzLCUp1Z-w,4676
13
13
  pykubegrader/grade_reports/__grade_reports.py,sha256=n8H_n9jdZRSPn2zlIf-GQt_Y8w91p6M8ZbdVH76Sg5k,2303
14
- pykubegrader/grade_reports/assignments.py,sha256=Wr5jvTeQ0a7Fl89e6EzXKqjhLrsA6KyObOdrJdN1V0Y,7614
14
+ pykubegrader/grade_reports/assignments.py,sha256=I9NesLhwLelgqUekXfQCEGH-zTMAOjfb942eZYeh-Yo,7811
15
15
  pykubegrader/grade_reports/class_grade_report.py,sha256=9Uuvy-xoABtjTjv-05VYhmowebgTrgEFSgh1Grws-cQ,5078
16
- pykubegrader/grade_reports/grade_report.py,sha256=jhWCtGADM3v9U2HhZUWpVBipvUuHvvOIhbftL2r0FKU,13735
17
- pykubegrader/grade_reports/grading_config.py,sha256=o_68FrjOLQWqDzEptnA8p42zClLHyqEadAiaa9K2cm0,2374
16
+ pykubegrader/grade_reports/grade_report.py,sha256=iTOe1sRNbsJc-Q4M_ELEMHePObLjHd0z-pm3G0sG37E,16962
17
+ pykubegrader/grade_reports/grading_config.py,sha256=FIK25yW0MbYCqCiCX9pxGLonnTjRvXFIOLrf9qZvFwY,2531
18
18
  pykubegrader/grade_reports/test.ipynb,sha256=NkMZHcfBd-uJw3i1Y9ux-epBP5GiVFEhda5wxEbK0cU,808
19
19
  pykubegrader/graders/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
20
20
  pykubegrader/graders/late_assignments.py,sha256=_2-rA5RqO0BWY9WAQA_mbCxxPKTOiJOl-byD2CYWaE0,1393
@@ -39,9 +39,9 @@ pykubegrader/widgets_base/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-
39
39
  pykubegrader/widgets_base/multi_select.py,sha256=WhpS7a8V3BOuEfEyFPzcDhMbgr7p1a4FFh_mKU1HLbI,4226
40
40
  pykubegrader/widgets_base/reading.py,sha256=ChUS3NOTa_HLtNpxR8hGX80LPKMvYMypnR6dFknfxus,5430
41
41
  pykubegrader/widgets_base/select.py,sha256=qP31bjTWIn8LEgKwtNUJbgJnum6P7bx6A_At-u1ivFk,2750
42
- PyKubeGrader-0.3.11.dist-info/LICENSE.txt,sha256=YTp-Ewc8Kems8PJEE27KnBPFnZSxoWvSg7nnknzPyYw,1546
43
- PyKubeGrader-0.3.11.dist-info/METADATA,sha256=FjeZZHM6puTdeXBo7buuWJ6IedQD0C-JkED8A3DsQZs,2758
44
- PyKubeGrader-0.3.11.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
45
- PyKubeGrader-0.3.11.dist-info/entry_points.txt,sha256=RR57KvzDRJrP4omy5heS5cZ3E7g56YxcxJhDnp57ZU0,253
46
- PyKubeGrader-0.3.11.dist-info/top_level.txt,sha256=e550Klfze6higFxER1V62fnGOcIgiKRbsrl9CC4UdtQ,13
47
- PyKubeGrader-0.3.11.dist-info/RECORD,,
42
+ PyKubeGrader-0.3.13.dist-info/LICENSE.txt,sha256=YTp-Ewc8Kems8PJEE27KnBPFnZSxoWvSg7nnknzPyYw,1546
43
+ PyKubeGrader-0.3.13.dist-info/METADATA,sha256=QXj0QbWpmsOkRnSlhMKyhpFrG8pPQcaNLqHOY_hArRk,2758
44
+ PyKubeGrader-0.3.13.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
45
+ PyKubeGrader-0.3.13.dist-info/entry_points.txt,sha256=RR57KvzDRJrP4omy5heS5cZ3E7g56YxcxJhDnp57ZU0,253
46
+ PyKubeGrader-0.3.13.dist-info/top_level.txt,sha256=e550Klfze6higFxER1V62fnGOcIgiKRbsrl9CC4UdtQ,13
47
+ PyKubeGrader-0.3.13.dist-info/RECORD,,
@@ -116,6 +116,10 @@ class Assignment(assignment_type):
116
116
  """
117
117
  if self.exempted:
118
118
  self.score = np.nan
119
+
120
+ # If the score is "---", return the score as is, this is an assignment that does not exist.
121
+ if self._score == "---":
122
+ return self.score
119
123
 
120
124
  # Saves a table with the score of the exempted assignment still recorded.
121
125
  try:
@@ -126,6 +130,7 @@ class Assignment(assignment_type):
126
130
  except:
127
131
  pass
128
132
  return self.score
133
+
129
134
  elif submission is not None:
130
135
  # Adjust the score based on submission
131
136
  score_ = self.grade_adjustment(submission)
@@ -10,6 +10,7 @@ from pykubegrader.grade_reports.grading_config import (
10
10
  optional_drop_week,
11
11
  exclude_from_running_avg,
12
12
  custom_grade_adjustments,
13
+ duplicated_scores,
13
14
  )
14
15
 
15
16
  import pandas as pd
@@ -29,7 +30,7 @@ class GradeReport:
29
30
  start_date (str, optional): The start date of the course. Defaults to "2025-01-06".
30
31
  verbose (bool, optional): Indicates if verbose output should be displayed. Defaults to True.
31
32
  """
32
- self.assignments, self.student_subs = get_assignments_submissions(params=params)
33
+ self.assignments, self.student_subs = get_assignments_submissions(params=params)
33
34
 
34
35
  self.start_date = start_date
35
36
  self.verbose = verbose
@@ -48,17 +49,47 @@ class GradeReport:
48
49
  self.calculate_grades()
49
50
  self.update_assignments_not_due_yet()
50
51
  self.calculate_grades()
52
+ self.duplicate_scores()
51
53
  self.drop_lowest_n_for_types(1)
52
54
  self.update_weekly_table()
53
55
  self._build_running_avg()
54
56
  self._calculate_final_average()
57
+ df = self.highlight_nans(self.weekly_grades_df, self.weekly_grades_df_display)
55
58
  if display_:
56
59
  try:
57
- display(self.weekly_grades_df)
60
+ display(df)
58
61
  display(self.weighted_average_grades)
59
62
  except: # noqa: E722
60
63
  pass
61
64
 
65
+ @staticmethod
66
+ def highlight_nans(nan_df, display_df, color='red'):
67
+ """
68
+ Highlights NaN values from nan_df on display_df.
69
+
70
+ Parameters:
71
+ nan_df (pd.DataFrame): DataFrame containing NaNs to be highlighted.
72
+ display_df (pd.DataFrame): DataFrame to be recolored.
73
+ color (str): Background color for NaNs. Default is 'red'.
74
+
75
+ Returns:
76
+ pd.io.formats.style.Styler: Styled DataFrame with NaNs highlighted.
77
+ """
78
+ # Ensure both DataFrames have the same index and columns
79
+ nan_mask = nan_df.isna().reindex_like(display_df)
80
+
81
+ # Function to apply the highlight conditionally
82
+ def apply_highlight(row):
83
+ return [
84
+ f'background-color: {color}' if nan_mask.loc[row.name, col] else ''
85
+ for col in row.index
86
+ ]
87
+
88
+ # Apply the highlighting row-wise
89
+ styled_df = display_df.style.apply(apply_highlight, axis=1)
90
+
91
+ return styled_df
92
+
62
93
  def update_assignments_not_due_yet(self):
63
94
  """
64
95
  Updates the score of assignments that are not due yet to NaN.
@@ -69,7 +100,24 @@ class GradeReport:
69
100
  due_date = datetime.fromisoformat(assignment.due_date.replace("Z", "+00:00"))
70
101
  if due_date > datetime.now(due_date.tzinfo) and assignment.score == 0:
71
102
  assignment.score = np.nan
103
+ assignment._score = "---"
72
104
  assignment.exempted = True
105
+
106
+
107
+ def color_cells(self, styler, week_list, assignment_list):
108
+ if week_list:
109
+ week = week_list.pop()
110
+ assignment = assignment_list.pop()
111
+
112
+ # Apply the style to the current cell
113
+ styler = styler.set_properties(
114
+ subset=pd.IndexSlice[[week], [assignment]],
115
+ **{'background-color': 'yellow'}
116
+ )
117
+ # Recursive call
118
+ return self.color_cells(styler, week_list, assignment_list)
119
+ else:
120
+ return styler
73
121
 
74
122
  def _calculate_final_average(self):
75
123
  total_percentage = 1
@@ -93,15 +141,27 @@ class GradeReport:
93
141
  ]
94
142
  )
95
143
 
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
-
144
+ # def grade_report(self):
145
+ # """Generates a grade report for the course.
146
+ # Returns:
147
+ # pd.DataFrame: A DataFrame containing the grade report or weekly grades only.
148
+ # """
149
+ # self._update_running_avg()
150
+ # return self.weekly_grades_df
151
+
104
152
  def update_weekly_table(self):
153
+ self._update_weekly_table_nan()
154
+ self._update_weekly_table_scores()
155
+
156
+ # TODO: populate with average scores calculated from the exempted
157
+ def _update_weekly_table_scores(self):
158
+ for assignment in self.graded_assignments:
159
+ if assignment.weekly:
160
+ self.weekly_grades_df_display.loc[f"week{assignment.week}", assignment.name] = (
161
+ assignment._score
162
+ )
163
+
164
+ def _update_weekly_table_nan(self):
105
165
  """Updates the weekly grades table with the calculated scores."""
106
166
  for assignment in self.graded_assignments:
107
167
  if assignment.weekly:
@@ -114,6 +174,7 @@ class GradeReport:
114
174
  for assignment_type, week in self.globally_exempted_assignments:
115
175
  try:
116
176
  self.get_graded_assignment(week, assignment_type)[0].exempted = True
177
+ self.get_graded_assignment(week, assignment_type)[0]._score = "---"
117
178
  except: # noqa: E722
118
179
  pass
119
180
 
@@ -293,7 +354,8 @@ class GradeReport:
293
354
  new_weekly_grades["inds"] = inds
294
355
  new_weekly_grades.set_index("inds", inplace=True)
295
356
  self.weekly_grades_df = new_weekly_grades
296
-
357
+ self.weekly_grades_df_display = new_weekly_grades.copy().astype(str)
358
+
297
359
  def _build_running_avg(self):
298
360
  """
299
361
  Subfunction to compute and update the Running Avg row, handling NaNs.
@@ -302,6 +364,9 @@ class GradeReport:
302
364
  self.weekly_grades_df.loc["Running Avg"] = self.weekly_grades_df.drop(
303
365
  "Running Avg", errors="ignore"
304
366
  ).mean(axis=0, skipna=True)
367
+ self.weekly_grades_df_display.loc["Running Avg"] = self.weekly_grades_df.drop(
368
+ "Running Avg", errors="ignore"
369
+ ).mean(axis=0, skipna=True)
305
370
 
306
371
  def drop_lowest_n_for_types(self, n, assignments_=None):
307
372
  """
@@ -337,13 +402,26 @@ class GradeReport:
337
402
 
338
403
  # Exempt the lowest `n` assignments
339
404
  dropped = []
340
- i = -1
405
+ i = 0
406
+ j = 0
341
407
  while i < n:
342
- i += 1
343
- valid_assignments[i].exempted = True
344
- if valid_assignments[i].week in self.optional_drop_week:
408
+ valid_assignments[i+j].exempted = True
409
+ if valid_assignments[i+j].week in self.optional_drop_week:
410
+ j += 1
345
411
  continue
346
- dropped.append(valid_assignments[i])
347
- self.student_assignments_dropped.append(valid_assignments[i])
412
+ dropped.append(valid_assignments[i+j])
413
+ self.student_assignments_dropped.append(valid_assignments[i+j])
414
+ i += 1
348
415
 
349
416
  self.calculate_grades()
417
+
418
+ def duplicate_scores(self):
419
+ """Duplicate scores from one assignment to another"""
420
+
421
+ for (week, assignment_type), (duplicate_week, duplicate_assignment_type) in duplicated_scores:
422
+ assignment = self.get_graded_assignment(week, assignment_type)[0]
423
+ duplicate_assignment = self.get_graded_assignment(duplicate_week, duplicate_assignment_type)[0]
424
+ duplicate_assignment.score = assignment.score
425
+ duplicate_assignment._score = assignment._score
426
+ duplicate_assignment.exempted = assignment.exempted
427
+
@@ -61,6 +61,9 @@ dropped_assignments = [
61
61
  "labattendance",
62
62
  ]
63
63
 
64
+ # Duplicated scores, a list of tuples of assignment types and weeks where the scores will be duplicated
65
+ duplicated_scores = [[(7, "lab"), (7, "homework")]]
66
+
64
67
  # TAs and other users to skip in class grade report
65
68
  skipped_users = ['JCA', 'jca92', 'cnp68', 'dak329', 'xz498', 'ag4328', 'rg897', 'jce63', 'qt49']
66
69