QuizGenerator 0.8.0__py3-none-any.whl → 0.8.1__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/contentast.py +42 -9
- QuizGenerator/premade_questions/cst334/process.py +1 -7
- QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +0 -1
- QuizGenerator/question.py +37 -0
- {quizgenerator-0.8.0.dist-info → quizgenerator-0.8.1.dist-info}/METADATA +1 -1
- {quizgenerator-0.8.0.dist-info → quizgenerator-0.8.1.dist-info}/RECORD +9 -9
- {quizgenerator-0.8.0.dist-info → quizgenerator-0.8.1.dist-info}/WHEEL +0 -0
- {quizgenerator-0.8.0.dist-info → quizgenerator-0.8.1.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.8.0.dist-info → quizgenerator-0.8.1.dist-info}/licenses/LICENSE +0 -0
QuizGenerator/contentast.py
CHANGED
|
@@ -2263,6 +2263,10 @@ class Answer(Leaf):
|
|
|
2263
2263
|
unit_part = f" {self.unit}" if self.unit else ""
|
|
2264
2264
|
|
|
2265
2265
|
return f"{label_part} {blank}{unit_part}".strip()
|
|
2266
|
+
|
|
2267
|
+
@classmethod
|
|
2268
|
+
def get_entry_warning(cls) -> List[str] | None:
|
|
2269
|
+
return None
|
|
2266
2270
|
|
|
2267
2271
|
# Factory methods for common answer types
|
|
2268
2272
|
@classmethod
|
|
@@ -2330,17 +2334,19 @@ class Answer(Leaf):
|
|
|
2330
2334
|
0.123444... → ["0.1234"]
|
|
2331
2335
|
"""
|
|
2332
2336
|
rounding_digits = Answer.DEFAULT_ROUNDING_DIGITS
|
|
2333
|
-
decimal.getcontext().prec = max(34, rounding_digits + 10)
|
|
2334
|
-
|
|
2335
2337
|
outs = set()
|
|
2336
2338
|
|
|
2337
2339
|
# Round to our standard precision first
|
|
2340
|
+
decimal.getcontext().prec = max(34, rounding_digits + 10)
|
|
2338
2341
|
q = decimal.Decimal(1).scaleb(-rounding_digits)
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2342
|
+
if isinstance(value, str) and '/' in value:
|
|
2343
|
+
f = Answer._to_fraction(value)
|
|
2344
|
+
decimal_value = decimal.Decimal(f.numerator) / decimal.Decimal(f.denominator)
|
|
2345
|
+
elif isinstance(value, fractions.Fraction):
|
|
2346
|
+
decimal_value = decimal.Decimal(value.numerator) / decimal.Decimal(value.denominator)
|
|
2347
|
+
else:
|
|
2348
|
+
decimal_value = decimal.Decimal(str(value))
|
|
2349
|
+
rounded_decimal = decimal_value.quantize(q, rounding=decimal.ROUND_HALF_UP)
|
|
2344
2350
|
|
|
2345
2351
|
# Fixed decimal form (e.g., "1.2500")
|
|
2346
2352
|
fixed_str = format(rounded_decimal, 'f')
|
|
@@ -2431,6 +2437,15 @@ class AnswerTypes:
|
|
|
2431
2437
|
|
|
2432
2438
|
# Concrete type answers
|
|
2433
2439
|
class Float(Answer):
|
|
2440
|
+
@classmethod
|
|
2441
|
+
def get_entry_warning(cls) -> List[str] | None:
|
|
2442
|
+
digits = Answer.DEFAULT_ROUNDING_DIGITS
|
|
2443
|
+
return [
|
|
2444
|
+
f"Round floats to {digits} decimal places (fewer if exact, e.g., `1.25`). "
|
|
2445
|
+
"No mixed numbers (e.g. use `5/4`, not `1 1/4`). "
|
|
2446
|
+
"Integers as integers (e.g., `2`, not `2/1`)."
|
|
2447
|
+
]
|
|
2448
|
+
|
|
2434
2449
|
def get_for_canvas(self, single_answer=False) -> List[dict]:
|
|
2435
2450
|
if single_answer:
|
|
2436
2451
|
canvas_answers = [
|
|
@@ -2460,8 +2475,8 @@ class AnswerTypes:
|
|
|
2460
2475
|
return canvas_answers
|
|
2461
2476
|
|
|
2462
2477
|
def get_display_string(self) -> str:
|
|
2463
|
-
|
|
2464
|
-
return f"{self.
|
|
2478
|
+
answer_strings = Answer.accepted_strings(self.value)
|
|
2479
|
+
return answer_strings[0] if len(answer_strings) > 0 else f"{self.value}"
|
|
2465
2480
|
|
|
2466
2481
|
class Int(Answer):
|
|
2467
2482
|
|
|
@@ -2486,6 +2501,10 @@ class AnswerTypes:
|
|
|
2486
2501
|
pass
|
|
2487
2502
|
|
|
2488
2503
|
class List(Answer):
|
|
2504
|
+
@classmethod
|
|
2505
|
+
def get_entry_warning(cls) -> List[str] | None:
|
|
2506
|
+
return ["Enter lists as comma-separated values with a space after the comma (e.g., `1, 2, 3`)."]
|
|
2507
|
+
|
|
2489
2508
|
def __init__(self, order_matters=True, *args, **kwargs):
|
|
2490
2509
|
super().__init__(*args, **kwargs)
|
|
2491
2510
|
self.order_matters = order_matters
|
|
@@ -2523,6 +2542,13 @@ class AnswerTypes:
|
|
|
2523
2542
|
"""
|
|
2524
2543
|
These are self-contained vectors that will go in a single answer block
|
|
2525
2544
|
"""
|
|
2545
|
+
|
|
2546
|
+
@classmethod
|
|
2547
|
+
def get_entry_warning(cls) -> List[str] | None:
|
|
2548
|
+
return [
|
|
2549
|
+
"Enter vectors as comma-separated values with a space after the comma, "
|
|
2550
|
+
"with optional parentheses (e.g., `1, 2` or `(1, 2)`)."
|
|
2551
|
+
]
|
|
2526
2552
|
|
|
2527
2553
|
# Canvas export methods (from misc.Answer)
|
|
2528
2554
|
def get_for_canvas(self, single_answer=False) -> List[dict]:
|
|
@@ -2565,6 +2591,13 @@ class AnswerTypes:
|
|
|
2565
2591
|
"""
|
|
2566
2592
|
Matrix answers generate multiple blank_ids (e.g., M_0_0, M_0_1, M_1_0, M_1_1).
|
|
2567
2593
|
"""
|
|
2594
|
+
|
|
2595
|
+
@classmethod
|
|
2596
|
+
def get_entry_warning(cls) -> List[str] | None:
|
|
2597
|
+
return [
|
|
2598
|
+
"For result matrices, enter `-` in any cell that does not exist.",
|
|
2599
|
+
*AnswerTypes.Float.get_entry_warning()
|
|
2600
|
+
]
|
|
2568
2601
|
|
|
2569
2602
|
def __init__(self, value, *args, **kwargs):
|
|
2570
2603
|
super().__init__(value=value, *args, **kwargs)
|
|
@@ -433,13 +433,7 @@ class SchedulingQuestion(ProcessQuestion, RegenerableChoiceMixin, TableQuestionM
|
|
|
433
433
|
f"Break any ties using the job number."
|
|
434
434
|
)
|
|
435
435
|
|
|
436
|
-
|
|
437
|
-
f"Please format answer as fractions, mixed numbers, or numbers rounded to a maximum of {ca.Answer.DEFAULT_ROUNDING_DIGITS} digits after the decimal. "
|
|
438
|
-
"Examples of appropriately formatted answers would be `0`, `3/2`, `1 1/3`, `1.6667`, and `1.25`. "
|
|
439
|
-
"Note that answers that can be rounded to whole numbers should be, rather than being left in fractional form."
|
|
440
|
-
])])
|
|
441
|
-
|
|
442
|
-
body = cls.create_fill_in_table_body(intro_text, instructions, scheduling_table)
|
|
436
|
+
body = cls.create_fill_in_table_body(intro_text, None, scheduling_table)
|
|
443
437
|
body.add_element(average_block)
|
|
444
438
|
return body
|
|
445
439
|
|
QuizGenerator/question.py
CHANGED
|
@@ -382,6 +382,7 @@ class RegenerableChoiceMixin:
|
|
|
382
382
|
del choice_info['_temp_fixed_value']
|
|
383
383
|
|
|
384
384
|
class Question(abc.ABC):
|
|
385
|
+
AUTO_ENTRY_WARNINGS = True
|
|
385
386
|
"""
|
|
386
387
|
Base class for all quiz questions with cross-format rendering support.
|
|
387
388
|
|
|
@@ -584,6 +585,10 @@ class Question(abc.ABC):
|
|
|
584
585
|
|
|
585
586
|
can_be_numerical = self._can_be_numerical_from_answers(answers)
|
|
586
587
|
|
|
588
|
+
if self.AUTO_ENTRY_WARNINGS:
|
|
589
|
+
warnings = self._entry_warnings_from_answers(answers)
|
|
590
|
+
components.body = self._append_entry_warnings(components.body, warnings)
|
|
591
|
+
|
|
587
592
|
config_params = dict(self.config_params)
|
|
588
593
|
if isinstance(ctx, dict) and ctx.get("_config_params"):
|
|
589
594
|
config_params.update(ctx.get("_config_params"))
|
|
@@ -723,6 +728,38 @@ class Question(abc.ABC):
|
|
|
723
728
|
merged.append(ans)
|
|
724
729
|
return merged
|
|
725
730
|
|
|
731
|
+
@classmethod
|
|
732
|
+
def _entry_warnings_from_answers(cls, answers: List[ca.Answer]) -> List[str]:
|
|
733
|
+
warnings: List[str] = []
|
|
734
|
+
seen: set[str] = set()
|
|
735
|
+
for answer in answers:
|
|
736
|
+
warning = None
|
|
737
|
+
if hasattr(answer.__class__, "get_entry_warning"):
|
|
738
|
+
warning = answer.__class__.get_entry_warning()
|
|
739
|
+
if not warning:
|
|
740
|
+
continue
|
|
741
|
+
if isinstance(warning, str):
|
|
742
|
+
warning_list = [warning]
|
|
743
|
+
else:
|
|
744
|
+
warning_list = list(warning)
|
|
745
|
+
for item in warning_list:
|
|
746
|
+
if item and item not in seen:
|
|
747
|
+
warnings.append(item)
|
|
748
|
+
seen.add(item)
|
|
749
|
+
return warnings
|
|
750
|
+
|
|
751
|
+
@classmethod
|
|
752
|
+
def _append_entry_warnings(cls, body: ca.Element, warnings: List[str]) -> ca.Element:
|
|
753
|
+
if not warnings:
|
|
754
|
+
return body
|
|
755
|
+
notes_lines = ["**Notes for answer entry**", ""]
|
|
756
|
+
notes_lines.extend(f"- {warning}" for warning in warnings)
|
|
757
|
+
warning_elements = ca.OnlyHtml([ca.Text("\n".join(notes_lines))])
|
|
758
|
+
if isinstance(body, ca.Container):
|
|
759
|
+
body.add_element(warning_elements)
|
|
760
|
+
return body
|
|
761
|
+
return ca.Section([body, warning_elements])
|
|
762
|
+
|
|
726
763
|
@classmethod
|
|
727
764
|
def _can_be_numerical_from_answers(cls, answers: List[ca.Answer]) -> bool:
|
|
728
765
|
return (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: QuizGenerator
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.1
|
|
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,13 +1,13 @@
|
|
|
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=
|
|
4
|
+
QuizGenerator/contentast.py,sha256=uTql3nvNg8DZnPxOg7S31vXbifSNfb3rFK5c_ihLsbg,87615
|
|
5
5
|
QuizGenerator/generate.py,sha256=dqF-WWmWxyJmPHl0gTYr3gNNxyF877fvXYaMvYA3uA8,15790
|
|
6
6
|
QuizGenerator/misc.py,sha256=MXrguUhhdrWSV4Hqdl4G21ktowODu1AcKy6-5mvy3aI,454
|
|
7
7
|
QuizGenerator/mixins.py,sha256=B9Ee52wUCeclmBTgonasHNo0WHvVOcnILsz0iecrf78,15705
|
|
8
8
|
QuizGenerator/performance.py,sha256=CM3zLarJXN5Hfrl4-6JRBqD03j4BU1B2QW699HAr1Ds,7002
|
|
9
9
|
QuizGenerator/qrcode_generator.py,sha256=S3mzZDk2UiHiw6ipSCpWPMhbKvSRR1P5ordZJUTo6ug,10776
|
|
10
|
-
QuizGenerator/question.py,sha256=
|
|
10
|
+
QuizGenerator/question.py,sha256=QsLKFEM8LzLkH1_5MOwMFRuqtTkEd7-a_eoaTKcttpU,33602
|
|
11
11
|
QuizGenerator/quiz.py,sha256=CEWy7FB7BZiK33s_wYs6MqGKDetc6htUaqvP3--2HzI,21621
|
|
12
12
|
QuizGenerator/regenerate.py,sha256=ZAs1mtERmO8JXza2tBqJpd-uJs9V7gS1jJ9A9gSb8jo,19764
|
|
13
13
|
QuizGenerator/typst_utils.py,sha256=JGQn_u5bEHd8HAtjAHuZoVJwLkx-Rd4ZCBWffwFZa3o,3136
|
|
@@ -22,7 +22,7 @@ QuizGenerator/premade_questions/cst334/math_questions.py,sha256=aUYbQxneL5MXE7Xo
|
|
|
22
22
|
QuizGenerator/premade_questions/cst334/memory_questions.py,sha256=g0EFJ2HUogYnOYMWYyn-z4lEv15Pfv5IdSvj0xGoKbI,51050
|
|
23
23
|
QuizGenerator/premade_questions/cst334/ostep13_vsfs.py,sha256=d9jjrynEw44vupAH_wKl57UoHooCNEJXaC5DoNYualk,16163
|
|
24
24
|
QuizGenerator/premade_questions/cst334/persistence_questions.py,sha256=9mgsX-3oWDQgm_n2YwmFSil0QPyzsruHzuqB-hfGMuA,16220
|
|
25
|
-
QuizGenerator/premade_questions/cst334/process.py,sha256=
|
|
25
|
+
QuizGenerator/premade_questions/cst334/process.py,sha256=0SqXkvdxaEJZXJA8fBqheh7F0PL8I6xgO5a8u2s2po4,37238
|
|
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=laBeC0tMc2EaLzCotGHQNzePPPOKS1EGgQirigNyi9M,13479
|
|
@@ -30,7 +30,7 @@ QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questio
|
|
|
30
30
|
QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py,sha256=8GtJX1DNNox-AgMvABFkRgmHB-lvrxMZKzv-3Ils_Jg,22380
|
|
31
31
|
QuizGenerator/premade_questions/cst463/gradient_descent/misc.py,sha256=0R-nFeD3zsqJyde5CXWrF6Npjmpx6_HbzfCbThLi3os,2657
|
|
32
32
|
QuizGenerator/premade_questions/cst463/math_and_data/__init__.py,sha256=EbIaUrx7_aK9j3Gd8Mk08h9GocTq_0OoNu2trfNwaU8,202
|
|
33
|
-
QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py,sha256=
|
|
33
|
+
QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py,sha256=4DLdo_8XDS_xtPA8R-wH4K0cKnMn4r5727Vszz8keTc,15565
|
|
34
34
|
QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py,sha256=VXQCLQEeNKxRDPn_fGW5nAPX-0betrZ8mURh0ElbNz0,12668
|
|
35
35
|
QuizGenerator/premade_questions/cst463/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
36
|
QuizGenerator/premade_questions/cst463/models/attention.py,sha256=iECxOoR0LEJAH_d7ZE3MoLOkdYVbGOKo4Dwf8Pww0tM,5443
|
|
@@ -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=bit_HfAG4K6yh9SZZw_HAPhFUVFkOBdZ2odwt-Cdvmo,42868
|
|
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=jZRbEqb65BAtliv_V9VR4kvpwOt-o10ApN7RmOIg3XI,30464
|
|
46
|
-
quizgenerator-0.8.
|
|
47
|
-
quizgenerator-0.8.
|
|
48
|
-
quizgenerator-0.8.
|
|
49
|
-
quizgenerator-0.8.
|
|
50
|
-
quizgenerator-0.8.
|
|
46
|
+
quizgenerator-0.8.1.dist-info/METADATA,sha256=Ef_TPUm2UKYIXnBMiaip6SaAyRjMD4G1MNdVejagfRw,8113
|
|
47
|
+
quizgenerator-0.8.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
48
|
+
quizgenerator-0.8.1.dist-info/entry_points.txt,sha256=aOIdRdw26xY8HkxOoKHBnUPe2mwGv5Ti3U1zojb6zxQ,98
|
|
49
|
+
quizgenerator-0.8.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
50
|
+
quizgenerator-0.8.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|