QuizGenerator 0.6.0__py3-none-any.whl → 0.6.2__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.
- QuizGenerator/canvas/canvas_interface.py +2 -1
- QuizGenerator/contentast.py +40 -69
- QuizGenerator/generate.py +21 -2
- QuizGenerator/premade_questions/cst334/process.py +1 -1
- {quizgenerator-0.6.0.dist-info → quizgenerator-0.6.2.dist-info}/METADATA +1 -1
- {quizgenerator-0.6.0.dist-info → quizgenerator-0.6.2.dist-info}/RECORD +9 -9
- {quizgenerator-0.6.0.dist-info → quizgenerator-0.6.2.dist-info}/WHEEL +0 -0
- {quizgenerator-0.6.0.dist-info → quizgenerator-0.6.2.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.6.0.dist-info → quizgenerator-0.6.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -154,7 +154,8 @@ class CanvasCourse(LMSWrapper):
|
|
|
154
154
|
# Get the question in a format that is ready for canvas (e.g. json)
|
|
155
155
|
# Use large gaps between base seeds to avoid overlap with backoff attempts
|
|
156
156
|
# Each variation gets seeds: base_seed, base_seed+1, base_seed+2, ... for backoffs
|
|
157
|
-
|
|
157
|
+
# Include question_i in the seed so different questions get different seed spaces
|
|
158
|
+
base_seed = (question_i * 100_000) + (attempt_number * 1000)
|
|
158
159
|
question_for_canvas = question.get__canvas(self.course, canvas_quiz, rng_seed=base_seed)
|
|
159
160
|
|
|
160
161
|
question_fingerprint = question_for_canvas["question_text"]
|
QuizGenerator/contentast.py
CHANGED
|
@@ -2338,6 +2338,23 @@ class ContentAST:
|
|
|
2338
2338
|
d = decimal.Decimal(f.numerator).quantize(q, rounding=decimal.ROUND_HALF_UP)
|
|
2339
2339
|
outs.add(format(d, 'f'))
|
|
2340
2340
|
|
|
2341
|
+
# Simple fraction
|
|
2342
|
+
if allow_simple_fraction:
|
|
2343
|
+
fr = f.limit_denominator(max_denominator)
|
|
2344
|
+
if fr == f:
|
|
2345
|
+
a, b = fr.numerator, fr.denominator
|
|
2346
|
+
if fr.denominator > 1:
|
|
2347
|
+
outs.add(f"{a}/{b}")
|
|
2348
|
+
if include_spaces:
|
|
2349
|
+
outs.add(f"{a} / {b}")
|
|
2350
|
+
if allow_mixed and b != 1 and abs(a) > b:
|
|
2351
|
+
sign = '-' if a < 0 else ''
|
|
2352
|
+
A = abs(a)
|
|
2353
|
+
whole, rem = divmod(A, b)
|
|
2354
|
+
outs.add(f"{sign}{whole} {rem}/{b}")
|
|
2355
|
+
else:
|
|
2356
|
+
return sorted(outs, key=lambda s: (len(s), s))
|
|
2357
|
+
|
|
2341
2358
|
# Fixed-decimal form
|
|
2342
2359
|
q = decimal.Decimal(1).scaleb(-ContentAST.Answer.DEFAULT_ROUNDING_DIGITS)
|
|
2343
2360
|
d = (decimal.Decimal(f.numerator) / decimal.Decimal(f.denominator)).quantize(q, rounding=decimal.ROUND_HALF_UP)
|
|
@@ -2354,20 +2371,6 @@ class ContentAST:
|
|
|
2354
2371
|
s = '0'
|
|
2355
2372
|
outs.add(s)
|
|
2356
2373
|
|
|
2357
|
-
# Simple fraction
|
|
2358
|
-
if allow_simple_fraction:
|
|
2359
|
-
fr = f.limit_denominator(max_denominator)
|
|
2360
|
-
if fr == f:
|
|
2361
|
-
a, b = fr.numerator, fr.denominator
|
|
2362
|
-
outs.add(f"{a}/{b}")
|
|
2363
|
-
if include_spaces:
|
|
2364
|
-
outs.add(f"{a} / {b}")
|
|
2365
|
-
if allow_mixed and b != 1 and abs(a) > b:
|
|
2366
|
-
sign = '-' if a < 0 else ''
|
|
2367
|
-
A = abs(a)
|
|
2368
|
-
whole, rem = divmod(A, b)
|
|
2369
|
-
outs.add(f"{sign}{whole} {rem}/{b}")
|
|
2370
|
-
|
|
2371
2374
|
return sorted(outs, key=lambda s: (len(s), s))
|
|
2372
2375
|
|
|
2373
2376
|
@staticmethod
|
|
@@ -2375,6 +2378,10 @@ class ContentAST:
|
|
|
2375
2378
|
"""Fix -0.0 display issue."""
|
|
2376
2379
|
return 0.0 if x == 0 else x
|
|
2377
2380
|
|
|
2381
|
+
@staticmethod
|
|
2382
|
+
def get_spacing_variations_of_list(l, remove_space=False):
|
|
2383
|
+
return [', '.join(l)] + ([', '.join(l)] if remove_space else [])
|
|
2384
|
+
|
|
2378
2385
|
|
|
2379
2386
|
class AnswerTypes:
|
|
2380
2387
|
# Multibase answers that can accept either hex, binary or decimal
|
|
@@ -2502,37 +2509,23 @@ class AnswerTypes:
|
|
|
2502
2509
|
canvas_answers = [
|
|
2503
2510
|
{
|
|
2504
2511
|
"blank_id": self.key,
|
|
2505
|
-
"answer_text":
|
|
2506
|
-
"answer_weight": 100 if self.correct else 0,
|
|
2507
|
-
},
|
|
2508
|
-
{
|
|
2509
|
-
"blank_id": self.key,
|
|
2510
|
-
"answer_text": ','.join(map(str, self.value)),
|
|
2512
|
+
"answer_text": spacing_variation,
|
|
2511
2513
|
"answer_weight": 100 if self.correct else 0,
|
|
2512
2514
|
}
|
|
2515
|
+
for spacing_variation in self.get_spacing_variations_of_list(map(str, self.value))
|
|
2513
2516
|
]
|
|
2514
2517
|
else:
|
|
2515
2518
|
canvas_answers = []
|
|
2516
|
-
|
|
2517
|
-
# With spaces
|
|
2518
|
-
canvas_answers.extend([
|
|
2519
|
+
canvas_answers = [
|
|
2519
2520
|
{
|
|
2520
2521
|
"blank_id": self.key,
|
|
2521
|
-
"answer_text":
|
|
2522
|
+
"answer_text": spacing_variation,
|
|
2522
2523
|
"answer_weight": 100 if self.correct else 0,
|
|
2523
2524
|
}
|
|
2524
2525
|
for possible_state in itertools.permutations(self.value)
|
|
2525
|
-
|
|
2526
|
+
for spacing_variation in self.get_spacing_variations_of_list(possible_state)
|
|
2527
|
+
]
|
|
2526
2528
|
|
|
2527
|
-
# Without spaces
|
|
2528
|
-
canvas_answers.extend([
|
|
2529
|
-
{
|
|
2530
|
-
"blank_id": self.key,
|
|
2531
|
-
"answer_text": ','.join(map(str, possible_state)),
|
|
2532
|
-
"answer_weight": 100 if self.correct else 0,
|
|
2533
|
-
}
|
|
2534
|
-
for possible_state in itertools.permutations(self.value)
|
|
2535
|
-
])
|
|
2536
2529
|
return canvas_answers
|
|
2537
2530
|
|
|
2538
2531
|
def get_display_string(self) -> str:
|
|
@@ -2555,41 +2548,19 @@ class AnswerTypes:
|
|
|
2555
2548
|
|
|
2556
2549
|
canvas_answers = []
|
|
2557
2550
|
for combination in itertools.product(*answer_variations):
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
{
|
|
2572
|
-
"blank_id": self.key,
|
|
2573
|
-
"answer_weight": 100 if self.correct else 0,
|
|
2574
|
-
"answer_text": f"({', '.join(list(combination))})",
|
|
2575
|
-
},
|
|
2576
|
-
{
|
|
2577
|
-
"blank_id": self.key,
|
|
2578
|
-
"answer_weight": 100 if self.correct else 0,
|
|
2579
|
-
"answer_text": f"({','.join(list(combination))})",
|
|
2580
|
-
},
|
|
2581
|
-
# Add square brackets
|
|
2582
|
-
{
|
|
2583
|
-
"blank_id": self.key,
|
|
2584
|
-
"answer_weight": 100 if self.correct else 0,
|
|
2585
|
-
"answer_text": f"[{', '.join(list(combination))}]",
|
|
2586
|
-
},
|
|
2587
|
-
{
|
|
2588
|
-
"blank_id": self.key,
|
|
2589
|
-
"answer_weight": 100 if self.correct else 0,
|
|
2590
|
-
"answer_text": f"[{','.join(list(combination))}]",
|
|
2591
|
-
}
|
|
2592
|
-
])
|
|
2551
|
+
for spacing_variation in self.get_spacing_variations_of_list(list(combination)):
|
|
2552
|
+
canvas_answers.extend([
|
|
2553
|
+
{
|
|
2554
|
+
"blank_id": self.key,
|
|
2555
|
+
"answer_weight": 100 if self.correct else 0,
|
|
2556
|
+
"answer_text": f"{spacing_variation}",
|
|
2557
|
+
}, # without parenthesis
|
|
2558
|
+
{
|
|
2559
|
+
"blank_id": self.key,
|
|
2560
|
+
"answer_weight": 100 if self.correct else 0,
|
|
2561
|
+
"answer_text": f"({spacing_variation})",
|
|
2562
|
+
} # with parenthesis
|
|
2563
|
+
])
|
|
2593
2564
|
return canvas_answers
|
|
2594
2565
|
|
|
2595
2566
|
def get_display_string(self) -> str:
|
QuizGenerator/generate.py
CHANGED
|
@@ -50,6 +50,8 @@ def parse_args():
|
|
|
50
50
|
# Testing flags
|
|
51
51
|
parser.add_argument("--test_all", type=int, default=0, metavar="N",
|
|
52
52
|
help="Generate N variations of ALL registered questions to test they work correctly")
|
|
53
|
+
parser.add_argument("--test_questions", nargs='+', metavar="NAME",
|
|
54
|
+
help="Only test specific question types by name (use with --test_all)")
|
|
53
55
|
parser.add_argument("--strict", action="store_true",
|
|
54
56
|
help="With --test_all, skip PDF/Canvas generation if any questions fail")
|
|
55
57
|
|
|
@@ -79,7 +81,8 @@ def test_all_questions(
|
|
|
79
81
|
generate_pdf: bool = False,
|
|
80
82
|
use_typst: bool = True,
|
|
81
83
|
canvas_course=None,
|
|
82
|
-
strict: bool = False
|
|
84
|
+
strict: bool = False,
|
|
85
|
+
question_filter: list = None
|
|
83
86
|
):
|
|
84
87
|
"""
|
|
85
88
|
Test all registered questions by generating N variations of each.
|
|
@@ -93,11 +96,26 @@ def test_all_questions(
|
|
|
93
96
|
use_typst: If True, use Typst for PDF generation; otherwise use LaTeX
|
|
94
97
|
canvas_course: If provided, push a test quiz to this Canvas course
|
|
95
98
|
strict: If True, skip PDF/Canvas generation if any questions fail
|
|
99
|
+
question_filter: If provided, only test questions whose names contain one of these strings (case-insensitive)
|
|
96
100
|
"""
|
|
97
101
|
# Ensure all premade questions are loaded
|
|
98
102
|
QuestionRegistry.load_premade_questions()
|
|
99
103
|
|
|
100
104
|
registered_questions = QuestionRegistry._registry
|
|
105
|
+
|
|
106
|
+
# Filter questions if a filter list is provided
|
|
107
|
+
if question_filter:
|
|
108
|
+
filter_lower = [f.lower() for f in question_filter]
|
|
109
|
+
registered_questions = {
|
|
110
|
+
name: cls for name, cls in registered_questions.items()
|
|
111
|
+
if any(f in name.lower() for f in filter_lower)
|
|
112
|
+
}
|
|
113
|
+
if not registered_questions:
|
|
114
|
+
print(f"No questions matched filter: {question_filter}")
|
|
115
|
+
print(f"Available questions: {sorted(QuestionRegistry._registry.keys())}")
|
|
116
|
+
return False
|
|
117
|
+
print(f"Filtered to {len(registered_questions)} questions matching: {question_filter}")
|
|
118
|
+
|
|
101
119
|
total_questions = len(registered_questions)
|
|
102
120
|
|
|
103
121
|
# Test defaults for questions that require external input
|
|
@@ -437,7 +455,8 @@ def main():
|
|
|
437
455
|
generate_pdf=True,
|
|
438
456
|
use_typst=getattr(args, 'typst', True),
|
|
439
457
|
canvas_course=canvas_course,
|
|
440
|
-
strict=args.strict
|
|
458
|
+
strict=args.strict,
|
|
459
|
+
question_filter=args.test_questions
|
|
441
460
|
)
|
|
442
461
|
exit(0 if success else 1)
|
|
443
462
|
|
|
@@ -324,7 +324,7 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
|
|
|
324
324
|
jobs_to_run=jobs,
|
|
325
325
|
selector=(lambda j, curr_time: (j.last_run, j.job_id)),
|
|
326
326
|
preemptable=True,
|
|
327
|
-
time_quantum=1e-
|
|
327
|
+
time_quantum=1e-05
|
|
328
328
|
)
|
|
329
329
|
case _:
|
|
330
330
|
self.run_simulation(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: QuizGenerator
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: Generate randomized quiz questions for Canvas LMS and PDF exams
|
|
5
5
|
Project-URL: Homepage, https://github.com/OtterDen-Lab/QuizGenerator
|
|
6
6
|
Project-URL: Documentation, https://github.com/OtterDen-Lab/QuizGenerator/tree/main/documentation
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
QuizGenerator/__init__.py,sha256=8EV-k90A3PNC8Cm2-ZquwNyVyvnwW1gs6u-nGictyhs,840
|
|
2
2
|
QuizGenerator/__main__.py,sha256=Dd9w4R0Unm3RiXztvR4Y_g9-lkWp6FHg-4VN50JbKxU,151
|
|
3
3
|
QuizGenerator/constants.py,sha256=AO-UWwsWPLb1k2JW6KP8rl9fxTcdT0rW-6XC6zfnDOs,4386
|
|
4
|
-
QuizGenerator/contentast.py,sha256=
|
|
5
|
-
QuizGenerator/generate.py,sha256=
|
|
4
|
+
QuizGenerator/contentast.py,sha256=gKma-uRzGs7TgzvxrBoXFMNzZN_c33y_1bBkv1yhWrw,92887
|
|
5
|
+
QuizGenerator/generate.py,sha256=AWzNL0QTYDTcJFKaYfHIoRHvhx9MYRAbsD6z7E5_c9k,15733
|
|
6
6
|
QuizGenerator/misc.py,sha256=wtlrEpmEpoE6vNRmgjNUmuWnRdQKSCYfrqeoTagNaxg,464
|
|
7
7
|
QuizGenerator/mixins.py,sha256=HEhdGdeghqGWoajADTAIdUjkzwDSYl1b65LAkUdV50U,19211
|
|
8
8
|
QuizGenerator/performance.py,sha256=CM3zLarJXN5Hfrl4-6JRBqD03j4BU1B2QW699HAr1Ds,7002
|
|
@@ -12,7 +12,7 @@ QuizGenerator/quiz.py,sha256=f2HLrawUlu3ULkNDzcihBWAt-e-49AIPz_l1edMAEQ0,21503
|
|
|
12
12
|
QuizGenerator/regenerate.py,sha256=Uh4B9aKQvL3zD7PT-uH-GvrcSuUygV1BimvPVuErc-g,16525
|
|
13
13
|
QuizGenerator/typst_utils.py,sha256=XtMEO1e4_Tg0G1zR9D1fmrYKlUfHenBPdGoCKR0DhZg,3154
|
|
14
14
|
QuizGenerator/canvas/__init__.py,sha256=TwFP_zgxPIlWtkvIqQ6mcvBNTL9swIH_rJl7DGKcvkQ,286
|
|
15
|
-
QuizGenerator/canvas/canvas_interface.py,sha256=
|
|
15
|
+
QuizGenerator/canvas/canvas_interface.py,sha256=StMcdXgLvTA1EayQ44m_le2GXGQpDQnduYXVeUYsqW0,24618
|
|
16
16
|
QuizGenerator/canvas/classes.py,sha256=v_tQ8t_JJplU9sv2p4YctX45Fwed1nQ2HC1oC9BnDNw,7594
|
|
17
17
|
QuizGenerator/premade_questions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
18
|
QuizGenerator/premade_questions/basic.py,sha256=vMyCIYU0IJBjQVE-XVzHr9axq_kZL2ka4K1MaqeQwXM,3428
|
|
@@ -22,7 +22,7 @@ QuizGenerator/premade_questions/cst334/math_questions.py,sha256=zwkm3OLOkDZ_fbPF
|
|
|
22
22
|
QuizGenerator/premade_questions/cst334/memory_questions.py,sha256=PUyXIoyrGImXhucx6KBgoAEYmQCzTSCz0DWUu_xo6Kc,54371
|
|
23
23
|
QuizGenerator/premade_questions/cst334/ostep13_vsfs.py,sha256=d9jjrynEw44vupAH_wKl57UoHooCNEJXaC5DoNYualk,16163
|
|
24
24
|
QuizGenerator/premade_questions/cst334/persistence_questions.py,sha256=pb63H47WlSsHi_nHRVhbwUHeybF2zbWL8vXbwOguAL0,17474
|
|
25
|
-
QuizGenerator/premade_questions/cst334/process.py,sha256=
|
|
25
|
+
QuizGenerator/premade_questions/cst334/process.py,sha256=aZKbsa9Cvh20HooaRV7CXv5kFAGvTkiWmYpQn6J62Nk,39865
|
|
26
26
|
QuizGenerator/premade_questions/cst463/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
27
|
QuizGenerator/premade_questions/cst463/gradient_descent/__init__.py,sha256=sH2CUV6zK9FT3jWTn453ys6_JTrUKRtZnU8hK6RmImU,240
|
|
28
28
|
QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py,sha256=PdWAJjgsiwYQsxeLlQiVDd3m97RUjUY1KJTJxyrrdRI,13984
|
|
@@ -43,8 +43,8 @@ QuizGenerator/premade_questions/cst463/neural-network-basics/__init__.py,sha256=
|
|
|
43
43
|
QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py,sha256=luWlTfj1UM1yQDQzs_tNzTV67qXhRUBwNt8QrV74XHs,46115
|
|
44
44
|
QuizGenerator/premade_questions/cst463/tensorflow-intro/__init__.py,sha256=G1gEHtG4KakYgi8ZXSYYhX6bQRtnm2tZVGx36d63Nmo,173
|
|
45
45
|
QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py,sha256=8Wo38kTd_n0Oau2ERpvcudB9uJiOVDYYQNeWu9v4Tyo,33516
|
|
46
|
-
quizgenerator-0.6.
|
|
47
|
-
quizgenerator-0.6.
|
|
48
|
-
quizgenerator-0.6.
|
|
49
|
-
quizgenerator-0.6.
|
|
50
|
-
quizgenerator-0.6.
|
|
46
|
+
quizgenerator-0.6.2.dist-info/METADATA,sha256=1sSZoucyx1bE_4rfCMPWnJeyqz-lQvGlboE3arf1uio,7212
|
|
47
|
+
quizgenerator-0.6.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
48
|
+
quizgenerator-0.6.2.dist-info/entry_points.txt,sha256=aOIdRdw26xY8HkxOoKHBnUPe2mwGv5Ti3U1zojb6zxQ,98
|
|
49
|
+
quizgenerator-0.6.2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
50
|
+
quizgenerator-0.6.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|