ygrader 2.4.0__tar.gz → 2.5.0__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.
- {ygrader-2.4.0/ygrader.egg-info → ygrader-2.5.0}/PKG-INFO +1 -1
- {ygrader-2.4.0 → ygrader-2.5.0}/setup.py +1 -1
- {ygrader-2.4.0 → ygrader-2.5.0}/ygrader/feedback.py +85 -81
- {ygrader-2.4.0 → ygrader-2.5.0/ygrader.egg-info}/PKG-INFO +1 -1
- {ygrader-2.4.0 → ygrader-2.5.0}/LICENSE +0 -0
- {ygrader-2.4.0 → ygrader-2.5.0}/setup.cfg +0 -0
- {ygrader-2.4.0 → ygrader-2.5.0}/test/test_interactive.py +0 -0
- {ygrader-2.4.0 → ygrader-2.5.0}/test/test_unittest.py +0 -0
- {ygrader-2.4.0 → ygrader-2.5.0}/ygrader/__init__.py +0 -0
- {ygrader-2.4.0 → ygrader-2.5.0}/ygrader/deductions.py +0 -0
- {ygrader-2.4.0 → ygrader-2.5.0}/ygrader/grader.py +0 -0
- {ygrader-2.4.0 → ygrader-2.5.0}/ygrader/grades_csv.py +0 -0
- {ygrader-2.4.0 → ygrader-2.5.0}/ygrader/grading_item.py +0 -0
- {ygrader-2.4.0 → ygrader-2.5.0}/ygrader/grading_item_config.py +0 -0
- {ygrader-2.4.0 → ygrader-2.5.0}/ygrader/score_input.py +0 -0
- {ygrader-2.4.0 → ygrader-2.5.0}/ygrader/send_ctrl_backtick.ahk +0 -0
- {ygrader-2.4.0 → ygrader-2.5.0}/ygrader/student_repos.py +0 -0
- {ygrader-2.4.0 → ygrader-2.5.0}/ygrader/upstream_merger.py +0 -0
- {ygrader-2.4.0 → ygrader-2.5.0}/ygrader/utils.py +0 -0
- {ygrader-2.4.0 → ygrader-2.5.0}/ygrader.egg-info/SOURCES.txt +0 -0
- {ygrader-2.4.0 → ygrader-2.5.0}/ygrader.egg-info/dependency_links.txt +0 -0
- {ygrader-2.4.0 → ygrader-2.5.0}/ygrader.egg-info/requires.txt +0 -0
- {ygrader-2.4.0 → ygrader-2.5.0}/ygrader.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ setup(
|
|
|
4
4
|
name="ygrader",
|
|
5
5
|
packages=["ygrader"],
|
|
6
6
|
package_data={"ygrader": ["*.ahk"]},
|
|
7
|
-
version="2.
|
|
7
|
+
version="2.5.0",
|
|
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",
|
|
@@ -5,7 +5,6 @@ import pathlib
|
|
|
5
5
|
import zipfile
|
|
6
6
|
from typing import Callable, Dict, Optional, Tuple
|
|
7
7
|
|
|
8
|
-
import numpy as np
|
|
9
8
|
import pandas
|
|
10
9
|
import yaml
|
|
11
10
|
|
|
@@ -14,8 +13,12 @@ from .grading_item_config import LearningSuiteColumn
|
|
|
14
13
|
from .utils import warning, print_color, TermColors
|
|
15
14
|
|
|
16
15
|
|
|
17
|
-
# Type alias for late penalty callback:
|
|
18
|
-
|
|
16
|
+
# Type alias for late penalty callback:
|
|
17
|
+
# (due_datetime, submitted_datetime, max_score, actual_score) -> new_score
|
|
18
|
+
# If submitted_datetime is None, the student submitted on time or no submit time was recorded.
|
|
19
|
+
LatePenaltyCallback = Callable[
|
|
20
|
+
[datetime.datetime, Optional[datetime.datetime], float, float], float
|
|
21
|
+
]
|
|
19
22
|
|
|
20
23
|
|
|
21
24
|
def _load_due_date_exceptions(
|
|
@@ -48,43 +51,13 @@ def _load_due_date_exceptions(
|
|
|
48
51
|
return exceptions
|
|
49
52
|
|
|
50
53
|
|
|
51
|
-
def
|
|
52
|
-
submit_time_str: Optional[str],
|
|
53
|
-
due_date: datetime.datetime,
|
|
54
|
-
) -> int:
|
|
55
|
-
"""Calculate the number of business days late from a submit time.
|
|
56
|
-
|
|
57
|
-
Args:
|
|
58
|
-
submit_time_str: ISO format timestamp string of submission.
|
|
59
|
-
due_date: The effective due date for the student.
|
|
60
|
-
|
|
61
|
-
Returns:
|
|
62
|
-
Number of business days late (0 if on time or no submit time).
|
|
63
|
-
"""
|
|
64
|
-
if not submit_time_str:
|
|
65
|
-
return 0
|
|
66
|
-
|
|
67
|
-
try:
|
|
68
|
-
submit_time = datetime.datetime.fromisoformat(submit_time_str)
|
|
69
|
-
except ValueError:
|
|
70
|
-
return 0
|
|
71
|
-
|
|
72
|
-
if submit_time <= due_date:
|
|
73
|
-
return 0
|
|
74
|
-
|
|
75
|
-
days_late = np.busday_count(due_date.date(), submit_time.date())
|
|
76
|
-
if days_late == 0:
|
|
77
|
-
days_late = 1 # Same day but after deadline
|
|
78
|
-
return int(days_late)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def _get_student_key_and_max_late_days(
|
|
54
|
+
def _get_student_key_and_submit_info(
|
|
82
55
|
net_id: str,
|
|
83
56
|
item_deductions: Dict[str, StudentDeductions],
|
|
84
57
|
due_date: Optional[datetime.datetime] = None,
|
|
85
58
|
due_date_exceptions: Optional[Dict[str, datetime.datetime]] = None,
|
|
86
|
-
) -> tuple:
|
|
87
|
-
"""Find the student key and
|
|
59
|
+
) -> Tuple[Optional[tuple], Optional[datetime.datetime], Optional[datetime.datetime]]:
|
|
60
|
+
"""Find the student key and submission timing info across all items.
|
|
88
61
|
|
|
89
62
|
Args:
|
|
90
63
|
net_id: The student's net ID.
|
|
@@ -93,10 +66,12 @@ def _get_student_key_and_max_late_days(
|
|
|
93
66
|
due_date_exceptions: Mapping from net_id to exception due date.
|
|
94
67
|
|
|
95
68
|
Returns:
|
|
96
|
-
Tuple of (student_key or None,
|
|
69
|
+
Tuple of (student_key or None, effective_due_date or None, latest_submit_time or None).
|
|
70
|
+
latest_submit_time is None if on time or no submit time recorded.
|
|
97
71
|
"""
|
|
98
|
-
max_late_days = 0
|
|
99
72
|
found_student_key = None
|
|
73
|
+
latest_submit_time: Optional[datetime.datetime] = None
|
|
74
|
+
effective_due_date: Optional[datetime.datetime] = due_date
|
|
100
75
|
|
|
101
76
|
if due_date_exceptions is None:
|
|
102
77
|
due_date_exceptions = {}
|
|
@@ -123,26 +98,35 @@ def _get_student_key_and_max_late_days(
|
|
|
123
98
|
if student_key:
|
|
124
99
|
found_student_key = student_key
|
|
125
100
|
|
|
126
|
-
# Calculate
|
|
101
|
+
# Calculate effective due date (using most generous exception for group)
|
|
127
102
|
if due_date is not None:
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
103
|
+
effective_due_date = due_date
|
|
104
|
+
for member_net_id in student_key:
|
|
105
|
+
if member_net_id in due_date_exceptions:
|
|
106
|
+
effective_due_date = max(
|
|
107
|
+
effective_due_date, due_date_exceptions[member_net_id]
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Get submit time
|
|
111
|
+
submit_time_str = deductions_obj.submit_time_by_students.get(student_key)
|
|
112
|
+
if submit_time_str:
|
|
113
|
+
try:
|
|
114
|
+
submit_time = datetime.datetime.fromisoformat(submit_time_str)
|
|
115
|
+
# Track latest submit time across all items
|
|
116
|
+
if latest_submit_time is None or submit_time > latest_submit_time:
|
|
117
|
+
latest_submit_time = submit_time
|
|
118
|
+
except ValueError:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
# Return None for submit_time if on time
|
|
122
|
+
if (
|
|
123
|
+
latest_submit_time is not None
|
|
124
|
+
and effective_due_date is not None
|
|
125
|
+
and latest_submit_time <= effective_due_date
|
|
126
|
+
):
|
|
127
|
+
latest_submit_time = None
|
|
128
|
+
|
|
129
|
+
return found_student_key, effective_due_date, latest_submit_time
|
|
146
130
|
|
|
147
131
|
|
|
148
132
|
def _calculate_student_score(
|
|
@@ -154,7 +138,7 @@ def _calculate_student_score(
|
|
|
154
138
|
warn_on_missing_callback: bool = True,
|
|
155
139
|
due_date: Optional[datetime.datetime] = None,
|
|
156
140
|
due_date_exceptions: Optional[Dict[str, datetime.datetime]] = None,
|
|
157
|
-
) -> Tuple[float, float,
|
|
141
|
+
) -> Tuple[float, float, Optional[datetime.datetime]]:
|
|
158
142
|
"""Calculate a student's final score.
|
|
159
143
|
|
|
160
144
|
Args:
|
|
@@ -162,12 +146,12 @@ def _calculate_student_score(
|
|
|
162
146
|
ls_column: The LearningSuiteColumn configuration.
|
|
163
147
|
item_deductions: Mapping from item name to StudentDeductions.
|
|
164
148
|
late_penalty_callback: Optional callback for late penalty.
|
|
165
|
-
warn_on_missing_callback: Whether to warn if late
|
|
149
|
+
warn_on_missing_callback: Whether to warn if late but no callback.
|
|
166
150
|
due_date: The default due date for the assignment.
|
|
167
151
|
due_date_exceptions: Mapping from net_id to exception due date.
|
|
168
152
|
|
|
169
153
|
Returns:
|
|
170
|
-
Tuple of (final_score, total_possible,
|
|
154
|
+
Tuple of (final_score, total_possible, submitted_datetime or None if on time).
|
|
171
155
|
"""
|
|
172
156
|
total_possible = sum(item.points for item in ls_column.items)
|
|
173
157
|
total_deductions = 0.0
|
|
@@ -193,21 +177,26 @@ def _calculate_student_score(
|
|
|
193
177
|
# Calculate score before late penalty
|
|
194
178
|
score = max(0, total_possible - total_deductions)
|
|
195
179
|
|
|
196
|
-
# Get
|
|
197
|
-
_,
|
|
180
|
+
# Get submit info
|
|
181
|
+
_, effective_due_date, submitted_datetime = _get_student_key_and_submit_info(
|
|
198
182
|
net_id, item_deductions, due_date, due_date_exceptions
|
|
199
183
|
)
|
|
200
184
|
|
|
201
|
-
# Apply late penalty if applicable
|
|
202
|
-
if
|
|
203
|
-
if late_penalty_callback:
|
|
204
|
-
score = max(
|
|
185
|
+
# Apply late penalty if applicable (submitted_datetime is None if on time)
|
|
186
|
+
if submitted_datetime is not None:
|
|
187
|
+
if late_penalty_callback and effective_due_date is not None:
|
|
188
|
+
score = max(
|
|
189
|
+
0,
|
|
190
|
+
late_penalty_callback(
|
|
191
|
+
effective_due_date, submitted_datetime, total_possible, score
|
|
192
|
+
),
|
|
193
|
+
)
|
|
205
194
|
elif warn_on_missing_callback:
|
|
206
195
|
warning(
|
|
207
|
-
f"Student {net_id}
|
|
196
|
+
f"Student {net_id} submitted late but no late penalty callback provided"
|
|
208
197
|
)
|
|
209
198
|
|
|
210
|
-
return score, total_possible,
|
|
199
|
+
return score, total_possible, submitted_datetime
|
|
211
200
|
|
|
212
201
|
|
|
213
202
|
def assemble_grades(
|
|
@@ -230,7 +219,8 @@ def assemble_grades(
|
|
|
230
219
|
output_zip_path: Path for the output zip file. If None, no zip is generated.
|
|
231
220
|
output_csv_path: Path for the output CSV file. If None, no CSV is generated.
|
|
232
221
|
late_penalty_callback: Optional callback function that takes
|
|
233
|
-
(
|
|
222
|
+
(due_datetime, submitted_datetime, max_score, actual_score) and returns the adjusted score.
|
|
223
|
+
submitted_datetime will be None if on time.
|
|
234
224
|
due_date: The default due date for the assignment. Required for late penalty.
|
|
235
225
|
due_date_exceptions_path: Path to YAML file with due date exceptions (net_id: "YYYY-MM-DD HH:MM:SS").
|
|
236
226
|
|
|
@@ -299,8 +289,13 @@ def assemble_grades(
|
|
|
299
289
|
f"but NOT [{', '.join(items_not_graded)}]",
|
|
300
290
|
)
|
|
301
291
|
|
|
292
|
+
# 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
|
|
295
|
+
)
|
|
296
|
+
|
|
302
297
|
# Calculate score before late penalty
|
|
303
|
-
score_before_late, total_possible,
|
|
298
|
+
score_before_late, total_possible, _ = _calculate_student_score(
|
|
304
299
|
net_id=net_id,
|
|
305
300
|
ls_column=ls_column,
|
|
306
301
|
item_deductions=subitem_deductions,
|
|
@@ -310,18 +305,25 @@ def assemble_grades(
|
|
|
310
305
|
due_date_exceptions=due_date_exceptions,
|
|
311
306
|
)
|
|
312
307
|
|
|
313
|
-
# Apply late penalty if applicable
|
|
308
|
+
# Apply late penalty if applicable (submitted_datetime is None if on time)
|
|
314
309
|
final_score = score_before_late
|
|
315
|
-
if
|
|
310
|
+
if (
|
|
311
|
+
submitted_datetime is not None
|
|
312
|
+
and late_penalty_callback
|
|
313
|
+
and effective_due_date is not None
|
|
314
|
+
):
|
|
316
315
|
final_score = max(
|
|
317
316
|
0,
|
|
318
317
|
late_penalty_callback(
|
|
319
|
-
|
|
318
|
+
effective_due_date,
|
|
319
|
+
submitted_datetime,
|
|
320
|
+
total_possible,
|
|
321
|
+
score_before_late,
|
|
320
322
|
),
|
|
321
323
|
)
|
|
322
324
|
print_color(
|
|
323
325
|
TermColors.YELLOW,
|
|
324
|
-
f"Late: {net_id} (
|
|
326
|
+
f"Late: {net_id} (submitted {submitted_datetime}): "
|
|
325
327
|
f"{score_before_late:.1f} -> {final_score:.1f}",
|
|
326
328
|
)
|
|
327
329
|
|
|
@@ -469,24 +471,26 @@ def _generate_student_feedback(
|
|
|
469
471
|
# Calculate score before late penalty (clamped to 0)
|
|
470
472
|
score_before_late = max(0, total_points_possible - total_points_deducted)
|
|
471
473
|
|
|
472
|
-
# Get
|
|
473
|
-
_,
|
|
474
|
+
# Get submit info for this student
|
|
475
|
+
_, effective_due_date, submitted_datetime = _get_student_key_and_submit_info(
|
|
474
476
|
net_id, subitem_deductions, due_date, due_date_exceptions
|
|
475
477
|
)
|
|
476
478
|
|
|
477
479
|
# Late penalty section
|
|
478
480
|
lines.append("")
|
|
479
481
|
lines.append("=" * 60)
|
|
480
|
-
if
|
|
482
|
+
if (
|
|
483
|
+
submitted_datetime is not None
|
|
484
|
+
and late_penalty_callback
|
|
485
|
+
and effective_due_date is not None
|
|
486
|
+
):
|
|
481
487
|
final_score = late_penalty_callback(
|
|
482
|
-
|
|
488
|
+
effective_due_date, submitted_datetime, total_points_possible, score_before_late
|
|
483
489
|
)
|
|
484
490
|
# Ensure final score is not negative
|
|
485
491
|
final_score = max(0, final_score)
|
|
486
492
|
late_penalty_points = score_before_late - final_score
|
|
487
|
-
late_label = (
|
|
488
|
-
f"Late Penalty ({max_late_days} day{'s' if max_late_days != 1 else ''}):"
|
|
489
|
-
)
|
|
493
|
+
late_label = f"Late Penalty (submitted {submitted_datetime.strftime('%Y-%m-%d %H:%M')}):"
|
|
490
494
|
lines.append(
|
|
491
495
|
f"{late_label:<{item_col_width}} {-late_penalty_points:>{score_col_width}.1f}"
|
|
492
496
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|