def establish_key_dict( answer_keys: data_exporting.OutputSheet ) -> typing.Dict[str, typing.List[str]]: """Takes the matrix of answer keys and transforms it into a dictionary that maps the test form codes to the list of correct answers. Treats the answer_keys data naively by assuming the following: * The column with the form codes comes before the answer columns. * The first answer column is named "Q1". * The answers are all in order. If these are wrong, the results will be incorrect. Also note: the returned list of answers matches the order of the questions, but the questions are named "Q1" through "Q75" and the answers are in indexes 0 through 74. """ keys = answer_keys.data form_code_column_name = data_exporting.COLUMN_NAMES[ grid_info.Field.TEST_FORM_CODE] try: # Get the index of the column that had the test form codes form_code_index = list_utils.find_index(keys[0], form_code_column_name) # After the test form codes column, search for the 1st question column # index answers_start_index = list_utils.find_index( keys[0][form_code_index + 1:], "Q1") + form_code_index + 1 except StopIteration: raise ValueError( "Invalid key matrix passed to scoring functions. Test form code column must be prior to answers columns, which must be named 'Q1' through 'QN'." ) return { key[form_code_index]: key[answers_start_index:] for key in keys[1:] }
def verify_answer_key_sheet(file_path: pathlib.Path) -> bool: try: with open(str(file_path), newline='') as file: reader = csv.reader(file) keys_column_name = data_exporting.COLUMN_NAMES[ grid_info.Field.TEST_FORM_CODE] names = next(reader) keys_column_name_index = list_utils.find_index( names, keys_column_name) list_utils.find_index(names[keys_column_name_index:], "Q1") return True except Exception: return False
def reorder(self, arrangement_file: pathlib.Path): """Reorder the sheet based on an arrangement map file. Raises TypeError if invalid arrangement file. Results will have empty form code index.""" # TODO: Validate arrangement file. # order_map will be a dict matching form code keys to a list where the # new index of question `i` in `key` is `order_map[key][i]` order_map: tp.Dict[str, tp.List[int]] = {} validate_order_map(order_map, self.num_questions) with open(str(arrangement_file), 'r', newline='') as file: reader = csv.reader(file) names = list_utils.strip_all(next(reader)) form_code_index = list_utils.find_index( names, COLUMN_NAMES[Field.TEST_FORM_CODE]) first_answer_index = list_utils.find_index(names, "Q1") for form in reader: stripped_form = list_utils.strip_all(form) form_code = stripped_form[form_code_index] to_order_zero_ind = [ int(n) - 1 for n in stripped_form[first_answer_index:] ] order_map[form_code] = to_order_zero_ind sheet_form_code_index = list_utils.find_index( self.data[0], COLUMN_NAMES[Field.TEST_FORM_CODE]) sheet_first_answer_index = list_utils.find_index(self.data[0], "Q1") rearranged = [self.data[0]] for row in self.data[1:]: original_form_code = row[sheet_form_code_index] try: order_map[original_form_code] except KeyError: raise ValueError( f"Arrangement file is missing entry for key '{original_form_code}'." ) else: row_reordered = row[:sheet_first_answer_index] + [ row[ind + sheet_first_answer_index] for ind in order_map[original_form_code] ] row_reordered[sheet_form_code_index] = "" rearranged.append(row_reordered) self.data = rearranged
def establish_key_dict(answer_keys: data_exporting.OutputSheet ) -> tp.Dict[str, tp.List[str]]: """Takes the matrix of answer keys and transforms it into a dictionary that maps the test form codes to the list of correct answers. Treats the answer_keys data naively by assuming the following: * The column with the form codes comes before the answer columns. * The first answer column is named "Q1". * The answers are all in order. If these are wrong, the results will be incorrect. Also note: the returned list of answers matches the order of the questions, but the questions are named "Q1" through "Q75" and the answers are in indexes 0 through 74. """ try: answers_start_index = list_utils.find_index(answer_keys.data[0], "Q1") except StopIteration: raise ValueError( "Invalid key matrix passed to scoring functions. Answers columns must be named 'Q1' through 'QN'." ) return { get_key_form_code(answer_keys, index): key[answers_start_index:] for index, key in enumerate(answer_keys.data[1:]) }
def score_results(results: data_exporting.OutputSheet, answer_keys: data_exporting.OutputSheet, num_questions: int) -> data_exporting.OutputSheet: answers = results.data keys = establish_key_dict(answer_keys) form_code_column_name = data_exporting.COLUMN_NAMES[ grid_info.Field.TEST_FORM_CODE] form_code_index = list_utils.find_index(answers[0], form_code_column_name) answers_start_index = list_utils.find_index( answers[0][form_code_index + 1:], "Q1") + form_code_index + 1 virtual_fields: tp.List[grid_info.RealOrVirtualField] = [ grid_info.VirtualField.SCORE, grid_info.VirtualField.POINTS ] columns = results.field_columns + virtual_fields scored_results = data_exporting.OutputSheet(columns, num_questions) for exam in answers[1:]: # Skip header row fields = { k: v for k, v in zip(results.field_columns, exam[:answers_start_index]) } form_code = exam[form_code_index] try: if "*" in keys: key = keys["*"] else: key = keys[form_code] except KeyError: fields[grid_info.VirtualField. SCORE] = data_exporting.KEY_NOT_FOUND_MESSAGE fields[grid_info.VirtualField. POINTS] = data_exporting.KEY_NOT_FOUND_MESSAGE scored_answers = [] else: scored_answers = [ int(actual == correct) for actual, correct in zip(exam[answers_start_index:], key) ] fields[grid_info.VirtualField.SCORE] = str( round(math_utils.mean(scored_answers) * 100, 2)) fields[grid_info.VirtualField.POINTS] = str(sum(scored_answers)) string_scored_answers = [str(s) for s in scored_answers] scored_results.add(fields, string_scored_answers) return scored_results
def save_reordered_version(sheet: OutputSheet, arrangement_file: pathlib.Path, save_path: pathlib.Path, filebasename: str, timestamp: datetime) -> pathlib.PurePath: """Reorder the output sheet based on a key arrangement file and save CSV.""" # order_map will be a dict matching form code keys to a list where the # new index of question `i` in `key` is `order_map[key][i]` order_map: typing.Dict[str, typing.List[int]] = {} with open(str(arrangement_file), 'r', newline='') as file: reader = csv.reader(file) names = next(reader) form_code_index = list_utils.find_index( names, COLUMN_NAMES[Field.TEST_FORM_CODE]) first_answer_index = list_utils.find_index(names, "Q1") for form in reader: form_code = form[form_code_index] to_order_zero_ind = [int(n) - 1 for n in form[first_answer_index:]] order_map[form_code] = to_order_zero_ind sheet_form_code_index = list_utils.find_index( sheet.data[0], COLUMN_NAMES[Field.TEST_FORM_CODE]) sheet_first_answer_index = list_utils.find_index(sheet.data[0], "Q1") sheet_total_score_index = list_utils.find_index( sheet.data[0], COLUMN_NAMES[VirtualField.SCORE]) results = [sheet.data[0]] for row in sheet.data[1:]: form_code = row[sheet_form_code_index] if row[sheet_total_score_index] != KEY_NOT_FOUND_MESSAGE: try: order_map[form_code] except IndexError: results.append(row) else: row_reordered = row[:sheet_first_answer_index] + [ row[ind + sheet_first_answer_index] for ind in order_map[form_code] ] results.append(row_reordered) else: results.append(row) output_path = save_path / f"{format_timestamp_for_file(timestamp)}__{filebasename}.csv" with open(str(output_path), 'w+', newline='') as output_file: writer = csv.writer(output_file) writer.writerows(results) return output_path
def get_key_form_code(answer_keys: data_exporting.OutputSheet, index: int) -> str: """Gets the form code of the answer key at the index given, where the first answer key has index=0.""" keys = answer_keys.data form_code_column_name = data_exporting.COLUMN_NAMES[ grid_info.Field.TEST_FORM_CODE] try: # Get the index of the column that had the test form codes form_code_index = list_utils.find_index(keys[0], form_code_column_name) return keys[index + 1][form_code_index] except StopIteration: return "*"
def sortByName(self): data = self.data[1:] col_names = self.data[0] try: primary_index = list_utils.find_index( col_names, COLUMN_NAMES[Field.LAST_NAME]) secondary_index = list_utils.find_index( col_names, COLUMN_NAMES[Field.FIRST_NAME]) tertiary_index = list_utils.find_index( col_names, COLUMN_NAMES[Field.MIDDLE_NAME]) except StopIteration: try: primary_index = list_utils.find_index( col_names, COLUMN_NAMES[Field.TEST_FORM_CODE]) secondary_index = None tertiary_index = None except StopIteration: return if tertiary_index is not None: data = sorted(data, key=operator.itemgetter(tertiary_index)) if secondary_index is not None: data = sorted(data, key=operator.itemgetter(secondary_index)) data = sorted(data, key=operator.itemgetter(primary_index)) self.data = [col_names] + data