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