Commit e5b363cc4798215fd26929007d22ef949ff9a235
1 parent
8f63323e
Exists in
master
and in
1 other branch
- checkbox options can have a pair (right,wrong) so that one is randomly selected.
- checkbox also has new 'choose' to limit the number of options selected.
Showing
4 changed files
with
70 additions
and
8 deletions
Show diff stats
BUGS.md
| ... | ... | @@ -6,14 +6,48 @@ |
| 6 | 6 | - on start topic, logs show questionhandler.get() twice. |
| 7 | 7 | - detect questions in questions.yaml without ref -> error ou generate default. |
| 8 | 8 | - Criar outra estrutura organizada em capítulos (conjuntos de tópicos). Permitir capítulos de capítulos, etc. talvez usar grafos de grafos... |
| 9 | -- session management. close after inactive time. | |
| 10 | 9 | - generators not working: bcrypt (ver blog) |
| 11 | 10 | - tabelas nas perguntas radio/checkbox não ocupam todo o espaço como em question. |
| 12 | 11 | |
| 13 | 12 | # TODO |
| 14 | 13 | |
| 15 | -- adicionar codigo para radio e checkboxes onde em vez de se dar uma lista de opcoes, dão-se 2 listas uma de opcoes correctas e outra de erradas. | |
| 14 | +```yaml | |
| 15 | +# constroi pergunta com opções indicadas e pontuação dada em baixo caso resposta | |
| 16 | +# marque essa opção ou o seu simétrico se não marcar | |
| 17 | +type: checkbox | |
| 18 | +options: | |
| 19 | + - option1 | |
| 20 | + - option2 | |
| 21 | + - option3 | |
| 22 | +correct: [1, -1, 1] | |
| 23 | + | |
| 24 | + | |
| 25 | +# Para cada opção escolhe aleatoriamente right ou wrong e controi pergunta como | |
| 26 | +# em cima. A ideia é wrongN ser a mesma pergunta mas ao contrário de rightN. | |
| 27 | +type: checkbox | |
| 28 | +options: | |
| 29 | + - [right1, wrong1] | |
| 30 | + - [right2, wrong2] | |
| 31 | + - [right3, wrong3] | |
| 32 | +correct: [1, 1.5, 1] | |
| 33 | + | |
| 34 | + | |
| 35 | +# randomly choose 1 right_option and choose more 2 from wrong_options. | |
| 36 | +type: radio | |
| 37 | +right_options: | |
| 38 | + - right1 | |
| 39 | + - right2 | |
| 40 | + - right3 | |
| 41 | +wrong_options: | |
| 42 | + - wrong1 | |
| 43 | + - wrong2 | |
| 44 | + - wrong3 | |
| 45 | + - wrong4 | |
| 46 | +choose: 3 | |
| 47 | +``` | |
| 48 | + | |
| 16 | 49 | - servir imagens/ficheiros. |
| 50 | +- session management. close after inactive time. | |
| 17 | 51 | - each topic only loads a sample of K questions (max) in random order. |
| 18 | 52 | - radio e checkboxes, aceitar numeros como seleccao das opcoes. |
| 19 | 53 | - reload das perguntas enquanto online. ver signal em http://stackabuse.com/python-async-await-tutorial/ |
| ... | ... | @@ -31,6 +65,7 @@ |
| 31 | 65 | |
| 32 | 66 | # FIXED |
| 33 | 67 | |
| 68 | +- checkbox: cada opção pode ser uma dupla (certo, errado) sendo escolhida uma aleatória. | |
| 34 | 69 | - async/threadpool no bcrypt do initdb. |
| 35 | 70 | - numero de estrelas depende da proporcao entre certas e erradas. |
| 36 | 71 | - image brand da universidade está esbatida. | ... | ... |
demo/math/questions.yaml
| 1 | +- ref: numbers | |
| 2 | + type: checkbox | |
| 3 | + title: Números pares e primos | |
| 4 | + text: Indique as afirmações verdadeiras. | |
| 5 | + options: | |
| 6 | + - ['3 é primo', '4 é primo'] | |
| 7 | + - ['2 é par', '3 é par'] | |
| 8 | + - ['1 é ímpar', '2 é ímpar'] | |
| 9 | + correct: [1,1,1] | |
| 10 | + | |
| 1 | 11 | - |
| 2 | 12 | ref: prime_numbers |
| 3 | 13 | type: radio | ... | ... |
knowledge.py
| ... | ... | @@ -31,12 +31,14 @@ class StudentKnowledge(object): |
| 31 | 31 | self.topic_sequence = self.recommend_topic_sequence() # ['a', 'b', ...] |
| 32 | 32 | self.unlock_topics() |
| 33 | 33 | |
| 34 | + | |
| 34 | 35 | # ------------------------------------------------------------------------ |
| 35 | 36 | # compute recommended sequence of topics ['a', 'b', ...] |
| 36 | 37 | # ------------------------------------------------------------------------ |
| 37 | 38 | def recommend_topic_sequence(self): |
| 38 | 39 | return list(nx.topological_sort(self.deps)) |
| 39 | 40 | |
| 41 | + | |
| 40 | 42 | # ------------------------------------------------------------------------ |
| 41 | 43 | # Updates the proficiency levels of the topics, with forgetting factor |
| 42 | 44 | # FIXME no dependencies are considered yet... |
| ... | ... | @@ -105,6 +107,7 @@ class StudentKnowledge(object): |
| 105 | 107 | self.current_question['start_time'] = datetime.now() |
| 106 | 108 | return True |
| 107 | 109 | |
| 110 | + | |
| 108 | 111 | # ------------------------------------------------------------------------ |
| 109 | 112 | # The topic has finished and there are no more questions. |
| 110 | 113 | # The topic level is updated in state and unlocks are performed. |
| ... | ... | @@ -164,6 +167,7 @@ class StudentKnowledge(object): |
| 164 | 167 | def get_current_question(self): |
| 165 | 168 | return self.current_question |
| 166 | 169 | |
| 170 | + # ------------------------------------------------------------------------ | |
| 167 | 171 | def get_finished_questions(self): |
| 168 | 172 | return self.finished_questions |
| 169 | 173 | ... | ... |
questions.py
| ... | ... | @@ -122,6 +122,7 @@ class QuestionCheckbox(Question): |
| 122 | 122 | shuffle (bool, default True) |
| 123 | 123 | correct (list of floats) |
| 124 | 124 | discount (bool, default True) |
| 125 | + choose (int) | |
| 125 | 126 | answer (None or an actual answer) |
| 126 | 127 | ''' |
| 127 | 128 | |
| ... | ... | @@ -137,18 +138,30 @@ class QuestionCheckbox(Question): |
| 137 | 138 | 'correct': [0.0] * n, # useful for questionaries |
| 138 | 139 | 'shuffle': True, |
| 139 | 140 | 'discount': True, |
| 141 | + 'choose': n, # number of options | |
| 140 | 142 | }) |
| 141 | 143 | |
| 142 | 144 | if len(self['correct']) != n: |
| 143 | - logger.error(f'Number of options and correct mismatch in "{self["ref"]}", file "{self["filename"]}".') | |
| 145 | + logger.error(f'Options and correct size mismatch in "{self["ref"]}", file "{self["filename"]}".') | |
| 146 | + | |
| 147 | + # if a option is a list of (right, wrong), pick one | |
| 148 | + # FIXME it's possible that all options are chosen wrong | |
| 149 | + options = [] | |
| 150 | + correct = [] | |
| 151 | + for o,c in zip(self['options'], self['correct']): | |
| 152 | + if isinstance(o, list): | |
| 153 | + r = random.randint(0,1) | |
| 154 | + o = o[r] | |
| 155 | + c = c if r==0 else -c | |
| 156 | + options.append(str(o)) | |
| 157 | + correct.append(float(c)) | |
| 144 | 158 | |
| 145 | 159 | # generate random permutation, e.g. [2,1,4,0,3] |
| 146 | 160 | # and apply to `options` and `correct` |
| 147 | 161 | if self['shuffle']: |
| 148 | - perm = list(range(n)) | |
| 149 | - random.shuffle(perm) | |
| 150 | - self['options'] = [ str(self['options'][i]) for i in perm ] | |
| 151 | - self['correct'] = [ float(self['correct'][i]) for i in perm ] | |
| 162 | + perm = random.sample(range(n), self['choose']) | |
| 163 | + self['options'] = [options[i] for i in perm] | |
| 164 | + self['correct'] = [correct[i] for i in perm] | |
| 152 | 165 | |
| 153 | 166 | #------------------------------------------------------------------------ |
| 154 | 167 | # can return negative values for wrong answers |
| ... | ... | @@ -316,7 +329,7 @@ class QuestionTextArea(Question): |
| 316 | 329 | |
| 317 | 330 | if self['answer'] is not None: |
| 318 | 331 | # correct answer |
| 319 | - out = run_script( | |
| 332 | + out = run_script( # and parse yaml ouput | |
| 320 | 333 | script=self['correct'], |
| 321 | 334 | stdin=self['answer'], |
| 322 | 335 | timeout=self['timeout'] | ... | ... |