2020from tempfile import gettempdir
2121
2222import pytest
23+ import yaml
2324
2425from access_moppy import ACCESS_ESM_CMORiser
26+ from access_moppy .utilities import _get_cmip7_to_cmip6_mapping
2527
2628# Import the utility function from conftest
2729from ..conftest import load_filtered_variables
5153 "CMIP7" : "access_moppy.vocabularies.cmip7-cmor-tables.tables" ,
5254}
5355
56+ CMIP7_REALM_TO_TABLE_FILE = {
57+ "atmos" : "CMIP7_atmos.json" ,
58+ "ocean" : "CMIP7_ocean.json" ,
59+ "seaIce" : "CMIP7_seaIce.json" ,
60+ "aerosol" : "CMIP7_aerosol.json" ,
61+ "land" : "CMIP7_land.json" ,
62+ "landIce" : "CMIP7_landIce.json" ,
63+ }
64+
65+ CMIP7_BASELINE_CONFIG_PATH = (
66+ Path (__file__ ).resolve ().parents [2 ]
67+ / "src/access_moppy/examples/batch_config_esm1-6_cmip7_baseline.yml"
68+ )
69+
5470
5571def _get_cmor_table_path (cmip_version : str , cmor_table_file : str ):
5672 """Resolve a bundled CMOR table path for the requested CMIP family."""
@@ -166,12 +182,50 @@ def _generate_variable_test_params(
166182 return params
167183
168184
185+ @lru_cache (maxsize = 1 )
186+ def _load_cmip7_baseline_variables () -> tuple [str , ...]:
187+ """Load the CMIP7 baseline variable list from the example batch config."""
188+ with CMIP7_BASELINE_CONFIG_PATH .open ("r" , encoding = "utf-8" ) as config_file :
189+ payload = yaml .safe_load (config_file ) or {}
190+
191+ variables = payload .get ("variables" , [])
192+ if not isinstance (variables , list ):
193+ return tuple ()
194+
195+ return tuple (
196+ str (variable ).strip () for variable in variables if str (variable ).strip ()
197+ )
198+
199+
200+ def _generate_cmip7_baseline_test_params () -> list [tuple [str , str , str , str , str ]]:
201+ """Generate CMIP7 params directly from the baseline example config."""
202+ params : list [tuple [str , str , str , str , str ]] = []
203+
204+ for cmip7_compound_name in _load_cmip7_baseline_variables ():
205+ realm = cmip7_compound_name .split ("." , 1 )[0 ]
206+ cmor_table_file = CMIP7_REALM_TO_TABLE_FILE .get (realm )
207+ if cmor_table_file is None :
208+ continue
209+
210+ params .append (
211+ (
212+ realm ,
213+ "ACCESS-ESM1-6" ,
214+ cmor_table_file ,
215+ "CMIP7" ,
216+ cmip7_compound_name ,
217+ )
218+ )
219+
220+ return params
221+
222+
169223# Generate variable-level test parameters for granular control
170224VARIABLE_TEST_PARAMS_CMIP6 = _generate_variable_test_params (tuple (CMOR_TABLES_CMIP6 ))
171225VARIABLE_TEST_PARAMS_CMIP6PLUS = _generate_variable_test_params (
172226 tuple (CMOR_TABLES_CMIP6PLUS )
173227)
174- VARIABLE_TEST_PARAMS_CMIP7 = _generate_variable_test_params ( tuple ( CMOR_TABLES_CMIP7 ) )
228+ VARIABLE_TEST_PARAMS_CMIP7 = _generate_cmip7_baseline_test_params ( )
175229
176230
177231def _parametrize_test_ids (param_set : tuple ) -> str :
@@ -187,6 +241,9 @@ def _parametrize_test_ids(param_set: tuple) -> str:
187241 return str (param_set )
188242
189243 table , model_id , cmor_table , cmip_version , variable = param_set
244+ if cmip_version == "CMIP7" and variable .count ("." ) >= 3 :
245+ return variable
246+
190247 suffix = ""
191248 if cmip_version == "CMIP7" :
192249 suffix = "-cmip7"
@@ -257,21 +314,41 @@ def _get_input_files_for_compound(
257314 # exposed through the Omon table in CMIP7 mappings.
258315 return None
259316
260- if table_name == "Omon" :
317+ if table_name in { "Omon" , "Oday" } :
261318 # For ocean variables, use only configured external ocean files.
262319 data_root = self ._configured_data_root ()
263320 if data_root is None :
264321 return []
265322
266323 try :
267- ocean_files = get_monthly_ocean_files (
268- compound_name ,
269- model_id = model_id ,
270- root_folder = str (data_root ),
271- target_folders = OCEAN_TARGET_FOLDERS ,
272- )
273- if ocean_files :
274- return [Path (f ) for f in ocean_files ]
324+ if table_name == "Omon" :
325+ ocean_files = get_monthly_ocean_files (
326+ compound_name ,
327+ model_id = model_id ,
328+ root_folder = str (data_root ),
329+ target_folders = OCEAN_TARGET_FOLDERS ,
330+ )
331+ if ocean_files :
332+ return [Path (f ) for f in ocean_files ]
333+ else :
334+ from access_moppy .utilities import load_model_mappings
335+
336+ _ , variable_name = compound_name .split ("." , 1 )
337+ mapping = load_model_mappings (compound_name , model_id = model_id )
338+ model_variables = mapping .get (variable_name , {}).get (
339+ "model_variables" , []
340+ )
341+
342+ daily_ocean_files : set [Path ] = set ()
343+ for model_variable in model_variables :
344+ daily_ocean_files .update (
345+ data_root .glob (
346+ f"output*/ocean/*-{ model_variable } -1daily-mean*.nc"
347+ )
348+ )
349+
350+ if daily_ocean_files :
351+ return sorted (daily_ocean_files )
275352 except Exception :
276353 pass
277354 return []
@@ -282,6 +359,19 @@ def _get_input_files_for_compound(
282359 )
283360 return external_files
284361
362+ if table_name == "SIday" :
363+ external_files = self ._discover_external_files (
364+ "ice/iceh-1daily-mean_*.nc" , max_files = 2
365+ )
366+ return external_files
367+
368+ if "1hr" in table_name .lower ():
369+ # Use hourly files for 1hr tables
370+ external_files = self ._discover_external_files (
371+ "atmosphere/netCDF/*_1hr.nc" , max_files = 2
372+ )
373+ return external_files
374+
285375 if "3hr" in table_name .lower ():
286376 # Use 3-hourly files for 3hr tables
287377 external_files = self ._discover_external_files (
@@ -415,23 +505,38 @@ def _run_cmorisation_variable(
415505 """
416506 # Map CMIP7 table names to CMIP6 equivalents if needed
417507 compound_table = table_name
508+ cmor_name = variable_name
509+ compound_name = f"{ compound_table } .{ variable_name } "
510+ input_lookup_compound = compound_name
511+
418512 if cmip_version == "CMIP7" :
419- cmip7_to_cmip6_table = {
420- "atmos" : "Amon" ,
421- "ocean" : "Omon" ,
422- "seaIce" : "SImon" ,
423- "aerosol" : "AERmon" ,
424- "land" : "Lmon" ,
425- }
426- compound_table = cmip7_to_cmip6_table .get (table_name , table_name )
513+ if variable_name .count ("." ) >= 3 :
514+ compound_name = variable_name
515+ cmip6_equivalent = _get_cmip7_to_cmip6_mapping (compound_name )
516+ if cmip6_equivalent is None :
517+ pytest .skip (f"No CMIP7->CMIP6 mapping found for { compound_name } " )
518+
519+ input_lookup_compound = cmip6_equivalent
520+ compound_table , cmor_name = cmip6_equivalent .split ("." , 1 )
521+ else :
522+ cmip7_to_cmip6_table = {
523+ "atmos" : "Amon" ,
524+ "ocean" : "Omon" ,
525+ "seaIce" : "SImon" ,
526+ "aerosol" : "AERmon" ,
527+ "land" : "Lmon" ,
528+ }
529+ compound_table = cmip7_to_cmip6_table .get (table_name , table_name )
530+ compound_name = f"{ compound_table } .{ variable_name } "
531+ input_lookup_compound = compound_name
532+ cmor_name = variable_name
427533
428534 # Skip ocean tests if ocean data is not available
429- if compound_table == "Omon" and not self ._ocean_data_available ():
535+ if compound_table in { "Omon" , "Oday" } and not self ._ocean_data_available ():
430536 pytest .skip (f"Ocean data directory not available; set { DATA_ROOT_ENV_VAR } " )
431537
432- compound_name = f"{ compound_table } .{ variable_name } "
433538 input_files = self ._get_input_files_for_compound (
434- compound_name , model_id = model_id
539+ input_lookup_compound , model_id = model_id
435540 )
436541
437542 # Skip if required files don't exist.
@@ -452,8 +557,9 @@ def _run_cmorisation_variable(
452557 "parent_source_id" : model_id ,
453558 "parent_mip_era" : cmip_version ,
454559 }
560+ safe_var_name = compound_name .replace ("." , "_" ).replace ("-" , "_" )
455561 output_dir = (
456- Path (gettempdir ()) / f"cmor_output_{ compound_table } _{ variable_name } "
562+ Path (gettempdir ()) / f"cmor_output_{ compound_table } _{ safe_var_name } "
457563 )
458564 drs_enabled = compliance_validation_tool == "wcrp"
459565
@@ -491,7 +597,7 @@ def _run_cmorisation_variable(
491597
492598 self ._validate_output_compliance (
493599 output_files [0 ],
494- variable_name ,
600+ cmor_name ,
495601 table_path ,
496602 cmip_version ,
497603 compliance_validation_tool ,
0 commit comments