Commit 89e08ad8067a6de0b3394f3b35399d2db2dc1b7e

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

- added missing comments.html template.

- added missing finished_topic.html template.
- support information "questions" again. They are useful to finish chapters.
@@ -15,29 +15,38 @@ logger = logging.getLogger(__name__) @@ -15,29 +15,38 @@ logger = logging.getLogger(__name__)
15 15
16 # ---------------------------------------------------------------------------- 16 # ----------------------------------------------------------------------------
17 # kowledge state of each student....?? 17 # kowledge state of each student....??
  18 +# Contains:
  19 +# state - dict of topics with state of unlocked topics
  20 +# deps - dependency graph
  21 +# topic_sequence - list with the order of recommended topics
18 # ---------------------------------------------------------------------------- 22 # ----------------------------------------------------------------------------
19 class StudentKnowledge(object): 23 class StudentKnowledge(object):
20 # ======================================================================= 24 # =======================================================================
21 # methods that update state 25 # methods that update state
22 # ======================================================================= 26 # =======================================================================
23 def __init__(self, deps, state={}): 27 def __init__(self, deps, state={}):
24 - # graph with topic dependencies shared between all students  
25 - self.deps = deps 28 + self.deps = deps # dependency graph shared among students
  29 + self.state = state # {'topic': {'level':0.5, 'date': datetime}, ...}
  30 + self.update_topic_levels() # forgetting factor
  31 + self.topic_sequence = self.recommend_topic_sequence() # ['a', 'b', ...]
  32 + self.unlock_topics()
26 33
27 - # state only contains unlocked topics  
28 - # {'topic_id': {'level':0.5, 'date': datetime}, ...}  
29 - self.state = state 34 + # ------------------------------------------------------------------------
  35 + # compute recommended sequence of topics ['a', 'b', ...]
  36 + # ------------------------------------------------------------------------
  37 + def recommend_topic_sequence(self):
  38 + return list(nx.topological_sort(self.deps))
30 39
31 - # update state level based on the elapsed time (no dependencies... FIXME) 40 + # ------------------------------------------------------------------------
  41 + # Updates the proficiency levels of the topics, with forgetting factor
  42 + # FIXME no dependencies are considered yet...
  43 + # ------------------------------------------------------------------------
  44 + def update_topic_levels(self):
32 now = datetime.now() 45 now = datetime.now()
33 - for s in state.values(): 46 + for s in self.state.values():
34 dt = now - s['date'] 47 dt = now - s['date']
35 s['level'] *= 0.8 ** dt.days # forgetting factor 0.95 48 s['level'] *= 0.8 ** dt.days # forgetting factor 0.95
36 49
37 - # compute recommended sequence of topics ['a', 'b', ...]  
38 - self.topic_sequence = list(nx.topological_sort(self.deps))  
39 -  
40 - self.unlock_topics()  
41 50
42 # ------------------------------------------------------------------------ 51 # ------------------------------------------------------------------------
43 # Unlock topics whose dependencies are satisfied (> min_level) 52 # Unlock topics whose dependencies are satisfied (> min_level)
@@ -86,7 +95,8 @@ class StudentKnowledge(object): @@ -86,7 +95,8 @@ class StudentKnowledge(object):
86 try: 95 try:
87 self.current_question = self.questions.pop(0) # FIXME crash if empty 96 self.current_question = self.questions.pop(0) # FIXME crash if empty
88 except IndexError: 97 except IndexError:
89 - self.finish_topic() 98 + # self.current_question = None
  99 + self.finish_topic() # FIXME if no questions, what should be done?
90 return False 100 return False
91 else: 101 else:
92 self.current_question['start_time'] = datetime.now() 102 self.current_question['start_time'] = datetime.now()
@@ -104,7 +104,7 @@ class LearnApp(object): @@ -104,7 +104,7 @@ class LearnApp(object):
104 # ------------------------------------------------------------------------ 104 # ------------------------------------------------------------------------
105 def check_answer(self, uid, answer): 105 def check_answer(self, uid, answer):
106 knowledge = self.online[uid]['state'] 106 knowledge = self.online[uid]['state']
107 - grade = knowledge.check_answer(answer) 107 + grade = knowledge.check_answer(answer) # also moves to next question
108 108
109 if knowledge.get_current_question() is None: 109 if knowledge.get_current_question() is None:
110 # finished topic, save into database 110 # finished topic, save into database
@@ -129,6 +129,7 @@ class LearnApp(object): @@ -129,6 +129,7 @@ class LearnApp(object):
129 a.date = date 129 a.date = date
130 130
131 s.add(a) 131 s.add(a)
  132 + logger.debug(f'Saved topic "{finished_topic}" into database')
132 133
133 # save answered questions from finished_questions list 134 # save answered questions from finished_questions list
134 s.add_all([ 135 s.add_all([
@@ -140,7 +141,7 @@ class LearnApp(object): @@ -140,7 +141,7 @@ class LearnApp(object):
140 student_id=uid, 141 student_id=uid,
141 topic_id=finished_topic) 142 topic_id=finished_topic)
142 for q in finished_questions]) 143 for q in finished_questions])
143 - 144 + logger.debug(f'Saved {len(finished_questions)} answers into database')
144 return grade 145 return grade
145 146
146 # ------------------------------------------------------------------------ 147 # ------------------------------------------------------------------------
@@ -169,9 +169,9 @@ class QuestionHandler(BaseHandler): @@ -169,9 +169,9 @@ class QuestionHandler(BaseHandler):
169 'numeric-interval': 'question-text.html', 169 'numeric-interval': 'question-text.html',
170 'textarea': 'question-textarea.html', 170 'textarea': 'question-textarea.html',
171 # -- information panels -- 171 # -- information panels --
172 - # 'information': 'question-information.html',  
173 - # 'info': 'question-information.html',  
174 - # 'success': 'question-success.html', 172 + 'information': 'question-information.html',
  173 + 'info': 'question-information.html',
  174 + 'success': 'question-success.html',
175 # 'warning': '', FIXME 175 # 'warning': '', FIXME
176 # 'warn': '', FIXME 176 # 'warn': '', FIXME
177 # 'alert': '', FIXME 177 # 'alert': '', FIXME
@@ -270,15 +270,19 @@ def main(): @@ -270,15 +270,19 @@ def main():
270 logging.info('====================================================') 270 logging.info('====================================================')
271 271
272 # --- start application 272 # --- start application
273 - logging.info('Starting App.')  
274 - learnapp = LearnApp(arg.conffile[0]) 273 + logging.info('Starting App')
  274 + try:
  275 + learnapp = LearnApp(arg.conffile[0])
  276 + except Exception as e:
  277 + logging.critical('Failed to start backend application')
  278 + raise e
275 279
276 # --- create web application 280 # --- create web application
277 logging.info('Starting Web App (tornado)') 281 logging.info('Starting Web App (tornado)')
278 try: 282 try:
279 webapp = WebApplication(learnapp, debug=arg.debug) 283 webapp = WebApplication(learnapp, debug=arg.debug)
280 except Exception as e: 284 except Exception as e:
281 - logging.critical('Failed to start application.') 285 + logging.critical('Failed to start web application.')
282 raise e 286 raise e
283 287
284 # --- create webserver 288 # --- create webserver
templates/comments.html 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +{% autoescape %}
  2 +
  3 +{% if comments %}
  4 +<div class="card border-danger mb-3">
  5 + <div class="card-body text-danger">
  6 + <p class="card-text">{{md(comments)}}</p>
  7 + </div>
  8 +</div>
  9 +{% end %}
templates/finished_topic.html 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +<img src="/static/trophy.svg" alt="trophy" class="img-fluid mx-auto d-block" width="35%">
0 \ No newline at end of file 2 \ No newline at end of file
@@ -137,18 +137,18 @@ def load_yaml(filename, default=None): @@ -137,18 +137,18 @@ def load_yaml(filename, default=None):
137 try: 137 try:
138 f = open(filename, 'r', encoding='utf-8') 138 f = open(filename, 'r', encoding='utf-8')
139 except FileNotFoundError: 139 except FileNotFoundError:
140 - logger.error(f'Can\'t open "{filename}": not found.') 140 + logger.error(f'Can\'t open "{filename}": not found')
141 except PermissionError: 141 except PermissionError:
142 - logger.error(f'Can\'t open "{filename}": no permission.') 142 + logger.error(f'Can\'t open "{filename}": no permission')
143 except IOError: 143 except IOError:
144 - logger.error(f'Can\'t open file "{filename}".') 144 + logger.error(f'Can\'t open file "{filename}"')
145 else: 145 else:
146 with f: 146 with f:
147 try: 147 try:
148 default = yaml.load(f) 148 default = yaml.load(f)
149 except yaml.YAMLError as e: 149 except yaml.YAMLError as e:
150 mark = e.problem_mark 150 mark = e.problem_mark
151 - logger.error(f'In YAML file "{filename}" near line {mark.line}, column {mark.column+1}.') 151 + logger.error(f'In YAML file "{filename}" near line {mark.line}, column {mark.column+1}')
152 finally: 152 finally:
153 return default 153 return default
154 154