From 382989c7b878dbf8deeae564f82008a4ceb822fa Mon Sep 17 00:00:00 2001 From: Jan Streffing Date: Thu, 13 Aug 2026 16:51:01 +0200 Subject: [PATCH] cmip7: restore flag-variable handling dropped by d82d1cf Re-applies the two hunks from #49 and #60, both merged and both absent from develop, master and every release since v2.3.2. d82d1cf ("Improve and Fix Checks", 19 Jul) rewrote plugins/cmip7/cmip7.py from a base predating both PRs, so the changes were removed rather than rebased. v2.3.3 was cut two days later and its release notes credit both fixes, but neither is in the tag: v2.3.2 _is_flag_variable absent v2.3.3 _is_flag_variable absent <- release notes claim it v2.3.4 _is_flag_variable absent develop / master absent Restored, byte-identical to what was reviewed and merged: #49 variable_id fallback in the len(geo_vars) == 0 branch of _get_geo_var. Without it any file whose data variable is CF flag-valued reports "No geophysical variable detected in the file." at HIGH, which is issue #48 as originally filed. #60 _is_flag_variable helper plus the is_flag gate on the [variable.type] check and the _FillValue / missing_value attribute rules. Without it flag-valued variables are checked against data_type = "float" and constant = 1.0e20, which no integer variable can satisfy. Verified on a real CMIP7 basin fx file (AWI-ESM3-4-2-veg-HR piControl): develop geo var = None 1 HIGH "No geophysical variable detected" this geo var = 'basin' 0 findings Existence, dimensions, shape and all other attribute rules still apply. Non-flag variables are unchanged. Closes #66. --- plugins/cmip7/cmip7.py | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/plugins/cmip7/cmip7.py b/plugins/cmip7/cmip7.py index a90daa9..cf52249 100644 --- a/plugins/cmip7/cmip7.py +++ b/plugins/cmip7/cmip7.py @@ -122,6 +122,22 @@ def _load_toml(path: str) -> dict: return toml.load(f) +def _is_flag_variable(ds, var_name): + """Return True iff ``var_name`` carries CF flag semantics + (``flag_values`` or ``flag_meanings``). CMIP7 region-selector + variables like ``basin`` and ``siline`` are integer flag variables + per CF ยง7.5 and CMIP7 data descriptor ``type: integer``. The default + geophysical_variable.toml rules assume continuous float fields + (float ``_FillValue = 1e20`` and float type check) and cannot + describe the flag case. + """ + try: + attrs = ds.variables[var_name].ncattrs() + except Exception: + return False + return "flag_values" in attrs or "flag_meanings" in attrs + + class Cmip7ProjectCheck(WCRPBaseCheck): _cc_spec = "wcrp_cmip7" _cc_spec_version = "1.0" @@ -254,6 +270,19 @@ def _get_geo_var( ctx = TestCtx(severity, "Geophysical Variable Detection") if len(geo_vars) == 0: + # CF detection returns zero candidates for files whose data + # variable carries flag_meanings (compliance-checker's + # is_geophysical heuristic treats those as QC flags). CMIP7 + # region-selector fx files (basin, siline, ...) are flag-valued + # by spec but ARE the geophysical variable of the file. Fall + # back to the global variable_id attribute, which CMIP7 defines + # as the single geophysical variable of the file, when the + # named variable exists in the dataset. + vid = getattr(ds, "variable_id", None) + vid = str(vid) if vid else None + if vid and vid in ds.variables: + self._geo_var_cache = vid + return vid, res ctx.add_failure("No geophysical variable detected in the file.") res.append(ctx.to_result()) return None, res @@ -519,6 +548,13 @@ def check_Geophysical_Variable(self, ds): return res vcfg = self.config.variable + # CF flag-valued variables (basin, siline, similar CMIP7 region + # selectors) are integer by construction. The default TOML rules + # (float type, _FillValue = 1e20, missing_value = 1e20) do not + # fit them. Gate the float type check and the two fill/missing + # attribute rules on ``is_flag``; everything else still applies. + # See #59. + is_flag = _is_flag_variable(ds, geo) # existence if vcfg.existence: @@ -526,7 +562,7 @@ def check_Geophysical_Variable(self, ds): res.extend(check_variable_existence(ds, geo, sev)) # type - if vcfg.type: + if vcfg.type and not is_flag: sev = self.get_severity(vcfg.type.severity) dt = (vcfg.type.data_type or "").lower() allowed = ["f"] if dt in {"float", "double", "real"} else None @@ -559,6 +595,8 @@ def check_Geophysical_Variable(self, ds): for attr_key, rule in vcfg.attributes.items(): sev = self.get_severity(rule.severity) name_in_file = rule.attribute_name or attr_key + if is_flag and name_in_file in ("_FillValue", "missing_value"): + continue res.extend( check_attribute_suite( ds=ds,