Commit 89e08ad8067a6de0b3394f3b35399d2db2dc1b7e
1 parent
bbe90cad
Exists in
master
and in
1 other branch
- added missing comments.html template.
- added missing finished_topic.html template. - support information "questions" again. They are useful to finish chapters.
Showing
6 changed files
with
49 additions
and
24 deletions
Show diff stats
knowledge.py
@@ -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() |
learnapp.py
@@ -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 | # ------------------------------------------------------------------------ |
serve.py
@@ -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 |
tools.py
@@ -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 |