Commit e5b363cc4798215fd26929007d22ef949ff9a235

Authored by Miguel Barão
1 parent 8f63323e
Exists in master and in 1 other branch dev

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