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'] | ... | ... |