def check_chart(state, extra_msg=None): solution_chart = find_chart(state.solution_data["charts"], state.sct_range) if len(state.student_data["charts"]) == 0: state.report(f"Please create a chart near `{state.sct_range}`.") student_chart = find_chart(state.student_data["charts"], state.sct_range) student_chart_anchor = row_columns_to_range(find_chart_anchor(student_chart)) solution_chart_type = infer_chart_type(solution_chart) chart_type_msg = ( f"The chart type of the chart at `{student_chart_anchor}` is not correct." ) if Dispatcher().select(f"spec.{solution_chart_type}", student_chart) is None: state.report(chart_type_msg) elif solution_chart_type == "basicChart": chart_type_path = "spec.basicChart.chartType" student_chart_type = Dispatcher().select(chart_type_path, student_chart) detailed_solution_chart_type = Dispatcher().select( chart_type_path, solution_chart ) if student_chart_type != detailed_solution_chart_type: state.report(chart_type_msg) return state.to_child( student_data=student_chart["spec"], solution_data=solution_chart["spec"], append_message=f"In the chart at `{student_chart_anchor}`, ", node_name=solution_chart_type, )
def manhattan_distance_to_chart(chart, sct_range): anchor_cell = find_chart_anchor(chart) x = anchor_cell["start_column"] y = anchor_cell["start_row"] sct_range_as_row_columns = range_to_row_columns(sct_range) x_sct = Dispatcher().select("start_column", sct_range_as_row_columns, fallback=0) y_sct = Dispatcher().select("start_row", sct_range_as_row_columns, fallback=0) return abs(x - x_sct) + abs(y - y_sct)
def find_chart_anchor(chart): raw_row_columns = Dispatcher().select( "position.overlayPosition.anchorCell", chart) return { "start_row": Dispatcher().select("rowIndex", raw_row_columns, fallback=0), "start_column": Dispatcher().select("columnIndex", raw_row_columns, fallback=0), }
def infer_chart_type(chart): chart_types = [ "basicChart", "pieChart", "bubbleChart", "candlestickChart", "orgChart", "histogramChart", "waterfallChart", "treemapChart", ] for chart_type in chart_types: if Dispatcher().select(f"spec.{chart_type}", chart) is not None: return chart_type
def has_equal_data_validation(state, incorrect_msg=None): child = check_range(state, field="dataValidations", field_msg="data validation") student_data_validation = child.student_data solution_data_validation = child.solution_data message = "In cell `{range}`, did you use the correct data validation?" # For now, we generally only support cells, not ranges. This means we always # have to look at the content at index 0, 0. condition_path = "0.0.condition" student_data_validation_condtion = Dispatcher().select( condition_path, student_data_validation ) solution_data_validation_condtion = Dispatcher().select( condition_path, solution_data_validation ) if student_data_validation_condtion != solution_data_validation_condtion: child.report( (incorrect_msg or message).format(**state.to_message_exposed_dict()) ) return state
def __init__(self, student_data, solution_data, sct_range, reporter, force_diagnose=False): self.student_data = student_data self.solution_data = solution_data self.sct_range = sct_range self.reporter = reporter self.messages = [] self.creator = None self.dispatcher = Dispatcher() self.node_name = "root" self.force_diagnose = force_diagnose
def has_equal_number_format(state, incorrect_msg=None): child = check_range(state, field="numberFormats", field_msg="number format") student_number_format = child.student_data solution_number_format = child.solution_data generated_message = None standard_message = "In cell `{range}`, did you use the correct number format?" # For now, we generally only support cells, not ranges. This means we always # have to look at the content at index 0, 0. type_path = "0.0.numberFormat.type" student_number_format_type = Dispatcher().select(type_path, student_number_format) solution_number_format_type = Dispatcher().select(type_path, solution_number_format) if student_number_format_type != solution_number_format_type: actual_type = number_format_types.get(student_number_format_type) expected_type = number_format_types.get(solution_number_format_type) if actual_type is None or expected_type is None: generated_message = standard_message else: generated_message = ( standard_message + f" Expected {expected_type}, but got {actual_type}.") elif student_number_format != solution_number_format: generated_message = standard_message if generated_message is not None: child.report( (incorrect_msg or generated_message).format(**state.to_message_exposed_dict())) return state