code: def assert_proper_grid_search(
      grid_search,
      expected_grid_size=None,
      lower_bound=None,
      upper_bound=None,
      score_func=EvaluationMetricEnum.MeanAbsolutePercentError.name,
      greater_is_better=False,
      cv_report_metrics_names=None,
  ):
      """Checks fitted hyperparameter grid search result.
  
      Parameters
      ----------
      grid_search : `sklearn.model_selection.RandomizedSearchCV`
          Fitted RandomizedSearchCV object
      expected_grid_size : `int` or None, default None
          Expected number of options evaluated in grid search.
          If None, does not check the expected size.
      lower_bound : `float` or None, default None
          Lower bound on CV test set error.
          If None, does not check the test error.
      upper_bound : `float` or None, default None
          Upper bound on CV test set error.
          If None, does not check the test error.
      score_func : `str` or callable, default ``EvaluationMetricEnum.MeanAbsolutePercentError.name``
          Score function used to select optimal model in CV.
          The same as passed to ``forecast_pipeline`` and grid search.
          If a callable, takes arrays ``y_true``, ``y_pred`` and returns a float.
          If a string, must be either a
          `~greykite.common.evaluation.EvaluationMetricEnum` member name
          or `~greykite.common.constants.FRACTION_OUTSIDE_TOLERANCE`.
      greater_is_better : `bool`, default False
          Whether higher values are better.
          Must be explicitly passed for testing (not derived from ``score_func``).
      cv_report_metrics_names : `list` [`str`] or None, default None
          Additional metrics besides ``metric`` calculated during CV.
          If None, no other metrics are checked in the result.
  
          Unlike in ``forecast_pipeline``, these are the expected names,
          in the CV output, such as:
  
              - ``enum.get_metric_name()``
              - ``"CUSTOM_SCORE_FUNC_NAME"``
              - ``"FRACTION_OUTSIDE_TOLERANCE_NAME"``
  
      Raises
      ------
      AssertionError
          If grid search did not run as expected.
      """
      _, _, short_name = get_score_func_with_aggregation(
          score_func=score_func,  # string or callable
          greater_is_better=greater_is_better,
          # Dummy value, doesn't matter because we ignore the returned `score_func`
          relative_error_tolerance=0.01,
      )
      # attributes are populated
      assert hasattr(grid_search, "best_estimator_")
      assert hasattr(grid_search, "cv_results_")
      if callable(grid_search.refit):
          # `grid_search.refit` is a callable if `grid_search` comes from
          # `forecast_pipeline`.
          # Checks if `best_index_` and `refit` match `metric` and `greater_is_better`.
          assert grid_search.best_index_ == grid_search.refit(
              grid_search.cv_results_
          )
          split_scores = grid_search.cv_results_[
              f"mean_test_{short_name}"
          ]
          expected_best_score = (
              max(split_scores)
              if greater_is_better
              else min(split_scores)
          )
          assert (
              split_scores[grid_search.best_index_]
              == expected_best_score
          )
          assert split_scores[grid_search.best_index_] is not None
          assert not np.isnan(split_scores[grid_search.best_index_])
          assert_refit(
              grid_search.refit,
              expected_metric=short_name,
              expected_greater_is_better=greater_is_better,
          )
      elif grid_search.refit is True:
          # In single metric evaluation, refit_metric is "score".
          short_name = "score"
          # `best_score_` is populated, and the optimal score is the highest
          # test set score. Metrics where `greater_is_better=False` are
          # assumed to be negated in the ``scoring`` parameter so that
          # higher values are better.
          assert hasattr(grid_search, "best_score_")
          best_score = grid_search.best_score_
          test_scores = grid_search.cv_results_[
              f"mean_test_{short_name}"
          ]
          best_score2 = test_scores[grid_search.best_index_]
          assert best_score == max(test_scores)
          assert best_score2 == max(test_scores)
  
      if expected_grid_size is not None:
          assert (
              len(grid_search.cv_results_[f"mean_test_{short_name}"])
              == expected_grid_size
          )
      # Parameters are populated
      assert_equal(
          grid_search.cv_results_["params"][grid_search.best_index_],
          grid_search.best_params_,
      )
  
      # All metrics are computed
      if cv_report_metrics_names is None:
          cv_report_metrics_names = []
      for expected_metric in cv_report_metrics_names + [short_name]:
          assert (
              f"mean_test_{expected_metric}"
              in grid_search.cv_results_.keys()
          )
          assert (
              f"std_test_{expected_metric}"
              in grid_search.cv_results_.keys()
          )
          assert (
              f"mean_train_{expected_metric}"
              in grid_search.cv_results_.keys()
          )
          assert (
              f"std_train_{expected_metric}"
              in grid_search.cv_results_.keys()
          )
  
      if lower_bound is not None or upper_bound is not None:
          grid_results = summarize_grid_search_results(
              grid_search, score_func=score_func
          )
          if lower_bound is not None:
              assert all(
                  grid_results[f"mean_test_{short_name}"] >= lower_bound
              )
          if upper_bound is not None:
              assert all(
                  grid_results[f"mean_test_{short_name}"] <= upper_bound
              )
  
imports: import warnings
  from functools import partial
  from typing import Optional
  import numpy
  import pandas
  from sklearn.model_selection import RandomizedSearchCV
  from sklearn.pipeline import Pipeline
  from greykite.common.constants import ACTUAL_COL
  from greykite.common.constants import PREDICTED_COL
  from greykite.common.constants import PREDICTION_BAND_COVERAGE
  from greykite.common.constants import TIME_COL
  from greykite.common.constants import VALUE_COL
  from greykite.common.evaluation import EvaluationMetricEnum
  from greykite.common.evaluation import fraction_outside_tolerance
  from greykite.common.python_utils import assert_equal
  from greykite.common.testing_utils import assert_eval_function_equal
  from greykite.framework.constants import FRACTION_OUTSIDE_TOLERANCE_NAME
  from greykite.framework.output.univariate_forecast import UnivariateForecast
  from greykite.framework.pipeline.pipeline import validate_pipeline_input
  from greykite.framework.pipeline.utils import get_score_func_with_aggregation
  from greykite.framework.utils.result_summary import summarize_grid_search_results
  from greykite.sklearn.estimator.simple_silverkite_estimator import SimpleSilverkiteEstimator
  from greykite.sklearn.sklearn_scorer import _PredictScorerDF
label_desc: Use image node_id 3 for calling a global function with example usage: assert_proper_grid_search(grid_search, expected_grid_size, lower_bound, upper_bound, score_func, greater_is_better, cv_report_metrics_names) without return types