Commit f4809e6f0f647e10fb039749d838ffe47f913a3f

Authored by Miguel Barão
1 parent 9a20acc8
Exists in master and in 1 other branch dev

- code cleaning questions.py

- fix image size in solution box
- update demo to match the new courses organization
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
... ...
demo/astronomy.yaml 0 → 100644
... ... @@ -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
... ...
demo/astronomy/solar-system/correct-first_3_planets.py 0 → 100755
... ... @@ -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)
... ...
demo/astronomy/solar-system/correct-timeout.py 0 → 100755
... ... @@ -0,0 +1,11 @@
  1 +#!/usr/bin/env python3
  2 +
  3 +import sys
  4 +import time
  5 +
  6 +s = sys.stdin.read()
  7 +
  8 +# generate timeout
  9 +time.sleep(100)
  10 +
  11 +print(0.5)
... ...
demo/astronomy/solar-system/public/earth.jpg 0 → 100644

1.02 MB

demo/astronomy/solar-system/public/jupiter.gif 0 → 100644

2.96 MB

demo/astronomy/solar-system/public/planets.png 0 → 100644

419 KB

demo/astronomy/solar-system/public/saturn.jpg 0 → 100644

117 KB

demo/astronomy/solar-system/questions.yaml 0 → 100644
... ... @@ -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 + ![Terra](earth.jpg)
  14 +
  15 +# ----------------------------------------------------------------------------
  16 +- type: radio
  17 + ref: largest-planet
  18 + title: Sistema solar
  19 + text: |
  20 + ![planetas](planets.png "Planetas do Sistema Solar")
  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 + ![Júpiter](jupiter.gif)
  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 + ![Saturno](saturn.jpg)
  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.
... ...
demo/courses.yaml 0 → 100644
... ... @@ -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
demo/math.yaml 0 → 100644
... ... @@ -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
... ...
demo/math/addition/addition-two-digits.py 0 → 100755
... ... @@ -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}.''')
... ...
demo/math/addition/questions.yaml 0 → 100644
... ... @@ -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)
demo/math/multiplication/multiplication-table.py 0 → 100755
... ... @@ -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}. $$''')
... ...
demo/math/multiplication/questions.yaml 0 → 100644
... ... @@ -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.
... ...
demo/math/prime-numbers/questions.yaml 0 → 100644
... ... @@ -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
... ... @@ -1,11 +0,0 @@
1   -#!/usr/bin/env python3
2   -
3   -import sys
4   -import time
5   -
6   -s = sys.stdin.read()
7   -
8   -# generate timeout
9   -time.sleep(100)
10   -
11   -print(0.5)
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   - ![Terra](earth.jpg)
16   -
17   -# ---------------------------------------------------------------------------
18   -- type: radio
19   - ref: solar-system
20   - title: Sistema solar
21   - text: |
22   - ![planetas](planets.png " Planetas do Sistema Solar")
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   - ![Júpiter](jupiter.gif)
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   - ![Saturno](saturn.jpg)
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.