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 | 15 | |
| 16 | 16 | # ---------------------------------------------------------------------------- |
| 17 | 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 | 23 | class StudentKnowledge(object): |
| 20 | 24 | # ======================================================================= |
| 21 | 25 | # methods that update state |
| 22 | 26 | # ======================================================================= |
| 23 | 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 | 45 | now = datetime.now() |
| 33 | - for s in state.values(): | |
| 46 | + for s in self.state.values(): | |
| 34 | 47 | dt = now - s['date'] |
| 35 | 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 | 52 | # Unlock topics whose dependencies are satisfied (> min_level) |
| ... | ... | @@ -86,7 +95,8 @@ class StudentKnowledge(object): |
| 86 | 95 | try: |
| 87 | 96 | self.current_question = self.questions.pop(0) # FIXME crash if empty |
| 88 | 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 | 100 | return False |
| 91 | 101 | else: |
| 92 | 102 | self.current_question['start_time'] = datetime.now() | ... | ... |
learnapp.py
| ... | ... | @@ -104,7 +104,7 @@ class LearnApp(object): |
| 104 | 104 | # ------------------------------------------------------------------------ |
| 105 | 105 | def check_answer(self, uid, answer): |
| 106 | 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 | 109 | if knowledge.get_current_question() is None: |
| 110 | 110 | # finished topic, save into database |
| ... | ... | @@ -129,6 +129,7 @@ class LearnApp(object): |
| 129 | 129 | a.date = date |
| 130 | 130 | |
| 131 | 131 | s.add(a) |
| 132 | + logger.debug(f'Saved topic "{finished_topic}" into database') | |
| 132 | 133 | |
| 133 | 134 | # save answered questions from finished_questions list |
| 134 | 135 | s.add_all([ |
| ... | ... | @@ -140,7 +141,7 @@ class LearnApp(object): |
| 140 | 141 | student_id=uid, |
| 141 | 142 | topic_id=finished_topic) |
| 142 | 143 | for q in finished_questions]) |
| 143 | - | |
| 144 | + logger.debug(f'Saved {len(finished_questions)} answers into database') | |
| 144 | 145 | return grade |
| 145 | 146 | |
| 146 | 147 | # ------------------------------------------------------------------------ | ... | ... |
serve.py
| ... | ... | @@ -169,9 +169,9 @@ class QuestionHandler(BaseHandler): |
| 169 | 169 | 'numeric-interval': 'question-text.html', |
| 170 | 170 | 'textarea': 'question-textarea.html', |
| 171 | 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 | 175 | # 'warning': '', FIXME |
| 176 | 176 | # 'warn': '', FIXME |
| 177 | 177 | # 'alert': '', FIXME |
| ... | ... | @@ -270,15 +270,19 @@ def main(): |
| 270 | 270 | logging.info('====================================================') |
| 271 | 271 | |
| 272 | 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 | 280 | # --- create web application |
| 277 | 281 | logging.info('Starting Web App (tornado)') |
| 278 | 282 | try: |
| 279 | 283 | webapp = WebApplication(learnapp, debug=arg.debug) |
| 280 | 284 | except Exception as e: |
| 281 | - logging.critical('Failed to start application.') | |
| 285 | + logging.critical('Failed to start web application.') | |
| 282 | 286 | raise e |
| 283 | 287 | |
| 284 | 288 | # --- create webserver | ... | ... |
tools.py
| ... | ... | @@ -137,18 +137,18 @@ def load_yaml(filename, default=None): |
| 137 | 137 | try: |
| 138 | 138 | f = open(filename, 'r', encoding='utf-8') |
| 139 | 139 | except FileNotFoundError: |
| 140 | - logger.error(f'Can\'t open "{filename}": not found.') | |
| 140 | + logger.error(f'Can\'t open "{filename}": not found') | |
| 141 | 141 | except PermissionError: |
| 142 | - logger.error(f'Can\'t open "{filename}": no permission.') | |
| 142 | + logger.error(f'Can\'t open "{filename}": no permission') | |
| 143 | 143 | except IOError: |
| 144 | - logger.error(f'Can\'t open file "{filename}".') | |
| 144 | + logger.error(f'Can\'t open file "{filename}"') | |
| 145 | 145 | else: |
| 146 | 146 | with f: |
| 147 | 147 | try: |
| 148 | 148 | default = yaml.load(f) |
| 149 | 149 | except yaml.YAMLError as e: |
| 150 | 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 | 152 | finally: |
| 153 | 153 | return default |
| 154 | 154 | ... | ... |