code: def buildDecisionTree(
      df: pd.DataFrame,
      root: int,
      file: str,
      config: dict,
      dataset_features: dict,
      parent_level: int = 0,
      leaf_id: int = 0,
      parents: str = "root",
      tree_id: int = 0,
      validation_df: Optional[pd.DataFrame] = None,
      main_process_id: Optional[int] = None,
  ) -> list:
      """
      Build a decition tree rules
      Args:
          df (pd.DataFrame): (sub) train set's data frame
          root (int): current depth
          file (str): target file to store rules
          config (dict): training configuration
          dataset_features (dict): pivot information about current
              dataframe's features with types
          parent_level (int): depth of the parent
          leaf_id (int): index of the current leaf. will use if
              for the first, and elif for the rest
          parents (str): id of the parent leaf as uuid. root is for
              the top one.
          tree_id (int): tree's id
          validation_df (pd.DataFrame): validation dataframe
          main_process_id (int): process id of the main trx
      Returns:
          models (list): list of dict as built model information
      """
      models = []
  
      decision_rules = []
  
      feature_names = df.columns[0:-1]
  
      enableParallelism = config["enableParallelism"]
  
      json_file = file.split(".")[0] + ".json"
  
      # --------------------------------------
  
      df_copy = df.copy()
  
      winner_name, num_of_instances, metric, _ = findDecision(
          df, config
      )
  
      # find winner index, this cannot be returned by find decision
      # because columns dropped in previous steps
      j = 0
      for i in dataset_features:
          if i == winner_name:
              winner_index = j
          j = j + 1
  
      numericColumn = False
      if dataset_features[winner_name] != "object":
          numericColumn = True
  
      # restoration
      columns = df.shape[1]
      for i in range(0, columns - 1):
          column_name = df_copy.columns[i]
          column_type = df_copy[column_name].dtypes
          if column_type != "object" and column_name != winner_name:
              df[column_name] = df_copy[column_name]
  
      classes = df[winner_name].value_counts().keys().tolist()
      logger.debug(f"classes: {classes} in {winner_name}")
      # -----------------------------------------------------
  
      num_cores = config["num_cores"]
  
      input_params = []
  
      # serial approach
      for i, current_class in enumerate(classes):
          subdataset = df[df[winner_name] == current_class]
          subdataset = subdataset.drop(columns=[winner_name])
          branch_index = i * 1
  
          # create branches serially
          if enableParallelism != True:
              if i == 0:
                  descriptor = {
                      "feature": winner_name,
                      "instances": num_of_instances,
                      # "metric_name": metric_name,
                      "metric_value": round(metric, 4),
                      "depth": parent_level + 1,
                  }
                  descriptor = "# " + json.dumps(descriptor)
  
                  functions.storeRule(
                      file, (functions.formatRule(root), "", descriptor)
                  )
  
              results = createBranch(
                  config,
                  current_class,
                  subdataset,
                  numericColumn,
                  branch_index,
                  winner_name,
                  winner_index,
                  root,
                  parents,
                  file,
                  dataset_features,
                  num_of_instances,
                  metric,
                  tree_id=tree_id,
                  main_process_id=main_process_id,
              )
  
              decision_rules = decision_rules + results
  
          else:
              input_params.append(
                  (
                      config,
                      current_class,
                      subdataset,
                      numericColumn,
                      branch_index,
                      winner_name,
                      winner_index,
                      root,
                      parents,
                      file,
                      dataset_features,
                      num_of_instances,
                      metric,
                      tree_id,
                      main_process_id,
                  )
              )
  
      # ---------------------------
      # add else condition in the decision tree
      if df.Decision.dtypes == "object":  # classification
          pivot = pd.DataFrame(df.Decision.value_counts()).sort_values(
              by=["count"], ascending=False
          )
          else_decision = f"return '{str(pivot.iloc[0].name)}'"
  
          if enableParallelism != True:
              functions.storeRule(
                  file, (functions.formatRule(root), "else:")
              )
              functions.storeRule(
                  file, (functions.formatRule(root + 1), else_decision)
              )
          else:  # parallelism
              leaf_id = str(uuid.uuid1())
  
              check_rule = "else: " + else_decision
  
              sample_rule = {}
              sample_rule["current_level"] = root
              sample_rule["leaf_id"] = leaf_id
              sample_rule["parents"] = parents
              sample_rule["rule"] = check_rule
              sample_rule["feature_idx"] = -1
              sample_rule["feature_name"] = ""
              sample_rule["instances"] = df.shape[0]
              sample_rule["metric"] = 0
              sample_rule["return_statement"] = 0
              sample_rule["tree_id"] = tree_id
  
              # json to string
              sample_rule = json.dumps(sample_rule)
              decision_rules.append(sample_rule)
  
      else:  # regression
          else_decision = f"return {df.Decision.mean()}"
  
          if enableParallelism != True:
              functions.storeRule(
                  file, (functions.formatRule(root), "else:")
              )
              functions.storeRule(
                  file, (functions.formatRule(root + 1), else_decision)
              )
          else:
              leaf_id = str(uuid.uuid1())
  
              check_rule = "else: " + else_decision
  
              sample_rule = {}
              sample_rule["current_level"] = root
              sample_rule["leaf_id"] = leaf_id
              sample_rule["parents"] = parents
              sample_rule["rule"] = check_rule
              sample_rule["tree_id"] = tree_id
              sample_rule["feature_name"] = ""
              sample_rule["instances"] = 0
              sample_rule["metric"] = 0
              sample_rule["return_statement"] = 1
  
              # json to string
              sample_rule = json.dumps(sample_rule)
              decision_rules.append(sample_rule)
  
      # ---------------------------
  
      try:
          main_process = psutil.Process(main_process_id)
          children = main_process.children(recursive=True)
          active_processes = len(children) + 1  # plus parent
          # active_processes = len(children)
      except:
          active_processes = 100  # set a large initial value
  
      results = []
      # create branches in parallel
      if enableParallelism == True:
          required_threads = active_processes + len(classes)
  
          # if parent_level == 0 and random_forest_enabled != True:
          if (
              main_process_id != None and num_cores >= required_threads
          ):  # len(classes) branches will be run in parallel
              # POOL_SIZE = num_cores
              POOL_SIZE = len(classes)
  
              # with closing(multiprocessing.Pool(POOL_SIZE)) as pool:
              with closing(CustomPool(POOL_SIZE)) as pool:
                  funclist = []
  
                  for input_param in input_params:
                      f = pool.apply_async(
                          createBranchWrapper,
                          [createBranch, input_param],
                      )
                      funclist.append(f)
  
                  # all functions registered here
  
                  for f in funclist:
                      branch_results = f.get(timeout=100000)
  
                      for branch_result in branch_results:
                          results.append(branch_result)
  
                  pool.close()
                  pool.terminate()
  
              # --------------------------------
  
          else:  # serial
              for input_param in input_params:
                  sub_results = createBranchWrapper(
                      createBranch, input_param
                  )
                  for sub_result in sub_results:
                      results.append(sub_result)
  
          # --------------------------------
  
          decision_rules = decision_rules + results
  
          # --------------------------------
  
          if root != 1:  # return children results until the root node
              return decision_rules
  
      # ---------------------------------------------
  
      if root == 1:
          if enableParallelism == True:
              # custom rules are stored in decision_rules. merge them all in a json file first
  
              json_rules = "[\n"  # initialize
  
              file_index = 0
              for custom_rule in decision_rules:
                  json_rules += custom_rule
  
                  if file_index < len(decision_rules) - 1:
                      json_rules += ", "
  
                  json_rules += "\n"
  
                  file_index = file_index + 1
  
              # -----------------------------------
  
              json_rules += "]"
              functions.createFile(json_file, json_rules)
  
              # -----------------------------------
              # reconstruct rules from json to py
  
              reconstructRules(json_file, feature_names)
  
              # -----------------------------------
  
          # is regular decision tree
          if (
              config["enableRandomForest"] != True
              and config["enableGBM"] != True
              and config["enableAdaboost"] != True
          ):
              # this is reguler decision tree. find accuracy here.
  
              module_name = "outputs/rules/rules"
              myrules = load_module(module_name)  # rules0
              models.append(myrules)
  
      return models
  
imports: import math
  import uuid
  import json
  import copy
  import gc
  from contextlib import closing
  from typing import Optional
  import psutil
  import numpy
  import pandas
  from chefboost.training import Preprocess
  from chefboost.commons import functions
  from chefboost.commons.logger import Logger
  from chefboost.commons.module import load_module
  from chefboost.commons.daemon import CustomPool
label_desc: Use image node_id 6 for calling a global function with example usage: buildDecisionTree(df, root, file, config, dataset_features, parent_level, leaf_id, parents, tree_id, validation_df, main_process_id) and returns: models, decision_rules