code: def preprocess(
          self, nb: "NotebookNode", resources: dict
      ) -> Tuple["NotebookNode", dict]:
          
          if "remove_solution" not in resources:
              raise KeyError("The resources dictionary must have a remove_solution key.")
          if resources["remove_solution"]:
              keep_cells_idx = []
              for index, cell in enumerate(nb.cells):
                  if "## solution" in cell.source.lower():
                      keep_cells_idx.append(index)
                  # The space at the end of the test string here is important
                  elif len(keep_cells_idx) > 0 and cell.source.startswith("### "):
                      keep_cells_idx.append(index)
  
              keep_cells = nb.cells[: keep_cells_idx[0] + 1]
              for i in keep_cells_idx[1:]:
                  keep_cells.append(nb.cells[i])
                  if resources["by_hand"]:
                      keep_cells.append(by_hand_cell)
                  else:
                      if "sketch" in nb.cells[i].source.lower():
                          keep_cells.append(sketch_cell)
                      else:
                          keep_cells.append(md_expl_cell)
                          keep_cells.append(code_ans_cell)
                          keep_cells.append(md_ans_cell)
              nb.cells = keep_cells
  
          return nb, resources
docs: Preprocess the entire notebook.