Commit f4809e6f0f647e10fb039749d838ffe47f913a3f
1 parent
9a20acc8
Exists in
master
and in
1 other branch
- code cleaning questions.py
- fix image size in solution box - update demo to match the new courses organization
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. |