ygrader 2.5.0__tar.gz → 2.5.1__tar.gz

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.4
2
2
  Name: ygrader
3
- Version: 2.5.0
3
+ Version: 2.5.1
4
4
  Summary: Grading scripts used in BYU's Electrical and Computer Engineering Department
5
5
  Home-page: https://github.com/byu-cpe/ygrader
6
6
  Author: Jeff Goeders
@@ -4,7 +4,7 @@ setup(
4
4
  name="ygrader",
5
5
  packages=["ygrader"],
6
6
  package_data={"ygrader": ["*.ahk"]},
7
- version="2.5.0",
7
+ version="2.5.1",
8
8
  description="Grading scripts used in BYU's Electrical and Computer Engineering Department",
9
9
  author="Jeff Goeders",
10
10
  author_email="jeff.goeders@gmail.com",
@@ -154,10 +154,13 @@ def _calculate_student_score(
154
154
  Tuple of (final_score, total_possible, submitted_datetime or None if on time).
155
155
  """
156
156
  total_possible = sum(item.points for item in ls_column.items)
157
- total_deductions = 0.0
157
+ total_score = 0.0
158
158
 
159
159
  for item in ls_column.items:
160
160
  deductions_obj = item_deductions.get(item.name)
161
+ student_graded = False
162
+ item_deduction_total = 0.0
163
+
161
164
  if deductions_obj:
162
165
  # Find the student's deductions
163
166
  student_key = None
@@ -170,12 +173,18 @@ def _calculate_student_score(
170
173
  break
171
174
 
172
175
  if student_key:
176
+ student_graded = True
173
177
  deductions = deductions_obj.deductions_by_students[student_key]
174
178
  for deduction in deductions:
175
- total_deductions += deduction.points
179
+ item_deduction_total += deduction.points
180
+
181
+ # Only award points if the student was graded for this item
182
+ if student_graded:
183
+ total_score += max(0, item.points - item_deduction_total)
184
+ # else: student gets 0 for this item (not graded)
176
185
 
177
- # Calculate score before late penalty
178
- score = max(0, total_possible - total_deductions)
186
+ # Score is already calculated
187
+ score = total_score
179
188
 
180
189
  # Get submit info
181
190
  _, effective_due_date, submitted_datetime = _get_student_key_and_submit_info(
@@ -290,8 +299,10 @@ def assemble_grades(
290
299
  )
291
300
 
292
301
  # Get submit info for this student
293
- _, effective_due_date, submitted_datetime = _get_student_key_and_submit_info(
294
- net_id, subitem_deductions, due_date, due_date_exceptions
302
+ _, effective_due_date, submitted_datetime = (
303
+ _get_student_key_and_submit_info(
304
+ net_id, subitem_deductions, due_date, due_date_exceptions
305
+ )
295
306
  )
296
307
 
297
308
  # Calculate score before late penalty
@@ -421,7 +432,8 @@ def _generate_student_feedback(
421
432
  total_points_possible += subitem_points_possible
422
433
 
423
434
  subitem_points_deducted = 0
424
- item_deductions = []
435
+ item_deduction_list = []
436
+ student_graded = False
425
437
 
426
438
  # Get deductions for this student in this item
427
439
  student_deductions_obj = subitem_deductions.get(item.name)
@@ -438,13 +450,19 @@ def _generate_student_feedback(
438
450
  break
439
451
 
440
452
  if student_key:
453
+ student_graded = True
441
454
  deductions = student_deductions_obj.deductions_by_students[student_key]
442
455
  for deduction in deductions:
443
- item_deductions.append((deduction.message, deduction.points))
456
+ item_deduction_list.append((deduction.message, deduction.points))
444
457
  subitem_points_deducted += deduction.points
445
458
 
446
- # Calculate item score
447
- subitem_score = max(0, subitem_points_possible - subitem_points_deducted)
459
+ # Calculate item score (0 if not graded)
460
+ if student_graded:
461
+ subitem_score = max(0, subitem_points_possible - subitem_points_deducted)
462
+ else:
463
+ subitem_score = 0
464
+ item_deduction_list.append(("Not graded", subitem_points_possible))
465
+ subitem_points_deducted = subitem_points_possible
448
466
  score_str = f"{subitem_score:.1f} / {subitem_points_possible:.1f}"
449
467
 
450
468
  # Item line with score
@@ -454,7 +472,7 @@ def _generate_student_feedback(
454
472
  )
455
473
 
456
474
  # Deduction lines (indented)
457
- for msg, pts in item_deductions:
475
+ for msg, pts in item_deduction_list:
458
476
  # Wrap long messages
459
477
  wrapped = _wrap_text(msg, deduction_msg_width)
460
478
  for i, line_text in enumerate(wrapped):
@@ -485,12 +503,17 @@ def _generate_student_feedback(
485
503
  and effective_due_date is not None
486
504
  ):
487
505
  final_score = late_penalty_callback(
488
- effective_due_date, submitted_datetime, total_points_possible, score_before_late
506
+ effective_due_date,
507
+ submitted_datetime,
508
+ total_points_possible,
509
+ score_before_late,
489
510
  )
490
511
  # Ensure final score is not negative
491
512
  final_score = max(0, final_score)
492
513
  late_penalty_points = score_before_late - final_score
493
- late_label = f"Late Penalty (submitted {submitted_datetime.strftime('%Y-%m-%d %H:%M')}):"
514
+ late_label = (
515
+ f"Late Penalty (submitted {submitted_datetime.strftime('%Y-%m-%d %H:%M')}):"
516
+ )
494
517
  lines.append(
495
518
  f"{late_label:<{item_col_width}} {-late_penalty_points:>{score_col_width}.1f}"
496
519
  )
@@ -37,12 +37,15 @@ class GradeItem:
37
37
  self.fcn_args_dict = fcn_args_dict if fcn_args_dict is not None else {}
38
38
  self.student_deductions = StudentDeductions(deductions_yaml_path)
39
39
  self.last_graded_net_ids = None # Track last graded student for undo
40
- self.names_by_netid = self._build_names_lookup() # net_id -> (first_name, last_name)
40
+ self.names_by_netid = (
41
+ self._build_names_lookup()
42
+ ) # net_id -> (first_name, last_name)
41
43
 
42
44
  def _build_names_lookup(self):
43
45
  """Build a lookup dictionary from net_id to (first_name, last_name) from the class list CSV."""
44
46
  # Import pandas here to avoid circular import and since it's already imported in grader.py
45
47
  import pandas # pylint: disable=import-outside-toplevel
48
+
46
49
  names_by_netid = {}
47
50
  try:
48
51
  df = pandas.read_csv(self.grader.class_list_csv_path)
@@ -51,7 +54,11 @@ class GradeItem:
51
54
  net_id = row["Net ID"]
52
55
  first_name = row["First Name"]
53
56
  last_name = row["Last Name"]
54
- if pandas.notna(net_id) and pandas.notna(first_name) and pandas.notna(last_name):
57
+ if (
58
+ pandas.notna(net_id)
59
+ and pandas.notna(first_name)
60
+ and pandas.notna(last_name)
61
+ ):
55
62
  names_by_netid[net_id] = (first_name, last_name)
56
63
  except (FileNotFoundError, pandas.errors.EmptyDataError, KeyError):
57
64
  pass # If we can't read the CSV, just use an empty dict
@@ -259,6 +266,9 @@ class GradeItem:
259
266
  # run again, but don't build
260
267
  build = False
261
268
  continue
269
+ if score == ScoreResult.EXIT:
270
+ print_color(TermColors.BLUE, "Exiting grader")
271
+ sys.exit(0)
262
272
  if score == ScoreResult.UNDO_LAST:
263
273
  # Undo the last graded student and signal to go back
264
274
  if self.last_graded_net_ids is not None:
@@ -276,7 +286,9 @@ class GradeItem:
276
286
  # Record score - save submit_time and ensure the student is in the deductions file
277
287
  # (even if they have no deductions, to indicate they were graded)
278
288
  if pending_submit_time is not None:
279
- self.student_deductions.set_submit_time(tuple(net_ids), pending_submit_time)
289
+ self.student_deductions.set_submit_time(
290
+ tuple(net_ids), pending_submit_time
291
+ )
280
292
  self.student_deductions.ensure_student_in_file(tuple(net_ids))
281
293
  # Track this student as last graded for undo functionality
282
294
  self.last_graded_net_ids = tuple(net_ids)
@@ -13,6 +13,7 @@ class ScoreResult(Enum):
13
13
  RERUN = auto()
14
14
  CREATE_DEDUCTION = auto()
15
15
  UNDO_LAST = auto()
16
+ EXIT = auto()
16
17
 
17
18
 
18
19
  def get_score(
@@ -55,8 +56,7 @@ def get_score(
55
56
  # Show current deductions for this student
56
57
  current_deductions = student_deductions.get_student_deductions(tuple(net_ids))
57
58
  print(
58
- fpad2
59
- + f"Current score: {TermColors.GREEN}{computed_score}{TermColors.END}"
59
+ fpad2 + f"Current score: {TermColors.GREEN}{computed_score}{TermColors.END}"
60
60
  )
61
61
  print(fpad2 + "Current deductions:")
62
62
  if current_deductions:
@@ -99,11 +99,15 @@ def get_score(
99
99
  left_items.append(("[g]", "Manage grades"))
100
100
  allowed_cmds["g"] = "manage"
101
101
 
102
- # Add undo option last if there's a last graded student
102
+ # Add undo option if there's a last graded student
103
103
  if last_graded_net_ids is not None:
104
104
  left_items.append(("[u]", f"Undo last ({last_graded_net_ids[0]})"))
105
105
  allowed_cmds["u"] = ScoreResult.UNDO_LAST
106
106
 
107
+ # Add exit option at bottom of left column
108
+ left_items.append(("[e]", "Exit grader"))
109
+ allowed_cmds["e"] = ScoreResult.EXIT
110
+
107
111
  # Format menu items in two columns
108
112
  col_width = 38 # Each column width (2 columns * 38 = 76 < 80)
109
113
  input_txt = (
@@ -269,7 +273,10 @@ def _manage_grades_interactive(student_deductions, names_by_netid=None):
269
273
  # Check if search matches first/last name
270
274
  if names_by_netid and net_id in names_by_netid:
271
275
  first_name, last_name = names_by_netid[net_id]
272
- if not list_all and (search_lower in first_name.lower() or search_lower in last_name.lower()):
276
+ if not list_all and (
277
+ search_lower in first_name.lower()
278
+ or search_lower in last_name.lower()
279
+ ):
273
280
  match_found = True
274
281
  display_parts.append(f"{first_name} {last_name} ({net_id})")
275
282
  else:
@@ -309,9 +316,11 @@ def _manage_grades_interactive(student_deductions, names_by_netid=None):
309
316
  if 0 <= idx < len(matches):
310
317
  student_key, display = matches[idx]
311
318
  # Confirm deletion
312
- confirm = input(
313
- f"Delete grade for {display}? This cannot be undone. [y/N]: "
314
- ).strip().lower()
319
+ confirm = (
320
+ input(f"Delete grade for {display}? This cannot be undone. [y/N]: ")
321
+ .strip()
322
+ .lower()
323
+ )
315
324
 
316
325
  if confirm == "y":
317
326
  student_deductions.clear_student_deductions(student_key)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ygrader
3
- Version: 2.5.0
3
+ Version: 2.5.1
4
4
  Summary: Grading scripts used in BYU's Electrical and Computer Engineering Department
5
5
  Home-page: https://github.com/byu-cpe/ygrader
6
6
  Author: Jeff Goeders
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes