text: import configparser
  from collections import OrderedDict
  from datetime import datetime, timedelta
  from multiprocessing.pool import Pool
  import multiprocessing
  
  from operator import itemgetter
  
  import dateparser
  import typer
  from dateparser.search import search_dates
  from safetypy import safetypy
  from iart.modules import csvExporter
  from tqdm import tqdm
  
  from pprint import pprint as pp
  
  IGNORED_TYPES = [
      "smartfield",
      "primeelement",
      "section",
      "category",
      "element",
      "information",
      "information - media",
      "information - ",
  ]
  COMMON_DATA = [
      "AuditID",
      "Name",
      "DateStarted",
      "DateCompleted",
      "ConductedBy",
      "Percentage Score",
      "Score",
      "Site",
      "Area",
      "Region",
      "Template Name",
  ]
  DELIMITER = chr(255)
  FAILED_DELIMITER = " [F]"
  
  
  class TemplateCreator:
      def __init__(
          self,
          template_id,
          sp,
          date_modified,
          date_before,
          header_only=False,
          completed="both",
      ):
          self.sp = sp
          self.header_only = header_only
          self.template_id = template_id
          self.most_recent_inspection = self.get_most_recent_inspection(completed)
          self.inspection_json = sp.get_audit(self.most_recent_inspection)
          self.current_table = csvExporter.CsvExporter(
              self.inspection_json, self.header_only
          )
          inspection_as_list = self.current_table.convert_audit_to_table()
          self.inspection_as_list = [
              item for item in inspection_as_list if item[1] not in IGNORED_TYPES
          ]
          self.prime_elements = self.map_prime_elements()
          self.inspection_list = sp.discover_audits(
              template_id=[template_id],
              modified_after=date_modified,
              modified_before=date_before,
              completed=completed,
          )
          self.all_inspections, self.full_combined = self.combine_inspections()
          self.csv_as_list = [
              {"AuditID": []},
              {"Name": []},
              {"DateStarted": []},
              {"DateCompleted": []},
              {"Percentage Score": []},
              {"Score": []},
              {"Site": []},
              {"Area": []},
              {"Region": []},
              {"Template Name": []},
          ]
          self.item_label_map, self.dict_label_map = self.map_item_id_to_label()
          manager = multiprocessing.Manager()
          self.L = manager.list()
  
      @staticmethod
      def chunks(lst, n):
          """Yield successive n-sized chunks from lst."""
          length = 0
          for i in range(0, len(lst), n):
              length += 1
              yield lst[i : i + n]
  
      def raise_pool(self, list_of_audits):
          # Establish the Pool
          self.L[:] = []
          threads = 4
          pool = Pool(processes=threads)
          list(
              tqdm(
                  pool.imap(self.process_audit, list_of_audits), total=len(list_of_audits)
              )
          )
          pool.close()
          pool.join()
          return list(self.L)
  
      def process_audit(self, audit_id):
          # Appends downloaded audits to a shared list (L)
          audit_id = audit_id["audit_id"]
          downloaded_audit = self.sp.get_audit(audit_id)
          self.L.append(downloaded_audit)
  
      def get_most_recent_inspection(self, completed):
          list_of_inspections = self.sp.discover_audits(
              template_id=[self.template_id], limit=1, order="desc", completed=completed
          )
          if list_of_inspections is not None:
              if list_of_inspections["count"] > 0:
                  return list_of_inspections["audits"][0]["audit_id"]
          else:
              return None
  
      def combine_inspections(self):
          all_inspections = []
          full_inspections = []
          if self.inspection_list:
              chunks_to_process = self.sp.chunks(self.inspection_list["audits"], 500000)
              number_of_inspections = len(self.inspection_list["audits"])
              print(f"Downloading {number_of_inspections} inspections")
              for chunk in chunks_to_process:
                  audits_to_process = self.sp.raise_pool(chunk)
                  inspection_pbar = tqdm(audits_to_process)
                  for inspection in inspection_pbar:
                      audit_id = inspection["audit_id"]
                      inspection_pbar.set_description(f"Processing {audit_id}")
                      inspection_json = inspection
                      current_table = csvExporter.CsvExporter(inspection_json)
                      inspection_as_list = current_table.convert_audit_to_table()
                      full_inspections.append(inspection_as_list)
                      inspection_as_list = [
                          item
                          for item in inspection_as_list
                          if item[1] not in IGNORED_TYPES
                      ]
                      all_inspections.append(inspection_as_list)
              return all_inspections, full_inspections
          return None, None
  
      def map_prime_elements(self):
          prime_elements = {}
          list_of_prime_elements = [
              item for item in self.inspection_as_list if item[35] == "primeelement"
          ]
          for pe in list_of_prime_elements:
              prime_elements[pe[14]] = pe[2]
          if prime_elements:
              return prime_elements
          else:
              return None
  
      def map_item_id_to_label(self):
          checked_dynamicfields = []
          inspection_as_list = self.inspection_as_list
          item_label_map = []
          dict_label_map = OrderedDict()
          count = 0
          for item in inspection_as_list:
              item_id = item[14]
              label = item[2]
              item_type = item[1]
              repeating_section = item[35]
              if item_type == "dynamicfield":
                  if item_id not in checked_dynamicfields:
                      rs_map = self.create_rs_map(item_id)
                      checked_dynamicfields.append(item_id)
                      self.csv_as_list.extend(rs_map)
                      continue
              elif repeating_section in [""]:
                  count += 1
                  rs_check = False
                  new_row = {item_id: []}
                  self.csv_as_list.append(new_row)
                  item_label_map.append(
                      {item_id: {"order": count, "label": label, "rs": rs_check}}
                  )
                  dict_label_map[item_id] = {
                      "order": count,
                      "label": label,
                      "rs": rs_check,
                      "category": item[34],
                  }
              else:
                  pass
  
          return item_label_map, dict_label_map
  
      def create_rs_map(self, item_id):
          columns = []
          for inspection in self.all_inspections:
              this_inspections_prime_elements = [
                  item
                  for item in inspection
                  if item[35] == "primeelement" and item[1] not in IGNORED_TYPES
              ]
              this_inspections_pe_dict = {}
              for pe in this_inspections_prime_elements:
                  this_inspections_pe_dict[pe[2]] = pe[14]
              for row in inspection:
                  if row[35] not in ["primeelement", ""]:
                      parent_id = list(row[35])[0]
                      if parent_id == item_id:
                          # section_title = list(row[35])[1]
                          section_number = list(row[35])[2]
                          category = row[34]
                          category = (
                              (category[:50] + "..") if len(category) > 50 else category
                          )
                          question = row[2]
                          question = (
                              (question[:150] + "..") if len(question) > 150 else question
                          )
                          pm_item_id = this_inspections_pe_dict.get(question)
                          new_label = self.prime_elements.get(pm_item_id)
                          if new_label != question:
                              question = new_label
                          new_row_title = f"{category} {section_number} / {question}"
                          new_row = {new_row_title: []}
                          if new_row not in columns:
                              columns.append(new_row)
          return columns
  
  
  class RowCreator:
      def __init__(self, inspection, template):
          full_combined_inspection = inspection
          self.inspection_as_list = [
              item for item in full_combined_inspection if item[1] not in IGNORED_TYPES
          ]
          self.list_of_prime_elements = [
              item for item in full_combined_inspection if item[35] == "primeelement"
          ]
          self.template_prime_elements = template.prime_elements
          self.csv_as_list = template.csv_as_list
          self.mapped_to_list = self.col_to_value()
  
      def col_to_value(self):
          list_of_prime_elements = self.list_of_prime_elements
          for row in self.csv_as_list:
              response_found = False
              item_id = list(row.keys())[0]
              if item_id in COMMON_DATA:
                  item_values = self.inspection_as_list[0]
                  common_data_dict = {
                      "AuditID": item_values[29],
                      "Name": item_values[21],
                      "DateStarted": item_values[26],
                      "DateCompleted": item_values[28],
                      "Percentage Score": item_values[24],
                      "Score": item_values[22],
                      "Site": item_values[42],
                      "Area": item_values[43],
                      "Region": item_values[44],
                      "Template Name": item_values[31],
                  }
                  common_response = common_data_dict.get(item_id)
                  row[item_id].append(common_response)
                  response_found = True
              else:
                  search_inspection = [
                      item for item in self.inspection_as_list if item[14] == item_id
                  ]
                  if search_inspection:
                      response = search_inspection[0][3]
                      failed_status = search_inspection[0][12]
                      if failed_status is True:
                          response += FAILED_DELIMITER
                      row[item_id].append(response)
                      response_found = True
                  else:
                      for item in self.inspection_as_list:
                          if item[35] not in ["primeelement", ""]:
                              parent_id = list(item[35])[0]
                              section_title = list(item[35])[1]
                              section_number = list(item[35])[2]
                              category = item[34]
                              category = (
                                  (category[:50] + "..")
                                  if len(category) > 50
                                  else category
                              )
                              question = item[2]
                              question = (
                                  (question[:150] + "..")
                                  if len(question) > 150
                                  else question
                              )
                              response = item[3]
                              for primeelement in list_of_prime_elements:
                                  if primeelement[2] == question:
                                      if question != self.template_prime_elements.get(
                                          primeelement[14]
                                      ):
                                          question = self.template_prime_elements.get(
                                              primeelement[14]
                                          )
                              new_row_title = f"{category} {section_number} / {question}"
                              if new_row_title == item_id and response_found is False:
                                  row[item_id].append(response)
                                  response_found = True
              if response_found is False:
                  row[item_id].append("")
          return self.csv_as_list
  
  
  class ConfigSetup:
      def __init__(self):
          self.config = self.get_config()
          self.sp = self.register_token()
          if self.sp:
              self.header_only = self.config["options"]["header_only"]
              self.token = self.config["options"]["token"]
              self.templates = self.get_templates()
              self.file_format = self.config["options"]["export_format"]
              self.to_search = self.config["options"]["to_search"]
              self.parsed_date = self.date_parser()
              self.before, self.after = self.get_dates_to_search()
              self.template_lookup = self.get_formatted_list_of_templates()
  
      def get_templates(self):
          if self.config["options"]["templates"] is None:
              return []
          elif self.config["options"]["templates"] == "all":
              return "all"
          if "," in str(self.config["options"]["templates"]):
              templates = self.config["options"]["templates"].split(",")
          else:
              templates = [self.config["options"]["templates"]]
          return templates
  
      @staticmethod
      def get_config():
          config = configparser.ConfigParser()
          config.read("config.ini")
          if config.sections():
              return config
          else:
              return None
  
      def register_token(self):
          config = self.config
          if self.config:
              if "options" in config:
                  if "token" in config["options"]:
                      try:
                          sp = safetypy.SafetyCulture(config["options"]["token"])
                          return sp
                      except:
                          return None
          else:
              return None
  
      def get_list_of_templates(self):
          list_of_templates = self.sp.discover_templates()
          if list_of_templates is not None:
              list_of_templates = list_of_templates["templates"]
              return list_of_templates
          else:
              return None
  
      def get_formatted_list_of_templates(self):
          all_templates = self.get_list_of_templates()
          all_templates = sorted(
              all_templates, key=itemgetter("modified_at"), reverse=True
          )
          template_list = [{"value": "all", "name": "Export All", "checked": True}]
          if all_templates:
              for template in all_templates:
                  if len(template_list) > 200:
                      break
                  else:
                      if template["name"] == "":
                          name = "Untitled template"
                      else:
                          name = template["name"].strip()
                      template_id = template["template_id"]
                      joined_name = f"{name} ({template_id})"
                      template_dict = {
                          "value": template_id,
                          "name": joined_name,
                      }
                      template_list.append(template_dict)
          else:
              template_list = None
          return template_list
  
      def date_parser(self):
          parsed_date = dateparser.parse(self.to_search)
          if not parsed_date:
              date = search_dates(self.to_search)
          else:
              date = parsed_date
          if not date:
              print(
                  "Unable to parse the data in your config file. Defaulting to 1 week ago."
              )
              date = datetime.now() - timedelta(weeks=1)
          return date
  
      def get_dates_to_search(self):
          before = datetime.now()
          after = before - timedelta(weeks=1)
          if self.parsed_date:
              if type(self.parsed_date) is list:
                  for i, date_to_search in enumerate(self.parsed_date):
                      if i == 0:
                          after = date_to_search[1]
                      if i == 1:
                          before = date_to_search[1]
                      if i > 1:
                          print("More than 2 dates provided, only taking the first two.")
              else:
                  after = self.parsed_date
  
              pretty_after_date = after.strftime(f"%A %d %B %Y at %X")
              pretty_after_date = typer.style(
                  pretty_after_date, fg=typer.colors.GREEN, bold=True
              )
              pretty_before_date = before.strftime(f"%A %d %B %Y at %X")
              pretty_before_date = typer.style(
                  pretty_before_date, fg=typer.colors.GREEN, bold=True
              )
              search_from_alert = (
                  f"Searching for inspections completed after "
                  f"{pretty_after_date} until {pretty_before_date} "
              )
              typer.echo(search_from_alert)
          else:
              print(
                  "Either unable to parse date or no date provided. Defaulting to 1 week ago."
              )
          return before, after
  