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 |