Commit 2265a158598c1603fc503e0f79e312c41b0193bc
1 parent
4cbc74ad
Exists in
master
and in
1 other branch
new transform option in text questions
Showing
6 changed files
with
59 additions
and
12 deletions
Show diff stats
.gitignore
| 1 | # Specify filepatterns you want git to ignore. | 1 | # Specify filepatterns you want git to ignore. |
| 2 | -/aprendizations.egg-info/ | ||
| 3 | -/aprendizations/__pycache__/ | ||
| 4 | -/demo/students.db | ||
| 5 | -/node_modules/ | ||
| 6 | -/.mypy_cache/ | 2 | +aprendizations.egg-info/ |
| 3 | +aprendizations/__pycache__/ | ||
| 4 | +aprendizations/.mypy_cache/ | ||
| 5 | +demo/students.db | ||
| 6 | +node_modules/ | ||
| 7 | +.mypy_cache/ | ||
| 7 | .DS_Store | 8 | .DS_Store |
| 8 | demo/.DS_Store | 9 | demo/.DS_Store |
| 9 | demo/solar_system/.DS_Store | 10 | demo/solar_system/.DS_Store |
BUGS.md
| 1 | 1 | ||
| 2 | # BUGS | 2 | # BUGS |
| 3 | 3 | ||
| 4 | + | ||
| 5 | +Traceback (most recent call last): | ||
| 6 | + File "/home/mjsb/.local/lib/python3.7/site-packages/tornado/web.py", line 1697, in _execute | ||
| 7 | + result = method(*self.path_args, **self.path_kwargs) | ||
| 8 | + File "/home/mjsb/.local/lib/python3.7/site-packages/tornado/web.py", line 3174, in wrapper | ||
| 9 | + return method(self, *args, **kwargs) | ||
| 10 | + File "/usr/home/mjsb/Work/Projects/aprendizations/aprendizations/serve.py", line 213, in get | ||
| 11 | + self.learn.start_course(uid, course) | ||
| 12 | + File "/usr/home/mjsb/Work/Projects/aprendizations/aprendizations/learnapp.py", line 275, in start_course | ||
| 13 | + student.start_course(course) | ||
| 14 | + File "/usr/home/mjsb/Work/Projects/aprendizations/aprendizations/student.py", line 57, in start_course | ||
| 15 | + self.topic_sequence = self.recommend_topic_sequence(topics) | ||
| 16 | + File "/usr/home/mjsb/Work/Projects/aprendizations/aprendizations/student.py", line 216, in recommend_topic_sequence | ||
| 17 | + ts.update(nx.ancestors(G, t)) | ||
| 18 | + File "/home/mjsb/.local/lib/python3.7/site-packages/networkx/algorithms/dag.py", line 92, in ancestors | ||
| 19 | + raise nx.NetworkXError("The node %s is not in the graph." % source) | ||
| 20 | +networkx.exception.NetworkXError: The node programming/languages/pseudo-tcg/functions-produtorio is not in the graph. | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | +- detectar se em courses.yaml falta declarar ficheiro. Por exemplo se houver goals que não estao em lado nenhum. | ||
| 4 | - se num topico, a ultima pergunta tem imagens, o servidor nao fornece as imagengs porque o current_topic passa a None antes de carregar no botao continuar. O caminho é prefix+None e dá erro. | 25 | - se num topico, a ultima pergunta tem imagens, o servidor nao fornece as imagengs porque o current_topic passa a None antes de carregar no botao continuar. O caminho é prefix+None e dá erro. |
| 5 | - registar last_seen e remover os antigos de cada vez que houver um login. | 26 | - registar last_seen e remover os antigos de cada vez que houver um login. |
| 6 | - initdb da integrity error se no mesmo comando existirem alunos repetidos (p.ex em ficheiros csv diferentes ou entre csv e opcao -a) | 27 | - initdb da integrity error se no mesmo comando existirem alunos repetidos (p.ex em ficheiros csv diferentes ou entre csv e opcao -a) |
aprendizations/learnapp.py
| @@ -352,7 +352,7 @@ class LearnApp(object): | @@ -352,7 +352,7 @@ class LearnApp(object): | ||
| 352 | topics: Dict[str, Dict] = config.get('topics', {}) | 352 | topics: Dict[str, Dict] = config.get('topics', {}) |
| 353 | g.add_nodes_from(topics.keys()) | 353 | g.add_nodes_from(topics.keys()) |
| 354 | for tref, attr in topics.items(): | 354 | for tref, attr in topics.items(): |
| 355 | - logger.debug(f' + {tref}...') | 355 | + logger.debug(f' + {tref}') |
| 356 | for d in attr.get('deps', []): | 356 | for d in attr.get('deps', []): |
| 357 | if d not in g.nodes(): | 357 | if d not in g.nodes(): |
| 358 | logger.error(f'Topic "{tref}" depends on "{d}" but it ' | 358 | logger.error(f'Topic "{tref}" depends on "{d}" but it ' |
| @@ -370,7 +370,6 @@ class LearnApp(object): | @@ -370,7 +370,6 @@ class LearnApp(object): | ||
| 370 | 370 | ||
| 371 | t['path'] = path.join(g.graph['prefix'], tref) # prefix/topic | 371 | t['path'] = path.join(g.graph['prefix'], tref) # prefix/topic |
| 372 | 372 | ||
| 373 | - | ||
| 374 | # ======================================================================== | 373 | # ======================================================================== |
| 375 | # methods that do not change state (pure functions) | 374 | # methods that do not change state (pure functions) |
| 376 | # ======================================================================== | 375 | # ======================================================================== |
| @@ -379,6 +378,7 @@ class LearnApp(object): | @@ -379,6 +378,7 @@ class LearnApp(object): | ||
| 379 | # Buils dictionary of question factories | 378 | # Buils dictionary of question factories |
| 380 | # ------------------------------------------------------------------------ | 379 | # ------------------------------------------------------------------------ |
| 381 | def make_factory(self) -> Dict[str, QFactory]: | 380 | def make_factory(self) -> Dict[str, QFactory]: |
| 381 | + | ||
| 382 | logger.info('Building questions factory:') | 382 | logger.info('Building questions factory:') |
| 383 | factory: Dict[str, QFactory] = {} | 383 | factory: Dict[str, QFactory] = {} |
| 384 | g = self.deps | 384 | g = self.deps |
aprendizations/questions.py
| @@ -234,7 +234,8 @@ class QuestionText(Question): | @@ -234,7 +234,8 @@ class QuestionText(Question): | ||
| 234 | 234 | ||
| 235 | self.set_defaults(QDict({ | 235 | self.set_defaults(QDict({ |
| 236 | 'text': '', | 236 | 'text': '', |
| 237 | - 'correct': [], | 237 | + 'correct': [], # no correct answers, always wrong |
| 238 | + 'transform': [], # transformations applied to the answer, in order | ||
| 238 | })) | 239 | })) |
| 239 | 240 | ||
| 240 | # make sure its always a list of possible correct answers | 241 | # make sure its always a list of possible correct answers |
| @@ -244,12 +245,36 @@ class QuestionText(Question): | @@ -244,12 +245,36 @@ class QuestionText(Question): | ||
| 244 | # make sure all elements of the list are strings | 245 | # make sure all elements of the list are strings |
| 245 | self['correct'] = [str(a) for a in self['correct']] | 246 | self['correct'] = [str(a) for a in self['correct']] |
| 246 | 247 | ||
| 248 | + # make sure that the answers are invariant with respect to the filters | ||
| 249 | + if any(c != self.transform(c) for c in self['correct']): | ||
| 250 | + logger.warning(f'in "{self["ref"]}", correct answers are not ' | ||
| 251 | + 'invariant wrt transformations') | ||
| 252 | + | ||
| 253 | + # ------------------------------------------------------------------------ | ||
| 254 | + # apply optional filters to the answer | ||
| 255 | + def transform(self, ans): | ||
| 256 | + for f in self['transform']: | ||
| 257 | + if f == 'remove_space': # removes all spaces | ||
| 258 | + ans = ans.replace(' ', '') | ||
| 259 | + elif f == 'trim': # removes spaces around | ||
| 260 | + ans = ans.strip() | ||
| 261 | + elif f == 'normalize_space': # replaces multiple spaces by one | ||
| 262 | + ans = re.sub(r'\s+', ' ', ans.strip()) | ||
| 263 | + elif f == 'lower': # convert to lowercase | ||
| 264 | + ans = ans.lower() | ||
| 265 | + elif f == 'upper': # convert to uppercase | ||
| 266 | + ans = ans.upper() | ||
| 267 | + else: | ||
| 268 | + logger.warning(f'in "{self["ref"]}", unknown transform "{f}"') | ||
| 269 | + return ans | ||
| 270 | + | ||
| 247 | # ------------------------------------------------------------------------ | 271 | # ------------------------------------------------------------------------ |
| 248 | def correct(self) -> None: | 272 | def correct(self) -> None: |
| 249 | super().correct() | 273 | super().correct() |
| 250 | 274 | ||
| 251 | if self['answer'] is not None: | 275 | if self['answer'] is not None: |
| 252 | - self['grade'] = 1.0 if self['answer'] in self['correct'] else 0.0 | 276 | + answer = self.transform(self['answer']) # apply transformations |
| 277 | + self['grade'] = 1.0 if answer in self['correct'] else 0.0 | ||
| 253 | 278 | ||
| 254 | 279 | ||
| 255 | # ============================================================================ | 280 | # ============================================================================ |
aprendizations/serve.py
| @@ -434,7 +434,7 @@ def run_webserver(app, | @@ -434,7 +434,7 @@ def run_webserver(app, | ||
| 434 | except Exception: | 434 | except Exception: |
| 435 | logger.critical('Failed to start web application.') | 435 | logger.critical('Failed to start web application.') |
| 436 | raise | 436 | raise |
| 437 | - sys.exit(1) | 437 | + # sys.exit(1) |
| 438 | else: | 438 | else: |
| 439 | logger.info('Web application started (tornado.web.Application)') | 439 | logger.info('Web application started (tornado.web.Application)') |
| 440 | 440 |
aprendizations/student.py
| @@ -3,7 +3,7 @@ | @@ -3,7 +3,7 @@ | ||
| 3 | from datetime import datetime | 3 | from datetime import datetime |
| 4 | import logging | 4 | import logging |
| 5 | import random | 5 | import random |
| 6 | -from typing import Dict, List, Optional, Tuple | 6 | +from typing import List, Optional, Tuple |
| 7 | 7 | ||
| 8 | # third party libraries | 8 | # third party libraries |
| 9 | import networkx as nx | 9 | import networkx as nx |
| @@ -176,7 +176,7 @@ class StudentState(object): | @@ -176,7 +176,7 @@ class StudentState(object): | ||
| 176 | dt = now - s['date'] | 176 | dt = now - s['date'] |
| 177 | try: | 177 | try: |
| 178 | forgetting_factor = self.deps.nodes[tref]['forgetting_factor'] | 178 | forgetting_factor = self.deps.nodes[tref]['forgetting_factor'] |
| 179 | - s['level'] *= forgetting_factor ** dt.days # forgetting factor | 179 | + s['level'] *= forgetting_factor ** dt.days # forgetting factor |
| 180 | except KeyError: | 180 | except KeyError: |
| 181 | logger.warning(f'Topic {tref} is not on the graph!') | 181 | logger.warning(f'Topic {tref} is not on the graph!') |
| 182 | 182 |