QuizGenerator 0.1.0__py3-none-any.whl → 0.1.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/generate.py +20 -117
- {quizgenerator-0.1.0.dist-info → quizgenerator-0.1.1.dist-info}/METADATA +1 -1
- {quizgenerator-0.1.0.dist-info → quizgenerator-0.1.1.dist-info}/RECORD +6 -6
- {quizgenerator-0.1.0.dist-info → quizgenerator-0.1.1.dist-info}/WHEEL +0 -0
- {quizgenerator-0.1.0.dist-info → quizgenerator-0.1.1.dist-info}/entry_points.txt +0 -0
- {quizgenerator-0.1.0.dist-info → quizgenerator-0.1.1.dist-info}/licenses/LICENSE +0 -0
QuizGenerator/generate.py
CHANGED
|
@@ -7,13 +7,10 @@ import subprocess
|
|
|
7
7
|
import tempfile
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from dotenv import load_dotenv
|
|
10
|
-
from QuizGenerator.canvas.canvas_interface import CanvasInterface
|
|
10
|
+
from QuizGenerator.canvas.canvas_interface import CanvasInterface
|
|
11
11
|
|
|
12
12
|
from QuizGenerator.quiz import Quiz
|
|
13
13
|
|
|
14
|
-
# Load environment variables from ~/.env
|
|
15
|
-
load_dotenv(Path.home() / '.env')
|
|
16
|
-
|
|
17
14
|
import logging
|
|
18
15
|
log = logging.getLogger(__name__)
|
|
19
16
|
|
|
@@ -23,20 +20,27 @@ from QuizGenerator.performance import PerformanceTracker
|
|
|
23
20
|
def parse_args():
|
|
24
21
|
parser = argparse.ArgumentParser()
|
|
25
22
|
|
|
26
|
-
parser.add_argument(
|
|
27
|
-
|
|
23
|
+
parser.add_argument(
|
|
24
|
+
"--env",
|
|
25
|
+
default=os.path.join(Path.home(), '.env'),
|
|
26
|
+
help="Path to .env file specifying canvas details"
|
|
27
|
+
)
|
|
28
28
|
|
|
29
29
|
parser.add_argument("--quiz_yaml", default=os.path.join(os.path.dirname(os.path.abspath(__file__)), "example_files/exam_generation.yaml"))
|
|
30
|
-
parser.add_argument("--num_canvas", default=0, type=int)
|
|
31
|
-
parser.add_argument("--num_pdfs", default=0, type=int)
|
|
32
30
|
parser.add_argument("--seed", type=int, default=None,
|
|
33
31
|
help="Random seed for quiz generation (default: None for random)")
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
parser.add_argument("--
|
|
37
|
-
|
|
32
|
+
|
|
33
|
+
# Canvas flags
|
|
34
|
+
parser.add_argument("--num_canvas", default=0, type=int, help="How many variations of each question to try to upload to canvas.")
|
|
35
|
+
parser.add_argument("--prod", action="store_true")
|
|
36
|
+
parser.add_argument("--course_id", type=int)
|
|
38
37
|
parser.add_argument("--delete-assignment-group", action="store_true",
|
|
39
38
|
help="Delete existing assignment group before uploading new quizzes")
|
|
39
|
+
|
|
40
|
+
# PDF Flags
|
|
41
|
+
parser.add_argument("--num_pdfs", default=0, type=int, help="How many PDF quizzes to create")
|
|
42
|
+
parser.add_argument("--typst", action="store_true",
|
|
43
|
+
help="Use Typst instead of LaTeX for PDF generation")
|
|
40
44
|
|
|
41
45
|
subparsers = parser.add_subparsers(dest='command')
|
|
42
46
|
test_parser = subparsers.add_parser("TEST")
|
|
@@ -54,111 +58,6 @@ def parse_args():
|
|
|
54
58
|
def test():
|
|
55
59
|
log.info("Running test...")
|
|
56
60
|
|
|
57
|
-
# Load the CST463 quiz configuration to test vector questions
|
|
58
|
-
quiz_yaml = os.path.join(os.path.dirname(os.path.abspath(__file__)), "example_files/cst463.yaml")
|
|
59
|
-
|
|
60
|
-
# Generate a quiz
|
|
61
|
-
quizzes = Quiz.from_yaml(quiz_yaml)
|
|
62
|
-
|
|
63
|
-
# Find a vector question to test
|
|
64
|
-
from QuizGenerator.question import QuestionRegistry
|
|
65
|
-
|
|
66
|
-
print("="*60)
|
|
67
|
-
print("CANVAS ANSWER DUPLICATION TEST")
|
|
68
|
-
print("="*60)
|
|
69
|
-
|
|
70
|
-
# Test multiple question types to find which ones use VECTOR variable kind
|
|
71
|
-
question_types = ["VectorAddition", "VectorDotProduct", "VectorMagnitude", "DerivativeBasic", "DerivativeChain"]
|
|
72
|
-
|
|
73
|
-
for question_type in question_types:
|
|
74
|
-
print(f"\n" + "="*20 + f" TESTING {question_type} " + "="*20)
|
|
75
|
-
question = QuestionRegistry.create(question_type, name=f"Test {question_type}", points_value=1)
|
|
76
|
-
|
|
77
|
-
print(f"Question answers: {len(question.answers)} answer objects")
|
|
78
|
-
|
|
79
|
-
# Show all individual answers and their Canvas representations
|
|
80
|
-
for key, answer in question.answers.items():
|
|
81
|
-
canvas_answers = answer.get_for_canvas()
|
|
82
|
-
print(f"\nAnswer key '{key}':")
|
|
83
|
-
print(f" Variable kind: {answer.variable_kind}")
|
|
84
|
-
print(f" Value: {answer.value}")
|
|
85
|
-
print(f" Canvas entries: {len(canvas_answers)}")
|
|
86
|
-
for i, canvas_answer in enumerate(canvas_answers):
|
|
87
|
-
print(f" {i+1}: '{canvas_answer['answer_text']}'")
|
|
88
|
-
|
|
89
|
-
# Show duplicates if they exist
|
|
90
|
-
texts = [ca['answer_text'] for ca in canvas_answers]
|
|
91
|
-
unique_texts = set(texts)
|
|
92
|
-
duplicates = len(texts) - len(unique_texts)
|
|
93
|
-
if duplicates > 0:
|
|
94
|
-
print(f" *** {duplicates} DUPLICATE ENTRIES FOUND ***")
|
|
95
|
-
for text in sorted(unique_texts):
|
|
96
|
-
count = texts.count(text)
|
|
97
|
-
if count > 1:
|
|
98
|
-
print(f" '{text}' appears {count} times")
|
|
99
|
-
|
|
100
|
-
# Check for phantom blank_ids that aren't displayed
|
|
101
|
-
print(f"\n" + "="*20 + " PHANTOM BLANK_ID CHECK " + "="*20)
|
|
102
|
-
|
|
103
|
-
for question_type in ["DerivativeBasic", "DerivativeChain"]:
|
|
104
|
-
print(f"\n--- {question_type} ---")
|
|
105
|
-
question = QuestionRegistry.create(question_type, name=f"Test {question_type}", points_value=1)
|
|
106
|
-
|
|
107
|
-
# Get the question body to see what blank_ids are actually displayed
|
|
108
|
-
body = question.get_body()
|
|
109
|
-
body_html = body.render("html")
|
|
110
|
-
|
|
111
|
-
print(f" HTML body preview:")
|
|
112
|
-
print(f" {body_html[:200]}...")
|
|
113
|
-
|
|
114
|
-
# Extract blank_ids from the HTML using regex
|
|
115
|
-
import re
|
|
116
|
-
displayed_blank_ids = set(re.findall(r'name="([^"]*)"', body_html))
|
|
117
|
-
|
|
118
|
-
# Get all blank_ids from the answers
|
|
119
|
-
question_type_enum, canvas_answers = question.get_answers()
|
|
120
|
-
all_blank_ids = set(answer['blank_id'] for answer in canvas_answers)
|
|
121
|
-
|
|
122
|
-
print(f" Total answers in self.answers: {len(question.answers)}")
|
|
123
|
-
print(f" Total Canvas answer entries: {len(canvas_answers)}")
|
|
124
|
-
print(f" Unique blank_ids in Canvas answers: {len(all_blank_ids)}")
|
|
125
|
-
print(f" Blank_ids displayed in HTML: {len(displayed_blank_ids)}")
|
|
126
|
-
|
|
127
|
-
# Find phantom blank_ids
|
|
128
|
-
phantom_blank_ids = all_blank_ids - displayed_blank_ids
|
|
129
|
-
if phantom_blank_ids:
|
|
130
|
-
print(f" *** PHANTOM BLANK_IDS FOUND: {phantom_blank_ids} ***")
|
|
131
|
-
for phantom_id in phantom_blank_ids:
|
|
132
|
-
phantom_answers = [a for a in canvas_answers if a['blank_id'] == phantom_id]
|
|
133
|
-
print(f" '{phantom_id}': {len(phantom_answers)} entries not displayed")
|
|
134
|
-
else:
|
|
135
|
-
print(f" No phantom blank_ids found")
|
|
136
|
-
|
|
137
|
-
# Show what blank_ids are actually displayed
|
|
138
|
-
print(f" Displayed blank_ids: {sorted(displayed_blank_ids)}")
|
|
139
|
-
print(f" All answer blank_ids: {sorted(all_blank_ids)}")
|
|
140
|
-
|
|
141
|
-
# Now create a synthetic test to demonstrate the VECTOR bug
|
|
142
|
-
print(f"\n" + "="*20 + " SYNTHETIC VECTOR TEST " + "="*20)
|
|
143
|
-
from QuizGenerator.misc import Answer
|
|
144
|
-
|
|
145
|
-
# Create a synthetic answer with VECTOR variable kind to demonstrate the bug
|
|
146
|
-
vector_answer = Answer(
|
|
147
|
-
key="test_vector",
|
|
148
|
-
value=[1, 2, 3], # 3D vector
|
|
149
|
-
variable_kind=Answer.VariableKind.VECTOR
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
canvas_answers = vector_answer.get_for_canvas()
|
|
153
|
-
print(f"Synthetic VECTOR answer:")
|
|
154
|
-
print(f" Value: {vector_answer.value}")
|
|
155
|
-
print(f" Canvas entries: {len(canvas_answers)}")
|
|
156
|
-
# Only show first few to save space
|
|
157
|
-
for i, canvas_answer in enumerate(canvas_answers[:5]):
|
|
158
|
-
print(f" {i+1}: '{canvas_answer['answer_text']}'")
|
|
159
|
-
if len(canvas_answers) > 5:
|
|
160
|
-
print(f" ... and {len(canvas_answers) - 5} more entries")
|
|
161
|
-
|
|
162
61
|
print("\n" + "="*60)
|
|
163
62
|
print("TEST COMPLETE")
|
|
164
63
|
print("="*60)
|
|
@@ -245,6 +144,7 @@ def generate_typst(typst_text, remove_previous=False):
|
|
|
245
144
|
if os.path.exists(tmp_typ.name):
|
|
246
145
|
os.unlink(tmp_typ.name)
|
|
247
146
|
|
|
147
|
+
|
|
248
148
|
def generate_quiz(
|
|
249
149
|
path_to_quiz_yaml,
|
|
250
150
|
num_pdfs=0,
|
|
@@ -337,6 +237,9 @@ def generate_quiz(
|
|
|
337
237
|
def main():
|
|
338
238
|
|
|
339
239
|
args = parse_args()
|
|
240
|
+
|
|
241
|
+
# Load environment variables
|
|
242
|
+
load_dotenv(args.env)
|
|
340
243
|
|
|
341
244
|
if args.command == "TEST":
|
|
342
245
|
test()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: QuizGenerator
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.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
|
|
@@ -3,7 +3,7 @@ QuizGenerator/__init__.py,sha256=8EV-k90A3PNC8Cm2-ZquwNyVyvnwW1gs6u-nGictyhs,840
|
|
|
3
3
|
QuizGenerator/__main__.py,sha256=Dd9w4R0Unm3RiXztvR4Y_g9-lkWp6FHg-4VN50JbKxU,151
|
|
4
4
|
QuizGenerator/constants.py,sha256=AO-UWwsWPLb1k2JW6KP8rl9fxTcdT0rW-6XC6zfnDOs,4386
|
|
5
5
|
QuizGenerator/contentast.py,sha256=bHC1D_OBrDCnTuXcTiI37Muk20mBxD1gjDArUBrGv9c,63657
|
|
6
|
-
QuizGenerator/generate.py,sha256=
|
|
6
|
+
QuizGenerator/generate.py,sha256=69VFl3f3zw1AZSVEVM6T0mGp-PRh-eUEnJo_Zbm9VHk,7732
|
|
7
7
|
QuizGenerator/logging.yaml,sha256=VJCdh26D8e_PNUs4McvvP1ojz9EVjQNifJzfhEk1Mbo,1114
|
|
8
8
|
QuizGenerator/misc.py,sha256=uIc1revyGZK8LL-1sJfhsoJ_MlYn75LPap6FRAiJWRQ,15635
|
|
9
9
|
QuizGenerator/mixins.py,sha256=RjV76C1tkTLSvhSoMys67W7UmR6y6wAAcBM2Msxdpd0,18186
|
|
@@ -37,8 +37,8 @@ QuizGenerator/premade_questions/cst463/neural-network-basics/__init__.py,sha256=
|
|
|
37
37
|
QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py,sha256=7ZtCQ2fXhIPSd99TstQfOKCN13GJE_56UfBQKdmzmMI,42398
|
|
38
38
|
QuizGenerator/premade_questions/cst463/tensorflow-intro/__init__.py,sha256=G1gEHtG4KakYgi8ZXSYYhX6bQRtnm2tZVGx36d63Nmo,173
|
|
39
39
|
QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py,sha256=dPn8Sj0yk4m02np62esMKZ7CvcljhYq3Tq51nY9aJnA,29781
|
|
40
|
-
quizgenerator-0.1.
|
|
41
|
-
quizgenerator-0.1.
|
|
42
|
-
quizgenerator-0.1.
|
|
43
|
-
quizgenerator-0.1.
|
|
44
|
-
quizgenerator-0.1.
|
|
40
|
+
quizgenerator-0.1.1.dist-info/METADATA,sha256=2dg0Oauxavs-bVXgGmZsLPEfC-iHuWATkKCHMDNES2E,7149
|
|
41
|
+
quizgenerator-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
42
|
+
quizgenerator-0.1.1.dist-info/entry_points.txt,sha256=iViWMzswXGe88WKoue_Ib-ODUSiT_j_6f1us28w9pkc,56
|
|
43
|
+
quizgenerator-0.1.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
44
|
+
quizgenerator-0.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|