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 | ... | ... |