QuizGenerator 0.4.0__tar.gz → 0.4.2__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.
Files changed (76) hide show
  1. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/PKG-INFO +1 -1
  2. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/contentast.py +2 -2
  3. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/neural-network-basics/neural_network_questions.py +23 -17
  4. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/question.py +60 -21
  5. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/pyproject.toml +1 -1
  6. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/pyproject_prev.toml +2 -1
  7. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/uv.lock +1 -1
  8. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/.envrc +0 -0
  9. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/.github/pull_request_template.md +0 -0
  10. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/.github/workflows/release.yaml +0 -0
  11. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/.gitignore +0 -0
  12. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/CLAUDE.md +0 -0
  13. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/CODEOWNERS +0 -0
  14. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/LICENSE +0 -0
  15. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/README.md +0 -0
  16. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/__init__.py +0 -0
  17. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/__main__.py +0 -0
  18. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/canvas/__init__.py +0 -0
  19. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/canvas/canvas_interface.py +0 -0
  20. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/canvas/classes.py +0 -0
  21. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/constants.py +0 -0
  22. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/generate.py +0 -0
  23. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/logging.yaml +0 -0
  24. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/misc.py +0 -0
  25. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/mixins.py +0 -0
  26. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/performance.py +0 -0
  27. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/__init__.py +0 -0
  28. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/basic.py +0 -0
  29. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst334/__init__.py +0 -0
  30. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst334/languages.py +0 -0
  31. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst334/math_questions.py +0 -0
  32. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst334/memory_questions.py +0 -0
  33. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst334/ostep13_vsfs.py +0 -0
  34. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst334/persistence_questions.py +0 -0
  35. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst334/process.py +0 -0
  36. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/__init__.py +0 -0
  37. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/gradient_descent/__init__.py +0 -0
  38. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/gradient_descent/gradient_calculation.py +0 -0
  39. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/gradient_descent/gradient_descent_questions.py +0 -0
  40. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/gradient_descent/loss_calculations.py +0 -0
  41. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/gradient_descent/misc.py +0 -0
  42. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/math_and_data/__init__.py +0 -0
  43. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/math_and_data/matrix_questions.py +0 -0
  44. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/math_and_data/vector_questions.py +0 -0
  45. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/models/__init__.py +0 -0
  46. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/models/attention.py +0 -0
  47. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/models/cnns.py +0 -0
  48. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/models/matrices.py +0 -0
  49. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/models/rnns.py +0 -0
  50. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/models/text.py +0 -0
  51. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/models/weight_counting.py +0 -0
  52. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/neural-network-basics/__init__.py +0 -0
  53. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/tensorflow-intro/__init__.py +0 -0
  54. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/premade_questions/cst463/tensorflow-intro/tensorflow_questions.py +0 -0
  55. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/qrcode_generator.py +0 -0
  56. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/quiz.py +0 -0
  57. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/regenerate.py +0 -0
  58. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/QuizGenerator/typst_utils.py +0 -0
  59. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/README.md +0 -0
  60. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/documentation/GRADING_GUIDE.md +0 -0
  61. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/documentation/LESSONS_LEARNED-adding_questions.md +0 -0
  62. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/documentation/PARAMETER_STANDARDS.md +0 -0
  63. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/documentation/README.md +0 -0
  64. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/documentation/WEB_UI_INTEGRATION.md +0 -0
  65. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/example_files/all_classes.yaml +0 -0
  66. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/example_files/cst334.yaml +0 -0
  67. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/example_files/cst463.yaml +0 -0
  68. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/example_files/exam_generation.yaml +0 -0
  69. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/example_files/scratch.yaml +0 -0
  70. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/example_files/specific_generators/cst334.caching.yaml +0 -0
  71. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/example_files/specific_generators/cst334.lab-address_translation.yaml +0 -0
  72. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/examples/README.md +0 -0
  73. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/examples/web_ui_integration_example.py +0 -0
  74. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/scripts/generate_practice_yaml.sh +0 -0
  75. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/scripts/print.sh +0 -0
  76. {quizgenerator-0.4.0 → quizgenerator-0.4.2}/scripts/vendor_lms_interface.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: QuizGenerator
3
- Version: 0.4.0
3
+ Version: 0.4.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
@@ -850,7 +850,7 @@ class ContentAST:
850
850
  if self.inline:
851
851
  return f"${self.latex}$"
852
852
  else:
853
- return r"$$ \displaystyle " + f"{self.latex}" + r" \; $$"
853
+ return r"$\displaystyle " + f"{self.latex}" + r"$"
854
854
 
855
855
  def render_html(self, **kwargs):
856
856
  if self.inline:
@@ -1048,7 +1048,7 @@ class ContentAST:
1048
1048
  if self.inline and self.bracket_type == "p":
1049
1049
  return f"$\\big(\\begin{{{matrix_env}}} {matrix_content} \\end{{{matrix_env}}}\\big)$"
1050
1050
  else:
1051
- return f"$$\\begin{{{matrix_env}}} {matrix_content} \\end{{{matrix_env}}}$$"
1051
+ return f"$\\begin{{{matrix_env}}} {matrix_content} \\end{{{matrix_env}}}$"
1052
1052
 
1053
1053
  def render_html(self, **kwargs):
1054
1054
  matrix_env = "smallmatrix" if self.inline else f"{self.bracket_type}matrix"
@@ -49,6 +49,7 @@ class SimpleNeuralNetworkBase(MatrixQuestion, abc.ABC):
49
49
  # Configuration
50
50
  self.activation_function = None
51
51
  self.use_bias = kwargs.get("use_bias", True)
52
+ self.param_digits = kwargs.get("param_digits", 1) # Precision for weights/biases
52
53
 
53
54
  # Network parameters (weights and biases)
54
55
  self.W1 = None # Input to hidden weights (num_hidden x num_inputs)
@@ -75,16 +76,19 @@ class SimpleNeuralNetworkBase(MatrixQuestion, abc.ABC):
75
76
  def _generate_network(self, weight_range=(-2, 2), input_range=(-3, 3)):
76
77
  """Generate random network parameters and input."""
77
78
  # Generate weights using MatrixQuestion's rounded matrix method
79
+ # Use param_digits to match display precision in tables and explanations
78
80
  self.W1 = self.get_rounded_matrix(
79
81
  (self.num_hidden, self.num_inputs),
80
82
  low=weight_range[0],
81
- high=weight_range[1]
83
+ high=weight_range[1],
84
+ digits_to_round=self.param_digits
82
85
  )
83
86
 
84
87
  self.W2 = self.get_rounded_matrix(
85
88
  (self.num_outputs, self.num_hidden),
86
89
  low=weight_range[0],
87
- high=weight_range[1]
90
+ high=weight_range[1],
91
+ digits_to_round=self.param_digits
88
92
  )
89
93
 
90
94
  # Generate biases
@@ -92,12 +96,14 @@ class SimpleNeuralNetworkBase(MatrixQuestion, abc.ABC):
92
96
  self.b1 = self.get_rounded_matrix(
93
97
  (self.num_hidden,),
94
98
  low=weight_range[0],
95
- high=weight_range[1]
99
+ high=weight_range[1],
100
+ digits_to_round=self.param_digits
96
101
  )
97
102
  self.b2 = self.get_rounded_matrix(
98
103
  (self.num_outputs,),
99
104
  low=weight_range[0],
100
- high=weight_range[1]
105
+ high=weight_range[1],
106
+ digits_to_round=self.param_digits
101
107
  )
102
108
  else:
103
109
  self.b1 = np.zeros(self.num_hidden)
@@ -256,7 +262,7 @@ class SimpleNeuralNetworkBase(MatrixQuestion, abc.ABC):
256
262
  for i in range(self.num_inputs):
257
263
  left_data.append([
258
264
  ContentAST.Equation(f"x_{i+1}", inline=True),
259
- f"{self.X[i]:.1f}"
265
+ f"{self.X[i]:.1f}" # Inputs are always integers or 1 decimal
260
266
  ])
261
267
 
262
268
  # Weights from input to hidden
@@ -264,14 +270,14 @@ class SimpleNeuralNetworkBase(MatrixQuestion, abc.ABC):
264
270
  for i in range(self.num_inputs):
265
271
  left_data.append([
266
272
  ContentAST.Equation(f"w_{{{j+1}{i+1}}}", inline=True),
267
- f"{self.W1[j, i]:.1f}"
273
+ f"{self.W1[j, i]:.{self.param_digits}f}"
268
274
  ])
269
275
 
270
276
  # Weights from hidden to output
271
277
  for i in range(self.num_hidden):
272
278
  left_data.append([
273
279
  ContentAST.Equation(f"w_{i+3}", inline=True),
274
- f"{self.W2[0, i]:.1f}"
280
+ f"{self.W2[0, i]:.{self.param_digits}f}"
275
281
  ])
276
282
 
277
283
  # Right table: Biases, Activations, Training context
@@ -283,14 +289,14 @@ class SimpleNeuralNetworkBase(MatrixQuestion, abc.ABC):
283
289
  for j in range(self.num_hidden):
284
290
  right_data.append([
285
291
  ContentAST.Equation(f"b_{j+1}", inline=True),
286
- f"{self.b1[j]:.1f}"
292
+ f"{self.b1[j]:.{self.param_digits}f}"
287
293
  ])
288
294
 
289
295
  # Output bias
290
296
  if self.use_bias:
291
297
  right_data.append([
292
298
  ContentAST.Equation(r"b_{out}", inline=True),
293
- f"{self.b2[0]:.1f}"
299
+ f"{self.b2[0]:.{self.param_digits}f}"
294
300
  ])
295
301
 
296
302
  # Hidden layer activations (if computed and requested)
@@ -628,11 +634,11 @@ class ForwardPassQuestion(SimpleNeuralNetworkBase):
628
634
  # Build equation for z_i
629
635
  terms = []
630
636
  for j in range(self.num_inputs):
631
- terms.append(f"({self.W1[i,j]:.1f})({self.X[j]:.1f})")
637
+ terms.append(f"({self.W1[i,j]:.{self.param_digits}f})({self.X[j]:.1f})")
632
638
 
633
639
  z_calc = " + ".join(terms)
634
640
  if self.use_bias:
635
- z_calc += f" + {self.b1[i]:.1f}"
641
+ z_calc += f" + {self.b1[i]:.{self.param_digits}f}"
636
642
 
637
643
  explanation.add_element(ContentAST.Equation(
638
644
  f"z_{i+1} = {z_calc} = {self.z1[i]:.4f}",
@@ -668,11 +674,11 @@ class ForwardPassQuestion(SimpleNeuralNetworkBase):
668
674
 
669
675
  terms = []
670
676
  for j in range(self.num_hidden):
671
- terms.append(f"({self.W2[0,j]:.1f})({self.a1[j]:.4f})")
677
+ terms.append(f"({self.W2[0,j]:.{self.param_digits}f})({self.a1[j]:.4f})")
672
678
 
673
679
  z_out_calc = " + ".join(terms)
674
680
  if self.use_bias:
675
- z_out_calc += f" + {self.b2[0]:.1f}"
681
+ z_out_calc += f" + {self.b2[0]:.{self.param_digits}f}"
676
682
 
677
683
  explanation.add_element(ContentAST.Equation(
678
684
  f"z_{{out}} = {z_out_calc} = {self.z2[0]:.4f}",
@@ -1197,7 +1203,7 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
1197
1203
  # Hidden layer
1198
1204
  z1_0 = self.W1[0, 0] * self.X[0] + self.W1[0, 1] * self.X[1] + self.b1[0]
1199
1205
  explanation.add_element(ContentAST.Equation(
1200
- f"z_1 = w_{{11}} x_1 + w_{{12}} x_2 + b_1 = {self.W1[0,0]:.1f} \\cdot {self.X[0]:.1f} + {self.W1[0,1]:.1f} \\cdot {self.X[1]:.1f} + {self.b1[0]:.1f} = {self.z1[0]:.4f}",
1206
+ f"z_1 = w_{{11}} x_1 + w_{{12}} x_2 + b_1 = {self.W1[0,0]:.{self.param_digits}f} \\cdot {self.X[0]:.1f} + {self.W1[0,1]:.{self.param_digits}f} \\cdot {self.X[1]:.1f} + {self.b1[0]:.{self.param_digits}f} = {self.z1[0]:.4f}",
1201
1207
  inline=False
1202
1208
  ))
1203
1209
 
@@ -1215,7 +1221,7 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
1215
1221
  # Output (pre-activation)
1216
1222
  z2 = self.W2[0, 0] * self.a1[0] + self.W2[0, 1] * self.a1[1] + self.b2[0]
1217
1223
  explanation.add_element(ContentAST.Equation(
1218
- f"z_{{out}} = w_3 h_1 + w_4 h_2 + b_2 = {self.W2[0,0]:.1f} \\cdot {self.a1[0]:.4f} + {self.W2[0,1]:.1f} \\cdot {self.a1[1]:.4f} + {self.b2[0]:.1f} = {self.z2[0]:.4f}",
1224
+ f"z_{{out}} = w_3 h_1 + w_4 h_2 + b_2 = {self.W2[0,0]:.{self.param_digits}f} \\cdot {self.a1[0]:.4f} + {self.W2[0,1]:.{self.param_digits}f} \\cdot {self.a1[1]:.4f} + {self.b2[0]:.{self.param_digits}f} = {self.z2[0]:.4f}",
1219
1225
  inline=False
1220
1226
  ))
1221
1227
 
@@ -1291,13 +1297,13 @@ class EndToEndTrainingQuestion(SimpleNeuralNetworkBase):
1291
1297
 
1292
1298
  new_w3 = self.new_W2[0, 0]
1293
1299
  explanation.add_element(ContentAST.Equation(
1294
- f"w_3^{{new}} = w_3 - \\alpha \\frac{{\\partial L}}{{\\partial w_3}} = {self.W2[0,0]:.1f} - {self.learning_rate} \\cdot {grad_w3:.4f} = {new_w3:.4f}",
1300
+ f"w_3^{{new}} = w_3 - \\alpha \\frac{{\\partial L}}{{\\partial w_3}} = {self.W2[0,0]:.{self.param_digits}f} - {self.learning_rate} \\cdot {grad_w3:.4f} = {new_w3:.4f}",
1295
1301
  inline=False
1296
1302
  ))
1297
1303
 
1298
1304
  new_w11 = self.new_W1[0, 0]
1299
1305
  explanation.add_element(ContentAST.Equation(
1300
- f"w_{{11}}^{{new}} = w_{{11}} - \\alpha \\frac{{\\partial L}}{{\\partial w_{{11}}}} = {self.W1[0,0]:.1f} - {self.learning_rate} \\cdot {grad_w11:.4f} = {new_w11:.4f}",
1306
+ f"w_{{11}}^{{new}} = w_{{11}} - \\alpha \\frac{{\\partial L}}{{\\partial w_{{11}}}} = {self.W1[0,0]:.{self.param_digits}f} - {self.learning_rate} \\cdot {grad_w11:.4f} = {new_w11:.4f}",
1301
1307
  inline=False
1302
1308
  ))
1303
1309
 
@@ -76,14 +76,21 @@ def parse_spacing(spacing_value) -> float:
76
76
 
77
77
  class QuestionRegistry:
78
78
  _registry = {}
79
+ _class_name_to_registered_name = {} # Reverse mapping: ClassName -> registered_name
79
80
  _scanned = False
80
-
81
+
81
82
  @classmethod
82
83
  def register(cls, question_type=None):
83
84
  def decorator(subclass):
84
85
  # Use the provided name or fall back to the class name
85
86
  name = question_type.lower() if question_type else subclass.__name__.lower()
86
87
  cls._registry[name] = subclass
88
+
89
+ # Build reverse mapping from class name to registered name
90
+ # This allows looking up by class name when QR codes store the class name
91
+ class_name = subclass.__name__.lower()
92
+ cls._class_name_to_registered_name[class_name] = name
93
+
87
94
  return subclass
88
95
  return decorator
89
96
 
@@ -97,29 +104,40 @@ class QuestionRegistry:
97
104
  # Check to see if it's in the registry
98
105
  question_key = question_type.lower()
99
106
  if question_key not in cls._registry:
100
- # Try stripping common course prefixes and module paths for backward compatibility
101
- for prefix in ['cst334.', 'cst463.']:
102
- if question_key.startswith(prefix):
103
- stripped_name = question_key[len(prefix):]
104
- if stripped_name in cls._registry:
105
- question_key = stripped_name
106
- break
107
- # Also try extracting just the final class name after dots
108
- if '.' in stripped_name:
109
- final_name = stripped_name.split('.')[-1]
107
+ # Try the reverse mapping from class name to registered name
108
+ # This handles cases where QR codes store class name (e.g., "RNNForwardPass")
109
+ # but the question is registered with a custom name (e.g., "cst463.rnn.forward-pass")
110
+ if question_key in cls._class_name_to_registered_name:
111
+ question_key = cls._class_name_to_registered_name[question_key]
112
+ log.debug(f"Resolved class name '{question_type}' to registered name '{question_key}'")
113
+ else:
114
+ # Try stripping common course prefixes and module paths for backward compatibility
115
+ for prefix in ['cst334.', 'cst463.']:
116
+ if question_key.startswith(prefix):
117
+ stripped_name = question_key[len(prefix):]
118
+ if stripped_name in cls._registry:
119
+ question_key = stripped_name
120
+ break
121
+ # Also try extracting just the final class name after dots
122
+ if '.' in stripped_name:
123
+ final_name = stripped_name.split('.')[-1]
124
+ if final_name in cls._registry:
125
+ question_key = final_name
126
+ break
127
+ else:
128
+ # As a final fallback, try just the last part after dots
129
+ if '.' in question_key:
130
+ final_name = question_key.split('.')[-1]
110
131
  if final_name in cls._registry:
111
132
  question_key = final_name
112
- break
113
- else:
114
- # As a final fallback, try just the last part after dots
115
- if '.' in question_key:
116
- final_name = question_key.split('.')[-1]
117
- if final_name in cls._registry:
118
- question_key = final_name
133
+ elif final_name in cls._class_name_to_registered_name:
134
+ # Try the class name reverse mapping on the final part
135
+ question_key = cls._class_name_to_registered_name[final_name]
136
+ log.debug(f"Resolved class name '{final_name}' to registered name '{question_key}'")
137
+ else:
138
+ raise ValueError(f"Unknown question type: {question_type}")
119
139
  else:
120
140
  raise ValueError(f"Unknown question type: {question_type}")
121
- else:
122
- raise ValueError(f"Unknown question type: {question_type}")
123
141
 
124
142
  new_question : Question = cls._registry[question_key](**kwargs)
125
143
  # Note: Don't call refresh() here - it will be called by get_question()
@@ -530,7 +548,8 @@ class Question(abc.ABC):
530
548
  )
531
549
 
532
550
  # Attach regeneration metadata to the question AST
533
- question_ast.question_class_name = self.__class__.__name__
551
+ # Use the registered name instead of class name for better QR code regeneration
552
+ question_ast.question_class_name = self._get_registered_name()
534
553
  question_ast.generation_seed = actual_seed
535
554
  question_ast.question_version = self.VERSION
536
555
  # Make a copy of config_params so each question AST has its own
@@ -642,6 +661,26 @@ class Question(abc.ABC):
642
661
  return True
643
662
  return False
644
663
 
664
+ def _get_registered_name(self) -> str:
665
+ """
666
+ Get the registered name for this question class.
667
+
668
+ Returns the name used when registering the question with @QuestionRegistry.register(),
669
+ which may be different from the class name (e.g., "cst463.rnn.forward-pass" vs "RNNForwardPass").
670
+
671
+ This is used for QR code generation to ensure regeneration works correctly.
672
+ Falls back to class name if not found in registry (shouldn't happen in practice).
673
+ """
674
+ class_name_lower = self.__class__.__name__.lower()
675
+ registered_name = QuestionRegistry._class_name_to_registered_name.get(class_name_lower)
676
+
677
+ if registered_name is None:
678
+ # Fallback to class name if not found (shouldn't happen but be defensive)
679
+ log.warning(f"Question {self.__class__.__name__} not found in registry reverse mapping, using class name")
680
+ return self.__class__.__name__
681
+
682
+ return registered_name
683
+
645
684
  class QuestionGroup():
646
685
 
647
686
  def __init__(self, questions_in_group: List[Question], pick_once : bool):
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "QuizGenerator"
7
- version = "0.4.0"
7
+ version = "0.4.2"
8
8
  description = "Generate randomized quiz questions for Canvas LMS and PDF exams"
9
9
  readme = "README.md"
10
10
  license = {text = "GPL-3.0-or-later"}
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "QuizGenerator"
7
- version = "0.3.1"
7
+ version = "0.4.1"
8
8
  description = "Generate randomized quiz questions for Canvas LMS and PDF exams"
9
9
  readme = "README.md"
10
10
  license = {text = "GPL-3.0-or-later"}
@@ -51,6 +51,7 @@ Repository = "https://github.com/OtterDen-Lab/QuizGenerator"
51
51
 
52
52
  [project.scripts]
53
53
  quizgen = "QuizGenerator.generate:main"
54
+ quizregen = "QuizGenerator.regenerate:main"
54
55
 
55
56
  [project.optional-dependencies]
56
57
  grading = [
@@ -1214,7 +1214,7 @@ wheels = [
1214
1214
 
1215
1215
  [[package]]
1216
1216
  name = "quizgenerator"
1217
- version = "0.4.0"
1217
+ version = "0.4.2"
1218
1218
  source = { editable = "." }
1219
1219
  dependencies = [
1220
1220
  { name = "canvasapi" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes