Repository: ioos/compliance-checker

Issue:
CF: incorrect errors on units attribute for status_flag variables
**Version of compliance checker running:** 5.0.2 (doesn't occur in 5.0.0)

**Describe the checker this affects:**  CF (all versions)

**Attach a minimal CDL or NetCDF file which is able to reproduce the issue**
```
netcdf sample {
dimensions:
	TIME = 11 ;
variables:
	double TIME(TIME) ;
		TIME:standard_name = "time" ;
		TIME:units = "days since 1950-01-01 00:00:00 UTC" ;
	byte TIME_quality_control(TIME) ;
		TIME_quality_control:standard_name = "time status_flag" ;
		TIME_quality_control:flag_values = 0b, 1b, 2b, 3b, 4b, 5b, 6b, 7b, 8b, 9b ;
		TIME_quality_control:flag_meanings = "no_qc_performed good_data probably_good_data bad_data_that_are_potentially_correctable bad_data value_changed not_used not_used interpolated_values missing_values" ;
	double LATITUDE(TIME) ;
		LATITUDE:standard_name = "latitude" ;
		LATITUDE:units = "degrees_north" ;
	byte LATITUDE_quality_control(TIME) ;
		LATITUDE_quality_control:standard_name = "latitude status_flag" ;
		LATITUDE_quality_control:flag_values = 0b, 1b, 2b, 3b, 4b, 5b, 6b, 7b, 8b, 9b ;
		LATITUDE_quality_control:flag_meanings = "no_qc_performed good_data probably_good_data bad_data_that_are_potentially_correctable bad_data value_changed not_used not_used interpolated_values missing_values" ;
	double LONGITUDE(TIME) ;
		LONGITUDE:standard_name = "longitude" ;
		LONGITUDE:units = "degrees_east" ;
	byte LONGITUDE_quality_control(TIME) ;
		LONGITUDE_quality_control:standard_name = "longitude status_flag" ;
		LONGITUDE_quality_control:flag_values = 0b, 1b, 2b, 3b, 4b, 5b, 6b, 7b, 8b, 9b ;
		LONGITUDE_quality_control:flag_meanings = "no_qc_performed good_data probably_good_data bad_data_that_are_potentially_correctable bad_data value_changed not_used not_used interpolated_values missing_values" ;
}
```


**To Reproduce:**
Run the CF-1.6 checker as `compliance-checker -t cf:1.6 sample.cdl`

**Describe the issue below:**
The output includes
```
                                     Errors                                     
--------------------------------------------------------------------------------
§3.1 Units
* time must be in a valid units format <unit> since <epoch> not None
* variables defining latitude ("LATITUDE_quality_control") must use degrees_north or degrees if defining a transformed grid. Currently None
* variables defining longitude ("LONGITUDE_quality_control") must use degrees_east or degrees if defining a transformed grid. Currently None
```
The first error message doesn't report the variable name, but I've confirmed it's referring to the `TIME_quality_control` variable. So all three errors are looking at flag variables (with a [standard_name_modifier](http://cfconventions.org/cf-conventions/v1.6.0/cf-conventions.html#standard-name-modifiers) "status_flag"). Obviously these should not have units.
CF: incorrect errors on units attribute for status_flag variables
**Version of compliance checker running:** 5.0.2 (doesn't occur in 5.0.0)

**Describe the checker this affects:**  CF (all versions)

**Attach a minimal CDL or NetCDF file which is able to reproduce the issue**
```
netcdf sample {
dimensions:
	TIME = 11 ;
variables:
	double TIME(TIME) ;
		TIME:standard_name = "time" ;
		TIME:units = "days since 1950-01-01 00:00:00 UTC" ;
	byte TIME_quality_control(TIME) ;
		TIME_quality_control:standard_name = "time status_flag" ;
		TIME_quality_control:flag_values = 0b, 1b, 2b, 3b, 4b, 5b, 6b, 7b, 8b, 9b ;
		TIME_quality_control:flag_meanings = "no_qc_performed good_data probably_good_data bad_data_that_are_potentially_correctable bad_data value_changed not_used not_used interpolated_values missing_values" ;
	double LATITUDE(TIME) ;
		LATITUDE:standard_name = "latitude" ;
		LATITUDE:units = "degrees_north" ;
	byte LATITUDE_quality_control(TIME) ;
		LATITUDE_quality_control:standard_name = "latitude status_flag" ;
		LATITUDE_quality_control:flag_values = 0b, 1b, 2b, 3b, 4b, 5b, 6b, 7b, 8b, 9b ;
		LATITUDE_quality_control:flag_meanings = "no_qc_performed good_data probably_good_data bad_data_that_are_potentially_correctable bad_data value_changed not_used not_used interpolated_values missing_values" ;
	double LONGITUDE(TIME) ;
		LONGITUDE:standard_name = "longitude" ;
		LONGITUDE:units = "degrees_east" ;
	byte LONGITUDE_quality_control(TIME) ;
		LONGITUDE_quality_control:standard_name = "longitude status_flag" ;
		LONGITUDE_quality_control:flag_values = 0b, 1b, 2b, 3b, 4b, 5b, 6b, 7b, 8b, 9b ;
		LONGITUDE_quality_control:flag_meanings = "no_qc_performed good_data probably_good_data bad_data_that_are_potentially_correctable bad_data value_changed not_used not_used interpolated_values missing_values" ;
}
```


**To Reproduce:**
Run the CF-1.6 checker as `compliance-checker -t cf:1.6 sample.cdl`

**Describe the issue below:**
The output includes
```
                                     Errors                                     
--------------------------------------------------------------------------------
§3.1 Units
* time must be in a valid units format <unit> since <epoch> not None
* variables defining latitude ("LATITUDE_quality_control") must use degrees_north or degrees if defining a transformed grid. Currently None
* variables defining longitude ("LONGITUDE_quality_control") must use degrees_east or degrees if defining a transformed grid. Currently None
```
The first error message doesn't report the variable name, but I've confirmed it's referring to the `TIME_quality_control` variable. So all three errors are looking at flag variables (with a [standard_name_modifier](http://cfconventions.org/cf-conventions/v1.6.0/cf-conventions.html#standard-name-modifiers) "status_flag"). Obviously these should not have units.


Discussion:
I think the cause of the issue is that the `standard_name_modifier` is separated out from the root `standard_name` [here](https://github.com/ioos/compliance-checker/blob/b4e39458bc445f7e1c08a074f844ef4600ecc2aa/compliance_checker/cf/cf_1_6.py#L892), but then the only modifier explicitly handled is "number_of_observations". The code should also skip further checks within that method when the modifier is "status_flag".
I think the cause of the issue is that the `standard_name_modifier` is separated out from the root `standard_name` [here](https://github.com/ioos/compliance-checker/blob/b4e39458bc445f7e1c08a074f844ef4600ecc2aa/compliance_checker/cf/cf_1_6.py#L892), but then the only modifier explicitly handled is "number_of_observations". The code should also skip further checks within that method when the modifier is "status_flag".

Resolving patch:
diff --git a/compliance_checker/cf/cf_1_6.py b/compliance_checker/cf/cf_1_6.py
--- a/compliance_checker/cf/cf_1_6.py
+++ b/compliance_checker/cf/cf_1_6.py
@@ -910,6 +910,9 @@ def _check_valid_standard_units(self, ds, variable_name):
             # 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


Test patch:
diff --git a/compliance_checker/tests/test_cf.py b/compliance_checker/tests/test_cf.py
--- a/compliance_checker/tests/test_cf.py
+++ b/compliance_checker/tests/test_cf.py
@@ -1661,13 +1661,22 @@ def test_check_standard_name_modifier_units(self):
         # 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))
-        assert scored != out_of
-        assert (
-            "units attribute for variable temperature_flag must be unset "
-            "when status_flag modifier is set"
-        )
+        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
 
         del temp_flag.units
         scored, out_of, messages = get_results(self.cf.check_units(dataset))