Commit 99ccba47206315e857b4af303ff38eaf3a166e0b
Exists in
master
and in
1 other branch
Merge branch 'master' into dev
Showing
31 changed files
with
414 additions
and
341 deletions
Show diff stats
BUGS.md
| 1 | 1 | |
| 2 | 2 | # BUGS |
| 3 | 3 | |
| 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. | |
| 5 | +- registar last_seen e remover os antigos de cada vez que houver um login. | |
| 4 | 6 | - initdb da integrity error se no mesmo comando existirem alunos repetidos (p.ex em ficheiros csv diferentes ou entre csv e opcao -a) |
| 5 | 7 | - permite definir goal, mas nao verifica se esta no grafo. rebenta no start_topic. |
| 6 | 8 | - double click submits twice. | ... | ... |
aprendizations/learnapp.py
| ... | ... | @@ -462,7 +462,7 @@ class LearnApp(object): |
| 462 | 462 | |
| 463 | 463 | # ------------------------------------------------------------------------ |
| 464 | 464 | def get_current_public_dir(self, uid: str) -> str: |
| 465 | - topic: str = self.online[uid]['state'].get_current_topic() | |
| 465 | + topic: str = self.online[uid]['state'].get_current_topic() # FIXME returns None if its the last question in the topic | |
| 466 | 466 | prefix: str = self.deps.graph['prefix'] |
| 467 | 467 | return path.join(prefix, topic, 'public') |
| 468 | 468 | ... | ... |
aprendizations/main.py
| ... | ... | @@ -140,21 +140,22 @@ def main(): |
| 140 | 140 | path.join(certs_dir, 'privkey.pem')) |
| 141 | 141 | except FileNotFoundError: |
| 142 | 142 | logging.critical(f'SSL certificates missing in {certs_dir}') |
| 143 | - print('--------------------------------------------------------------') | |
| 144 | - print('Certificates should be issued by a certificate authority (CA),') | |
| 145 | - print('such as https://letsencrypt.org. ') | |
| 146 | - print('For testing purposes a selfsigned certificate can be generated') | |
| 147 | - print('locally by running: ') | |
| 148 | - print(' ') | |
| 149 | - print(' openssl req -x509 -newkey rsa:4096 -keyout privkey.pem \\ ') | |
| 150 | - print(' -out cert.pem -days 365 -nodes ') | |
| 151 | - print(' ') | |
| 152 | - print('Copy the cert.pem and privkey.pem files to: ') | |
| 153 | - print(' ') | |
| 154 | - print(f' {certs_dir:<62}') | |
| 155 | - print(' ') | |
| 156 | - print('(See README.md for more information) ') | |
| 157 | - print('--------------------------------------------------------------') | |
| 143 | + print('--------------------------------------------------------------', | |
| 144 | + 'Certificates should be issued by a certificate authority (CA),', | |
| 145 | + 'such as https://letsencrypt.org. ', | |
| 146 | + 'For testing purposes a selfsigned certificate can be generated', | |
| 147 | + 'locally by running: ', | |
| 148 | + ' ', | |
| 149 | + ' openssl req -x509 -newkey rsa:4096 -keyout privkey.pem \\ ', | |
| 150 | + ' -out cert.pem -days 365 -nodes ', | |
| 151 | + ' ', | |
| 152 | + 'Copy the cert.pem and privkey.pem files to: ', | |
| 153 | + ' ', | |
| 154 | + f' {certs_dir:<62}', | |
| 155 | + ' ', | |
| 156 | + 'See README.md for more information ', | |
| 157 | + '--------------------------------------------------------------', | |
| 158 | + sep='\n') | |
| 158 | 159 | sys.exit(1) |
| 159 | 160 | else: |
| 160 | 161 | logging.info('SSL certificates loaded') |
| ... | ... | @@ -167,20 +168,21 @@ def main(): |
| 167 | 168 | check=arg.check) |
| 168 | 169 | except DatabaseUnusableError: |
| 169 | 170 | logging.critical('Failed to start application.') |
| 170 | - print('--------------------------------------------------------------') | |
| 171 | - print('Could not find a usable database. Use one of the follwing ') | |
| 172 | - print('commands to initialize: ') | |
| 173 | - print(' ') | |
| 174 | - print(' initdb-aprendizations --admin # add admin ') | |
| 175 | - print(' initdb-aprendizations -a 86 "Max Smart" # add student ') | |
| 176 | - print(' initdb-aprendizations students.csv # add many students') | |
| 177 | - print('--------------------------------------------------------------') | |
| 171 | + print('--------------------------------------------------------------', | |
| 172 | + 'Could not find a usable database. Use one of the follwing ', | |
| 173 | + 'commands to initialize: ', | |
| 174 | + ' ', | |
| 175 | + ' initdb-aprendizations --admin # add admin ', | |
| 176 | + ' initdb-aprendizations -a 86 "Max Smart" # add student ', | |
| 177 | + ' initdb-aprendizations students.csv # add many students', | |
| 178 | + '--------------------------------------------------------------', | |
| 179 | + sep='\n') | |
| 178 | 180 | sys.exit(1) |
| 179 | 181 | except Exception: |
| 180 | - logging.critical('Failed to start application.') | |
| 182 | + logging.critical('Failed to start backend.') | |
| 181 | 183 | sys.exit(1) |
| 182 | 184 | else: |
| 183 | - logging.info('Backend application started') | |
| 185 | + logging.info('Backend started') | |
| 184 | 186 | |
| 185 | 187 | # --- run webserver forever |
| 186 | 188 | run_webserver(app=learnapp, ssl=ssl_ctx, port=arg.port, debug=arg.debug) | ... | ... |
aprendizations/questions.py
| 1 | 1 | |
| 2 | 2 | # python standard library |
| 3 | +import asyncio | |
| 3 | 4 | import random |
| 4 | 5 | import re |
| 5 | 6 | from os import path |
| ... | ... | @@ -21,10 +22,10 @@ class QuestionException(Exception): |
| 21 | 22 | pass |
| 22 | 23 | |
| 23 | 24 | |
| 24 | -# =========================================================================== | |
| 25 | +# ============================================================================ | |
| 25 | 26 | # Questions derived from Question are already instantiated and ready to be |
| 26 | 27 | # presented to students. |
| 27 | -# =========================================================================== | |
| 28 | +# ============================================================================ | |
| 28 | 29 | class Question(dict): |
| 29 | 30 | ''' |
| 30 | 31 | Classes derived from this base class are meant to instantiate questions |
| ... | ... | @@ -41,7 +42,7 @@ class Question(dict): |
| 41 | 42 | 'comments': '', |
| 42 | 43 | 'solution': '', |
| 43 | 44 | 'files': {}, |
| 44 | - 'max_tries': 3, | |
| 45 | + # 'max_tries': 3, | |
| 45 | 46 | })) |
| 46 | 47 | |
| 47 | 48 | def correct(self) -> None: |
| ... | ... | @@ -57,7 +58,7 @@ class Question(dict): |
| 57 | 58 | self.setdefault(k, v) |
| 58 | 59 | |
| 59 | 60 | |
| 60 | -# ========================================================================== | |
| 61 | +# ============================================================================ | |
| 61 | 62 | class QuestionRadio(Question): |
| 62 | 63 | '''An instance of QuestionRadio will always have the keys: |
| 63 | 64 | type (str) |
| ... | ... | @@ -92,7 +93,7 @@ class QuestionRadio(Question): |
| 92 | 93 | for x in range(n)] |
| 93 | 94 | |
| 94 | 95 | if len(self['correct']) != n: |
| 95 | - msg = (f'Options and correct mismatch in ' | |
| 96 | + msg = ('Number of options and correct differ in ' | |
| 96 | 97 | f'"{self["ref"]}", file "{self["filename"]}".') |
| 97 | 98 | logger.error(msg) |
| 98 | 99 | raise QuestionException(msg) |
| ... | ... | @@ -138,7 +139,7 @@ class QuestionRadio(Question): |
| 138 | 139 | self['grade'] = x |
| 139 | 140 | |
| 140 | 141 | |
| 141 | -# =========================================================================== | |
| 142 | +# ============================================================================ | |
| 142 | 143 | class QuestionCheckbox(Question): |
| 143 | 144 | '''An instance of QuestionCheckbox will always have the keys: |
| 144 | 145 | type (str) |
| ... | ... | @@ -218,7 +219,7 @@ class QuestionCheckbox(Question): |
| 218 | 219 | self['grade'] = x / sum_abs |
| 219 | 220 | |
| 220 | 221 | |
| 221 | -# =========================================================================== | |
| 222 | +# ============================================================================ | |
| 222 | 223 | class QuestionText(Question): |
| 223 | 224 | '''An instance of QuestionText will always have the keys: |
| 224 | 225 | type (str) |
| ... | ... | @@ -251,7 +252,7 @@ class QuestionText(Question): |
| 251 | 252 | self['grade'] = 1.0 if self['answer'] in self['correct'] else 0.0 |
| 252 | 253 | |
| 253 | 254 | |
| 254 | -# =========================================================================== | |
| 255 | +# ============================================================================ | |
| 255 | 256 | class QuestionTextRegex(Question): |
| 256 | 257 | '''An instance of QuestionTextRegex will always have the keys: |
| 257 | 258 | type (str) |
| ... | ... | @@ -281,7 +282,7 @@ class QuestionTextRegex(Question): |
| 281 | 282 | self['grade'] = 1.0 if ok else 0.0 |
| 282 | 283 | |
| 283 | 284 | |
| 284 | -# =========================================================================== | |
| 285 | +# ============================================================================ | |
| 285 | 286 | class QuestionNumericInterval(Question): |
| 286 | 287 | '''An instance of QuestionTextNumeric will always have the keys: |
| 287 | 288 | type (str) |
| ... | ... | @@ -316,7 +317,7 @@ class QuestionNumericInterval(Question): |
| 316 | 317 | self['grade'] = 1.0 if lower <= answer <= upper else 0.0 |
| 317 | 318 | |
| 318 | 319 | |
| 319 | -# =========================================================================== | |
| 320 | +# ============================================================================ | |
| 320 | 321 | class QuestionTextArea(Question): |
| 321 | 322 | '''An instance of QuestionTextArea will always have the keys: |
| 322 | 323 | type (str) |
| ... | ... | @@ -397,7 +398,7 @@ class QuestionTextArea(Question): |
| 397 | 398 | logger.error(f'Invalid grade in "{self["correct"]}".') |
| 398 | 399 | |
| 399 | 400 | |
| 400 | -# =========================================================================== | |
| 401 | +# ============================================================================ | |
| 401 | 402 | class QuestionInformation(Question): |
| 402 | 403 | # ------------------------------------------------------------------------ |
| 403 | 404 | def __init__(self, q: QDict) -> None: |
| ... | ... | @@ -412,30 +413,38 @@ class QuestionInformation(Question): |
| 412 | 413 | self['grade'] = 1.0 # always "correct" but points should be zero! |
| 413 | 414 | |
| 414 | 415 | |
| 415 | -# =========================================================================== | |
| 416 | +# ============================================================================ | |
| 417 | +# | |
| 416 | 418 | # QFactory is a class that can generate question instances, e.g. by shuffling |
| 417 | 419 | # options, running a script to generate the question, etc. |
| 418 | 420 | # |
| 419 | -# To generate an instance of a question we use the method generate() where | |
| 420 | -# the argument is the reference of the question we wish to produce. | |
| 421 | -# The generate() method returns a question instance of the correct class. | |
| 421 | +# To generate an instance of a question we use the method generate(). | |
| 422 | +# It returns a question instance of the correct class. | |
| 423 | +# There is also an asynchronous version called gen_async(). This version is | |
| 424 | +# synchronous for all question types (radio, checkbox, etc) except for generator | |
| 425 | +# types which run asynchronously. | |
| 422 | 426 | # |
| 423 | 427 | # Example: |
| 424 | 428 | # |
| 425 | -# # generate a question instance from a dictionary | |
| 426 | -# qdict = { | |
| 429 | +# # make a factory for a question | |
| 430 | +# qfactory = QFactory({ | |
| 427 | 431 | # 'type': 'radio', |
| 428 | 432 | # 'text': 'Choose one', |
| 429 | 433 | # 'options': ['a', 'b'] |
| 430 | -# } | |
| 431 | -# qfactory = QFactory(qdict) | |
| 434 | +# }) | |
| 435 | +# | |
| 436 | +# # generate synchronously | |
| 432 | 437 | # question = qfactory.generate() |
| 433 | 438 | # |
| 439 | +# # generate asynchronously | |
| 440 | +# question = await qfactory.gen_async() | |
| 441 | +# | |
| 434 | 442 | # # answer one question and correct it |
| 435 | 443 | # question['answer'] = 42 # set answer |
| 436 | 444 | # question.correct() # correct answer |
| 437 | 445 | # grade = question['grade'] # get grade |
| 438 | -# =========================================================================== | |
| 446 | +# | |
| 447 | +# ============================================================================ | |
| 439 | 448 | class QFactory(object): |
| 440 | 449 | # Depending on the type of question, a different question class will be |
| 441 | 450 | # instantiated. All these classes derive from the base class `Question`. |
| ... | ... | @@ -456,44 +465,10 @@ class QFactory(object): |
| 456 | 465 | def __init__(self, qdict: QDict = QDict({})) -> None: |
| 457 | 466 | self.question = qdict |
| 458 | 467 | |
| 459 | - # ----------------------------------------------------------------------- | |
| 460 | - # Given a ref returns an instance of a descendent of Question(), | |
| 461 | - # i.e. a question object (radio, checkbox, ...). | |
| 462 | - # ----------------------------------------------------------------------- | |
| 463 | - def generate(self) -> Question: | |
| 464 | - # FIXME this is almost the same as the one below | |
| 465 | - # return asyncio.run(self.gen_async()) | |
| 466 | - | |
| 467 | - logger.debug(f'generating {self.question["ref"]}...') | |
| 468 | - # Shallow copy so that script generated questions will not replace | |
| 469 | - # the original generators | |
| 470 | - q = self.question.copy() | |
| 471 | - q['qid'] = str(uuid.uuid4()) # unique for each generated question | |
| 472 | - | |
| 473 | - # If question is of generator type, an external program will be run | |
| 474 | - # which will print a valid question in yaml format to stdout. This | |
| 475 | - # output is then yaml parsed into a dictionary `q`. | |
| 476 | - if q['type'] == 'generator': | |
| 477 | - logger.debug(f' \\_ Running "{q["script"]}".') | |
| 478 | - q.setdefault('args', []) | |
| 479 | - q.setdefault('stdin', '') # FIXME is it really necessary? | |
| 480 | - script = path.join(q['path'], q['script']) | |
| 481 | - out = run_script(script=script, args=q['args'], stdin=q['stdin']) | |
| 482 | - q.update(out) | |
| 483 | - | |
| 484 | - # Finally we create an instance of Question() | |
| 485 | - try: | |
| 486 | - qinstance = self._types[q['type']](QDict(q)) # of matching class | |
| 487 | - except QuestionException as e: | |
| 488 | - logger.error(e) | |
| 489 | - raise e | |
| 490 | - except KeyError: | |
| 491 | - logger.error(f'Invalid type "{q["type"]}" in "{q["ref"]}"') | |
| 492 | - raise | |
| 493 | - else: | |
| 494 | - return qinstance | |
| 495 | - | |
| 496 | - # ----------------------------------------------------------------------- | |
| 468 | + # ------------------------------------------------------------------------ | |
| 469 | + # generates a question instance of QuestionRadio, QuestionCheckbox, ..., | |
| 470 | + # which is a descendent of base class Question. | |
| 471 | + # ------------------------------------------------------------------------ | |
| 497 | 472 | async def gen_async(self) -> Question: |
| 498 | 473 | logger.debug(f'generating {self.question["ref"]}...') |
| 499 | 474 | # Shallow copy so that script generated questions will not replace |
| ... | ... | @@ -524,3 +499,7 @@ class QFactory(object): |
| 524 | 499 | raise |
| 525 | 500 | else: |
| 526 | 501 | return qinstance |
| 502 | + | |
| 503 | + # ------------------------------------------------------------------------ | |
| 504 | + def generate(self) -> Question: | |
| 505 | + return asyncio.get_event_loop().run_until_complete(self.gen_async()) | ... | ... |
aprendizations/serve.py
| ... | ... | @@ -433,6 +433,7 @@ def run_webserver(app, |
| 433 | 433 | webapp = WebApplication(app, debug=debug) |
| 434 | 434 | except Exception: |
| 435 | 435 | logger.critical('Failed to start web application.') |
| 436 | + raise | |
| 436 | 437 | sys.exit(1) |
| 437 | 438 | else: |
| 438 | 439 | logger.info('Web application started (tornado.web.Application)') | ... | ... |
aprendizations/tools.py
| ... | ... | @@ -110,14 +110,14 @@ class HighlightRenderer(mistune.Renderer): |
| 110 | 110 | return highlight(code, lexer, formatter) |
| 111 | 111 | |
| 112 | 112 | def table(self, header, body): |
| 113 | - return '<table class="table table-sm"><thead class="thead-light">' \ | |
| 114 | - + header + '</thead><tbody>' + body + '</tbody></table>' | |
| 113 | + return ('<table class="table table-sm"><thead class="thead-light">' | |
| 114 | + f'{header}</thead><tbody>{body}</tbody></table>') | |
| 115 | 115 | |
| 116 | 116 | def image(self, src, title, alt): |
| 117 | 117 | alt = mistune.escape(alt, quote=True) |
| 118 | 118 | title = mistune.escape(title or '', quote=True) |
| 119 | - return f'<img src="/file/{src}" ' \ | |
| 120 | - f'alt="{alt}" title="{title}">' | |
| 119 | + return (f'<img src="/file/{src}" alt="{alt}" title="{title}"' | |
| 120 | + 'class="img-fluid">') | |
| 121 | 121 | # class="img-fluid mx-auto d-block" |
| 122 | 122 | |
| 123 | 123 | # Pass math through unaltered - mathjax does the rendering in the browser | ... | ... |
| ... | ... | @@ -0,0 +1,26 @@ |
| 1 | +--- | |
| 2 | +# ---------------------------------------------------------------------------- | |
| 3 | +# optional values applied to each topic, if undefined there | |
| 4 | +# ---------------------------------------------------------------------------- | |
| 5 | + | |
| 6 | +# defaults: | |
| 7 | +# file: questions.yaml | |
| 8 | +# shuffle_questions: true | |
| 9 | +# choose: 6 | |
| 10 | +# max_tries: 2 | |
| 11 | +# forgetting_factor: 0.97 | |
| 12 | +# min_level: 0.01 | |
| 13 | +# append_wrong: true | |
| 14 | + | |
| 15 | +# ---------------------------------------------------------------------------- | |
| 16 | +# topics and their dependencies | |
| 17 | +# ---------------------------------------------------------------------------- | |
| 18 | + | |
| 19 | +topics: | |
| 20 | + astronomy/solar-system: | |
| 21 | + name: Sistema solar | |
| 22 | + | |
| 23 | + # astronomy/milky-way: | |
| 24 | + # name: Via Láctea | |
| 25 | + # deps: | |
| 26 | + # - solar-system | ... | ... |
| ... | ... | @@ -0,0 +1,28 @@ |
| 1 | +#!/usr/bin/env python3 | |
| 2 | + | |
| 3 | +import re | |
| 4 | +import sys | |
| 5 | +import time | |
| 6 | + | |
| 7 | +s = sys.stdin.read() | |
| 8 | + | |
| 9 | +ans = set(re.findall(r'[\w]+', s.lower())) # convert answer to lowercase | |
| 10 | +ans.difference_update({'e', 'a', 'o', 'planeta', 'planetas'}) # ignore words | |
| 11 | + | |
| 12 | +# correct set of planets | |
| 13 | +planets = {'mercúrio', 'vénus', 'terra'} | |
| 14 | + | |
| 15 | +correct = set.intersection(ans, planets) # the ones I got right | |
| 16 | +wrong = set.difference(ans, planets) # the ones I got wrong | |
| 17 | + | |
| 18 | +grade = (len(correct) - len(wrong)) / len(planets) | |
| 19 | + | |
| 20 | +comments = 'Certo' if grade == 1.0 else 'as iniciais dos planetas são M, V e T' | |
| 21 | + | |
| 22 | +out = f'''--- | |
| 23 | +grade: {grade} | |
| 24 | +comments: {comments}''' | |
| 25 | + | |
| 26 | +time.sleep(2) # simulate computation time (may generate timeout) | |
| 27 | + | |
| 28 | +print(out) | ... | ... |
1.02 MB
2.96 MB
419 KB
117 KB
| ... | ... | @@ -0,0 +1,66 @@ |
| 1 | +--- | |
| 2 | +# ---------------------------------------------------------------------------- | |
| 3 | +- type: text | |
| 4 | + ref: planet-earth | |
| 5 | + title: Sistema solar | |
| 6 | + text: O nosso planeta chama-se planeta... | |
| 7 | + correct: ['Terra', 'terra'] | |
| 8 | + # opcional | |
| 9 | + answer: dos macacos? | |
| 10 | + solution: | | |
| 11 | + O nosso planeta é o planeta **Terra**. | |
| 12 | + | |
| 13 | +  | |
| 14 | + | |
| 15 | +# ---------------------------------------------------------------------------- | |
| 16 | +- type: radio | |
| 17 | + ref: largest-planet | |
| 18 | + title: Sistema solar | |
| 19 | + text: | | |
| 20 | +  | |
| 21 | + | |
| 22 | + Qual é o maior planeta do Sistema Solar? | |
| 23 | + options: | |
| 24 | + - Mercúrio | |
| 25 | + - Marte | |
| 26 | + - Júpiter | |
| 27 | + - Saturno | |
| 28 | + - Têm todos o mesmo tamanho | |
| 29 | + # opcional | |
| 30 | + correct: 2 | |
| 31 | + shuffle: false | |
| 32 | + solution: | | |
| 33 | + O maior planeta é Júpiter. Tem uma massa 1000 vezes inferior ao Sol, mas | |
| 34 | + ainda assim 2.5 vezes maior que a massa de todos os outros planetas juntos. | |
| 35 | + É um gigante gasoso maioritariamente composto por hidrogénio. | |
| 36 | + | |
| 37 | +  | |
| 38 | + | |
| 39 | +# ---------------------------------------------------------------------------- | |
| 40 | +- type: text-regex | |
| 41 | + ref: saturn | |
| 42 | + title: Sistema solar | |
| 43 | + text: O planeta do sistema solar conhecido pelos seus aneis é o planeta... | |
| 44 | + correct: '[Ss]aturno' | |
| 45 | + solution: | | |
| 46 | + O planeta Saturno é famoso pelos seus anéis. | |
| 47 | + É o segundo maior planeta do Sistema Solar. | |
| 48 | + Tal como Júpiter, é um gigante gasoso. | |
| 49 | + Os seus anéis são formados por partículas de gelo. | |
| 50 | + | |
| 51 | +  | |
| 52 | + | |
| 53 | +# ---------------------------------------------------------------------------- | |
| 54 | +- type: textarea | |
| 55 | + ref: first_3_planets | |
| 56 | + title: Sistema solar | |
| 57 | + text: | | |
| 58 | + Qual o nome dos três planetas mais próximos do Sol? | |
| 59 | + Exemplo `Ceres, Krypton e Vulcano` | |
| 60 | + correct: correct-first_3_planets.py | |
| 61 | + # correct: correct-timeout.py | |
| 62 | + # opcional | |
| 63 | + answer: Ceres, Krypton e Vulcano | |
| 64 | + timeout: 3 | |
| 65 | + solution: | | |
| 66 | + Os 3 planetas mais perto do Sol são Mercúrio, Vénus e Terra. | ... | ... |
| ... | ... | @@ -0,0 +1,28 @@ |
| 1 | +--- | |
| 2 | +# ---------------------------------------------------------------------------- | |
| 3 | +# import topics and their dependencies | |
| 4 | +# ---------------------------------------------------------------------------- | |
| 5 | +topics_from: | |
| 6 | + - math.yaml | |
| 7 | + - astronomy.yaml | |
| 8 | + | |
| 9 | +# ---------------------------------------------------------------------------- | |
| 10 | +# course names, their goals and other details | |
| 11 | +# ---------------------------------------------------------------------------- | |
| 12 | +courses: | |
| 13 | + math: | |
| 14 | + title: Matemática | |
| 15 | + description: | | |
| 16 | + Adição, multiplicação e números primos. | |
| 17 | + goals: | |
| 18 | + - math/addition | |
| 19 | + - math/multiplication | |
| 20 | + - math/prime-numbers | |
| 21 | + | |
| 22 | + astronomy: | |
| 23 | + title: Astronomia | |
| 24 | + description: | | |
| 25 | + Sistema Solar e Via Láctea. | |
| 26 | + goals: | |
| 27 | + - astronomy/solar-system | |
| 28 | + # - astronomy/milky-way | ... | ... |
demo/demo.yaml
| ... | ... | @@ -1,29 +0,0 @@ |
| 1 | ---- | |
| 2 | - | |
| 3 | -title: Example | |
| 4 | -database: students.db | |
| 5 | - | |
| 6 | -# values applied to each topic, if undefined there | |
| 7 | -file: questions.yaml | |
| 8 | -shuffle_questions: true | |
| 9 | -choose: 6 | |
| 10 | -max_tries: 2 | |
| 11 | -forgetting_factor: 0.97 | |
| 12 | -min_level: 0.01 | |
| 13 | -append_wrong: true | |
| 14 | - | |
| 15 | -# ---------------------------------------------------------------------------- | |
| 16 | -topics: | |
| 17 | - # topic without dependencies | |
| 18 | - math: | |
| 19 | - name: Matemática | |
| 20 | - file: questions.yaml | |
| 21 | - choose: 6 | |
| 22 | - shuffle: true | |
| 23 | - | |
| 24 | - # topic with one dependency | |
| 25 | - solar_system: | |
| 26 | - name: Sistema solar | |
| 27 | - deps: | |
| 28 | - - math | |
| 29 | - forgetting_factor: 0.1 |
| ... | ... | @@ -0,0 +1,31 @@ |
| 1 | +--- | |
| 2 | +# ---------------------------------------------------------------------------- | |
| 3 | +# optional values applied to each topic, if undefined there | |
| 4 | +# ---------------------------------------------------------------------------- | |
| 5 | + | |
| 6 | +# defaults: | |
| 7 | +# file: questions.yaml | |
| 8 | +# shuffle_questions: true | |
| 9 | +# choose: 6 | |
| 10 | +# max_tries: 2 | |
| 11 | +# forgetting_factor: 0.97 | |
| 12 | +# min_level: 0.01 | |
| 13 | +# append_wrong: true | |
| 14 | + | |
| 15 | +# ---------------------------------------------------------------------------- | |
| 16 | +# topics and their dependencies | |
| 17 | +# ---------------------------------------------------------------------------- | |
| 18 | + | |
| 19 | +topics: | |
| 20 | + math/addition: | |
| 21 | + name: Adição | |
| 22 | + | |
| 23 | + math/multiplication: | |
| 24 | + name: Multiplicação | |
| 25 | + deps: | |
| 26 | + - math/addition | |
| 27 | + | |
| 28 | + math/prime-numbers: | |
| 29 | + name: Números primos | |
| 30 | + deps: | |
| 31 | + - math/multiplication | ... | ... |
| ... | ... | @@ -0,0 +1,20 @@ |
| 1 | +#!/usr/bin/env python3 | |
| 2 | + | |
| 3 | +import random | |
| 4 | +import sys | |
| 5 | + | |
| 6 | +a = int(sys.argv[1]) | |
| 7 | +b = int(sys.argv[2]) | |
| 8 | + | |
| 9 | +x = random.randint(a, b) | |
| 10 | +y = random.randint(a, b) | |
| 11 | +r = x + y | |
| 12 | + | |
| 13 | +print(f'''--- | |
| 14 | +type: text | |
| 15 | +title: Adição de números com 2 algarismos | |
| 16 | +text: | | |
| 17 | + Qual o resultado da soma ${x}+{y}$? | |
| 18 | +correct: ['{r}'] | |
| 19 | +solution: | | |
| 20 | + O resultado é {r}.''') | ... | ... |
| ... | ... | @@ -0,0 +1,22 @@ |
| 1 | +--- | |
| 2 | +# --------------------------------------------------------------------------- | |
| 3 | +- type: generator | |
| 4 | + ref: addition-two-digits | |
| 5 | + script: addition-two-digits.py | |
| 6 | + args: [10, 20] | |
| 7 | + | |
| 8 | +- type: checkbox | |
| 9 | + ref: addition-properties | |
| 10 | + title: Propriedades da adição | |
| 11 | + text: Indique quais as propriedades que a adição satisfaz. | |
| 12 | + options: | |
| 13 | + # right | |
| 14 | + - Existência de elemento neutro, $x+0=x$. | |
| 15 | + - Existência de inverso aditivo (simétrico), $x+(-x)=0$. | |
| 16 | + - Propriedade associativa, $(x+y)+z = x+(y+z)$. | |
| 17 | + - Propriedade comutativa, $x+y=y+x$. | |
| 18 | + # wrong | |
| 19 | + - Existência de elemento absorvente, $x+1=1$. | |
| 20 | + correct: [1, 1, 1, 1, -1] | |
| 21 | + solution: | | |
| 22 | + A adição não tem elemento absorvente. | ... | ... |
demo/math/gen-multiples-of-3.py
| ... | ... | @@ -1,29 +0,0 @@ |
| 1 | -#!/usr/bin/env python3 | |
| 2 | - | |
| 3 | -import random | |
| 4 | -import sys | |
| 5 | - | |
| 6 | -a, b = map(int, sys.argv[1:]) # get command line arguments | |
| 7 | - | |
| 8 | -numbers = list(range(a, b)) | |
| 9 | -random.shuffle(numbers) | |
| 10 | -numbers = numbers[:8] | |
| 11 | -correct = [1 if n % 3 == 0 else -1 for n in numbers] | |
| 12 | - | |
| 13 | -multiples = [str(n) for n in numbers if n % 3 == 0] | |
| 14 | -if multiples: | |
| 15 | - solution = f'Os números múltiplos de 3 são: {", ".join(multiples)}.' | |
| 16 | -else: | |
| 17 | - solution = f'Nenhum número mostrado é múltiplo de 3.' | |
| 18 | - | |
| 19 | - | |
| 20 | -q = f'''--- | |
| 21 | -type: checkbox | |
| 22 | -title: Múltiplos de 3 | |
| 23 | -text: Indique quais dos seguintes números são múltiplos de 3. | |
| 24 | -options: {numbers} | |
| 25 | -correct: {correct} | |
| 26 | -solution: | | |
| 27 | - {solution}''' | |
| 28 | - | |
| 29 | -print(q) |
| ... | ... | @@ -0,0 +1,27 @@ |
| 1 | +#!/usr/bin/env python3 | |
| 2 | + | |
| 3 | +import random | |
| 4 | +import sys | |
| 5 | + | |
| 6 | +# can be repeated | |
| 7 | +# x = random.randint(2, 9) | |
| 8 | +# y = random.randint(2, 9) | |
| 9 | + | |
| 10 | +# with x != y | |
| 11 | +x, y = random.sample(range(2,10), k=2) | |
| 12 | +r = x * y | |
| 13 | + | |
| 14 | +yy = '+'.join([str(y)]*x) | |
| 15 | +xx = '+'.join([str(x)]*y) | |
| 16 | + | |
| 17 | +print(f'''--- | |
| 18 | +type: text | |
| 19 | +title: Multiplicação (tabuada) | |
| 20 | +text: | | |
| 21 | + Qual o resultado da multiplicação ${x}\\times {y}$? | |
| 22 | +correct: ['{r}'] | |
| 23 | +solution: | | |
| 24 | + A multiplicação é a repetição da soma. Podemos fazer de duas maneiras: | |
| 25 | + $$ {x}\\times {y} = {yy} = {r} $$ | |
| 26 | + ou | |
| 27 | + $$ {y}\\times {x} = {xx} = {r}. $$''') | ... | ... |
| ... | ... | @@ -0,0 +1,24 @@ |
| 1 | +--- | |
| 2 | +# --------------------------------------------------------------------------- | |
| 3 | +- type: generator | |
| 4 | + ref: multiplication-table | |
| 5 | + script: multiplication-table.py | |
| 6 | + | |
| 7 | +- type: checkbox | |
| 8 | + ref: multiplication-properties | |
| 9 | + title: Propriedades da multiplicação | |
| 10 | + text: Indique quais as propriedades que a multiplicação satisfaz. | |
| 11 | + options: | |
| 12 | + # right | |
| 13 | + - Existência de elemento neutro, $1x=x$. | |
| 14 | + - Propriedade associativa, $(xy)z = x(yz)$. | |
| 15 | + - Propriedade comutativa, $xy=yx$. | |
| 16 | + - Existência de elemento absorvente, $0x=0$. | |
| 17 | + # wrong | |
| 18 | + - Existência de inverso, todos os números $x$ tem um inverso $1/x$ tal que | |
| 19 | + $x(1/x)=1$. | |
| 20 | + correct: [1, 1, 1, 1, -1] | |
| 21 | + solution: | | |
| 22 | + Na multiplicação nem todos os números têm inverso. Só têm inverso os números | |
| 23 | + diferentes de zero. | |
| 24 | + As outras propriedades são satisfeitas para todos os números. | ... | ... |
| ... | ... | @@ -0,0 +1,59 @@ |
| 1 | +--- | |
| 2 | +# --------------------------------------------------------------------------- | |
| 3 | +- type: radio | |
| 4 | + ref: prime-number-definition | |
| 5 | + title: Números primos | |
| 6 | + text: | | |
| 7 | + Qual a definição de número primo? | |
| 8 | + options: | |
| 9 | + - Um número $n>1$ é primo se só é divisível por si próprio e pela unidade. | |
| 10 | + - Um número $n\ge 2$ é primo se só é divisível por si próprio e pela unidade. | |
| 11 | + - Um número $n>1$ é primo se os seus divisores são apenas $1$ e $n$. | |
| 12 | + - Um número $n\ge 2$ é primo se os seus divisores são apenas $1$ e $n$. | |
| 13 | + # wrong | |
| 14 | + - Um número $n$ é primo se é divisível por si próprio e pela unidade. | |
| 15 | + - Um número $n>1$ é primo se é divisível por si próprio e pela unidade. | |
| 16 | + - Um número $n\ge 2$ é primo se não tem divisores. | |
| 17 | + choose: 3 | |
| 18 | + correct: [1, 1, 1, 1, 0, 0, 0] | |
| 19 | + solution: | |
| 20 | + Um número $n$ é primo se $n\ge 2$ e se os únicos divisores forem $1$ e $n$. | |
| 21 | + | |
| 22 | +# --------------------------------------------------------------------------- | |
| 23 | +- type: checkbox | |
| 24 | + ref: even-odd-prime | |
| 25 | + title: Números pares, ímpares e primos | |
| 26 | + text: Indique as afirmações verdadeiras. | |
| 27 | + options: | |
| 28 | + - ['3 é primo', '4 é primo'] | |
| 29 | + - ['2 é par', '3 é par'] | |
| 30 | + - ['1 é ímpar', '2 é ímpar'] | |
| 31 | + correct: [1, 1, 1] | |
| 32 | + solution: | | |
| 33 | + A tabela seguinte resume as propriedades dos números 1 a 4: | |
| 34 | + | |
| 35 | + Número | ímpar | par | primo | |
| 36 | + :-----:|:-----:|:---:|:------: | |
| 37 | + 1 | S | N | N | |
| 38 | + 2 | N | S | S | |
| 39 | + 3 | S | N | S | |
| 40 | + 4 | N | S | N | |
| 41 | + | |
| 42 | +# --------------------------------------------------------------------------- | |
| 43 | +- type: radio | |
| 44 | + ref: prime-numbers | |
| 45 | + title: Números primos | |
| 46 | + text: Qual dos seguintes números é um primo múltiplo de 3? | |
| 47 | + options: | |
| 48 | + - 3 | |
| 49 | + # wrong | |
| 50 | + - 9 | |
| 51 | + - 13 | |
| 52 | + - 21 | |
| 53 | + - 15 | |
| 54 | + - Não há primos múltiplos de 3. | |
| 55 | + max_tries: 1 | |
| 56 | + solution: | | |
| 57 | + O único número primo múltiplo de 3 é o próprio 3. | |
| 58 | + Todos os outros múltiplos (6, 9, 12, 15, ...) têm 3 como divisor e portanto | |
| 59 | + não são primos. | ... | ... |
demo/math/questions.yaml
| ... | ... | @@ -1,87 +0,0 @@ |
| 1 | ---- | |
| 2 | -# --------------------------------------------------------------------------- | |
| 3 | -- type: radio | |
| 4 | - ref: distributive_property | |
| 5 | - title: Propriedade distributiva | |
| 6 | - text: | | |
| 7 | - Qual das seguintes opções usa a propriedade distributiva para calcular | |
| 8 | - $9\times 2 - 2\times 3$? | |
| 9 | - options: | |
| 10 | - # correct | |
| 11 | - - $2\times(9 - 3)$ | |
| 12 | - - $(9-3)\times 2$ | |
| 13 | - # wrong | |
| 14 | - - $18 - 6 = 12$ | |
| 15 | - - $2\times 9 - 2\times 3$ | |
| 16 | - - $2\times(9-2\times 3)$ | |
| 17 | - correct: [1, 1, 0, 0, 0] | |
| 18 | - choose: 3 | |
| 19 | - max_tries: 1 | |
| 20 | - solution: | | |
| 21 | - Colocando o 2 em evidência, obtém-se a resposta correcta $2\times(9 - 3)$ | |
| 22 | - ou $(9-3)\times 2$. | |
| 23 | - | |
| 24 | -# --------------------------------------------------------------------------- | |
| 25 | -- type: checkbox | |
| 26 | - ref: numbers | |
| 27 | - title: Números pares e primos | |
| 28 | - text: Indique as afirmações verdadeiras. | |
| 29 | - options: | |
| 30 | - - ['3 é primo', '4 é primo'] | |
| 31 | - - ['2 é par', '3 é par'] | |
| 32 | - - ['1 é ímpar', '2 é ímpar'] | |
| 33 | - correct: [1, 1, 1] | |
| 34 | - solution: | | |
| 35 | - A tabela seguinte resume as propriedades dos números 1 a 4: | |
| 36 | - | |
| 37 | - Número | ímpar | par | primo | |
| 38 | - :-----:|:-----:|:---:|:------: | |
| 39 | - 1 | S | N | N | |
| 40 | - 2 | N | S | S | |
| 41 | - 3 | S | N | S | |
| 42 | - 4 | N | S | N | |
| 43 | - | |
| 44 | -# --------------------------------------------------------------------------- | |
| 45 | -- type: radio | |
| 46 | - ref: prime_numbers | |
| 47 | - title: Números primos | |
| 48 | - text: Qual dos seguintes números é primo? | |
| 49 | - options: | |
| 50 | - - 13 | |
| 51 | - - 12 | |
| 52 | - - 14 | |
| 53 | - - 1, a **unidade** | |
| 54 | - max_tries: 1 | |
| 55 | - solution: | | |
| 56 | - O único número primo é o 13. | |
| 57 | - | |
| 58 | - O número 1, embora apenas seja divisível por ele próprio e pela unidade | |
| 59 | - (que neste caso coincide com ele próprio), não é um número primo. Apenas | |
| 60 | - são considerados números primos a partir do 2. | |
| 61 | - | |
| 62 | -# --------------------------------------------------------------------------- | |
| 63 | -- type: checkbox | |
| 64 | - ref: math-expressions | |
| 65 | - title: Expressões matemáticas | |
| 66 | - text: Quais das seguintes expressões são verdadeiras? | |
| 67 | - options: | |
| 68 | - - $1 > 0$ | |
| 69 | - - $\sqrt{3} > \sqrt{2}$ | |
| 70 | - - $e^{i\pi} + 1 = 0$ | |
| 71 | - - $\frac{\partial f(x,y)}{\partial z} = 1$ | |
| 72 | - - $-1 > 1$ | |
| 73 | - # how many points for each checkmark (normalized afterwards): | |
| 74 | - correct: [1, 1, 1, -1, -1] | |
| 75 | - solution: | | |
| 76 | - Das 5 opções existem 2 falsas. A derivada parcial é zero uma vez que a | |
| 77 | - função não varia com $z$. A desigualdade $-1 > 1$ é também falsa, os | |
| 78 | - números negativos são menores que os positivos. | |
| 79 | - | |
| 80 | -# --------------------------------------------------------------------------- | |
| 81 | -# the program should print a question in yaml format. The args will be | |
| 82 | -# sent as command line options when the program is run. | |
| 83 | -- type: generator | |
| 84 | - ref: overflow | |
| 85 | - script: gen-multiples-of-3.py | |
| 86 | - args: [11, 120] | |
| 87 | - choose: 3 |
demo/solar_system/correct-first_3_planets.py
| ... | ... | @@ -1,28 +0,0 @@ |
| 1 | -#!/usr/bin/env python3 | |
| 2 | - | |
| 3 | -import re | |
| 4 | -import sys | |
| 5 | -import time | |
| 6 | - | |
| 7 | -s = sys.stdin.read() | |
| 8 | - | |
| 9 | -ans = set(re.findall(r'[\w]+', s.lower())) # convert answer to lowercase | |
| 10 | -ans.difference_update({'e', 'a', 'o', 'planeta', 'planetas'}) # ignore words | |
| 11 | - | |
| 12 | -# correct set of planets | |
| 13 | -planets = {'mercúrio', 'vénus', 'terra'} | |
| 14 | - | |
| 15 | -correct = set.intersection(ans, planets) # the ones I got right | |
| 16 | -wrong = set.difference(ans, planets) # the ones I got wrong | |
| 17 | - | |
| 18 | -grade = (len(correct) - len(wrong)) / len(planets) | |
| 19 | - | |
| 20 | -comments = 'Certo' if grade == 1.0 else 'as iniciais dos planetas são M, V e T' | |
| 21 | - | |
| 22 | -out = f'''--- | |
| 23 | -grade: {grade} | |
| 24 | -comments: {comments}''' | |
| 25 | - | |
| 26 | -time.sleep(2) # simulate computation time (may generate timeout) | |
| 27 | - | |
| 28 | -print(out) |
demo/solar_system/correct-timeout.py
demo/solar_system/public/earth.jpg
1.02 MB
demo/solar_system/public/jupiter.gif
2.96 MB
demo/solar_system/public/planets.png
419 KB
demo/solar_system/public/saturn.jpg
117 KB
demo/solar_system/questions.yaml
| ... | ... | @@ -1,69 +0,0 @@ |
| 1 | ---- | |
| 2 | - | |
| 3 | -# --------------------------------------------------------------------------- | |
| 4 | -- type: text | |
| 5 | - ref: home-planet | |
| 6 | - title: Sistema solar | |
| 7 | - text: O nosso planeta chama-se planeta... | |
| 8 | - correct: ['Terra', 'terra'] | |
| 9 | - # opcional | |
| 10 | - answer: Não é Marte... | |
| 11 | - solution: | | |
| 12 | - Embora algumas pessoas andem muitas vezes na Lua, o nosso planeta é o | |
| 13 | - planeta **Terra**! | |
| 14 | - | |
| 15 | -  | |
| 16 | - | |
| 17 | -# --------------------------------------------------------------------------- | |
| 18 | -- type: radio | |
| 19 | - ref: solar-system | |
| 20 | - title: Sistema solar | |
| 21 | - text: | | |
| 22 | -  | |
| 23 | - | |
| 24 | - Qual é o maior planeta do Sistema Solar? | |
| 25 | - | |
| 26 | - options: | |
| 27 | - - Mercúrio | |
| 28 | - - Marte | |
| 29 | - - Júpiter | |
| 30 | - - Têm todos o mesmo tamanho | |
| 31 | - # opcional | |
| 32 | - correct: 2 | |
| 33 | - shuffle: false | |
| 34 | - discount: true | |
| 35 | - solution: | | |
| 36 | - O maior planeta é Júpiter. Tem uma massa 1000 vezes inferior ao Sol, mas | |
| 37 | - ainda assim 2.5 vezes maior que a massa de todos os outros planetas juntos. | |
| 38 | - É um gigante gasoso maioritariamente composto por hidrogénio. | |
| 39 | - | |
| 40 | -  | |
| 41 | - | |
| 42 | -# --------------------------------------------------------------------------- | |
| 43 | -- type: text-regex | |
| 44 | - ref: saturn | |
| 45 | - title: Sistema solar | |
| 46 | - text: O planeta do sistema solar conhecido por ter aneis é o planeta... | |
| 47 | - correct: '[Ss]aturno' | |
| 48 | - solution: | | |
| 49 | - O planeta Saturno é famoso pelos seus anéis. | |
| 50 | - É o segundo maior planeta do Sistema Solar, a seguir a Júpiter. | |
| 51 | - Tal como Júpiter, é um gigante gasoso. | |
| 52 | - Os seus anéis são formados por partículas de gelo. | |
| 53 | - | |
| 54 | -  | |
| 55 | - | |
| 56 | -# --------------------------------------------------------------------------- | |
| 57 | -- type: textarea | |
| 58 | - ref: first_3_planets | |
| 59 | - title: Sistema solar | |
| 60 | - text: | | |
| 61 | - Qual o nome dos três planetas mais próximos do Sol? | |
| 62 | - Exemplo `Lua, Ceres e Vulcano` | |
| 63 | - correct: correct-first_3_planets.py | |
| 64 | - # correct: correct-timeout.py | |
| 65 | - # opcional | |
| 66 | - answer: Vulcano, Krypton, Plutão | |
| 67 | - timeout: 3 | |
| 68 | - solution: | | |
| 69 | - Os 3 planetas mais perto do Sol são: Mercúrio, Vénus e Terra. |