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,14 +6,48 @@ | ||
6 | - on start topic, logs show questionhandler.get() twice. | 6 | - on start topic, logs show questionhandler.get() twice. |
7 | - detect questions in questions.yaml without ref -> error ou generate default. | 7 | - detect questions in questions.yaml without ref -> error ou generate default. |
8 | - Criar outra estrutura organizada em capítulos (conjuntos de tópicos). Permitir capítulos de capítulos, etc. talvez usar grafos de grafos... | 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 | - generators not working: bcrypt (ver blog) | 9 | - generators not working: bcrypt (ver blog) |
11 | - tabelas nas perguntas radio/checkbox não ocupam todo o espaço como em question. | 10 | - tabelas nas perguntas radio/checkbox não ocupam todo o espaço como em question. |
12 | 11 | ||
13 | # TODO | 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 | - servir imagens/ficheiros. | 49 | - servir imagens/ficheiros. |
50 | +- session management. close after inactive time. | ||
17 | - each topic only loads a sample of K questions (max) in random order. | 51 | - each topic only loads a sample of K questions (max) in random order. |
18 | - radio e checkboxes, aceitar numeros como seleccao das opcoes. | 52 | - radio e checkboxes, aceitar numeros como seleccao das opcoes. |
19 | - reload das perguntas enquanto online. ver signal em http://stackabuse.com/python-async-await-tutorial/ | 53 | - reload das perguntas enquanto online. ver signal em http://stackabuse.com/python-async-await-tutorial/ |
@@ -31,6 +65,7 @@ | @@ -31,6 +65,7 @@ | ||
31 | 65 | ||
32 | # FIXED | 66 | # FIXED |
33 | 67 | ||
68 | +- checkbox: cada opção pode ser uma dupla (certo, errado) sendo escolhida uma aleatória. | ||
34 | - async/threadpool no bcrypt do initdb. | 69 | - async/threadpool no bcrypt do initdb. |
35 | - numero de estrelas depende da proporcao entre certas e erradas. | 70 | - numero de estrelas depende da proporcao entre certas e erradas. |
36 | - image brand da universidade está esbatida. | 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 | ref: prime_numbers | 12 | ref: prime_numbers |
3 | type: radio | 13 | type: radio |
knowledge.py
@@ -31,12 +31,14 @@ class StudentKnowledge(object): | @@ -31,12 +31,14 @@ class StudentKnowledge(object): | ||
31 | self.topic_sequence = self.recommend_topic_sequence() # ['a', 'b', ...] | 31 | self.topic_sequence = self.recommend_topic_sequence() # ['a', 'b', ...] |
32 | self.unlock_topics() | 32 | self.unlock_topics() |
33 | 33 | ||
34 | + | ||
34 | # ------------------------------------------------------------------------ | 35 | # ------------------------------------------------------------------------ |
35 | # compute recommended sequence of topics ['a', 'b', ...] | 36 | # compute recommended sequence of topics ['a', 'b', ...] |
36 | # ------------------------------------------------------------------------ | 37 | # ------------------------------------------------------------------------ |
37 | def recommend_topic_sequence(self): | 38 | def recommend_topic_sequence(self): |
38 | return list(nx.topological_sort(self.deps)) | 39 | return list(nx.topological_sort(self.deps)) |
39 | 40 | ||
41 | + | ||
40 | # ------------------------------------------------------------------------ | 42 | # ------------------------------------------------------------------------ |
41 | # Updates the proficiency levels of the topics, with forgetting factor | 43 | # Updates the proficiency levels of the topics, with forgetting factor |
42 | # FIXME no dependencies are considered yet... | 44 | # FIXME no dependencies are considered yet... |
@@ -105,6 +107,7 @@ class StudentKnowledge(object): | @@ -105,6 +107,7 @@ class StudentKnowledge(object): | ||
105 | self.current_question['start_time'] = datetime.now() | 107 | self.current_question['start_time'] = datetime.now() |
106 | return True | 108 | return True |
107 | 109 | ||
110 | + | ||
108 | # ------------------------------------------------------------------------ | 111 | # ------------------------------------------------------------------------ |
109 | # The topic has finished and there are no more questions. | 112 | # The topic has finished and there are no more questions. |
110 | # The topic level is updated in state and unlocks are performed. | 113 | # The topic level is updated in state and unlocks are performed. |
@@ -164,6 +167,7 @@ class StudentKnowledge(object): | @@ -164,6 +167,7 @@ class StudentKnowledge(object): | ||
164 | def get_current_question(self): | 167 | def get_current_question(self): |
165 | return self.current_question | 168 | return self.current_question |
166 | 169 | ||
170 | + # ------------------------------------------------------------------------ | ||
167 | def get_finished_questions(self): | 171 | def get_finished_questions(self): |
168 | return self.finished_questions | 172 | return self.finished_questions |
169 | 173 |
questions.py
@@ -122,6 +122,7 @@ class QuestionCheckbox(Question): | @@ -122,6 +122,7 @@ class QuestionCheckbox(Question): | ||
122 | shuffle (bool, default True) | 122 | shuffle (bool, default True) |
123 | correct (list of floats) | 123 | correct (list of floats) |
124 | discount (bool, default True) | 124 | discount (bool, default True) |
125 | + choose (int) | ||
125 | answer (None or an actual answer) | 126 | answer (None or an actual answer) |
126 | ''' | 127 | ''' |
127 | 128 | ||
@@ -137,18 +138,30 @@ class QuestionCheckbox(Question): | @@ -137,18 +138,30 @@ class QuestionCheckbox(Question): | ||
137 | 'correct': [0.0] * n, # useful for questionaries | 138 | 'correct': [0.0] * n, # useful for questionaries |
138 | 'shuffle': True, | 139 | 'shuffle': True, |
139 | 'discount': True, | 140 | 'discount': True, |
141 | + 'choose': n, # number of options | ||
140 | }) | 142 | }) |
141 | 143 | ||
142 | if len(self['correct']) != n: | 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 | # generate random permutation, e.g. [2,1,4,0,3] | 159 | # generate random permutation, e.g. [2,1,4,0,3] |
146 | # and apply to `options` and `correct` | 160 | # and apply to `options` and `correct` |
147 | if self['shuffle']: | 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 | # can return negative values for wrong answers | 167 | # can return negative values for wrong answers |
@@ -316,7 +329,7 @@ class QuestionTextArea(Question): | @@ -316,7 +329,7 @@ class QuestionTextArea(Question): | ||
316 | 329 | ||
317 | if self['answer'] is not None: | 330 | if self['answer'] is not None: |
318 | # correct answer | 331 | # correct answer |
319 | - out = run_script( | 332 | + out = run_script( # and parse yaml ouput |
320 | script=self['correct'], | 333 | script=self['correct'], |
321 | stdin=self['answer'], | 334 | stdin=self['answer'], |
322 | timeout=self['timeout'] | 335 | timeout=self['timeout'] |