Repository: ioos/compliance-checker
Pull request #871 (2021-07-19 23:46:30)

Don't unmask masked values when running actual_range checks
Eliminates a problem where the _FillValue values would be compared
against actual_range values, causing erroneous failures.  Also fixes a
minor scoring bug in case valid_min and/or valid_max are not defined.


Patch:
diff --git a/compliance_checker/cf/cf.py b/compliance_checker/cf/cf.py
index 99cc71d..5313b9f 100644
--- a/compliance_checker/cf/cf.py
+++ b/compliance_checker/cf/cf.py
@@ -4584,9 +4584,6 @@ class CF1_7Check(CF1_6Check):
                 continue  # having this attr is only suggested, no Result needed
             else:
 
-                if variable.mask:  # remove mask
-                    variable.set_auto_mask(False)
-
                 out_of += 1
                 try:
                     if (
@@ -4620,21 +4617,26 @@ class CF1_7Check(CF1_6Check):
 
                 # check equality to existing min/max values
                 # NOTE this is a data check
-                out_of += 1
-                if (not np.isclose(variable.actual_range[0], variable[:].min())) or (
-                    not np.isclose(variable.actual_range[1], variable[:].max())
-                ):
-                    msgs.append(
-                        "actual_range elements of '{}' inconsistent with its min/max values".format(
-                            name
+                # If every value is masked, a data check of actual_range isn't
+                # appropriate, so skip.
+                if not (hasattr(variable[:], "mask") and variable[:].mask.all()):
+                    # if min/max values aren't close to actual_range bounds,
+                    # fail.
+                    out_of += 1
+                    if not np.isclose(
+                        variable.actual_range[0], variable[:].min()
+                    ) or not np.isclose(variable.actual_range[1], variable[:].max()):
+                        msgs.append(
+                            "actual_range elements of '{}' inconsistent with its min/max values".format(
+                                name
+                            )
                         )
-                    )
-                else:
-                    score += 1
+                    else:
+                        score += 1
 
                 # check that the actual range is within the valid range
-                out_of += 1
                 if hasattr(variable, "valid_range"):  # check within valid_range
+                    out_of += 1
                     if (variable.actual_range[0] < variable.valid_range[0]) or (
                         variable.actual_range[1] > variable.valid_range[1]
                     ):
@@ -4643,13 +4645,13 @@ class CF1_7Check(CF1_6Check):
                                 name
                             )
                         )
-                else:
-                    score += 1
+                    else:
+                        score += 1
 
                 # check the elements of the actual range have the appropriate
                 # relationship to the valid_min and valid_max
-                out_of += 2
                 if hasattr(variable, "valid_min"):
+                    out_of += 1
                     if variable.actual_range[0] < variable.valid_min:
                         msgs.append(
                             '"{}"\'s actual_range first element must be >= valid_min ({})'.format(
@@ -4659,6 +4661,7 @@ class CF1_7Check(CF1_6Check):
                     else:
                         score += 1
                 if hasattr(variable, "valid_max"):
+                    out_of += 1
                     if variable.actual_range[1] > variable.valid_max:
                         msgs.append(
                             '"{}"\'s actual_range second element must be <= valid_max ({})'.format(


Test patch:
diff --git a/compliance_checker/tests/test_cf.py b/compliance_checker/tests/test_cf.py
index 778df91..40d87e4 100644
--- a/compliance_checker/tests/test_cf.py
+++ b/compliance_checker/tests/test_cf.py
@@ -1527,6 +1527,38 @@ class TestCF1_7(BaseTestCase):
         )
         dataset.close()
 
+        # case If the data is packed and valid_range is defined
+        dataset = MockTimeSeries()
+        dataset.createVariable("a", "d", ("time",))
+        dataset.variables["a"][0] = 1
+        dataset.variables["a"][1] = 2
+        dataset.variables["a"].add_offset = 2.0
+        dataset.variables["a"].scale_factor = 10
+        dataset.variables["a"].setncattr("actual_range", [12, 22])
+        dataset.variables["a"].setncattr("valid_range", [0, 100])
+        result = self.cf.check_actual_range(dataset)
+        score, out_of, messages = get_results(result)
+        assert score == out_of
+        assert len(messages) == 0
+        dataset.close()
+
+        # check that scale_factor operates properly to min and max values
+        # case If _FillValues is used
+        dataset = MockTimeSeries()
+        dataset.createVariable("a", "d", ("time",), fill_value=9999.9)
+        dataset.variables["a"][0] = 1
+        dataset.variables["a"][1] = 2
+        dataset.variables["a"].add_offset = 2.0
+        dataset.variables["a"].scale_factor = 10
+        # Check against set _FillValue to ensure it's not accidentally slipping
+        # by.
+        dataset.variables["a"].setncattr("actual_range", [12, 22])
+        result = self.cf.check_actual_range(dataset)
+        score, out_of, messages = get_results(result)
+        assert score == out_of
+        assert len(messages) == 0
+        dataset.close()
+
         # check equality to valid_range attr
         dataset = MockTimeSeries()
         dataset.createVariable("a", "d", ("time",))