22# (C) Crown Copyright 2024-2026, Met Office.
33# The LICENSE.md file contains full licensing details.
44"""
5- Generate CDDS request configuration.
5+ Generate CDDS request configuration for CMEW .
66
7- THIS VERSION USES ONLY environment variables populated from rose-suite.conf.
7+ Two-run only:
88
9- Supported modes:
9+ Reference run (REF_*):
10+ - REF_MODEL_ID
11+ - REF_SUITE_ID
12+ - REF_CALENDAR
13+ - REF_VARIANT_LABEL
1014
11- 1) "Legacy" serial/unit-test mode (RUN_LABEL may be unset):
12- - REQUIRES two-run configuration in the environment (REF_* and eval vars).
13- - If RUN_LABEL is unset, defaults to generating the EVAL request
14- (uses SUITE_ID / MODEL_ID / CALENDAR / VARIANT_LABEL).
15+ Evaluation run (non-REF):
16+ - MODEL_ID
17+ - SUITE_ID
18+ - CALENDAR
19+ - VARIANT_LABEL
1520
16- 2) Two-model "multi-run" mode via task parameterisation:
17- - RUN_LABEL is set to a suite_id (e.g. u-bv526 or u-cw673).
18- - If RUN_LABEL == REF_SUITE_ID -> use REF_* variables.
19- - If RUN_LABEL == SUITE_ID -> use non-REF variables.
20- - Any other RUN_LABEL is an error (no silent fallback).
21+ Selection rule:
22+ - RUN_LABEL must be set and must match either REF_SUITE_ID or SUITE_ID.
23+ - If RUN_LABEL == REF_SUITE_ID -> use REF_* metadata.
24+ - If RUN_LABEL == SUITE_ID -> use non-REF metadata.
2125
2226Naming requirement:
23- - ALWAYS set workflow_basename = suite_id, so CDDS paths are cdds_<suite_id>.
27+ - ALWAYS set workflow_basename = suite_id so CDDS paths are cdds_<suite_id>.
28+
29+ Environment variables are accessed directly via os.environ[...].
2430"""
2531
2632from __future__ import annotations
3137
3238
3339def create_request () -> configparser .ConfigParser :
34- # ---------------------------------------------------------------------
35- # 0) Enforce "two-run legacy": require BOTH ref and eval env variables
36- # ---------------------------------------------------------------------
37- required = [
38- "START_YEAR" ,
39- "NUMBER_OF_YEARS" ,
40- "INSTITUTION_ID" ,
41- "ROOT_PROC_DIR" ,
42- "ROOT_DATA_DIR" ,
43- "VARIABLES_PATH" ,
44- # Reference run
45- "REF_MODEL_ID" ,
46- "REF_SUITE_ID" ,
47- "REF_CALENDAR" ,
48- "REF_VARIANT_LABEL" ,
49- # Evaluation run
50- "MODEL_ID" ,
51- "SUITE_ID" ,
52- "CALENDAR" ,
53- "VARIANT_LABEL" ,
54- ]
55- missing = [
56- k for k in required if not (os .environ .get (k , "" ) or "" ).strip ()
57- ]
58- if missing :
59- raise KeyError (
60- "Two-run legacy is required; missing environment variables: "
61- + ", " .join (missing )
62- )
63-
64- # ---------------------------------------------------------------------
65- # 1) Time window
66- # ---------------------------------------------------------------------
67- start_year = int (os .environ .get ("START_YEAR" , "" ).strip ())
68- number_of_years = int (os .environ .get ("NUMBER_OF_YEARS" , "" ).strip ())
40+ # Required time window
41+ start_year = int (os .environ ["START_YEAR" ])
42+ number_of_years = int (os .environ ["NUMBER_OF_YEARS" ])
6943 end_year = start_year + number_of_years
7044
71- # ---------------------------------------------------------------------
72- # 2) Resolve which run we're generating (Option B)
73- # - RUN_LABEL may be unset -> default to SUITE_ID (eval)
74- # ---------------------------------------------------------------------
75- run_label = (os .environ .get ("RUN_LABEL" , "" ) or "" ).strip ()
76- ref_suite_id = (os .environ .get ("REF_SUITE_ID" , "" ) or "" ).strip ()
77- suite_id = (os .environ .get ("SUITE_ID" , "" ) or "" ).strip ()
45+ # Required global metadata
46+ institution_id = os .environ ["INSTITUTION_ID" ]
47+ variables_path = os .environ ["VARIABLES_PATH" ]
48+ root_proc_dir = os .environ ["ROOT_PROC_DIR" ]
49+ root_data_dir = os .environ ["ROOT_DATA_DIR" ]
50+
51+ # Enforce two-run config exists (KeyError if missing)
52+ ref_model_id = os .environ ["REF_MODEL_ID" ]
53+ ref_suite_id = os .environ ["REF_SUITE_ID" ]
54+ ref_calendar = os .environ ["REF_CALENDAR" ]
55+ ref_variant_label = os .environ ["REF_VARIANT_LABEL" ]
56+
57+ model_id = os .environ ["MODEL_ID" ]
58+ suite_id = os .environ ["SUITE_ID" ]
59+ calendar = os .environ ["CALENDAR" ]
60+ variant_label = os .environ ["VARIANT_LABEL" ]
61+
62+ # Must be set in two-run mode
63+ run_label = os .environ ["RUN_LABEL" ].strip ()
7864
79- if not run_label :
80- run_label = suite_id
65+ # Optional experiment IDs (default to "amip" if not provided)
66+ ref_experiment_id = (
67+ os .environ .get ("REF_EXPERIMENT_ID" , "amip" ).strip () or "amip"
68+ )
69+ experiment_id = os .environ .get ("EXPERIMENT_ID" , "amip" ).strip () or "amip"
8170
82- # ---------------------------------------------------------------------
83- # 3) Resolve per-run metadata from env only
84- # ---------------------------------------------------------------------
8571 if run_label == ref_suite_id :
86- meta_model_id = (os .environ .get ("REF_MODEL_ID" , "" ) or "" ).strip ()
87- meta_suite_id = ref_suite_id
88- meta_calendar = (os .environ .get ("REF_CALENDAR" , "" ) or "" ).strip ()
89- meta_variant_label = (
90- os .environ .get ("REF_VARIANT_LABEL" , "" ) or ""
91- ).strip ()
92- meta_experiment_id = (
93- os .environ .get ("REF_EXPERIMENT_ID" , "amip" ) or "amip"
94- ).strip ()
72+ chosen_model_id = ref_model_id
73+ chosen_suite_id = ref_suite_id
74+ chosen_calendar = ref_calendar
75+ chosen_variant_label = ref_variant_label
76+ chosen_experiment_id = ref_experiment_id
9577 elif run_label == suite_id :
96- meta_model_id = (os .environ .get ("MODEL_ID" , "" ) or "" ).strip ()
97- meta_suite_id = suite_id
98- meta_calendar = (os .environ .get ("CALENDAR" , "" ) or "" ).strip ()
99- meta_variant_label = (
100- os .environ .get ("VARIANT_LABEL" , "" ) or ""
101- ).strip ()
102- meta_experiment_id = (
103- os .environ .get ("EXPERIMENT_ID" , "amip" ) or "amip"
104- ).strip ()
78+ chosen_model_id = model_id
79+ chosen_suite_id = suite_id
80+ chosen_calendar = calendar
81+ chosen_variant_label = variant_label
82+ chosen_experiment_id = experiment_id
10583 else :
10684 raise KeyError (
107- "RUN_LABEL must match one of the configured suite IDs . "
108- f"Got RUN_LABEL='{ run_label } '. "
109- f"Expected REF_SUITE_ID=' { ref_suite_id } ' or SUITE_ID='{ suite_id } '."
85+ "RUN_LABEL must match REF_SUITE_ID or SUITE_ID . "
86+ f"Got RUN_LABEL='{ run_label } ', REF_SUITE_ID=' { ref_suite_id } ', "
87+ f"SUITE_ID='{ suite_id } '."
11088 )
11189
112- # Naming requirement: always suite_id
113- workflow_basename = meta_suite_id
90+ # Requirement: ALWAYS use suite_id for basename (so cdds_<suite_id>)
91+ workflow_basename = chosen_suite_id
11492
115- # Avoid ConfigParser interpolation problems (e.g. '%' in URLs)
93+ # Avoid ConfigParser interpolation issues (e.g. '%' in URLs)
11694 request = configparser .RawConfigParser ()
11795
118- # ---------------------------------------------------------------------
119- # 4) Populate request sections
120- # ---------------------------------------------------------------------
12196 request ["metadata" ] = {
12297 "base_date" : "1850-01-01T00:00:00" ,
12398 "branch_method" : "no parent" ,
124- "calendar" : meta_calendar ,
125- "experiment_id" : meta_experiment_id or "amip" ,
126- "institution_id" : ( os . environ . get ( "INSTITUTION_ID" , "" ) or "" ). strip () ,
99+ "calendar" : chosen_calendar ,
100+ "experiment_id" : chosen_experiment_id ,
101+ "institution_id" : institution_id ,
127102 "license" : "GCModelDev model data is licensed under the Open Government License v3 (https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/)" , # noqa: E501
128103 "mip" : "ESMVal" ,
129104 "mip_era" : "GCModelDev" ,
130- "model_id" : meta_model_id ,
105+ "model_id" : chosen_model_id ,
131106 "model_type" : "AGCM AER" ,
132107 "sub_experiment_id" : "none" ,
133- "variant_label" : meta_variant_label ,
108+ "variant_label" : chosen_variant_label ,
134109 }
135110
136111 request ["common" ] = {
@@ -141,22 +116,20 @@ def create_request() -> configparser.ConfigParser:
141116 ),
142117 "mode" : "relaxed" ,
143118 "package" : "round-1" ,
144- "root_proc_dir" : ( os . environ . get ( "ROOT_PROC_DIR" , "" ) or "" ). strip () ,
145- "root_data_dir" : ( os . environ . get ( "ROOT_DATA_DIR" , "" ) or "" ). strip () ,
119+ "root_proc_dir" : root_proc_dir ,
120+ "root_data_dir" : root_data_dir ,
146121 "workflow_basename" : workflow_basename ,
147122 }
148123
149124 request ["data" ] = {
150125 "end_date" : f"{ end_year } -01-01T00:00:00" ,
151126 "mass_data_class" : "crum" ,
152127 "model_workflow_branch" : "trunk" ,
153- "model_workflow_id" : meta_suite_id ,
128+ "model_workflow_id" : chosen_suite_id ,
154129 "model_workflow_revision" : "not used except with data request" ,
155130 "start_date" : f"{ start_year } -01-01T00:00:00" ,
156131 "streams" : "apm" ,
157- "variable_list_file" : (
158- os .environ .get ("VARIABLES_PATH" , "" ) or ""
159- ).strip (),
132+ "variable_list_file" : variables_path ,
160133 }
161134
162135 request ["misc" ] = {"atmos_timestep" : "1200" }
@@ -174,15 +147,12 @@ def write_request(
174147 request : configparser .ConfigParser , target_path : Path
175148) -> None :
176149 target_path .parent .mkdir (parents = True , exist_ok = True )
177- with open (target_path , mode = "w" , encoding = "utf-8" ) as file_handle :
178- request .write (file_handle )
150+ with open (target_path , mode = "w" , encoding = "utf-8" ) as fh :
151+ request .write (fh )
179152
180153
181154def main () -> None :
182- request_path = (os .environ .get ("REQUEST_PATH" , "" ) or "" ).strip ()
183- if not request_path :
184- raise KeyError ("REQUEST_PATH must be set" )
185- target_path = Path (request_path )
155+ target_path = Path (os .environ ["REQUEST_PATH" ])
186156 request = create_request ()
187157 write_request (request , target_path )
188158
0 commit comments