From 5a1669c901e54c0848a6940b1412f38f67c010b7 Mon Sep 17 00:00:00 2001 From: Jan Streffing Date: Fri, 19 Jun 2026 09:13:17 +0200 Subject: [PATCH 1/4] cmip7: fall back to variable_id when CF detection finds zero geophysical variables CMIP7 region-selector fx files (basin, siline, similar) carry their data variable as a CF flag-valued integer (flag_values + flag_meanings mapping integer codes to region/section names). compliance-checker's is_geophysical excludes any variable with flag_meanings from the geophysical-variable set as a heuristic for status flags. The exclusion is right for QC flags but wrong for these region selectors, whose standard_name is "region" not "status_flag", and which ARE the file's data variable by CMIP7 design. The existing variable_id disambiguation in _get_geo_var only fires when CF returned multiple candidates. When CF returns zero (the basin/siline case) the function bails with "No geophysical variable detected in the file." at HIGH severity, which blocks ESGF publication. Extend the variable_id fallback to the zero-candidate branch. If CF detection finds nothing and the global variable_id attribute names an existing variable, accept that variable as the file's geophysical variable. The strict CF heuristic stays as the primary path; the fallback only kicks in when both (a) CF found zero candidates AND (b) the CMIP7-canonical variable_id attribute is present. --- plugins/cmip7/cmip7.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/plugins/cmip7/cmip7.py b/plugins/cmip7/cmip7.py index a90daa9..afc30bc 100644 --- a/plugins/cmip7/cmip7.py +++ b/plugins/cmip7/cmip7.py @@ -254,6 +254,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 From 05585030cc39114e28a581ff4e3a122bdfb8050f Mon Sep 17 00:00:00 2001 From: Jan Streffing Date: Sat, 20 Jun 2026 07:58:36 +0200 Subject: [PATCH 2/4] TIME001: include 1hr / 3hr / 6hr in AVERAGE_CORRECTION_FREQ CMIP7 (and CMIP6 archive practice) ships sub-daily tavg files with midpointed time stamps. _is_instantaneous already handles tpt rules via cell_methods, so adding the bare freq names only changes behaviour for tavg sub-daily, in the direction the spec implies. Closes #51. --- checks/time_checks/time_constants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/checks/time_checks/time_constants.py b/checks/time_checks/time_constants.py index 2c91353..2e6db58 100644 --- a/checks/time_checks/time_constants.py +++ b/checks/time_checks/time_constants.py @@ -11,6 +11,7 @@ # resolves them to use_midpoint=False without needing a Pt suffix. AVERAGE_CORRECTION_FREQ = [ "day", "mon", "monPt", "yr", "yrPt", "1hrCM", "sem", + "1hr", "3hr", "6hr", ] From 8e62595260feb48d09c77f22b62fbeb826974c79 Mon Sep 17 00:00:00 2001 From: sol1105 <10836031+sol1105@users.noreply.github.com> Date: Wed, 5 Aug 2026 07:59:12 +0200 Subject: [PATCH 3/4] Adding decadal frequency to AVERAGE_CORRECTION_FREQ --- checks/time_checks/time_constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checks/time_checks/time_constants.py b/checks/time_checks/time_constants.py index 2e6db58..afcae7f 100644 --- a/checks/time_checks/time_constants.py +++ b/checks/time_checks/time_constants.py @@ -11,7 +11,7 @@ # resolves them to use_midpoint=False without needing a Pt suffix. AVERAGE_CORRECTION_FREQ = [ "day", "mon", "monPt", "yr", "yrPt", "1hrCM", "sem", - "1hr", "3hr", "6hr", + "1hr", "3hr", "6hr", "dec", ] From 2e2f86ac606aab527608e44de66d8d1fdf82cd9a Mon Sep 17 00:00:00 2001 From: Jan Streffing Date: Mon, 6 Jul 2026 22:14:41 +0200 Subject: [PATCH 4/4] cmip7: skip fill/type checks on CF flag-valued geophysical variables Closes #59. CMIP7 region-selector variables (basin, siline, similar) are CF flag- valued integers. The default geophysical_variable.toml rules describe a canonical continuous float field (data_type = float, _FillValue = 1e20, missing_value = 1e20), which cannot express the flag case and cannot be conditionalised in TOML. After #49 taught the plugin to recognise flag-valued variables as the file's geo var, five HIGH findings still fire on basin fx files: ATTR001 (missing) or ATTR002/ATTR004 (wrong type/constant) for both _FillValue and missing_value, plus VAR005 (int vs float type check). Adds _is_flag_variable(ds, var_name) helper and threads a single is_flag gate through check_Geophysical_Variable: - [variable.type] check skipped for flag variables. - Attribute rules whose attribute_name is _FillValue or missing_value skipped for flag variables. Existence, dimensions, shape, and all other attribute rules still apply. Non-flag variables unchanged. Verified against synthetic reproducer from #59 (5 -> 0 findings for _FillValue / missing_value / VAR005) and against real AWI-ESM3-4-2-veg- HR piControl 1851 basin fx file. --- plugins/cmip7/cmip7.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/plugins/cmip7/cmip7.py b/plugins/cmip7/cmip7.py index afc30bc..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" @@ -532,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: @@ -539,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 @@ -572,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,