excel2moodle 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.
excel2moodle/__main__.py CHANGED
@@ -2,9 +2,10 @@
2
2
 
3
3
  import logging.config as logConfig
4
4
  import signal
5
+ import sys
5
6
  from pathlib import Path
6
7
 
7
- from PySide6 import QtWidgets, sys
8
+ from PySide6.QtWidgets import QApplication
8
9
 
9
10
  import excel2moodle
10
11
  from excel2moodle import e2mMetadata, mainLogger
@@ -23,7 +24,7 @@ def main() -> None:
23
24
  logfile.replace(f"{logfile}.old")
24
25
  logConfig.dictConfig(config=loggerConfig)
25
26
  signal.signal(signal.SIGINT, signal.SIG_DFL)
26
- app = QtWidgets.QApplication(sys.argv)
27
+ app = QApplication(sys.argv)
27
28
  settings = Settings()
28
29
  database: dataStructure.QuestionDB = dataStructure.QuestionDB(settings)
29
30
  window = ui.MainWindow(settings, database)
@@ -5,10 +5,9 @@ from typing import TYPE_CHECKING
5
5
  import lxml.etree as ET
6
6
  import pandas as pd
7
7
 
8
- from excel2moodle.core.settings import Tags
9
-
10
8
  if TYPE_CHECKING:
11
9
  from excel2moodle.core.question import Question
10
+ from excel2moodle.core.settings import Tags
12
11
 
13
12
  loggerObj = logging.getLogger(__name__)
14
13
 
@@ -30,7 +29,7 @@ class Category:
30
29
  self.desc = str(description)
31
30
  self.dataframe: pd.DataFrame = dataframe
32
31
  self.settings: dict[str, float | str] = settings if settings else {}
33
- self.questions: dict[int, Question] = {}
32
+ self._questions: dict[int, Question]
34
33
  self.maxVariants: int | None = None
35
34
  loggerObj.info("initializing Category %s", self.NAME)
36
35
 
@@ -50,6 +49,18 @@ class Category:
50
49
  def id(self) -> str:
51
50
  return f"{self.n:02d}"
52
51
 
52
+ @property
53
+ def questions(self) -> dict:
54
+ if not hasattr(self, "_questions"):
55
+ msg = "Category question are not yet initialized"
56
+ raise ValueError(msg)
57
+ return self._questions
58
+
59
+ def appendQuestion(self, questionNumber: int, question) -> None:
60
+ if not hasattr(self, "_questions"):
61
+ self._questions: dict[int, Question] = {}
62
+ self._questions[questionNumber] = question
63
+
53
64
  def __hash__(self) -> int:
54
65
  return hash(self.NAME)
55
66
 
@@ -58,13 +69,20 @@ class Category:
58
69
  return self.NAME == other.NAME
59
70
  return False
60
71
 
61
- def getCategoryHeader(self) -> ET.Element:
72
+ def getCategoryHeader(self, subCategory: str | None = None) -> ET.Element:
62
73
  """Insert an <question type='category'> before all Questions of this Category."""
63
74
  header = ET.Element("question", type="category")
64
75
  cat = ET.SubElement(header, "category")
65
76
  info = ET.SubElement(header, "info", format="html")
66
- ET.SubElement(cat, "text").text = f"$module$/top/{self.NAME}"
77
+ catStr = (
78
+ f"$module$/top/{self.NAME}"
79
+ if subCategory is None
80
+ else f"$module$/top/{self.NAME}/Question-{subCategory[2:]}_Variants"
81
+ )
82
+ ET.SubElement(cat, "text").text = catStr
67
83
  ET.SubElement(info, "text").text = str(self.desc)
68
- ET.SubElement(header, "idnumber").text = self.id
84
+ ET.SubElement(header, "idnumber").text = (
85
+ f"cat-{self.id}" if subCategory is None else f"variants-{subCategory}"
86
+ )
69
87
  ET.indent(header)
70
88
  return header
@@ -3,6 +3,7 @@
3
3
  At the heart is the class ``xmlTest``
4
4
  """
5
5
 
6
+ import datetime as dt
6
7
  import logging
7
8
  import sys
8
9
  from concurrent.futures import ProcessPoolExecutor, as_completed
@@ -11,14 +12,16 @@ from typing import TYPE_CHECKING
11
12
 
12
13
  import lxml.etree as ET # noqa: N812
13
14
  import pandas as pd
15
+ import yaml
14
16
  from PySide6.QtCore import QObject, Signal
15
17
  from PySide6.QtWidgets import QDialog
16
18
 
19
+ from excel2moodle import __version__
17
20
  from excel2moodle.core import stringHelpers
18
21
  from excel2moodle.core.category import Category
19
22
  from excel2moodle.core.exceptions import InvalidFieldException, QNotParsedException
20
23
  from excel2moodle.core.globals import Tags
21
- from excel2moodle.core.question import Question
24
+ from excel2moodle.core.question import ParametricQuestion, Question
22
25
  from excel2moodle.core.settings import Settings
23
26
  from excel2moodle.core.validator import Validator
24
27
  from excel2moodle.logger import LogAdapterQuestionID
@@ -72,9 +75,10 @@ class QuestionDB:
72
75
  def __init__(self, settings: Settings) -> None:
73
76
  self.settings = settings
74
77
  self.window: QMainWindow | None = None
75
- self.version = None
76
78
  self.categoriesMetaData: pd.DataFrame
77
79
  self.categories: dict[str, Category]
80
+ self._exportedQuestions: list[Question] = []
81
+ self._exportedAll: bool = False
78
82
 
79
83
  @property
80
84
  def spreadsheet(self) -> Path:
@@ -94,22 +98,39 @@ class QuestionDB:
94
98
  ``categoriesMetaData`` dataframe
95
99
  Setup the categories and store them in ``self.categories = {}``
96
100
  Pass the question data to the categories.
101
+
102
+ Raises
103
+ ------
104
+ ValueError
105
+ When there is no 'seetings' worksheet in the file.
106
+ InvalidFieldException
107
+ When the settings are invalid
108
+
109
+ Before raising it logges the exceptions with a meaningful message.
110
+
97
111
  """
98
112
  sheetPath = sheetPath if sheetPath else self.spreadsheet
99
113
  logger.info("Start Parsing the Excel Metadata Sheet\n")
100
- with Path(sheetPath).open("rb") as f:
101
- settingDf = pd.read_excel(
102
- f,
103
- sheet_name="settings",
104
- index_col=0,
105
- engine="calamine",
114
+ try:
115
+ with Path(sheetPath).open("rb") as f:
116
+ settingDf = pd.read_excel(
117
+ f,
118
+ sheet_name="settings",
119
+ index_col=0,
120
+ engine="calamine",
121
+ )
122
+ except ValueError:
123
+ logger.exception(
124
+ "Did you forget to specify a 'settings' sheet in the file?"
106
125
  )
107
- logger.debug("Found the settings: \n\t%s", settingDf)
108
- settingDf = self.harmonizeDFIndex(settingDf)
109
- for tag, value in settingDf.iterrows():
110
- val = value.iloc[0]
111
- if pd.notna(val):
112
- self.settings.set(tag, val)
126
+ raise
127
+ logger.debug("Found the settings: \n\t%s", settingDf)
128
+ settingDf = self.harmonizeDFIndex(settingDf)
129
+ for tag, value in settingDf.iterrows():
130
+ val = value.iloc[0]
131
+ if pd.notna(val):
132
+ self.settings.set(tag, val)
133
+
113
134
  try:
114
135
  self._validateProjectSettings(sheetPath=sheetPath)
115
136
  except InvalidFieldException:
@@ -342,13 +363,18 @@ class QuestionDB:
342
363
  else:
343
364
  msg = "couldn't setup Parser"
344
365
  raise QNotParsedException(msg, question.id)
345
- category.questions[qNumber] = question
366
+ category.appendQuestion(qNumber, question)
346
367
  return question
347
368
 
348
369
  def appendQuestions(
349
- self, questions: list[QuestionItem], file: Path | None = None
370
+ self,
371
+ questions: list[QuestionItem],
372
+ file: Path | None = None,
373
+ pCount: int = 0,
374
+ qCount: int = 0,
350
375
  ) -> None:
351
376
  """Append selected question Elements to the tree."""
377
+ self._exportedQuestions.clear()
352
378
  tree = ET.Element("quiz")
353
379
  catdict: dict[Category, list[Question]] = {}
354
380
  for q in questions:
@@ -365,6 +391,8 @@ class QuestionDB:
365
391
  includeHeader=self.settings.get(Tags.INCLUDEINCATS),
366
392
  )
367
393
  stringHelpers.printDom(tree, file=file)
394
+ if self.settings.get(Tags.GENEXPORTREPORT):
395
+ self.generateExportReport(file, pCount=pCount, qCount=qCount)
368
396
 
369
397
  def _appendQElements(
370
398
  self,
@@ -373,17 +401,78 @@ class QuestionDB:
373
401
  tree: ET.Element,
374
402
  includeHeader: bool = True,
375
403
  ) -> None:
376
- if includeHeader:
404
+ variant: int = self.settings.get(Tags.QUESTIONVARIANT)
405
+ if includeHeader or variant == -1:
377
406
  tree.append(cat.getCategoryHeader())
378
407
  logger.debug(f"Appended a new category item {cat=}")
379
- variant: int = self.settings.get(Tags.QUESTIONVARIANT)
408
+ self._exportedAll: bool = True
380
409
  for q in qList:
381
- if hasattr(q, "variants") and q.variants is not None:
382
- if variant == 0 or variant > q.variants:
383
- dialog = QuestionVariantDialog(self.window, q)
384
- if dialog.exec() == QDialog.Accepted:
385
- variant = dialog.variant
386
- logger.debug("Die Fragen-Variante %s wurde gewählt", variant)
387
- else:
388
- logger.warning("Keine Fragenvariante wurde gewählt.")
389
- tree.append(q.getUpdatedElement(variant=variant))
410
+ if not isinstance(q, ParametricQuestion):
411
+ tree.append(q.getUpdatedElement())
412
+ self._exportedQuestions.append(q)
413
+ continue
414
+ if variant == -1:
415
+ tree.append(cat.getCategoryHeader(subCategory=q.id))
416
+ for var in range(q.parametrics.variants):
417
+ tree.append(q.getUpdatedElement(variant=var))
418
+ elif variant == 0 or variant > q.parametrics.variants:
419
+ dialog = QuestionVariantDialog(self.window, q)
420
+ if dialog.exec() == QDialog.Accepted:
421
+ variant = dialog.variant
422
+ logger.debug("Die Fragen-Variante %s wurde gewählt", variant)
423
+ else:
424
+ logger.warning("Keine Fragenvariante wurde gewählt.")
425
+ tree.append(q.getUpdatedElement(variant=variant))
426
+ self._exportedQuestions.append(q)
427
+
428
+ def generateExportReport(
429
+ self, file: Path | None = None, pCount: int = 0, qCount: int = 0
430
+ ) -> None:
431
+ """Generate a YAML report of the exported questions."""
432
+ if not self._exportedQuestions:
433
+ return
434
+ if file:
435
+ base_path = file.with_name(f"{file.stem}_export_report.yaml")
436
+ else:
437
+ base_path = self.spreadsheet.parent / "export_report.yaml"
438
+
439
+ for i in range(99):
440
+ report_path = base_path.with_name(f"{base_path.stem}-{i:02d}.yaml")
441
+ if not report_path.resolve().exists():
442
+ break
443
+
444
+ report_data = {
445
+ "export_metadata": {
446
+ "export_time": dt.datetime.now(tz=None).strftime("%Y-%m-%d %H:%M:%S"),
447
+ "excel2moodle_version": __version__,
448
+ },
449
+ "categories": {},
450
+ }
451
+ if qCount != 0:
452
+ report_data["export_metadata"]["question_count"] = qCount
453
+ if pCount != 0:
454
+ report_data["export_metadata"]["total_point_count"] = pCount
455
+
456
+ sorted_questions = sorted(
457
+ self._exportedQuestions, key=lambda q: (q.category.name, q.id)
458
+ )
459
+
460
+ for question in sorted_questions:
461
+ category_name = question.category.name
462
+ if category_name not in report_data["categories"]:
463
+ report_data["categories"][category_name] = {
464
+ "description": question.category.desc,
465
+ "questions": [],
466
+ }
467
+
468
+ question_data = {"id": question.id, "name": question.name}
469
+ if isinstance(question, ParametricQuestion) and question.currentVariant > 0:
470
+ if self._exportedAll:
471
+ question_data["exported_variant"] = "all"
472
+ else:
473
+ question_data["exported_variant"] = question.currentVariant + 1
474
+
475
+ report_data["categories"][category_name]["questions"].append(question_data)
476
+
477
+ with report_path.open("w") as f:
478
+ yaml.dump(report_data, f, sort_keys=False)
@@ -5,9 +5,6 @@ This module host different functions. All of them will return an ``lxml.etree.El
5
5
 
6
6
  import lxml.etree as ET
7
7
 
8
- import excel2moodle.core.etHelpers as eth
9
- from excel2moodle.core.globals import TextElements, feedbackStr, feedBElements
10
-
11
8
  from .globals import Tags, XMLTags
12
9
 
13
10
 
@@ -48,20 +45,3 @@ def getCdatTxtElement(subEle: ET._Element | list[ET._Element]) -> ET.Element:
48
45
  ET.tostring(subEle, encoding="unicode", pretty_print=True),
49
46
  )
50
47
  return textEle
51
-
52
-
53
- def getFeedBEle(
54
- feedback: XMLTags,
55
- text: str | None = None,
56
- style: TextElements | None = None,
57
- ) -> ET.Element:
58
- """Gets ET Elements with the feedback for the question."""
59
- span = feedBElements[feedback] if style is None else style.create()
60
- if text is None:
61
- text = feedbackStr[feedback]
62
- ele = ET.Element(feedback, format="html")
63
- par = TextElements.PLEFT.create()
64
- span.text = text
65
- par.append(span)
66
- ele.append(eth.getCdatTxtElement(par))
67
- return ele
@@ -89,12 +89,3 @@ feedBElements = {
89
89
  XMLTags.ANSFEEDBACK: TextElements.SPANGREEN.create(),
90
90
  XMLTags.GENFEEDB: TextElements.SPANGREEN.create(),
91
91
  }
92
- feedbackStr = {
93
- XMLTags.CORFEEDB: "Die Frage wurde richtig beantwortet",
94
- XMLTags.PCORFEEDB: "Die Frage wurde teilweise richtig beantwortet",
95
- XMLTags.INCORFEEDB: "Die Frage wurde Falsch beantwortet",
96
- XMLTags.GENFEEDB: "Sie haben eine Antwort abgegeben",
97
- "right": "richtig",
98
- "wrong": "falsch",
99
- "right1Percent": "Gratultaion, die Frage wurde im Rahmen der Toleranz richtig beantwortet",
100
- }
@@ -9,7 +9,6 @@ from excel2moodle.core.globals import (
9
9
  Tags,
10
10
  TextElements,
11
11
  XMLTags,
12
- feedbackStr,
13
12
  feedBElements,
14
13
  )
15
14
  from excel2moodle.core.question import Picture, Question
@@ -30,7 +29,6 @@ class QuestionParser:
30
29
 
31
30
  def __init__(self) -> None:
32
31
  """Initialize the general Question parser."""
33
- self.genFeedbacks: list[XMLTags] = []
34
32
  self.logger: logging.LoggerAdapter
35
33
 
36
34
  def setup(self, question: Question) -> None:
@@ -84,7 +82,7 @@ class QuestionParser:
84
82
  It uses the data from ``self.rawInput`` if ``text`` is type``DFIndex``
85
83
  Otherwise the value of ``text`` will be inserted.
86
84
  """
87
- t = self.rawInput[text] if isinstance(text, Tags) else text
85
+ t = self.rawInput.get(text) if isinstance(text, Tags) else text
88
86
  if txtEle is False:
89
87
  self.tmpEle.append(eth.getElement(eleName, t, **attribs))
90
88
  elif txtEle is True:
@@ -111,12 +109,11 @@ class QuestionParser:
111
109
  self.appendToTmpEle(XMLTags.NAME, text=Tags.NAME, txtEle=True)
112
110
  self.appendToTmpEle(XMLTags.ID, text=self.question.id)
113
111
  textRootElem = ET.Element(XMLTags.QTEXT, format="html")
114
- mainTextEle = ET.SubElement(textRootElem, "text")
112
+ self.mainTextEle = ET.SubElement(textRootElem, "text")
115
113
  self.tmpEle.append(textRootElem)
116
- self.appendToTmpEle(XMLTags.POINTS, text=str(self.question.points))
114
+ if self.question.qtype != "CLOZE":
115
+ self.appendToTmpEle(XMLTags.POINTS, text=str(self.question.points))
117
116
  self._appendStandardTags()
118
- for feedb in self.genFeedbacks:
119
- self.tmpEle.append(eth.getFeedBEle(feedb))
120
117
 
121
118
  self.htmlRoot = ET.Element("div")
122
119
  self.htmlRoot.append(self.getMainTextElement())
@@ -139,10 +136,17 @@ class QuestionParser:
139
136
  if ansList is not None:
140
137
  for ele in ansList:
141
138
  self.tmpEle.append(ele)
139
+ self._finalizeParsing()
140
+
141
+ def _finalizeParsing(self) -> None:
142
+ """Pass the parsed element trees to the question.
143
+
144
+ Intended for the subclasses to do extra stuff.
145
+ """
142
146
  self.question._element = self.tmpEle
143
147
  self.question.htmlRoot = self.htmlRoot
144
148
  self.question.isParsed = True
145
- self.question.textElement = mainTextEle
149
+ self.question.textElement = self.mainTextEle
146
150
  self.logger.info("Sucessfully parsed")
147
151
 
148
152
  def getFeedBEle(
@@ -153,7 +157,8 @@ class QuestionParser:
153
157
  ) -> ET.Element:
154
158
  span = feedBElements[feedback] if style is None else style.create()
155
159
  if text is None:
156
- text = feedbackStr[feedback]
160
+ self.logger.error("Giving a feedback without providing text is nonsens")
161
+ text = self.rawInput.get(Tags.GENERALFB)
157
162
  ele = ET.Element(feedback, format="html")
158
163
  par = TextElements.PLEFT.create()
159
164
  span.text = text
@@ -169,6 +174,7 @@ class QuestionParser:
169
174
  self,
170
175
  result: float = 0.0,
171
176
  fraction: float = 100,
177
+ feedback: str | None = None,
172
178
  format: str = "moodle_auto_format",
173
179
  ) -> ET.Element:
174
180
  """Get ``<answer/>`` Element specific for the numerical Question.
@@ -184,11 +190,13 @@ class QuestionParser:
184
190
  fraction=str(fraction),
185
191
  format=format,
186
192
  )
193
+ if feedback is None:
194
+ feedback = self.rawInput.get(Tags.TRUEFB)
187
195
  ansEle.append(
188
- eth.getFeedBEle(
189
- XMLTags.ANSFEEDBACK,
190
- feedbackStr["right1Percent"],
191
- TextElements.SPANGREEN,
196
+ self.getFeedBEle(
197
+ feedback=XMLTags.ANSFEEDBACK,
198
+ text=feedback,
199
+ style=TextElements.SPANGREEN,
192
200
  ),
193
201
  )
194
202
  absTolerance = round(result * self.rawInput.get(Tags.TOLERANCE), 4)
@@ -2,6 +2,7 @@ import base64
2
2
  import logging
3
3
  import math
4
4
  import re
5
+ from copy import deepcopy
5
6
  from pathlib import Path
6
7
  from types import UnionType
7
8
  from typing import TYPE_CHECKING, ClassVar, Literal, overload
@@ -37,12 +38,23 @@ class QuestionData(dict):
37
38
  @overload
38
39
  def get(
39
40
  self,
40
- key: Literal[Tags.NAME, Tags.ANSTYPE, Tags.PICTURE, Tags.EQUATION],
41
+ key: Literal[
42
+ Tags.NAME,
43
+ Tags.ANSTYPE,
44
+ Tags.PICTURE,
45
+ Tags.EQUATION,
46
+ Tags.TRUEFB,
47
+ Tags.FALSEFB,
48
+ Tags.GENERALFB,
49
+ Tags.WRONGSIGNFB,
50
+ ],
51
+ default: object = None,
41
52
  ) -> str: ...
42
53
  @overload
43
54
  def get(
44
55
  self,
45
56
  key: Literal[Tags.BPOINTS, Tags.TRUE, Tags.FALSE, Tags.QUESTIONPART, Tags.TEXT],
57
+ default: object = None,
46
58
  ) -> list: ...
47
59
  @overload
48
60
  def get(
@@ -53,19 +65,22 @@ class QuestionData(dict):
53
65
  Tags.ANSPICWIDTH,
54
66
  Tags.WRONGSIGNPERCENT,
55
67
  ],
68
+ default: object = None,
56
69
  ) -> int: ...
57
70
  @overload
58
71
  def get(
59
- self, key: Literal[Tags.PARTTYPE, Tags.TYPE]
72
+ self, key: Literal[Tags.PARTTYPE, Tags.TYPE], default: object = None
60
73
  ) -> Literal["MC", "NFM", "CLOZE"]: ...
61
74
  @overload
62
75
  def get(
63
- self, key: Literal[Tags.TOLERANCE, Tags.POINTS, Tags.FIRSTRESULT]
76
+ self,
77
+ key: Literal[Tags.TOLERANCE, Tags.POINTS, Tags.FIRSTRESULT],
78
+ default: object = None,
64
79
  ) -> float: ...
65
80
  @overload
66
- def get(self, key: Literal[Tags.RESULT]) -> float | str: ...
81
+ def get(self, key: Literal[Tags.RESULT], default: object = None) -> float | str: ...
67
82
 
68
- def get(self, key: Tags, default=None):
83
+ def get(self, key, default=None):
69
84
  """Get the value for `key` with correct type.
70
85
 
71
86
  If `key == Tags.TOLERANCE` the tolerance is checked to be a perc. fraction
@@ -172,7 +187,7 @@ class Question:
172
187
  self.textElement.text = ET.CDATA(
173
188
  ET.tostring(self.htmlRoot, encoding="unicode", pretty_print=True)
174
189
  )
175
- return self._element
190
+ return deepcopy(self._element)
176
191
 
177
192
  def _setID(self, id=0) -> None:
178
193
  if id == 0:
@@ -186,6 +201,11 @@ class ParametricQuestion(Question):
186
201
  super().__init__(*args, **kwargs)
187
202
  self.rules: list[str] = []
188
203
  self.parametrics: Parametrics
204
+ self._variant: int
205
+
206
+ @property
207
+ def currentVariant(self) -> int:
208
+ return self._variant
189
209
 
190
210
  def getUpdatedElement(self, variant: int = 0) -> ET.Element:
191
211
  """Update the bulletItem.text With the values for variant.
@@ -201,6 +221,7 @@ class ParametricQuestion(Question):
201
221
  self.bulletList.updateBullets(
202
222
  variables=self.parametrics.variables, variant=variant
203
223
  )
224
+ self._variant = variant
204
225
  return super().getUpdatedElement(variant)
205
226
 
206
227
 
@@ -272,32 +293,31 @@ class Parametrics:
272
293
  self.results[num] = []
273
294
  for variant in range(self.variants):
274
295
  type(self).setupAstIntprt(self._variables, variant)
275
- self.logger.debug("Setup The interpreter for variant: %s", variant)
296
+ self.logger.debug("Setup The interpreter for variant: %s", variant + 1)
276
297
  for num, eq in self.equations.items():
277
298
  result = type(self).astEval(eq)
299
+ if not isinstance(result, float | int):
300
+ msg = f"The expression: '{eq}' = {result} could not be evaluated."
301
+ raise QNotParsedException(msg, self.id)
278
302
  self.logger.info(
279
303
  "Calculated expr. %s (variant %s): %s = %.3f ",
280
304
  num,
281
- variant,
305
+ variant + 1,
282
306
  eq,
283
307
  result,
284
308
  )
285
- if isinstance(result, float | int):
286
- if variant == 0 and not math.isclose(
287
- result, self._resultChecker[num], rel_tol=0.01
288
- ):
289
- self.logger.warning(
290
- "The calculated result %s differs from given firstResult: %s",
291
- result,
292
- self._resultChecker,
293
- )
294
- self.logger.debug(
295
- "Calculated result %s for variant %s", result, variant
309
+ if variant == 0 and not math.isclose(
310
+ result, self._resultChecker[num], rel_tol=0.01
311
+ ):
312
+ self.logger.warning(
313
+ "The calculated result %s differs from given firstResult: %s",
314
+ result,
315
+ self._resultChecker,
296
316
  )
297
- self.results[num].append(round(result, 3))
298
- else:
299
- msg = f"The expression {eq} lead to: {result=} could not be evaluated."
300
- raise QNotParsedException(msg, self.id)
317
+ self.logger.debug(
318
+ "Calculated result %s for variant %s", result, variant
319
+ )
320
+ self.results[num].append(round(result, 3))
301
321
  return self.results
302
322
 
303
323
  def resetVariables(self) -> None:
@@ -60,8 +60,9 @@ class Tags(StrEnum):
60
60
  """Get type of the keys data."""
61
61
  return self._typ_
62
62
 
63
- QUESTIONVARIANT = "defaultQuestionVariant", int, 1, "testgen"
64
- INCLUDEINCATS = "includeCats", bool, False, "testgen"
63
+ QUESTIONVARIANT = "defaultquestionvariant", int, 1, "testgen"
64
+ INCLUDEINCATS = "includecats", bool, False, "testgen"
65
+ GENEXPORTREPORT = "exportreport", bool, False, "testgen"
65
66
  TOLERANCE = "tolerance", float, 0.01, "parser/nf"
66
67
  PICTUREFOLDER = "pictureFolder", Path, None, "core"
67
68
  PICTURESUBFOLDER = "imgfolder", str, "Abbildungen", "project"
@@ -87,8 +88,13 @@ class Tags(StrEnum):
87
88
  POINTS = "points", float, 1.0
88
89
  PICTUREWIDTH = "imgwidth", int, 500
89
90
  ANSPICWIDTH = "answerimgwidth", int, 120
90
- WRONGSIGNPERCENT = "wrongsignpercent", int, 50
91
91
  FIRSTRESULT = "firstresult", float, 0
92
+ WRONGSIGNPERCENT = "wrongsignpercent", int, 50
93
+ WRONGSIGNFB = "wrongsignfeedback", str, "your result has the wrong sign (+-)"
94
+ TRUEFB = "truefeedback", str, "congratulations!!! your answer is right."
95
+ FALSEFB = "falsefeedback", str, "Your answer is sadly wrong, try again!!!"
96
+ PCORRECFB = "partialcorrectfeedback", str, "Your answer is partially right."
97
+ GENERALFB = "feedback", str, "You answered this question."
92
98
 
93
99
 
94
100
  class Settings:
@@ -121,7 +127,7 @@ class Settings:
121
127
  ) -> int: ...
122
128
  @overload
123
129
  @classmethod
124
- def get(cls, key: Literal[Tags.INCLUDEINCATS]) -> bool: ...
130
+ def get(cls, key: Literal[Tags.INCLUDEINCATS, Tags.GENEXPORTREPORT]) -> bool: ...
125
131
  @overload
126
132
  @classmethod
127
133
  def get(
@@ -132,6 +138,7 @@ class Settings:
132
138
  Tags.LOGFILE,
133
139
  Tags.CATEGORIESSHEET,
134
140
  Tags.IMPORTMODULE,
141
+ Tags.WRONGSIGNFB,
135
142
  ],
136
143
  ) -> str: ...
137
144
  @overload
@@ -32,8 +32,6 @@ from pathlib import Path
32
32
 
33
33
  import pandas as pd
34
34
 
35
- from excel2moodle.core import numericMultiQ as nmq
36
-
37
35
  # Hier Bitte die Frage angeben, die getestet Werden soll:
38
36
 
39
37
  # ===========================================================