Repository: ioos/compliance-checker
Pull request #992 (2023-03-02 15:54:21)

Update check_cell_measures() function in cf_1_6.y
added a test to verify units are recognized by UDUNITS in the check_cell_measures() function


Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index 563dd9b..0000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-# See https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/keeping-your-actions-up-to-date-with-dependabot
-
-version: 2
-updates:
-
-  - package-ecosystem: "github-actions"
-    directory: "/"
-    schedule:
-      interval: "daily"
-    labels:
-      - "Bot"
diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml
index 946ca04..21a106e 100644
--- a/.github/workflows/codecov.yml
+++ b/.github/workflows/codecov.yml
@@ -17,7 +17,7 @@ jobs:
       fail-fast: false
 
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v2
 
     - name: Setup Conda
       uses: s-weigand/setup-conda@v1
@@ -50,6 +50,6 @@ jobs:
       continue-on-error: true
 
     - name: Upload to codecov
-      uses: codecov/codecov-action@v3
+      uses: codecov/codecov-action@v2
       with:
         files: coverage.xml
diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml
index 9107f72..ec452e1 100644
--- a/.github/workflows/pypi.yml
+++ b/.github/workflows/pypi.yml
@@ -27,7 +27,7 @@ jobs:
       run: git fetch origin 'refs/tags/*:refs/tags/*'
 
     - name: Set up Python
-      uses: actions/setup-python@v4
+      uses: actions/setup-python@v2
       with:
         # no cf_untis wheels for 3.10 yet
         python-version: "3.9"
@@ -50,7 +50,7 @@ jobs:
 
     - name: Publish a Python distribution to PyPI
       if: success() && github.event_name == 'release'
-      uses: pypa/gh-action-pypi-publish@v1.6.4
+      uses: pypa/gh-action-pypi-publish@v1.5.0
       with:
         user: __token__
         password: ${{ secrets.PYPI_ACCESS_TOKEN }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 2c1d41f..a29bcfc 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
 repos:
 - repo: https://github.com/pre-commit/pre-commit-hooks
-  rev: v4.4.0
+  rev: v4.3.0
   hooks:
     - id: trailing-whitespace
       exclude: compliance_checker/tests/data
@@ -13,31 +13,31 @@ repos:
     - id: requirements-txt-fixer
 
 - repo: https://github.com/PyCQA/flake8
-  rev: 6.0.0
+  rev: 5.0.4
   hooks:
     - id: flake8
       exclude: docs/source/conf.py
       args: [--max-line-length=200, "--ignore=E203,E501,W503", "--select=select=C,E,F,W,B,B950"]
 
 - repo: https://github.com/pycqa/isort
-  rev: 5.12.0
+  rev: 5.10.1
   hooks:
   - id: isort
     additional_dependencies: [toml]
     args: ["--profile", "black", "--filter-files"]
 
 - repo: https://github.com/psf/black
-  rev: 23.1.0
+  rev: 22.8.0
   hooks:
   - id: black
     language_version: python3
 
 - repo: https://github.com/codespell-project/codespell
-  rev: v2.2.2
+  rev: v2.2.1
   hooks:
     - id: codespell
       args:
-        - --ignore-words-list=degreee,varn,poit,uint,sur,herat,claus,dedent,dedenting,dedents,dedented
+        - --ignore-words-list=degreee,varn,poit,uint,sur,herat,claus
       exclude: >
           (?x)^(
               .*\.xml|
diff --git a/compliance_checker/acdd.py b/compliance_checker/acdd.py
index 008e79a..40bcfbd 100644
--- a/compliance_checker/acdd.py
+++ b/compliance_checker/acdd.py
@@ -26,12 +26,14 @@ from compliance_checker.util import dateparse, datetime_is_iso, kvp_convert
 
 
 class ACDDBaseCheck(BaseCheck):
+
     _cc_spec = "acdd"
     _cc_description = "Attribute Conventions for Dataset Discovery (ACDD)"
     _cc_url = "http://wiki.esipfed.org/index.php?title=Category:Attribute_Conventions_Dataset_Discovery"
     _cc_display_headers = {3: "Highly Recommended", 2: "Recommended", 1: "Suggested"}
 
     def __init__(self):
+
         self.high_rec_atts = ["title", "keywords", "summary"]
 
         self.rec_atts = [
@@ -254,6 +256,7 @@ class ACDDBaseCheck(BaseCheck):
         # identify lat var(s) as per CF 4.1
         lat_vars = {}  # var -> number of criteria passed
         for name, var in ds.variables.items():
+
             # must have units
             if not hasattr(var, "units"):
                 continue
@@ -351,6 +354,7 @@ class ACDDBaseCheck(BaseCheck):
         # identify lon var(s) as per CF 4.2
         lon_vars = {}  # var -> number of criteria passed
         for name, var in ds.variables.items():
+
             # must have units
             if not hasattr(var, "units"):
                 continue
@@ -675,6 +679,7 @@ class ACDDNCCheck(BaseNCCheck, ACDDBaseCheck):
 
 
 class ACDD1_1Check(ACDDNCCheck):
+
     _cc_spec_version = "1.1"
     _cc_description = "Attribute Conventions for Dataset Discovery (ACDD) 1.1"
     register_checker = True
@@ -694,6 +699,7 @@ class ACDD1_1Check(ACDDNCCheck):
 
 
 class ACDD1_3Check(ACDDNCCheck):
+
     _cc_spec_version = "1.3"
     _cc_description = "Attribute Conventions for Dataset Discovery (ACDD) 1.3"
     register_checker = True
@@ -753,6 +759,7 @@ class ACDD1_3Check(ACDDNCCheck):
             "date_{}".format(suffix)
             for suffix in ("issued", "modified", "metadata_modified")
         ):
+
             self.sug_atts[k] = partial(_check_attr_is_iso_date, k)
 
     def check_metadata_link(self, ds):
diff --git a/compliance_checker/base.py b/compliance_checker/base.py
index cfc3ec1..acd080b 100644
--- a/compliance_checker/base.py
+++ b/compliance_checker/base.py
@@ -258,6 +258,7 @@ class Result(object):
         check_method=None,
         variable_name=None,
     ):
+
         self.weight = weight
 
         if value is None:
diff --git a/compliance_checker/cf/appendix_d.py b/compliance_checker/cf/appendix_d.py
index c11c5d9..5e80ca5 100644
--- a/compliance_checker/cf/appendix_d.py
+++ b/compliance_checker/cf/appendix_d.py
@@ -73,7 +73,7 @@ dimless_vertical_coordinates_1_6 = {  # only for CF-1.6
         ocean_computed_standard_names,
     ),
     "ocean_sigma_z_coordinate": (
-        {"sigma", "eta", "depth", "depth_c", "zlev"},
+        {"sigma", "eta", "depth", "depth_c", "nsigma", "zlev"},
         ocean_computed_standard_names,
     ),
     "ocean_double_sigma_coordinate": (
diff --git a/compliance_checker/cf/cf_1_6.py b/compliance_checker/cf/cf_1_6.py
index db6baa6..b8a1937 100644
--- a/compliance_checker/cf/cf_1_6.py
+++ b/compliance_checker/cf/cf_1_6.py
@@ -2,7 +2,6 @@ import difflib
 import logging
 from collections import defaultdict
 
-import cftime
 import numpy as np
 import regex
 from cf_units import Unit
@@ -405,69 +404,6 @@ class CF1_6Check(CFNCCheck):
                 )
         return valid_dimension_order.to_result()
 
-    def check_fill_value_equal_missing_value(self, ds):
-        """
-        If both missing_value and _FillValue be used, they should have the same value.
-        This according to CF §2.5.1 Recommendations:
-
-        :param netCDF4.Dataset ds: An open netCDF dataset
-        :rtype: list
-        :return: List of Results
-        """
-        fails = []
-        total = 0
-
-        for name, variable in ds.variables.items():
-            # If the variable have a defined _FillValue a defined missing_value check it.
-
-            if hasattr(variable, "_FillValue") and hasattr(variable, "missing_value"):
-                total = total + 1
-                if variable._FillValue != variable.missing_value:
-                    fails.append(
-                        "For the variable {} the missing_value must be equal to the _FillValue".format(
-                            variable.name
-                        )
-                    )
-
-        return Result(
-            BaseCheck.MEDIUM,
-            (len(fails), total),
-            self.section_titles["2.5"],
-            msgs=fails,
-        )
-
-    def check_valid_range_or_valid_min_max_present(self, ds):
-        """
-        The valid_range attribute must not be present if the valid_min
-        and/or valid_max attributes are present. This according to 2.5.1 Requirements.
-
-        :param netCDF4.Dataset ds: An open netCDF dataset
-        :rtype: list
-        :return: List of Results
-        """
-        fails = []
-        total = 0
-
-        for name, variable in ds.variables.items():
-            if hasattr(variable, "valid_max") and (
-                hasattr(variable, "valid_min") or hasattr(variable, "valid_range")
-            ):
-                total = total + 1
-
-                fails.append(
-                    "For the variable {} the valid_range attribute must not be present "
-                    "if the valid_min and/or valid_max attributes are present".format(
-                        variable.name
-                    )
-                )
-
-        return Result(
-            BaseCheck.MEDIUM,
-            (len(fails), total),
-            self.section_titles["2.5"],
-            msgs=fails,
-        )
-
     def check_fill_value_outside_valid_range(self, ds):
         """
         Checks each variable's _FillValue to ensure that it's in valid_range or
@@ -901,9 +837,6 @@ class CF1_6Check(CFNCCheck):
             # number_of_observations should short circuit and not continue
             # on to further units checks
             return valid_standard_units.to_result()
-        elif standard_name_modifier == "status_flag":
-            # no units required - skip further checks
-            return valid_standard_units.to_result()
 
         # This section represents the different cases where simple udunits
         # comparison isn't comprehensive enough to determine if the units are
@@ -1885,66 +1818,25 @@ class CF1_6Check(CFNCCheck):
 
         ret_val = []
 
-        def check_standard_calendar_no_cross(time_var):
-            """
-            Check that the time variable does not cross the date
-            1582-10-15 when standard or gregorian calendars are used
-            """
-            # IMPLEMENTATION CONFORMANCE 4.4.1 RECOMMENDED 2/2
-            # Only get non-nan/FillValue times, as these are the only things
-            # that make sense for conversion.  Furthermore, non-null checks
-            # should be made for time coordinate variables anyways, so errors
-            # should be caught where implemented there
-            crossover_date = cftime.DatetimeGregorian(1582, 10, 15)
-            times = cftime.num2date(time_var[:].compressed(), time_var.units)
-
-            no_cross_1582 = ~np.any(times < crossover_date)
-            if no_cross_1582:
-                reasoning = (
-                    f"Variable {time_var.name} has standard or Gregorian "
-                    "calendar and does not cross 1582-10-15T00:00Z"
-                )
-            else:
-                reasoning = (
-                    f"Variable {time_var.name} has time values "
-                    "prior to 1582-10-15T00:00Z and utilizes "
-                    "the standard or Gregorian calendar"
-                )
-
-            return Result(
-                BaseCheck.LOW, no_cross_1582, self.section_titles["4.4"], [reasoning]
-            )
-
         # if has a calendar, check that it is within the valid values
         # otherwise no calendar is valid
-
         for time_var in ds.get_variables_by_attributes(
             calendar=lambda c: c is not None
         ):
-            if time_var.calendar.lower() == "gregorian":
-                reasoning = (
-                    f"For time variable {time_var.name}, when using "
-                    "the standard Gregorian calendar, the value "
-                    '"standard" is preferred over "gregorian" for '
-                    "the calendar attribute"
-                )
-                result = Result(
-                    BaseCheck.LOW, False, self.section_titles["4.4"], [reasoning]
-                )
-                ret_val.append(result)
-                # check here and in the below case that time does not cross
-                # thee date 1582-10-15 as requested by CF conformance
-                ret_val.append(check_standard_calendar_no_cross(time_var))
-            elif time_var.calendar == "standard":
-                ret_val.append(check_standard_calendar_no_cross(time_var))
+            reasoning = None
+            standard_calendar = time_var.calendar in valid_calendars
+
             # if a nonstandard calendar, then leap_years and leap_months must
             # must be present
-            if time_var.calendar.lower() not in valid_calendars:
+            if not standard_calendar:
                 result = self._check_leap_time(time_var)
             # passes if the calendar is valid, otherwise notify of invalid
             # calendar
             else:
-                result = Result(BaseCheck.LOW, True, self.section_titles["4.4"], None)
+                result = Result(
+                    BaseCheck.LOW, True, self.section_titles["4.4"], reasoning
+                )
+
             ret_val.append(result)
 
         return ret_val
@@ -2776,7 +2668,7 @@ class CF1_6Check(CFNCCheck):
                         dic_to_be_verified = {
                             (var.cell_measures).split(":")[0]: cell_meas_var.units
                         }
-                        print(dic_to_be_verified, "\n")
+                        
                         if not set(dic_to_be_verified).issubset(dic_expected):
                             valid = False
                             reasoning.append(
@@ -2784,7 +2676,17 @@ class CF1_6Check(CFNCCheck):
                                 "units that are consistent with the measure type."
                                 "i.e. {}.".format(cell_meas_var_name, dic_expected)
                             )
-
+                       
+                        # verify units are recognized by UDUNITS
+                        valid_udunits = self._check_valid_udunits(ds, cell_meas_var_name)
+                        if valid_udunits.value[0] != valid_udunits.value[1]:
+                            valid = False
+                            reasoning.append(
+                                 "Cell measure variable {} referred to by "
+                                 "{} has a unit {} not recognized by UDUNITS".format(
+                                 cell_meas_var_name, var.name, cell_meas_var.units 
+                                 )
+                            )
                     if not set(cell_meas_var.dimensions).issubset(var.dimensions):
                         valid = False
                         reasoning.append(
@@ -2863,6 +2765,7 @@ class CF1_6Check(CFNCCheck):
                         or var_str == "area"
                         or var_str in getattr(var, "coordinates", "")
                     ):
+
                         valid = True
                     else:
                         valid = False
@@ -3240,6 +3143,7 @@ class CF1_6Check(CFNCCheck):
         """
         ret_val = []
         for name, var in ds.variables.items():
+
             add_offset = getattr(var, "add_offset", None)
             scale_factor = getattr(var, "scale_factor", None)
             if not (add_offset or scale_factor):
@@ -3409,29 +3313,7 @@ class CF1_6Check(CFNCCheck):
                         compress_var.name, not_in_dims
                     )
                 )
-            # IMPLEMENTATION CONFORMANCE 8.2 REQUIRED 3/3
-            # The values of the associated coordinate variable must be in the range
-            # starting with 0 and going up to the product of the compressed dimension
-            # sizes minus 1 (CDL index conventions).
-
-            # Put the the values of the associated coordinate variable into a list
-            coord_list_size = [
-                item.size
-                for item in ds.dimensions.values()
-                if item.name in compress_set
-            ]
-            # get the upper limt of the dimenssion size
-            upper_limit_size = np.prod(coord_list_size) - 1
 
-            for coord_size in coord_list_size:
-                if coord_size not in range(0, upper_limit_size):
-                    valid = False
-                    reasoning.append(
-                        "The dimenssion size {} referenced by the compress attribute is not "
-                        "in the range (0, The product of the compressed dimension sizes minus 1)".format(
-                            coord_size
-                        )
-                    )
             result = Result(
                 BaseCheck.MEDIUM, valid, self.section_titles["8.2"], reasoning
             )
diff --git a/compliance_checker/cf/cf_1_7.py b/compliance_checker/cf/cf_1_7.py
index a72b103..0729dac 100644
--- a/compliance_checker/cf/cf_1_7.py
+++ b/compliance_checker/cf/cf_1_7.py
@@ -124,6 +124,7 @@ class CF1_7Check(CF1_6Check):
             if not hasattr(variable, "actual_range"):
                 continue  # having this attr is only suggested, no Result needed
             else:
+
                 out_of += 1
                 try:
                     if (
@@ -242,10 +243,6 @@ class CF1_7Check(CF1_6Check):
             variable = ds.variables[variable_name]
             valid = True
             reasoning = []
-
-            # 7.1 Required 1/5:
-            # The type of the bounds attribute is a string whose value is a single variable name.
-            # The specified variable must exist in the file.
             if boundary_variable_name not in ds.variables:
                 valid = False
                 reasoning.append(
@@ -256,8 +253,6 @@ class CF1_7Check(CF1_6Check):
                 )
             else:
                 boundary_variable = ds.variables[boundary_variable_name]
-
-            # 7.1 Required 2/5:
             # The number of dimensions in the bounds variable should always be
             # the number of dimensions in the referring variable + 1
             if boundary_variable.ndim < 2:
@@ -290,8 +285,7 @@ class CF1_7Check(CF1_6Check):
                     "".format(variable.name, boundary_variable.dimensions)
                 )
 
-            # 7.1 Required 2/5: continue
-            # Ensure p vertices form a valid simplex given previous a...n
+            # ensure p vertices form a valid simplex given previous a...n
             # previous auxiliary coordinates
             if (
                 ds.dimensions[boundary_variable.dimensions[-1]].size
@@ -307,38 +301,6 @@ class CF1_7Check(CF1_6Check):
                     )
                 )
 
-            # 7.1 Required 3/5:
-            # A boundary variable must be a numeric data type
-            if boundary_variable.dtype.kind not in "biufc":
-                valid = False
-                reasoning.append(
-                    "Boundary variable {} specified by {}".format(
-                        boundary_variable.name, variable.name
-                    )
-                    + "must be a numeric data type "
-                )
-
-            # 7.1 Required 4/5:
-            # If a boundary variable has units, standard_name, axis, positive, calendar, leap_month,
-            # leap_year or month_lengths attributes, they must agree with those of its associated variable.
-            if boundary_variable.__dict__.keys():
-                for item in boundary_variable.__dict__.keys():
-                    if hasattr(variable, item):
-                        if getattr(variable, item) != getattr(boundary_variable, item):
-                            valid = False
-                            reasoning.append(
-                                "'{}' has attr '{}' with value '{}' that does not agree "
-                                "with its associated variable ('{}')'s attr value '{}'"
-                                "".format(
-                                    boundary_variable_name,
-                                    item,
-                                    getattr(boundary_variable, item),
-                                    variable.name,
-                                    getattr(variable, item),
-                                )
-                            )
-
-            # 7.1 Required 5/5:
             # check if formula_terms is present in the var; if so,
             # the bounds variable must also have a formula_terms attr
             if hasattr(variable, "formula_terms"):
@@ -350,82 +312,12 @@ class CF1_7Check(CF1_6Check):
                         )
                     )
 
-            # 7.1 Recommendations 2/2
-            # Boundary variables should not have the _FillValue, missing_value, units, standard_name, axis,
-            # positive, calendar, leap_month, leap_year or month_lengths attributes.
-            attributes_to_check = {
-                "_FillValue",
-                "missing_value",
-                "units",
-                "standard_name",
-                "axis",
-                "positive",
-                "calendar",
-                "leap_month",
-                "leap_year",
-                "month_lengths",
-            }
-            if boundary_variable.__dict__.keys():
-                lst1 = boundary_variable.__dict__.keys()
-                lst2 = attributes_to_check
-                unwanted_attributes = [value for value in lst1 if value in lst2]
-                if unwanted_attributes:
-                    valid = False
-                    reasoning.append(
-                        "The Boundary variables '{}' should not have the attributes: '{}'".format(
-                            boundary_variable_name, unwanted_attributes
-                        )
-                    )
-
             result = Result(
                 BaseCheck.MEDIUM, valid, self.section_titles["7.1"], reasoning
             )
             ret_val.append(result)
         return ret_val
 
-    def check_cell_boundaries_interval(self, ds):
-        """
-        7.1 Cell Boundaries
-        Recommendations: (1/2)
-        The points specified by a coordinate or auxiliary coordinate variable
-        should lie within, or on the boundary, of the cells specified by the
-        associated boundary variable.
-        """
-        ret_val = []
-        reasoning = []
-        for variable_name, boundary_variable_name in cfutil.get_cell_boundary_map(
-            ds
-        ).items():
-            valid = True
-
-            variable = ds.variables[variable_name]
-            boundary_variable = ds.variables[boundary_variable_name]
-
-            for ii in range(len(variable[:])):
-                if abs(boundary_variable[ii][1]) >= abs(boundary_variable[ii][0]):
-                    if not (
-                        (abs(variable[ii]) >= abs(boundary_variable[ii][0]))
-                        and (abs(variable[ii]) <= abs(boundary_variable[ii][1]))
-                    ):
-                        valid = False
-                        reasoning.append(
-                            "The points specified by the coordinate variable {} ({})"
-                            " lie outside the boundary of the cell specified by the "
-                            "associated boundary variable {} ({})".format(
-                                variable_name,
-                                variable[ii],
-                                boundary_variable_name,
-                                boundary_variable[ii],
-                            )
-                        )
-
-                result = Result(
-                    BaseCheck.MEDIUM, valid, self.section_titles["7.1"], reasoning
-                )
-                ret_val.append(result)
-                print(ret_val)
-            return ret_val
-
     def check_cell_measures(self, ds):
         """
         A method to over-ride the CF1_6Check method. In CF 1.7, it is specified
diff --git a/compliance_checker/cf/cf_1_8.py b/compliance_checker/cf/cf_1_8.py
index 6fcc99b..2aa643b 100644
--- a/compliance_checker/cf/cf_1_8.py
+++ b/compliance_checker/cf/cf_1_8.py
@@ -94,6 +94,7 @@ class CF1_8Check(CF1_7Check):
 
             for attr in ginstance.ncattrs():
                 if attr in CF1_8Check.ROOT_GROUP_ONLY_ATTRS:
+
                     ctx_hi.messages.append(
                         f'§2.7.2 Attribute "{ attr }" MAY ONLY be used in the root group '
                         "and SHALL NOT be duplicated or overridden in child groups."
@@ -102,6 +103,7 @@ class CF1_8Check(CF1_7Check):
                     results.append(ctx_hi.to_result())
 
                 elif attr in CF1_8Check.NON_ROOT_GROUP_OPT:
+
                     ctx_lo.messages.append(
                         f"§2.7.2 Note: attribute '{ attr }' found on non-root group '{ gname }'. "
                         "This is optional for non-root groups. It is allowed in order to provide additional "
diff --git a/compliance_checker/cf/cf_base.py b/compliance_checker/cf/cf_base.py
index 3fe3f1d..07f0ca7 100644
--- a/compliance_checker/cf/cf_base.py
+++ b/compliance_checker/cf/cf_base.py
@@ -729,6 +729,7 @@ class CFBaseCheck(BaseCheck):
 
         self._metadata_vars[ds] = []
         for name, var in ds.variables.items():
+
             if name in self._find_ancillary_vars(ds) or name in self._find_coord_vars(
                 ds
             ):
diff --git a/compliance_checker/cf/util.py b/compliance_checker/cf/util.py
index 627bfe4..c39eb0e 100644
--- a/compliance_checker/cf/util.py
+++ b/compliance_checker/cf/util.py
@@ -250,6 +250,7 @@ class NCGraph(object):
     def __init__(
         self, ds, name, nc_object, self_reference_variables, reference_map=None
     ):
+
         self.ds = ds
         self.name = name
         self.coords = DotDict()
diff --git a/compliance_checker/ioos.py b/compliance_checker/ioos.py
index dbe27d0..0d1fe29 100644
--- a/compliance_checker/ioos.py
+++ b/compliance_checker/ioos.py
@@ -440,6 +440,7 @@ class IOOS1_2Check(IOOSNCCheck):
     register_checker = True
 
     def __init__(self):
+
         # instantiate objects used for delegation
         self.acdd1_6 = ACDD1_3Check()
         self.cf1_7 = CF1_7Check()
@@ -951,6 +952,7 @@ class IOOS1_2Check(IOOSNCCheck):
             )
 
     def _check_feattype_timeseries_cf_role(self, ds):
+
         ts_msg = (
             "Dimension length of variable with cf_role={cf_role} "
             "(the '{dim_type}' dimension) is {dim_len}. "
@@ -995,6 +997,7 @@ class IOOS1_2Check(IOOSNCCheck):
         )
 
     def _check_feattype_timeseriesprof_cf_role(self, ds):
+
         ts_prof_msg = (
             "Dimension length of non-platform variable with cf_role={cf_role} "
             " (the '{dim_type}' dimension) is {dim_len}. "
@@ -1595,6 +1598,7 @@ class IOOS1_2Check(IOOSNCCheck):
         for v in ds.get_variables_by_attributes(
             standard_name=lambda x: x in self._qartod_std_names
         ):
+
             missing_msg = "flag_{} not present on {}"
 
             # check if each has flag_values, flag_meanings
@@ -1777,6 +1781,7 @@ class IOOSBaseSOSCheck(BaseCheck):
 
 
 class IOOSSOSGCCheck(BaseSOSGCCheck, IOOSBaseSOSCheck):
+
     # set up namespaces for XPath
     ns = Namespaces().get_namespaces(["sos", "gml", "xlink"])
     ns["ows"] = Namespaces().get_namespace("ows110")
@@ -1855,6 +1860,7 @@ class IOOSSOSGCCheck(BaseSOSGCCheck, IOOSBaseSOSCheck):
 
 
 class IOOSSOSDSCheck(BaseSOSDSCheck, IOOSBaseSOSCheck):
+
     # set up namespaces for XPath
     ns = Namespaces().get_namespaces(["sml", "swe", "gml", "xlink"])
 
diff --git a/compliance_checker/suite.py b/compliance_checker/suite.py
index ba3e3fe..f2beb9b 100644
--- a/compliance_checker/suite.py
+++ b/compliance_checker/suite.py
@@ -345,8 +345,23 @@ class CheckSuite(object):
                         )
                     )
                     check_max_level = BaseCheck.HIGH
+                else:
+                    try:
+                        check_max_level = check_lookup[split_check_spec[1]]
+                    except KeyError:
+                        warnings.warn(
+                            "Skip specifier '{}' on check '{}' not found,"
+                            " defaulting to skip entire check".format(
+                                split_check_spec[1], check_name
+                            )
+                        )
+                        check_max_level = BaseCheck.HIGH
 
-            check_dict[check_name] = check_max_level
+                check_dict[check_name] = check_max_level
+        else:
+            for check_name in skip_checks:
+                # always process
+                check_dict[check_name] = 0
 
         return check_dict
 
@@ -944,6 +959,7 @@ class CheckSuite(object):
         ret_val = []
 
         for k, v in grouped:  # iterate through the grouped tuples
+
             k = k[0]  # slice ("name", weight_val) --> "name"
             v = list(v)  # from itertools._grouper to list
 


Test patch:
diff --git a/.github/workflows/cc-checker-ugrid-test.yml b/.github/workflows/cc-checker-ugrid-test.yml
index 4e7c507..bcff1f7 100644
--- a/.github/workflows/cc-checker-ugrid-test.yml
+++ b/.github/workflows/cc-checker-ugrid-test.yml
@@ -10,7 +10,7 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v2
 
     - name: Setup Conda
       uses: s-weigand/setup-conda@v1
diff --git a/.github/workflows/cc-plugin-glider-test.yml b/.github/workflows/cc-plugin-glider-test.yml
index 6cf7f8d..ea03393 100644
--- a/.github/workflows/cc-plugin-glider-test.yml
+++ b/.github/workflows/cc-plugin-glider-test.yml
@@ -10,7 +10,7 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v2
 
     - name: Setup Conda
       uses: s-weigand/setup-conda@v1
diff --git a/.github/workflows/cc-plugin-sgrid-test.yml b/.github/workflows/cc-plugin-sgrid-test.yml
index 7c61810..e4622ca 100644
--- a/.github/workflows/cc-plugin-sgrid-test.yml
+++ b/.github/workflows/cc-plugin-sgrid-test.yml
@@ -10,7 +10,7 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v2
 
     - name: Setup Conda
       uses: s-weigand/setup-conda@v1
diff --git a/.github/workflows/default-tests.yml b/.github/workflows/default-tests.yml
index a2ae017..1a8abf5 100644
--- a/.github/workflows/default-tests.yml
+++ b/.github/workflows/default-tests.yml
@@ -15,7 +15,7 @@ jobs:
       fail-fast: false
 
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v2
 
     - name: Setup Conda
       uses: s-weigand/setup-conda@v1
diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml
index 1961424..1b41fee 100644
--- a/.github/workflows/integration-tests.yml
+++ b/.github/workflows/integration-tests.yml
@@ -10,7 +10,7 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v2
 
     - name: Setup Conda
       uses: s-weigand/setup-conda@v1
diff --git a/compliance_checker/tests/helpers.py b/compliance_checker/tests/helpers.py
index 9852ad5..10ae53c 100644
--- a/compliance_checker/tests/helpers.py
+++ b/compliance_checker/tests/helpers.py
@@ -216,6 +216,7 @@ class MockRaggedArrayRepr(MockNetCDF):
             )
 
             if structure == "contiguous":
+
                 # create count variable
                 self.createVariable(
                     "counter_var",
@@ -229,6 +230,7 @@ class MockRaggedArrayRepr(MockNetCDF):
                 )
 
             else:
+
                 # create index variable
                 self.createVariable(
                     "index_var",
diff --git a/compliance_checker/tests/test_cf.py b/compliance_checker/tests/test_cf.py
index dd293cd..e3e3ac6 100644
--- a/compliance_checker/tests/test_cf.py
+++ b/compliance_checker/tests/test_cf.py
@@ -354,77 +354,6 @@ class TestCF1_6(BaseTestCase):
         self.assertEqual((3, 3), result.value)
         self.assertEqual([], result.msgs)
 
-    def test_check_fill_value_equal_missing_value(self):
-        """
-        According to CF §2.5.1 Recommendations: If both missing_value and _FillValue be used,
-        they should have the same value.
-        """
-        # TEST CONFORMANCE 2.5.1 RECOMMENDED
-        dataset = MockTimeSeries()
-        # Case of _FillValue and missing_value are not equal
-        dataset.createVariable("a", "d", ("time",), fill_value=9999.9)
-        dataset.variables["a"][0] = 1
-        dataset.variables["a"][1] = 2
-        dataset.variables["a"].setncattr("missing_value", [9939.9])
-
-        # Case of _FillValue and missing_value are equal
-        dataset.createVariable("b", "d", ("time",), fill_value=9999.9)
-        dataset.variables["b"][0] = 1
-        dataset.variables["b"][1] = 2
-        dataset.variables["b"].setncattr("missing_value", [9999.9])
-
-        result = self.cf.check_fill_value_equal_missing_value(dataset)
-
-        # check if the test fails when when variable "a" is checked.
-        expected_msgs = [
-            f"For the variable {v_name} the missing_value must be equal to the _FillValue"
-            for v_name in ("a")
-        ]
-
-        assert result.msgs == expected_msgs
-
-    def test_check_valid_range_or_valid_min_max_present(self):
-        """
-        2.5.1 Missing data, valid and actual range of data
-        Requirements:
-        The valid_range attribute must not be present if the
-        valid_min and/or valid_max attributes are present.
-        """
-        # TEST CONFORMANCE 2.5.1 REQUIRED
-        dataset = MockTimeSeries()
-        # Case of valid_min, valid_max, and valid_range are present
-        dataset.createVariable("a", "d", ("time",), fill_value=9999.9)
-        dataset.variables["a"][0] = 1
-        dataset.variables["a"][1] = 2
-        dataset.variables["a"].setncattr("valid_min", [-10])
-        dataset.variables["a"].setncattr("valid_max", [10])
-        dataset.variables["a"].setncattr("valid_range", [-10, 10])
-
-        # Case of valid_min and valid_max are present and valid_range is absent
-        dataset.createVariable("b", "d", ("time",), fill_value=9999.9)
-        dataset.variables["b"][0] = 1
-        dataset.variables["b"][1] = 2
-        dataset.variables["a"].setncattr("valid_min", [-10])
-        dataset.variables["a"].setncattr("valid_max", [10])
-
-        # Case of valid_min and valid_max are absent and valid_range is present
-        dataset.createVariable("c", "d", ("time",), fill_value=9999.9)
-        dataset.variables["c"][0] = 1
-        dataset.variables["c"][1] = 2
-        dataset.variables["c"].setncattr("valid_range", [-10, 10])
-
-        result = self.cf.check_valid_range_or_valid_min_max_present(dataset)
-
-        # check if the test fails when when variable "a" is checked.
-        expected_msgs = [
-            f"For the variable {v_name} the valid_range attribute must not be present "
-            f"if the valid_min and/or valid_max attributes are present"
-            for v_name in ("a")
-        ]
-
-        assert result.msgs == expected_msgs
-        assert result.value[0] == result.value[1]
-
     def test_check_fill_value_outside_valid_range(self):
         """
         2.5.1 The _FillValue should be outside the range specified by valid_range (if used) for a variable.
@@ -1131,7 +1060,7 @@ class TestCF1_6(BaseTestCase):
         self.assertTrue(
             no_missing_terms(
                 "ocean_sigma_z_coordinate",
-                {"sigma", "eta", "depth", "depth_c", "zlev"},
+                {"sigma", "eta", "depth", "depth_c", "nsigma", "zlev"},
                 dimless_vertical_coordinates_1_6,
             )
         )
@@ -1258,25 +1187,6 @@ class TestCF1_6(BaseTestCase):
 
         dataset = MockTimeSeries()
         dataset.variables["time"]
-        # test case insensivity
-        valid_calendars = (
-            "GREGORIAN",
-            "STANDARD",
-            "PROLEPTIC_GREGORIAN",
-            "NOLEAP",
-            "365_DAY",
-            "ALL_LEAP",
-            "366_DAY",
-            "360_DAY",
-            "JULIAN",
-            "NONE",
-        )
-        for calendar_uppercase in valid_calendars:
-            results = self.cf.check_calendar(dataset)
-            scored, out_of, messages = get_results(results)
-            assert scored == out_of
-
-        # test custom month length calendars
         dataset.variables["time"].calendar = "custom"
         dataset.variables["time"].month_lengths = np.array([30.3], dtype=np.double)
         results = self.cf.check_calendar(dataset)
@@ -1284,7 +1194,7 @@ class TestCF1_6(BaseTestCase):
         assert bad_month_msg in messages
 
         dataset.variables["time"].month_lengths = np.array(
-            [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], dtype=int
+            [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], dtype=np.uint8
         )
         results = self.cf.check_calendar(dataset)
         scored, out_of, messages = get_results(results)
@@ -1327,36 +1237,6 @@ class TestCF1_6(BaseTestCase):
         scored, out_of, messages = get_results(results)
         assert leap_year_msg not in messages
 
-        for calendar in ("standard", "gregorian"):
-            dataset.variables["time"].calendar = calendar
-            dataset.variables["time"].units = "seconds since 1582-10-15T00:00Z"
-            # 500 element array with some failing values
-            # _FillValue at first element even though should not be present
-            # in time coordinate variable to test bad data handling
-            # TEST CONFORMANCE 4.4.1 RECOMMENDED 4/4
-            dataset.variables["time"][1:] = np.arange(-2, 497)
-            results = self.cf.check_calendar(dataset)
-            scored, out_of, messages = get_results(results)
-            assert messages[-1] == (
-                "Variable time has time values prior to "
-                "1582-10-15T00:00Z and utilizes the "
-                "standard or Gregorian calendar"
-            )
-            dataset.variables["time"][:] = np.arange(0, 500)
-            results = self.cf.check_calendar(dataset)
-            scored, out_of, messages = get_results(results)
-            assert messages[-1] == (
-                "Variable time has standard or Gregorian "
-                "calendar and does not cross 1582-10-15T00:00Z"
-            )
-            # TEST CONFORMANCE 4.4.1 RECOMMENDED 3/4
-            if calendar == "gregorian":
-                assert (
-                    "For time variable time, when using the standard "
-                    'Gregorian calendar, the value "standard" is preferred '
-                    'over "gregorian" for the calendar attribute' in messages
-                )
-
     def test_check_aux_coordinates(self):
         dataset = self.load_dataset(STATIC_FILES["illegal-aux-coords"])
         results = self.cf.check_aux_coordinates(dataset)
@@ -1425,6 +1305,7 @@ class TestCF1_6(BaseTestCase):
         assert all(r.name == expected_name for r in results.values())
 
     def test_is_geophysical(self):
+
         # check whether string type variable, which are not `cf_role`, are
         # properly processed
         dataset = self.load_dataset(STATIC_FILES["string"])
@@ -1652,22 +1533,13 @@ class TestCF1_6(BaseTestCase):
         # units should not exist for
         temp_flag.units = "1"
 
-        time_flag = dataset.createVariable("time_flag", "i1", ("time",))
-        time_flag.standard_name = "time status_flag"
-        time_flag.flag_values = np.array([1, 2], dtype=np.int8)
-        time_flag.flag_meanings = "good bad"
-
-        lat_flag = dataset.createVariable("lat_flag", "i1", ("time",))
-        lat_flag.standard_name = "latitude status_flag"
-
         temp.ancillary_variables = "temp_flag"
         scored, out_of, messages = get_results(self.cf.check_units(dataset))
-        n_failed = out_of - scored
-        assert n_failed == 1
-        expected_messages = {
-            "units attribute for variable temp_flag must be unset when status_flag standard name modifier is set"
-        }
-        assert set(messages) == expected_messages
+        assert scored != out_of
+        assert (
+            "units attribute for variable temperature_flag must be unset "
+            "when status_flag modifier is set"
+        )
 
         del temp_flag.units
         scored, out_of, messages = get_results(self.cf.check_units(dataset))
@@ -1712,6 +1584,7 @@ class TestCF1_6(BaseTestCase):
         suite.run(dataset, "cf")
 
     def test_variable_feature_check(self):
+
         # non-compliant dataset -- 1/1 fail
         dataset = self.load_dataset(STATIC_FILES["bad-trajectory"])
         results = self.cf.check_variable_features(dataset)
@@ -1985,7 +1858,7 @@ class TestCF1_7(BaseTestCase):
         dataset = self.load_dataset(STATIC_FILES["grid-boundaries"])
         results = self.cf.check_cell_boundaries(dataset)
         score, out_of, messages = get_results(results)
-        assert (score, out_of) == (0, 2)
+        assert (score, out_of) == (2, 2)
 
         dataset = self.load_dataset(STATIC_FILES["cf_example_cell_measures"])
         results = self.cf.check_cell_boundaries(dataset)
@@ -2023,38 +1896,6 @@ class TestCF1_7(BaseTestCase):
                 in messages
             )
 
-    def test_check_cell_boundaries_interval(self):
-        """
-        7.1 Cell Boundaries
-        Recommendations: (1/2)
-        The points specified by a coordinate or auxiliary coordinate variable
-        should lie within, or on the boundary, of the cells specified by the
-        associated boundary variable.
-        """
-
-        # create Cells on a longitude axis
-        dataset = MockTimeSeries()
-        dataset.createDimension("rnv", 2)
-        dataset.createDimension("rlon", 2)
-        dataset.createVariable("rlon", "d", ("rlon",))
-        dataset.createVariable("rlon_bnds", "d", ("rlon", "rnv"))
-
-        rlon = dataset.variables["rlon"]
-        rlon.standard_name = "longitude"
-        rlon.units = "degrees_east"
-        rlon.axis = "X"
-        rlon.long_name = "Longitude"
-        rlon.bounds = "rlon_bnds"
-        rlon[:] = np.array([-97.5, -99.5], dtype=np.float64)
-
-        rlon_bnds = dataset.variables["rlon_bnds"]
-        rlon_bnds.long_name = "Longitude Cell Boundaries"
-        rlon_bnds[:] = np.array([[-97, -98], [-98, -99]], dtype=np.float64)
-
-        results = self.cf.check_cell_boundaries_interval(dataset)
-        score, out_of, messages = get_results(results)
-        assert (score, out_of) == (1, 2)
-
     def test_cell_measures(self):
         """Over-ride the test_cell_measures from CF1_6"""
 
@@ -2610,6 +2451,7 @@ class TestCF1_7(BaseTestCase):
         dataset.close()
 
     def test_check_gmattr_existence_condition_ell_pmerid_hdatum(self):
+
         # test good (all)
         dataset = MockTimeSeries()
         dataset.createVariable("lev", "d")  # dtype=double, dims=1
@@ -3029,6 +2871,7 @@ class TestCFUtil(BaseTestCase):
         )
 
     def test_is_dataset_valid_ragged_array_repr_featureType(self):
+
         # first test single featureType
 
         # ----- timeseries, indexed ----- #
diff --git a/compliance_checker/tests/test_cf_integration.py b/compliance_checker/tests/test_cf_integration.py
index 3b102a4..ab1e849 100644
--- a/compliance_checker/tests/test_cf_integration.py
+++ b/compliance_checker/tests/test_cf_integration.py
@@ -220,6 +220,7 @@ mult_msgs_diff = "Failed to find the following messages:\n{missing_msgs}\n\n\
 
 
 class TestCFIntegration:
+
     # --------------------------------------------------------------------------------
     # Helper Methods
     # --------------------------------------------------------------------------------
diff --git a/compliance_checker/tests/test_feature_detection.py b/compliance_checker/tests/test_feature_detection.py
index c2b76fd..a6c938e 100644
--- a/compliance_checker/tests/test_feature_detection.py
+++ b/compliance_checker/tests/test_feature_detection.py
@@ -444,6 +444,7 @@ class TestFeatureDetection(TestCase):
         )
 
     def test_is_dataset_valid_ragged_array_repr_featureType(self):
+
         # first test single featureType
 
         # ----- timeseries, indexed ----- #
diff --git a/compliance_checker/tests/test_ioos_profile.py b/compliance_checker/tests/test_ioos_profile.py
index e536f4f..9f8d2db 100644
--- a/compliance_checker/tests/test_ioos_profile.py
+++ b/compliance_checker/tests/test_ioos_profile.py
@@ -314,6 +314,7 @@ class TestIOOS1_2(BaseTestCase):
         self.ioos = IOOS1_2Check()
 
     def test_check_geophysical_vars_have_attrs(self):
+
         # create geophysical variable
         ds = MockTimeSeries()  # time, lat, lon, depth
         temp = ds.createVariable("temp", np.float64, dimensions=("time",))
@@ -379,6 +380,7 @@ class TestIOOS1_2(BaseTestCase):
         self.assertEqual(scored, out_of)
 
     def test_check_geospatial_vars_have_attrs(self):
+
         # create geophysical variable
         ds = MockTimeSeries()  # time, lat, lon, depth
         temp = ds.createVariable(
@@ -527,6 +529,7 @@ class TestIOOS1_2(BaseTestCase):
         )
 
     def test_check_instrument_variables(self):
+
         ds = MockTimeSeries()  # time, lat, lon, depth
 
         # no instrument variable, should pass
@@ -686,6 +689,7 @@ class TestIOOS1_2(BaseTestCase):
         self.assertEqual(res.msgs, [])
 
     def test_check_single_platform(self):
+
         ds = MockTimeSeries()  # time, lat, lon, depth
 
         # no global attr but also no platform variables, should pass
@@ -939,6 +943,7 @@ class TestIOOS1_2(BaseTestCase):
         self.assertLess(scored, out_of)
 
     def test_check_feattype_timeseries_cf_role(self):
+
         # featureType: timeseries and timeseries - msingle station require same tests
 
         # for ftype in ("timeseries", "timeseries - single station", "timeseries - multiple station"):
diff --git a/compliance_checker/tests/test_suite.py b/compliance_checker/tests/test_suite.py
index 9689de7..bc79b0a 100644
--- a/compliance_checker/tests/test_suite.py
+++ b/compliance_checker/tests/test_suite.py
@@ -6,7 +6,6 @@ from pathlib import Path
 import numpy as np
 from pkg_resources import resource_filename
 
-from compliance_checker.acdd import ACDDBaseCheck
 from compliance_checker.base import BaseCheck, GenericFile, Result
 from compliance_checker.suite import CheckSuite
 
@@ -107,17 +106,10 @@ class TestSuite(unittest.TestCase):
         ds = self.cs.load_dataset(static_files["2dim"])
         # exclude title from the check attributes
         score_groups = self.cs.run_all(ds, ["acdd"], skip_checks=["check_high"])
-        msg_set = {
-            msg
+        assert all(
+            sg.name not in {"Conventions", "title", "keywords", "summary"}
             for sg in score_groups["acdd"][0]
-            for msg in sg.msgs
-            if sg.weight == BaseCheck.HIGH
-        }
-        skipped_messages = {
-            att + " not present" for att in ACDDBaseCheck().high_rec_atts
-        }
-        # none of the skipped messages should be in the result set
-        self.assertTrue(len(msg_set & skipped_messages) == 0)
+        )
 
     def test_skip_check_level(self):
         """Checks level limited skip checks"""