canvaslms 5.7__py3-none-any.whl → 5.8__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.
- canvaslms/cli/results.nw +128 -14
- canvaslms/cli/results.py +73 -9
- canvaslms/grades/Makefile +3 -0
- canvaslms/grades/grades.nw +1 -0
- canvaslms/grades/participation.nw +218 -0
- canvaslms/grades/participation.py +70 -0
- {canvaslms-5.7.dist-info → canvaslms-5.8.dist-info}/METADATA +1 -1
- {canvaslms-5.7.dist-info → canvaslms-5.8.dist-info}/RECORD +11 -9
- {canvaslms-5.7.dist-info → canvaslms-5.8.dist-info}/WHEEL +0 -0
- {canvaslms-5.7.dist-info → canvaslms-5.8.dist-info}/entry_points.txt +0 -0
- {canvaslms-5.7.dist-info → canvaslms-5.8.dist-info}/licenses/LICENSE +0 -0
canvaslms/cli/results.nw
CHANGED
|
@@ -39,6 +39,7 @@ import importlib.machinery
|
|
|
39
39
|
import importlib.util
|
|
40
40
|
import os
|
|
41
41
|
import pathlib
|
|
42
|
+
import pkgutil
|
|
42
43
|
import re
|
|
43
44
|
import sys
|
|
44
45
|
|
|
@@ -55,13 +56,27 @@ The command requires two arguments: course and assignment.
|
|
|
55
56
|
We also want the option to filter on users.
|
|
56
57
|
We can add these by using [[add_assignment_option]], however, we don't need the
|
|
57
58
|
ungraded flag as we want to export results (\ie graded material).
|
|
58
|
-
Also, we can just add the [[add_user_or_group_option]] to be able to filter on
|
|
59
|
+
Also, we can just add the [[add_user_or_group_option]] to be able to filter on
|
|
59
60
|
users or groups.
|
|
61
|
+
|
|
62
|
+
The epilog contains a formatted list of available grading modules.
|
|
63
|
+
We use [[RawDescriptionHelpFormatter]] to preserve the whitespace formatting in
|
|
64
|
+
the epilog while still allowing argument help text to wrap normally.
|
|
65
|
+
|
|
66
|
+
However, we must be careful about how we construct the epilog string.
|
|
67
|
+
When [[black]] formats the generated Python, triple-quoted strings passed as
|
|
68
|
+
arguments get indented on continuation lines.
|
|
69
|
+
Since [[RawDescriptionHelpFormatter]] preserves this whitespace literally, we'd
|
|
70
|
+
end up with oddly indented prose text in the help output.
|
|
71
|
+
|
|
72
|
+
To avoid this, we build the epilog via the [[format_results_epilog]] function,
|
|
73
|
+
which joins individual lines without introducing unwanted indentation.
|
|
60
74
|
<<add results command to subp>>=
|
|
61
75
|
results_parser = subp.add_parser("results",
|
|
62
76
|
help="Lists results of a course",
|
|
63
77
|
description="""<<results command description>>""",
|
|
64
|
-
epilog=
|
|
78
|
+
epilog=format_results_epilog(),
|
|
79
|
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
65
80
|
results_parser.set_defaults(func=results_command)
|
|
66
81
|
assignments.add_assignment_option(results_parser, ungraded=False)
|
|
67
82
|
users.add_user_or_group_option(results_parser)
|
|
@@ -83,13 +98,10 @@ prevent the student from getting a grade. Output format, CSV:
|
|
|
83
98
|
<course code> <component code> <student ID> <missing assignment> <reason>
|
|
84
99
|
|
|
85
100
|
The reason can be "not submitted" or "not graded".
|
|
86
|
-
|
|
87
|
-
If you specify an assignment group, the results of the assignments in that
|
|
88
|
-
group will be summarized. You can supply your own function for summarizing
|
|
89
|
-
grades through the -S option. See `pydoc3 canvaslms.grades` for different
|
|
90
|
-
options.
|
|
91
|
-
@ We will cover the option for and loading of the custom summary module later,
|
|
101
|
+
@ We will cover the option for and loading of the custom summary module later,
|
|
92
102
|
in \cref{custom-summary-modules}.
|
|
103
|
+
We will also cover the [[format_grading_modules_help]] function, which
|
|
104
|
+
dynamically discovers available modules in the [[canvaslms.grades]] package.
|
|
93
105
|
|
|
94
106
|
Now, that [[results_command]] function must take three arguments: [[config]],
|
|
95
107
|
[[canvas]] and [[args]].
|
|
@@ -395,18 +407,120 @@ if grade is None or grade_date is None \
|
|
|
395
407
|
continue
|
|
396
408
|
@
|
|
397
409
|
|
|
410
|
+
\subsection{Discovering available grading modules}
|
|
411
|
+
\label{grading-module-discovery}
|
|
412
|
+
|
|
413
|
+
When users install [[canvaslms]] via pipx, the package resides in an isolated
|
|
414
|
+
virtual environment.
|
|
415
|
+
This means the traditional approach of using [[pydoc3 canvaslms.grades]] to
|
|
416
|
+
discover available modules no longer works---pydoc searches the system Python
|
|
417
|
+
path, not pipx's isolated environment.
|
|
418
|
+
|
|
419
|
+
To solve this, we dynamically discover available modules at runtime using
|
|
420
|
+
[[pkgutil.iter_modules]].
|
|
421
|
+
This works regardless of installation method because we introspect from within
|
|
422
|
+
the package itself.
|
|
423
|
+
If a module fails to import (for example, due to missing dependencies), we
|
|
424
|
+
simply skip it rather than failing the entire discovery process.
|
|
425
|
+
|
|
426
|
+
We provide two helper functions: one to list the modules with their
|
|
427
|
+
descriptions, and one to format them for display in help text.
|
|
428
|
+
<<functions>>=
|
|
429
|
+
def list_grading_modules():
|
|
430
|
+
"""
|
|
431
|
+
Discover available grading modules in canvaslms.grades package.
|
|
432
|
+
Returns a list of (module_name, description) tuples.
|
|
433
|
+
"""
|
|
434
|
+
import canvaslms.grades
|
|
435
|
+
|
|
436
|
+
modules = []
|
|
437
|
+
# These are internal modules, not user-facing grading strategies
|
|
438
|
+
excluded = {"__init__", "grades"}
|
|
439
|
+
|
|
440
|
+
for importer, modname, ispkg in pkgutil.iter_modules(canvaslms.grades.__path__):
|
|
441
|
+
if modname in excluded:
|
|
442
|
+
continue
|
|
443
|
+
try:
|
|
444
|
+
module = importlib.import_module(f"canvaslms.grades.{modname}")
|
|
445
|
+
doc = module.__doc__
|
|
446
|
+
if doc:
|
|
447
|
+
# Take only the first line of the docstring
|
|
448
|
+
first_line = doc.strip().split('\n')[0]
|
|
449
|
+
else:
|
|
450
|
+
first_line = "(no description)"
|
|
451
|
+
modules.append((modname, first_line))
|
|
452
|
+
except ImportError:
|
|
453
|
+
continue
|
|
454
|
+
|
|
455
|
+
return sorted(modules)
|
|
456
|
+
|
|
457
|
+
def format_grading_modules_help():
|
|
458
|
+
"""Format the list of grading modules for help text display."""
|
|
459
|
+
modules = list_grading_modules()
|
|
460
|
+
if not modules:
|
|
461
|
+
return " (no modules found)"
|
|
462
|
+
|
|
463
|
+
lines = []
|
|
464
|
+
for name, desc in modules:
|
|
465
|
+
lines.append(f" canvaslms.grades.{name}: {desc}")
|
|
466
|
+
return "\n\n".join(lines)
|
|
467
|
+
|
|
468
|
+
def format_results_epilog():
|
|
469
|
+
"""
|
|
470
|
+
Build the epilog for results command help.
|
|
471
|
+
|
|
472
|
+
We construct the epilog by joining lines explicitly rather than using a
|
|
473
|
+
triple-quoted string. This avoids indentation issues when black formats
|
|
474
|
+
the generated Python code---triple-quoted strings in function arguments
|
|
475
|
+
get continuation-line indentation that RawDescriptionHelpFormatter
|
|
476
|
+
would preserve literally.
|
|
477
|
+
"""
|
|
478
|
+
parts = [
|
|
479
|
+
"If you specify an assignment group, the results of the assignments in that",
|
|
480
|
+
"group will be summarized. You can supply your own function for summarizing",
|
|
481
|
+
"grades through the -S option.",
|
|
482
|
+
"",
|
|
483
|
+
"Available grading modules:",
|
|
484
|
+
format_grading_modules_help(),
|
|
485
|
+
"",
|
|
486
|
+
"You can also provide a path to your own Python file with a summarize_group",
|
|
487
|
+
"function."
|
|
488
|
+
]
|
|
489
|
+
return "\n".join(parts)
|
|
490
|
+
@
|
|
491
|
+
|
|
492
|
+
The [[list_grading_modules]] function uses [[pkgutil.iter_modules]] to iterate
|
|
493
|
+
over all submodules in the [[canvaslms.grades]] package.
|
|
494
|
+
We exclude [[__init__]] (the package initializer) and [[grades]] (an internal
|
|
495
|
+
module) since these are not user-facing grading strategies.
|
|
496
|
+
|
|
497
|
+
For each module, we import it and extract the first line of its docstring as a
|
|
498
|
+
brief description.
|
|
499
|
+
This gives users immediate visibility into what each module does, right in the
|
|
500
|
+
help output.
|
|
501
|
+
|
|
502
|
+
The [[format_grading_modules_help]] function joins module entries with blank
|
|
503
|
+
lines between them (double newline) for visual separation in the help output.
|
|
504
|
+
This makes the list easier to scan when module descriptions vary in length.
|
|
505
|
+
|
|
506
|
+
The [[format_results_epilog]] function builds the complete epilog by joining
|
|
507
|
+
individual lines.
|
|
508
|
+
This approach avoids the pitfall where [[black]] indents continuation lines in
|
|
509
|
+
triple-quoted strings, which [[RawDescriptionHelpFormatter]] would preserve
|
|
510
|
+
literally, resulting in oddly indented prose in the help output.
|
|
511
|
+
|
|
512
|
+
|
|
398
513
|
\subsection{Loading a custom summary module}
|
|
399
514
|
\label{custom-summary-modules}
|
|
400
515
|
|
|
401
|
-
Different teachers have different policies for merging several assignments into
|
|
516
|
+
Different teachers have different policies for merging several assignments into
|
|
402
517
|
one grade.
|
|
403
518
|
We now want to provide a way to override the default function.
|
|
404
519
|
<<summary module option doc>>=
|
|
405
|
-
Name of Python module or file
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
canvaslms.grades` for alternative modules or how to build your own.
|
|
520
|
+
Name of Python module or file to load with a custom summarization function.
|
|
521
|
+
Default: `{default_summary_module}`. Available modules: """ + \
|
|
522
|
+
", ".join(m[0] for m in list_grading_modules()) + """. \
|
|
523
|
+
Or provide a path to your own Python file.
|
|
410
524
|
<<add option for custom summary module>>=
|
|
411
525
|
default_summary_module = "canvaslms.grades.conjunctavg"
|
|
412
526
|
results_parser.add_argument("-S", "--summary-module",
|
canvaslms/cli/results.py
CHANGED
|
@@ -12,6 +12,7 @@ import importlib.machinery
|
|
|
12
12
|
import importlib.util
|
|
13
13
|
import os
|
|
14
14
|
import pathlib
|
|
15
|
+
import pkgutil
|
|
15
16
|
import re
|
|
16
17
|
import sys
|
|
17
18
|
|
|
@@ -250,6 +251,71 @@ def summarize_modules(canvas, args):
|
|
|
250
251
|
]
|
|
251
252
|
|
|
252
253
|
|
|
254
|
+
def list_grading_modules():
|
|
255
|
+
"""
|
|
256
|
+
Discover available grading modules in canvaslms.grades package.
|
|
257
|
+
Returns a list of (module_name, description) tuples.
|
|
258
|
+
"""
|
|
259
|
+
import canvaslms.grades
|
|
260
|
+
|
|
261
|
+
modules = []
|
|
262
|
+
# These are internal modules, not user-facing grading strategies
|
|
263
|
+
excluded = {"__init__", "grades"}
|
|
264
|
+
|
|
265
|
+
for importer, modname, ispkg in pkgutil.iter_modules(canvaslms.grades.__path__):
|
|
266
|
+
if modname in excluded:
|
|
267
|
+
continue
|
|
268
|
+
try:
|
|
269
|
+
module = importlib.import_module(f"canvaslms.grades.{modname}")
|
|
270
|
+
doc = module.__doc__
|
|
271
|
+
if doc:
|
|
272
|
+
# Take only the first line of the docstring
|
|
273
|
+
first_line = doc.strip().split("\n")[0]
|
|
274
|
+
else:
|
|
275
|
+
first_line = "(no description)"
|
|
276
|
+
modules.append((modname, first_line))
|
|
277
|
+
except ImportError:
|
|
278
|
+
continue
|
|
279
|
+
|
|
280
|
+
return sorted(modules)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def format_grading_modules_help():
|
|
284
|
+
"""Format the list of grading modules for help text display."""
|
|
285
|
+
modules = list_grading_modules()
|
|
286
|
+
if not modules:
|
|
287
|
+
return " (no modules found)"
|
|
288
|
+
|
|
289
|
+
lines = []
|
|
290
|
+
for name, desc in modules:
|
|
291
|
+
lines.append(f" canvaslms.grades.{name}: {desc}")
|
|
292
|
+
return "\n\n".join(lines)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def format_results_epilog():
|
|
296
|
+
"""
|
|
297
|
+
Build the epilog for results command help.
|
|
298
|
+
|
|
299
|
+
We construct the epilog by joining lines explicitly rather than using a
|
|
300
|
+
triple-quoted string. This avoids indentation issues when black formats
|
|
301
|
+
the generated Python code---triple-quoted strings in function arguments
|
|
302
|
+
get continuation-line indentation that RawDescriptionHelpFormatter
|
|
303
|
+
would preserve literally.
|
|
304
|
+
"""
|
|
305
|
+
parts = [
|
|
306
|
+
"If you specify an assignment group, the results of the assignments in that",
|
|
307
|
+
"group will be summarized. You can supply your own function for summarizing",
|
|
308
|
+
"grades through the -S option.",
|
|
309
|
+
"",
|
|
310
|
+
"Available grading modules:",
|
|
311
|
+
format_grading_modules_help(),
|
|
312
|
+
"",
|
|
313
|
+
"You can also provide a path to your own Python file with a summarize_group",
|
|
314
|
+
"function.",
|
|
315
|
+
]
|
|
316
|
+
return "\n".join(parts)
|
|
317
|
+
|
|
318
|
+
|
|
253
319
|
def load_module(module_name):
|
|
254
320
|
"""
|
|
255
321
|
Load a module from the file system or a built-in module.
|
|
@@ -334,10 +400,8 @@ def add_command(subp):
|
|
|
334
400
|
<course code> <component code> <student ID> <missing assignment> <reason>
|
|
335
401
|
|
|
336
402
|
The reason can be "not submitted" or "not graded".""",
|
|
337
|
-
epilog=
|
|
338
|
-
|
|
339
|
-
grades through the -S option. See `pydoc3 canvaslms.grades` for different
|
|
340
|
-
options.""",
|
|
403
|
+
epilog=format_results_epilog(),
|
|
404
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
341
405
|
)
|
|
342
406
|
results_parser.set_defaults(func=results_command)
|
|
343
407
|
assignments.add_assignment_option(results_parser, ungraded=False)
|
|
@@ -362,11 +426,11 @@ def add_command(subp):
|
|
|
362
426
|
"--summary-module",
|
|
363
427
|
required=False,
|
|
364
428
|
default=default_summary_module,
|
|
365
|
-
help=f"""Name of Python module or file
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
429
|
+
help=f"""Name of Python module or file to load with a custom summarization function.
|
|
430
|
+
Default: `{default_summary_module}`. Available modules: """
|
|
431
|
+
+ ", ".join(m[0] for m in list_grading_modules())
|
|
432
|
+
+ """. \
|
|
433
|
+
Or provide a path to your own Python file.""",
|
|
370
434
|
)
|
|
371
435
|
default_missing_module = "canvaslms.cli.results"
|
|
372
436
|
results_parser.add_argument(
|
canvaslms/grades/Makefile
CHANGED
|
@@ -6,6 +6,7 @@ MODULES+= disjunctmax.py
|
|
|
6
6
|
MODULES+= maxgradesurvey.py
|
|
7
7
|
MODULES+= conjunctavgsurvey.py
|
|
8
8
|
MODULES+= tilkryLAB1.py
|
|
9
|
+
MODULES+= participation.py
|
|
9
10
|
|
|
10
11
|
.PHONY: all
|
|
11
12
|
all: grades.tex
|
|
@@ -14,6 +15,7 @@ all: disjunctmax.tex
|
|
|
14
15
|
all: maxgradesurvey.tex
|
|
15
16
|
all: conjunctavgsurvey.tex
|
|
16
17
|
all: tilkryLAB1.tex
|
|
18
|
+
all: participation.tex
|
|
17
19
|
all: ${MODULES}
|
|
18
20
|
|
|
19
21
|
grades.tex: conjunctavg.tex
|
|
@@ -21,6 +23,7 @@ grades.tex: disjunctmax.tex
|
|
|
21
23
|
grades.tex: maxgradesurvey.tex
|
|
22
24
|
grades.tex: conjunctavgsurvey.tex
|
|
23
25
|
grades.tex: tilkryLAB1.tex
|
|
26
|
+
grades.tex: participation.tex
|
|
24
27
|
|
|
25
28
|
__init__.py: init.py
|
|
26
29
|
${MV} $^ $@
|
canvaslms/grades/grades.nw
CHANGED
|
@@ -135,3 +135,4 @@ We can also give the relative or absolute path to \texttt{mysum.py} instead.
|
|
|
135
135
|
\input{../src/canvaslms/grades/disjunctmax.tex}
|
|
136
136
|
\input{../src/canvaslms/grades/maxgradesurvey.tex}
|
|
137
137
|
\input{../src/canvaslms/grades/tilkryLAB1.tex}
|
|
138
|
+
\input{../src/canvaslms/grades/participation.tex}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
\section{Conjunctive P/F grading, the \texttt{participation} module}
|
|
2
|
+
|
|
3
|
+
This module implements conjunctive P/F grading for participation-based
|
|
4
|
+
assignments.
|
|
5
|
+
It's useful when students must complete \emph{all} assignments in a group to
|
|
6
|
+
pass, where each assignment is graded as complete/incomplete or 100/0.
|
|
7
|
+
|
|
8
|
+
As an example, consider a course where students pass by participating in
|
|
9
|
+
seminars and completing reflections:
|
|
10
|
+
\begin{minted}{text}
|
|
11
|
+
$ canvaslms assignments list -c vetcyb25h -A "^Participation INL1" \
|
|
12
|
+
| cut -f 1-3
|
|
13
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Reflection on literature reviews
|
|
14
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Overview of Science in Security
|
|
15
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 How to Design Computer Security Experiments
|
|
16
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 How do you know it's secure? Passwords
|
|
17
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension, literature review: Of passwords and people
|
|
18
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Graphical Passwords: Learning from the First Twelve Years
|
|
19
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Achieving Rigor in Literature Reviews Insights from Qualitative Data Analysis and Tool-Support
|
|
20
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Live seminar 13/11 at 13:15
|
|
21
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Of Passwords and People, Measuring the Effect of Password-Composition Policies
|
|
22
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Can long passwords be secure and usable?
|
|
23
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Why phishing works
|
|
24
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Live seminar 6/11 at 15:15
|
|
25
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 The RSA and ElGamal cryptosystems
|
|
26
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 On the Security of EIGamal Based Encryption
|
|
27
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Stealing Keys from PCs using a Radio: Cheap Electromagnetic Attacks on Windowed Exponentiation
|
|
28
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Timing Analysis of Keystrokes and Timing Attacks on SSH
|
|
29
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Reflection on the use of models, part I
|
|
30
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Theorem proving: 1. Introduction
|
|
31
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Theorem proving: 2. Formal methods and Interactive Theorem Proving
|
|
32
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Theorem proving: 4. Examples for what can be verified with Interactive Theorem Provers
|
|
33
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Theorem proving: 5. Limitations of Interactive Theorem Proving and Conclusion
|
|
34
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Model checking: algorithmic verification and debugging
|
|
35
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Reflection on the use of models, part II
|
|
36
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Live seminar 28/11 at 10:15
|
|
37
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Dos and Don'ts of Machine Learning in Computer Security
|
|
38
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Reflection on the use of statistics
|
|
39
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Why Johnny can't encrypt
|
|
40
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Comparing the Usability of Cryptographic APIs
|
|
41
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Reflection on qualitative methods
|
|
42
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 SoK: Science, Security and the Elusive Goal of Security as a Scientific Pursuit
|
|
43
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Reflection on Science in Security
|
|
44
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Live seminar 12/12 at 12:15
|
|
45
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Tor: The Second-Generation Onion Router
|
|
46
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Users get routed: traffic correlation on tor by realistic adversaries
|
|
47
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Shadow: Running Tor in a Box for Accurate and Efficient Experimentation
|
|
48
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Website Fingerprinting with Website Oracles
|
|
49
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Comprehension: Online Website Fingerprinting: Evaluating Website Fingerprinting Attacks on Tor in the Real World
|
|
50
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Reflection on the use of models, part III
|
|
51
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Live seminar 3/12 at 12:15
|
|
52
|
+
DA2215 HT25 (vetcyb25h) Participation INL1 Reflection on inferences, experiments and measurements
|
|
53
|
+
\end{minted}
|
|
54
|
+
The exact assignments and names might change though.
|
|
55
|
+
But that's fine.
|
|
56
|
+
The grading system remains the same, we have the following grades:
|
|
57
|
+
\begin{minted}{text}
|
|
58
|
+
$ canvaslms submissions list -c vetcyb25h -A "^Participation INL1" \
|
|
59
|
+
| cut -f 4 | sort -u
|
|
60
|
+
|
|
61
|
+
100
|
|
62
|
+
complete
|
|
63
|
+
incomplete
|
|
64
|
+
\end{minted}
|
|
65
|
+
We want all assignments to have either complete or 100 to get a passing grade.
|
|
66
|
+
Otherwise the student gets an F.
|
|
67
|
+
|
|
68
|
+
\subsection{Grading logic: conjunctive P/F}
|
|
69
|
+
|
|
70
|
+
This module implements what we call \emph{conjunctive P/F grading}.
|
|
71
|
+
Let's contrast this with other approaches to understand what makes it unique:
|
|
72
|
+
\begin{description}
|
|
73
|
+
\item[conjunctavg] All assignments must pass, and A--E grades are averaged.
|
|
74
|
+
Here we have no A--E grades, only complete/incomplete or 100/empty.
|
|
75
|
+
\item[disjunctmax] At least one assignment must pass (disjunctive), and we take
|
|
76
|
+
the best grade.
|
|
77
|
+
Here \emph{all} assignments must pass (conjunctive), and the only possible
|
|
78
|
+
grades are P or F.
|
|
79
|
+
\end{description}
|
|
80
|
+
|
|
81
|
+
The grading rule is simple: if \emph{all} participation assignments have a
|
|
82
|
+
passing grade, the student passes.
|
|
83
|
+
If \emph{any} assignment is missing or has an incomplete grade, the student
|
|
84
|
+
fails.
|
|
85
|
+
|
|
86
|
+
\subsection{Module structure}
|
|
87
|
+
|
|
88
|
+
We follow the standard module structure: a [[summarize_group]] function that
|
|
89
|
+
iterates over users and delegates to a [[summarize]] helper function.
|
|
90
|
+
<<[[participation.py]]>>=
|
|
91
|
+
"""
|
|
92
|
+
<<module doc>>
|
|
93
|
+
"""
|
|
94
|
+
import datetime as dt
|
|
95
|
+
from canvaslms.cli import results
|
|
96
|
+
from canvasapi.exceptions import ResourceDoesNotExist
|
|
97
|
+
|
|
98
|
+
<<helper functions>>
|
|
99
|
+
|
|
100
|
+
def summarize_group(assignments_list, users_list):
|
|
101
|
+
"""
|
|
102
|
+
Summarizes participation assignments using conjunctive P/F grading.
|
|
103
|
+
All assignments must have 'complete' or '100' for P, otherwise F.
|
|
104
|
+
"""
|
|
105
|
+
for user in users_list:
|
|
106
|
+
grade, grade_date, graders = summarize(user, assignments_list)
|
|
107
|
+
yield [user, grade, grade_date, *graders]
|
|
108
|
+
@
|
|
109
|
+
|
|
110
|
+
<<module doc>>=
|
|
111
|
+
Summarizes participation assignments using conjunctive P/F grading.
|
|
112
|
+
All assignments must have 'complete' or '100' for P, otherwise F.
|
|
113
|
+
@
|
|
114
|
+
|
|
115
|
+
\subsection{What counts as passing?}
|
|
116
|
+
|
|
117
|
+
A grade is considered passing if it is either:
|
|
118
|
+
\begin{itemize}
|
|
119
|
+
\item [[complete]] (case-insensitive, so [[Complete]] or [[COMPLETE]] also work)
|
|
120
|
+
\item [[100]] (a numeric grade indicating full completion)
|
|
121
|
+
\end{itemize}
|
|
122
|
+
|
|
123
|
+
Anything else---including [[incomplete]], [[None]], or a missing
|
|
124
|
+
submission---counts as not passing.
|
|
125
|
+
<<helper functions>>=
|
|
126
|
+
def is_passing_grade(grade):
|
|
127
|
+
"""
|
|
128
|
+
Returns True if the grade indicates passing (complete or 100).
|
|
129
|
+
"""
|
|
130
|
+
if grade is None:
|
|
131
|
+
return False
|
|
132
|
+
if isinstance(grade, str):
|
|
133
|
+
if grade.casefold() == "complete":
|
|
134
|
+
return True
|
|
135
|
+
if grade == "100":
|
|
136
|
+
return True
|
|
137
|
+
return False
|
|
138
|
+
@
|
|
139
|
+
|
|
140
|
+
\subsection{Summarizing a student's participation}
|
|
141
|
+
|
|
142
|
+
The [[summarize]] function iterates through all assignments and checks whether
|
|
143
|
+
each one has a passing grade.
|
|
144
|
+
We collect dates and graders along the way.
|
|
145
|
+
<<helper functions>>=
|
|
146
|
+
def summarize(user, assignments_list):
|
|
147
|
+
"""
|
|
148
|
+
Extracts user's submissions for all participation assignments.
|
|
149
|
+
Returns (grade, date, graders) where grade is P if all passed, F otherwise.
|
|
150
|
+
"""
|
|
151
|
+
passed = []
|
|
152
|
+
dates = []
|
|
153
|
+
graders = []
|
|
154
|
+
|
|
155
|
+
for assignment in assignments_list:
|
|
156
|
+
<<get submission for assignment>>
|
|
157
|
+
<<check if grade is passing>>
|
|
158
|
+
<<add graders to [[graders]] list>>
|
|
159
|
+
<<add date to [[dates]] list>>
|
|
160
|
+
|
|
161
|
+
<<determine final grade>>
|
|
162
|
+
|
|
163
|
+
return (final_grade, final_date, graders)
|
|
164
|
+
@
|
|
165
|
+
|
|
166
|
+
For each assignment, we attempt to fetch the student's submission.
|
|
167
|
+
If the submission doesn't exist (which can happen in rare cases), we treat it
|
|
168
|
+
as not passing.
|
|
169
|
+
<<get submission for assignment>>=
|
|
170
|
+
try:
|
|
171
|
+
submission = assignment.get_submission(user,
|
|
172
|
+
include=["submission_history"])
|
|
173
|
+
except ResourceDoesNotExist:
|
|
174
|
+
passed.append(False)
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
submission.assignment = assignment
|
|
178
|
+
@
|
|
179
|
+
|
|
180
|
+
We extract the grade from the submission and check whether it's passing.
|
|
181
|
+
<<check if grade is passing>>=
|
|
182
|
+
grade = submission.grade
|
|
183
|
+
passed.append(is_passing_grade(grade))
|
|
184
|
+
@
|
|
185
|
+
|
|
186
|
+
We collect all graders from the submission history.
|
|
187
|
+
This ensures we credit everyone who participated in the grading process, not
|
|
188
|
+
just the last person to grade.
|
|
189
|
+
<<add graders to [[graders]] list>>=
|
|
190
|
+
graders += results.all_graders(submission)
|
|
191
|
+
@
|
|
192
|
+
|
|
193
|
+
For the date, we prefer the submission date but fall back to the grading date
|
|
194
|
+
if no submission was made (for example, oral presentations where the student
|
|
195
|
+
didn't submit anything).
|
|
196
|
+
<<add date to [[dates]] list>>=
|
|
197
|
+
grade_date = submission.submitted_at or submission.graded_at
|
|
198
|
+
if grade_date:
|
|
199
|
+
grade_date = dt.date.fromisoformat(grade_date.split("T")[0])
|
|
200
|
+
dates.append(grade_date)
|
|
201
|
+
@
|
|
202
|
+
|
|
203
|
+
\subsection{Determining the final grade}
|
|
204
|
+
|
|
205
|
+
The final grade is P if \emph{all} assignments passed, F otherwise.
|
|
206
|
+
If there are no dates (meaning the student has no activity at all), we return
|
|
207
|
+
[[None]] for both grade and date.
|
|
208
|
+
<<determine final grade>>=
|
|
209
|
+
if dates:
|
|
210
|
+
final_date = max(dates)
|
|
211
|
+
if all(passed):
|
|
212
|
+
final_grade = "P"
|
|
213
|
+
else:
|
|
214
|
+
final_grade = "F"
|
|
215
|
+
else:
|
|
216
|
+
final_date = None
|
|
217
|
+
final_grade = None
|
|
218
|
+
@
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Summarizes participation assignments using conjunctive P/F grading.
|
|
3
|
+
All assignments must have 'complete' or '100' for P, otherwise F.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import datetime as dt
|
|
7
|
+
from canvaslms.cli import results
|
|
8
|
+
from canvasapi.exceptions import ResourceDoesNotExist
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def is_passing_grade(grade):
|
|
12
|
+
"""
|
|
13
|
+
Returns True if the grade indicates passing (complete or 100).
|
|
14
|
+
"""
|
|
15
|
+
if grade is None:
|
|
16
|
+
return False
|
|
17
|
+
if isinstance(grade, str):
|
|
18
|
+
if grade.casefold() == "complete":
|
|
19
|
+
return True
|
|
20
|
+
if grade == "100":
|
|
21
|
+
return True
|
|
22
|
+
return False
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def summarize(user, assignments_list):
|
|
26
|
+
"""
|
|
27
|
+
Extracts user's submissions for all participation assignments.
|
|
28
|
+
Returns (grade, date, graders) where grade is P if all passed, F otherwise.
|
|
29
|
+
"""
|
|
30
|
+
passed = []
|
|
31
|
+
dates = []
|
|
32
|
+
graders = []
|
|
33
|
+
|
|
34
|
+
for assignment in assignments_list:
|
|
35
|
+
try:
|
|
36
|
+
submission = assignment.get_submission(user, include=["submission_history"])
|
|
37
|
+
except ResourceDoesNotExist:
|
|
38
|
+
passed.append(False)
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
submission.assignment = assignment
|
|
42
|
+
grade = submission.grade
|
|
43
|
+
passed.append(is_passing_grade(grade))
|
|
44
|
+
graders += results.all_graders(submission)
|
|
45
|
+
grade_date = submission.submitted_at or submission.graded_at
|
|
46
|
+
if grade_date:
|
|
47
|
+
grade_date = dt.date.fromisoformat(grade_date.split("T")[0])
|
|
48
|
+
dates.append(grade_date)
|
|
49
|
+
|
|
50
|
+
if dates:
|
|
51
|
+
final_date = max(dates)
|
|
52
|
+
if all(passed):
|
|
53
|
+
final_grade = "P"
|
|
54
|
+
else:
|
|
55
|
+
final_grade = "F"
|
|
56
|
+
else:
|
|
57
|
+
final_date = None
|
|
58
|
+
final_grade = None
|
|
59
|
+
|
|
60
|
+
return (final_grade, final_date, graders)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def summarize_group(assignments_list, users_list):
|
|
64
|
+
"""
|
|
65
|
+
Summarizes participation assignments using conjunctive P/F grading.
|
|
66
|
+
All assignments must have 'complete' or '100' for P, otherwise F.
|
|
67
|
+
"""
|
|
68
|
+
for user in users_list:
|
|
69
|
+
grade, grade_date, graders = summarize(user, assignments_list)
|
|
70
|
+
yield [user, grade, grade_date, *graders]
|
|
@@ -26,8 +26,8 @@ canvaslms/cli/pages.nw,sha256=njm6oQT22ryI8Z7O62d-qivQjbMX_je5OwhgHEvtVUg,28934
|
|
|
26
26
|
canvaslms/cli/pages.py,sha256=lW8DpMeyQQoVcu7ztSj_PdW-oUJC0HSh5mDeo8BuaRc,27713
|
|
27
27
|
canvaslms/cli/quizzes.nw,sha256=xgivQGColOzayvMn3OcxJf0V7E2K2lzindzPeBqymBw,262940
|
|
28
28
|
canvaslms/cli/quizzes.py,sha256=1gX16Mo49rO4j98xy_j7xSuIs0E9DcOJigB90imBNR4,198225
|
|
29
|
-
canvaslms/cli/results.nw,sha256=
|
|
30
|
-
canvaslms/cli/results.py,sha256=
|
|
29
|
+
canvaslms/cli/results.nw,sha256=SQIiCwcQUO-irUD6-Ya-4QBVHo19uv5QHH7GXNvdrDg,26862
|
|
30
|
+
canvaslms/cli/results.py,sha256=2KGcqGzmrfqc2Jj5ddQcrm33qF_DYHoljTol4qI9pSg,16845
|
|
31
31
|
canvaslms/cli/results.py.broken,sha256=njHu8mKfPHqH4daxy-4LMpO6FdUBLPHiVKFFmyH8aJQ,13047
|
|
32
32
|
canvaslms/cli/submissions.nw,sha256=RCpN7p2Oe9sbMeIPv16CeUGvuesyT9Z5f2PdIdKxHy8,97760
|
|
33
33
|
canvaslms/cli/submissions.py,sha256=mvXiknSAxmdvoHg6V6k8IqTNdRx2FZqBskniIDRcpw8,49196
|
|
@@ -36,7 +36,7 @@ canvaslms/cli/users.nw,sha256=WOutY4VTeyDBqOIbHk6VY_nxgJPk1eKU9CvH6nqjsE4,35236
|
|
|
36
36
|
canvaslms/cli/users.py,sha256=YBmL7tUStFd_JPNHTIN1H_n-DmLgYv54JdQrcIRsUDk,16979
|
|
37
37
|
canvaslms/cli/utils.nw,sha256=E6mIJFZc_mCWIvfCHgOPqFWbOgLKcATEJlJiGZMyuMk,16270
|
|
38
38
|
canvaslms/cli/utils.py,sha256=RO3-TZzgxSC662vB34WOt4V9S1bHv6N2B9eVo3sFavY,4138
|
|
39
|
-
canvaslms/grades/Makefile,sha256=
|
|
39
|
+
canvaslms/grades/Makefile,sha256=NQoyTmiaW2Cw8sfM9LNYjiNN0rYxM8UmoLpDbdKHwu4,893
|
|
40
40
|
canvaslms/grades/__init__.py,sha256=wDg-x-fbSsph2R0vradLXO4oDR1yG8Z-6whW2l5_pX8,1027
|
|
41
41
|
canvaslms/grades/conjunctavg.nw,sha256=uaHcE7l72o66FUjK8x1KijE5YeMsU74WFS-j-ZCVBq0,4671
|
|
42
42
|
canvaslms/grades/conjunctavg.py,sha256=ARLEWdtiDre8LDsL0DVKjQ9tfJslZAJept1fbhmKc9A,3006
|
|
@@ -44,10 +44,12 @@ canvaslms/grades/conjunctavgsurvey.nw,sha256=O158fPIv4y1_Qx8yB9O4wV9wMoVC5b5rVYG
|
|
|
44
44
|
canvaslms/grades/conjunctavgsurvey.py,sha256=G-1i2BeapSsXJ9XfxEK0ShK5UxlqQpBcu9yJH9F1Sys,2730
|
|
45
45
|
canvaslms/grades/disjunctmax.nw,sha256=tiPMd59OBsZyUdW_KqY-kSqQVL9I7u7tMSSyOQDa8YI,3982
|
|
46
46
|
canvaslms/grades/disjunctmax.py,sha256=6dMah6yxHpJHKdO72SRh47GLC38UinMl0HVdGSIZePM,2703
|
|
47
|
-
canvaslms/grades/grades.nw,sha256=
|
|
47
|
+
canvaslms/grades/grades.nw,sha256=ldR-_7l3L5ymHJV82TBnQ1YLn2kwyD2Cw3bW--Q-iiY,5096
|
|
48
48
|
canvaslms/grades/grades.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
49
|
canvaslms/grades/maxgradesurvey.nw,sha256=agYMqH20K1DLZ0pB9CJPye0E1g_A-ImdGJJzO03HFcE,3263
|
|
50
50
|
canvaslms/grades/maxgradesurvey.py,sha256=eEysQNgHuFM9-2vrx4h2ovmK5TsXbRI1ukRx7tsct7Y,2089
|
|
51
|
+
canvaslms/grades/participation.nw,sha256=pqSxNdIrAbLvSXAmxLpgLm263yPObGqbEPibZ0BAHj0,9439
|
|
52
|
+
canvaslms/grades/participation.py,sha256=Pex6-vYfqB33Lizn3Pkn6yZUq0l1LZG40555uXSxCj4,2054
|
|
51
53
|
canvaslms/grades/tilkryLAB1.nw,sha256=xkieCGinC0sLpwsBPlOCxXjUPUiHITQzV7QTnJo3-io,10186
|
|
52
54
|
canvaslms/grades/tilkryLAB1.py,sha256=_-p2DqeNZCnomCcXkUSSK4_KffJcaPwpjEBzfXlWsqg,3214
|
|
53
55
|
canvaslms/hacks/Makefile,sha256=Xxk4Fw6U_QSB8KgBlSL8-vUqYIzWzDOXgN1_LNgmNdU,345
|
|
@@ -57,8 +59,8 @@ canvaslms/hacks/attachment_cache.py,sha256=LcOZqaa6jPrEJWUD-JYN5GTc3bxCbv2fr_vqu
|
|
|
57
59
|
canvaslms/hacks/canvasapi.nw,sha256=ixmIHn4tgy-ZKtQ1rqWSw97hfY2m0qtGX0de2x89lwA,136470
|
|
58
60
|
canvaslms/hacks/canvasapi.py,sha256=A-r48x7gO6143_QkuZ8n6EW66i-a2AXXr7X7oeehOAU,31868
|
|
59
61
|
canvaslms/hacks/test_hacks.py,sha256=JSJNvZqHu1E_s51HsPD7yr1gC-R-xVe-tuMMAKU9Gj8,66709
|
|
60
|
-
canvaslms-5.
|
|
61
|
-
canvaslms-5.
|
|
62
|
-
canvaslms-5.
|
|
63
|
-
canvaslms-5.
|
|
64
|
-
canvaslms-5.
|
|
62
|
+
canvaslms-5.8.dist-info/METADATA,sha256=Xz-kANG9wsPDHQvYbkE_0HfRM1jLN3z8qhmwA0w75pM,6078
|
|
63
|
+
canvaslms-5.8.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
|
|
64
|
+
canvaslms-5.8.dist-info/entry_points.txt,sha256=lyblfkLbodN5yb7q1c6-rwIoJPV-ygXrB9PYb5boHXM,48
|
|
65
|
+
canvaslms-5.8.dist-info/licenses/LICENSE,sha256=N_TKsbzzD5Ax5fWJqEQk9bkwtf394MJkNeFld4HV6-E,1074
|
|
66
|
+
canvaslms-5.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|