# python standard library import random from datetime import datetime import logging # libraries import networkx as nx # this project import questions # setup logger for this module logger = logging.getLogger(__name__) # ---------------------------------------------------------------------------- # kowledge state of each student....?? # ---------------------------------------------------------------------------- class Knowledge(object): # ======================================================================= # methods that update state # ======================================================================= def __init__(self, depgraph, state={}, student=''): self.depgraph = depgraph self.state = state # {'topic_id': {'level':0.5, 'date': datetime}, ...} self.student = student # compute recommended sequence of topics ['a', 'b',...] self.topic_sequence = nx.topological_sort(self.depgraph) print(self.topic_sequence) print(self.depgraph.edges()) # select a topic to do and initialize questions self.start_topic() # ------------------------------------------------------------------------ # Start a new topic. If not provided, selects the first with level < 0.8 # If all levels > 0.8, will stay in the last one forever... # ------------------------------------------------------------------------ def start_topic(self, topic=None): if topic is None: for topic in self.topic_sequence: unlocked = topic in self.state needs_work = unlocked and self.state[topic]['level'] < 0.8 factory = self.depgraph.node[topic]['factory'] if needs_work and factory: break # logger.info(f'{self.student} skipped topic "{topic}"') else: factory = self.depgraph.node[topic]['factory'] # FIXME if factory is empty??? self.current_topic = topic logger.info(f'User "{self.student}" topic set to "{topic}"') # generate question instances for current topic questionlist = self.depgraph.node[topic]['questions'] factory = self.depgraph.node[topic]['factory'] self.questions = [factory[qref].generate() for qref in questionlist] self.current_question = self.questions.pop(0) self.current_question['start_time'] = datetime.now() self.finished_questions = [] # ------------------------------------------------------------------------ # returns the current question with correction, time and comments updated # ------------------------------------------------------------------------ def check_answer(self, answer): q = self.current_question q['finish_time'] = datetime.now() grade = q.correct(answer) logger.debug(f'User {self.student}: grade = {grade}') # new question if answer is correct if grade > 0.999: self.finished_questions.append(q) try: self.current_question = self.questions.pop(0) # FIXME empty? except IndexError: self.current_question = None self.state[self.current_topic] = { 'level': 1.0, 'date': datetime.now() } else: self.current_question['start_time'] = datetime.now() else: # FIXME debug this factory = self.depgraph.node[self.current_topic]['factory'] self.questions.append(factory[q['ref']].generate()) print([q['ref'] for q in self.questions]) return q # ======================================================================== # pure functions of the state (no side effects) # ======================================================================== # ------------------------------------------------------------------------ def get_current_question(self): return self.current_question # ------------------------------------------------------------------------ def get_current_topic(self): return self.current_topic # ------------------------------------------------------------------------ def get_knowledge_state(self): # [('topic', 0.9), ...] ts = [] for t in self.topic_sequence: if t in self.state: ts.append((t, self.state[t]['level'])) else: ts.append((t, 0.0)) return ts # ------------------------------------------------------------------------ def get_topic_progress(self): return len(self.finished_questions) / (1 + len(self.finished_questions) + len(self.questions))