def get_other_answers_simple(pool, seeded_answers, get_student_item_dict, num_responses): """ Get answers from others with simple algorithm, which picks one answer for each option. Args: see `get_other_answers` num_responses (int): the number of responses to be returned. This value may not be respected if there is not enough answers to return Returns: dict: answers based on the selection algorithm """ ret = [] # clean up answers so that all keys are int pool = {int(k): v for k, v in pool.items()} total_in_pool = len(seeded_answers) merged_pool = convert_seeded_answers(seeded_answers) student_id = get_student_item_dict()['student_id'] # merge the dictionaries in the answer dictionary for key in pool: total_in_pool += len(pool[key]) # if student_id has value, we assume the student just submitted an answer. So removing it # from total number in the pool if student_id in pool[key].keys(): total_in_pool -= 1 if key in merged_pool: merged_pool[key].update(pool[key].items()) else: merged_pool[key] = pool[key] # remember which option+student_id is selected, so that we don't have duplicates in the result selected = [] # loop until we have enough answers to return while len(ret) < min(num_responses, total_in_pool): for option, students in merged_pool.items(): student = student_id i = 0 while (student == student_id or i > 100) and (str(option) + student) not in selected: # retry until we got a different one or after 100 retries # we are suppose to get a different student answer or a seeded one in a few tries # as we have at least one seeded answer for each option in the algo. And it is not # suppose to overflow i order to break the loop student = random.choice(students.keys()) i += 1 selected.append(str(option)+student) if student.startswith('seeded'): # seeded answer, get the rationale from local rationale = students[student] else: student_item = get_student_item_dict(student) submission = sas_api.get_answers_for_student(student_item) rationale = submission.get_rationale(0) ret.append({'option': option, 'rationale': rationale}) # check if we have enough answers if len(ret) >= min(num_responses, total_in_pool): break return {"answers": ret}
def get_other_answers_simple(pool, seeded_answers, get_student_item_dict, num_responses): """ Get answers from others with simple algorithm, which picks one answer for each option. Args: see `get_other_answers` num_responses (int): the number of responses to be returned. This value may not be respected if there is not enough answers to return Returns: dict: answers based on the selection algorithm """ ret = [] # clean up answers so that all keys are int pool = {int(k): v for k, v in pool.items()} merged_pool = convert_seeded_answers(seeded_answers) student_id = get_student_item_dict()['student_id'] # merge the dictionaries in the answer dictionary for key in pool: merged_pool.setdefault(key, {}) merged_pool[key].update(pool[key]) # Pop student's own answer, if exists merged_pool[key].pop(student_id, None) # loop until we have enough answers to return or when there is nothing more to return while len(ret) < num_responses and merged_pool: for option, students in merged_pool.items(): rationale = None while students: student = random.choice(students.keys()) # remove the chosen answer from pool content = students.pop(student, None) if student.startswith('seeded'): # seeded answer, get the rationale from local rationale = content else: student_item = get_student_item_dict(student) submission = sas_api.get_answers_for_student(student_item) # Make sure the answer is still the one we want. # It may have changed (e.g. instructor deleted the student state # and the student re-submitted a diff answer) if submission.has_revision(0) and submission.get_vote( 0) == option: rationale = submission.get_rationale(0) if rationale: ret.append({'option': option, 'rationale': rationale}) break if not students: del merged_pool[option] # check if we have enough answers if len(ret) >= num_responses: break return {"answers": ret}
def get_other_answers_random(pool, seeded_answers, get_student_item_dict, num_responses): """ Get answers from others with random algorithm, which randomly select answer from the pool. Student may get three answers for option 1 or one answer for option 1 and two answers for option 2. Args: see `get_other_answers` num_responses (int): the number of responses to be returned. This value may not be respected if there is not enough answers to return Returns: dict: answers based on the selection algorithm """ ret = [] # clean up answers so that all keys are int pool = {int(k): v for k, v in pool.items()} seeded = { 'seeded' + str(index): answer for index, answer in enumerate(seeded_answers) } merged_pool = seeded.keys() for key in pool: merged_pool += pool[key].keys() # shuffle random.shuffle(merged_pool) # get student identifier student_id = get_student_item_dict()['student_id'] for student in merged_pool: if len(ret) >= num_responses: # have enough answers break elif student == student_id: # this is the student's answer so don't return continue if student.startswith('seeded'): option = seeded[student]['answer'] rationale = seeded[student]['rationale'] else: student_item = get_student_item_dict(student) submission = sas_api.get_answers_for_student(student_item) if submission.has_revision(0): rationale = submission.get_rationale(0) option = submission.get_vote(0) else: continue ret.append({'option': option, 'rationale': rationale}) return {"answers": ret}
def get_other_answers_random(pool, seeded_answers, get_student_item_dict, num_responses): """ Get answers from others with random algorithm, which randomly select answer from the pool. Student may get three answers for option 1 or one answer for option 1 and two answers for option 2. Args: see `get_other_answers` num_responses (int): the number of responses to be returned. This value may not be respected if there is not enough answers to return Returns: dict: answers based on the selection algorithm """ ret = [] # clean up answers so that all keys are int pool = {int(k): v for k, v in pool.items()} seeded = {'seeded'+str(index): answer for index, answer in enumerate(seeded_answers)} merged_pool = seeded.keys() for key in pool: merged_pool += pool[key].keys() # shuffle random.shuffle(merged_pool) # get student identifier student_id = get_student_item_dict()['student_id'] for student in merged_pool: if len(ret) >= num_responses: # have enough answers break elif student == student_id: # this is the student's answer so don't return continue if student.startswith('seeded'): option = seeded[student]['answer'] rationale = seeded[student]['rationale'] else: student_item = get_student_item_dict(student) submission = sas_api.get_answers_for_student(student_item) rationale = submission.get_rationale(0) option = submission.get_vote(0) ret.append({'option': option, 'rationale': rationale}) return {"answers": ret}
def get_answers_for_student(self): """ Retrieve answers from backend for current user """ return sas_api.get_answers_for_student(self.get_student_item_dict())
def refresh_answers(answers_shown, option, pool, seeded_answers, get_student_item_dict, seeded_first=False): """ Refresh the answers shown for given option Args: answers_shown (dict): answers being shown that need to be refreshed. Format: {'answers': [ {'option': 0, 'rationale': 'rationale A'}, {'option': 1, 'rationale': 'rationale B'}, ]} option (int): the option to refresh pool (dict): answer pool, format: { option1_index: { student_id: { can store algorithm specific info here } }, option2_index: { student_id: { ... } } } seeded_answers (list): seeded answers from instructor [ {'answer': 0, 'rationale': 'rationale A'}, {'answer': 1, 'rationale': 'rationale B'}, ] get_student_item_dict (callable): get student item dict function to return student item dict seeded_first (boolean): refresh with answers from seeded_answers first, when exhausted, pick from pool Returns: dict: refreshed answers lists { 'answers': [ {'option': 0, 'rationale': 'rationale A'}, {'option': 1, 'rationale': 'rationale B'}, ] } """ ret = copy.deepcopy(answers_shown) # clean up answers so that all keys are int pool = {int(k): v for k, v in pool.items()} seeded_pool = convert_seeded_answers(seeded_answers) student_id = get_student_item_dict()['student_id'] available_students = copy.deepcopy(pool.get(option, {})) available_students.pop(student_id, None) # if seed answers have higher priority, fill the available seeds. # otherwise merge them into available students available_seeds = {} if seeded_first and seeded_pool.get(option, {}): available_seeds = copy.deepcopy(seeded_pool.get(option, {})) else: for key in seeded_pool.get(option, {}): available_students[key] = seeded_pool.get(option, {}).get(key, None) for answer in ret.get('answers', []): if answer.get('option', None) == option: rationale = None while available_seeds: key = random.choice(available_seeds.keys()) rationale = available_seeds.pop(key, None) if rationale is not None: answer['rationale'] = rationale break while available_students and rationale is None: key = random.choice(available_students.keys()) # remove the chosen answer from pool content = available_students.pop(key, None) if key.startswith('seeded'): rationale = content else: student_item = get_student_item_dict(key) submission = sas_api.get_answers_for_student(student_item) # Make sure the answer is still the one we want. # It may have changed (e.g. instructor deleted the student state # and the student re-submitted a diff answer) if submission.has_revision(0) and submission.get_vote( 0) == option: rationale = submission.get_rationale(0) if rationale: answer['rationale'] = rationale break # random.shuffle(ret['answers']) return ret