CMIP7-data-request-api 1.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. CMIP7_data_request_api-1.1.2.dist-info/LICENSE +21 -0
  2. CMIP7_data_request_api-1.1.2.dist-info/METADATA +210 -0
  3. CMIP7_data_request_api-1.1.2.dist-info/RECORD +36 -0
  4. CMIP7_data_request_api-1.1.2.dist-info/WHEEL +5 -0
  5. CMIP7_data_request_api-1.1.2.dist-info/entry_points.txt +2 -0
  6. CMIP7_data_request_api-1.1.2.dist-info/top_level.txt +1 -0
  7. data_request_api/__init__.py +1 -0
  8. data_request_api/command_line/__init__.py +0 -0
  9. data_request_api/command_line/export_dreq_lists_json.py +136 -0
  10. data_request_api/dev/JA/__init__.py +0 -0
  11. data_request_api/dev/JA/check_plev_requests.py +416 -0
  12. data_request_api/dev/JA/read_feedback_spreadsheet.py +141 -0
  13. data_request_api/dev/JA/workflow_example_GRtest.py +275 -0
  14. data_request_api/dev/JA/workflow_example_test.py +222 -0
  15. data_request_api/dev/MM/checksum.py +68 -0
  16. data_request_api/dev/MM/walking_data_request.ipynb +380 -0
  17. data_request_api/dev/MS/dreq_content_and_walking_data_request.ipynb +3755 -0
  18. data_request_api/dev/__init__.py +0 -0
  19. data_request_api/stable/__init__.py +0 -0
  20. data_request_api/stable/content/README.MD +106 -0
  21. data_request_api/stable/content/__init__.py +0 -0
  22. data_request_api/stable/content/dreq_api/__init__.py +0 -0
  23. data_request_api/stable/content/dreq_api/consolidate_export.py +488 -0
  24. data_request_api/stable/content/dreq_api/dreq_content.py +593 -0
  25. data_request_api/stable/content/dreq_api/mapping_table.py +335 -0
  26. data_request_api/stable/content/dreq_api/test_dreq_content.py +194 -0
  27. data_request_api/stable/content/dump_transformation.py +550 -0
  28. data_request_api/stable/query/__init__.py +0 -0
  29. data_request_api/stable/query/data_request.py +1120 -0
  30. data_request_api/stable/query/dreq_classes.py +372 -0
  31. data_request_api/stable/query/dreq_query.py +981 -0
  32. data_request_api/stable/query/vocabulary_server.py +208 -0
  33. data_request_api/stable/utilities/__init__.py +0 -0
  34. data_request_api/stable/utilities/logger.py +71 -0
  35. data_request_api/stable/utilities/tools.py +49 -0
  36. data_request_api/version.py +16 -0
@@ -0,0 +1,416 @@
1
+ #!/usr/bin/env python
2
+ '''
3
+ Find out the pressure levels (plev) of requested variables.
4
+ Output json files summarizing the info.
5
+
6
+ This script started life dealing just with plevs, but has evolved to gather other variable info.
7
+ Some of the bits might be useful elsewhere.
8
+ '''
9
+
10
+ import os
11
+ import json
12
+
13
+
14
+ import data_request_api.stable.content.dreq_api.dreq_content as dc
15
+ import data_request_api.stable.query.dreq_query as dq
16
+ from data_request_api.stable.query import dreq_classes
17
+
18
+ from collections import OrderedDict, defaultdict
19
+ from copy import deepcopy
20
+
21
+ from importlib import reload
22
+ reload(dq)
23
+ reload(dc)
24
+
25
+ ###############################################################################
26
+ # Load data request content
27
+
28
+ # use_dreq_version = 'first_export'
29
+ # use_dreq_version = 'v1.0alpha'
30
+ use_dreq_version = 'v1.0beta'
31
+
32
+ # # Download specified version of data request content (if not locally cached)
33
+ # dc.retrieve(use_dreq_version, export='raw')
34
+ # # Load content into python dict
35
+ # content = dc.load(use_dreq_version, consolidate=False, export='raw')
36
+
37
+ use_consolidated = True
38
+ use_export = 'release'
39
+ use_export = 'raw'
40
+
41
+ load_manually = not True
42
+
43
+ if load_manually:
44
+ # just for testing
45
+ path = '/home/rja001/code/dreq/CMIP7_DReq_Content/airtable_export'
46
+ filepath = os.path.join(path, 'dreq_working.json')
47
+ with open(filepath, 'r') as f:
48
+ content = json.load(f)
49
+ print('loaded ' + filepath)
50
+
51
+ use_consolidated = False
52
+
53
+
54
+ else:
55
+ # this is the usual thing
56
+
57
+ # Download specified version of data request content (if not locally cached)
58
+ dc.retrieve(use_dreq_version, export=use_export)
59
+ # Load content into python dict
60
+ content = dc.load(use_dreq_version, export=use_export, consolidate=use_consolidated)
61
+
62
+
63
+
64
+ print(content.keys())
65
+
66
+ ###############################################################################
67
+ # Find variables requested for a set of opportunities.
68
+
69
+ dq.DREQ_VERSION = use_dreq_version
70
+ # Initialize table objects to represent the various tables in the data request
71
+ # base = dq.create_dreq_tables_for_request(content)
72
+ base = dq.create_dreq_tables_for_request(deepcopy(content), consolidated=use_consolidated)
73
+
74
+ # use subset of opportunities:
75
+ # use_opps = []
76
+ # use_opps.append('Baseline Climate Variables for Earth System Modelling')
77
+ # use_opps.append('Synoptic systems and impacts')
78
+
79
+ # use all opportunities:
80
+ Opps = base['Opportunity']
81
+ use_opps = [opp.title for opp in Opps.records.values()]
82
+
83
+ # dict to store all of opportunity's vars, grouped by priority:
84
+ opp_vars_by_priority = {opp_title : OrderedDict() for opp_title in use_opps}
85
+ # dict to store all of opportunity's vars, grouped by variable group:
86
+ opp_vars_by_group = {opp_title : OrderedDict() for opp_title in use_opps}
87
+
88
+ VarGroups = base['Variable Group']
89
+ Vars = base['Variables']
90
+
91
+ if 'Priority Level' in base:
92
+ PriorityLevel = base['Priority Level']
93
+ priority_levels = [rec.name for rec in PriorityLevel.records.values()]
94
+ else:
95
+ # retain this option for non-consolidated raw export?
96
+ priority_levels = ['Core', 'High', 'Medium', 'Low']
97
+
98
+ # Loop over opportunities to get requested variables for each one.
99
+ # Requested experiments are ignored because here we only want the variables.
100
+ opp_ids = dq.get_opp_ids(use_opps, Opps)
101
+ verbose = False
102
+ for opp_id in opp_ids:
103
+ opp = Opps.records[opp_id] # one record from the Opportunity table
104
+ print(f'Opportunity: {opp.title}')
105
+
106
+ for link in opp.variable_groups:
107
+ # var_group = VarGroups.records[link.record_id]
108
+ var_group = VarGroups.get_record(link)
109
+
110
+ if not hasattr(var_group, 'variables'):
111
+ continue
112
+
113
+ if isinstance(var_group.priority_level, str):
114
+ # retain this option for non-consolidated raw export?
115
+ priority_level = var_group.priority_level
116
+ else:
117
+ priority = PriorityLevel.get_record(var_group.priority_level[0])
118
+ priority_level = priority.name
119
+
120
+ assert var_group.name not in opp_vars_by_group[opp.title], 'variable group name is not unique in this opportunity!'
121
+ # opp_vars_by_group[opp.title][var_group.name] = set()
122
+ opp_vars_by_group[opp.title][var_group.name] = []
123
+
124
+ if priority_level not in opp_vars_by_priority[opp.title]:
125
+ # opp_vars_by_priority[opp.title][priority_level] = set()
126
+ opp_vars_by_priority[opp.title][priority_level] = []
127
+
128
+ for link in var_group.variables:
129
+ var = Vars.get_record(link)
130
+ var_name = dq.get_unique_var_name(var)
131
+
132
+ # opp_vars_by_group[opp.title][var_group.name].add(var_name)
133
+ opp_vars_by_group[opp.title][var_group.name].append(var_name)
134
+
135
+ # opp_vars_by_priority[opp.title][priority_level].add(var_name)
136
+ if var_name not in opp_vars_by_priority[opp.title][priority_level]:
137
+ # If the same variable is requested by >1 variable group at the same priority level, it might already be in the list
138
+ opp_vars_by_priority[opp.title][priority_level].append(var_name)
139
+
140
+ if len(opp_vars_by_group[opp.title][var_group.name]) != len(set(opp_vars_by_group[opp.title][var_group.name])):
141
+ raise Exception('overlap between variable groups for opportunity: ' + opp.title)
142
+
143
+ ###############################################################################
144
+ # The above has used the "request" part of the data request to find out what variables
145
+ # are requested by each opportunity.
146
+ # Now use the "data" part, i.e. tables that define the variables, to retrieve info
147
+ # about each variable.
148
+
149
+ # base = dq.create_dreq_tables_for_variables(content)
150
+ base = dq.create_dreq_tables_for_variables(deepcopy(content), consolidated=use_consolidated)
151
+
152
+
153
+ Vars = base['Variables']
154
+
155
+ # Choose which table to use for freqency
156
+
157
+ # freq_table_name = 'Frequency' # not available in v1.0beta release export, need to use CMIP7 or CMIP6 one instead
158
+ # freq_table_name = 'CMIP7 Frequency'
159
+ # freq_table_name = 'CMIP6 Frequency (legacy)'
160
+
161
+ try_freq_table_name = []
162
+ try_freq_table_name.append('Frequency')
163
+ try_freq_table_name.append('CMIP7 Frequency')
164
+ try_freq_table_name.append('CMIP6 Frequency (legacy)')
165
+
166
+ for freq_table_name in try_freq_table_name:
167
+ freq_attr_name = dreq_classes.format_attribute_name(freq_table_name)
168
+ # assert freq_attr_name in Vars.attr2field, 'attribute not found: ' + freq_attr_name
169
+ if freq_attr_name not in Vars.attr2field:
170
+ continue
171
+ if 'frequency' not in Vars.attr2field:
172
+ # code below assumes a variable's frequency is given by its "frequency"
173
+ Vars.rename_attr(freq_attr_name, 'frequency')
174
+ if freq_table_name in base:
175
+ Frequency = base[freq_table_name]
176
+ break
177
+
178
+ SpatialShape = base['Spatial Shape']
179
+ Dimensions = base['Coordinates and Dimensions']
180
+ TemporalShape = base['Temporal Shape']
181
+ CellMethods = base['Cell Methods']
182
+ PhysicalParameter = base['Physical Parameters']
183
+
184
+ CFStandardName = None
185
+ if 'CF Standard Names' in base:
186
+ CFStandardName = base['CF Standard Names']
187
+
188
+ # Use compound name to look up record id of each variable in the Vars table
189
+ var_name_map = {record.compound_name : record_id for record_id, record in Vars.records.items()}
190
+ assert len(var_name_map) == len(Vars.records), 'compound names do not uniquely map to variable record ids'
191
+
192
+ # Dicts to store the results
193
+ plev_info = {} # records list of pressure levels in each plev set, indexed by plev set name
194
+ opp_var_info = OrderedDict()
195
+ opp_var_info_by_group = OrderedDict()
196
+ opp_var_plev = OrderedDict()
197
+ all_var_info = {}
198
+ opp_vars_at_multiple_priorities = defaultdict(set)
199
+
200
+ # Loop over opportunities (sorted by title)
201
+ opp_titles = sorted(list(opp_vars_by_priority), key=str.lower)
202
+ for opp_title in opp_titles:
203
+
204
+ var_plev = OrderedDict() # records plev set, indexed by compound name
205
+ var_info = OrderedDict() # records a collection of info about a variable, indexed by compound name
206
+ opp_var_plev[opp_title] = var_plev
207
+ opp_var_info[opp_title] = var_info
208
+
209
+ # Determin var_plev, var_info for all variables requested by this opportunity
210
+ for priority_level, var_names in opp_vars_by_priority[opp_title].items():
211
+ # print('\n', priority_level, var_names)
212
+ for var_name in var_names:
213
+ record_id = var_name_map[var_name]
214
+ var = Vars.records[record_id] # variable record, representing one variable
215
+ # var = Vars.get_record(record_id)
216
+ assert var.compound_name == var_name, 'is compound name being used as the unique variable name?'
217
+ # print(' ', priority_level, var_name, record_id)
218
+ del record_id
219
+
220
+ if var_name in var_info:
221
+ # This means the variable is already requested at another priority level in opportunity.
222
+ # Should this be allowed? Not sure.
223
+ # If not allowed, then should be cleaned up in the primary info source (i.e., Airtable).
224
+ # Output a json file summarizing the offending variables.
225
+ opp_vars_at_multiple_priorities[opp_title].add(var_name)
226
+
227
+ # Follow links starting from the variable record to find out info about the variable
228
+ var_info[var_name] = OrderedDict()
229
+
230
+ if isinstance(var.frequency[0], str):
231
+ # retain this option for non-consolidated raw export?
232
+ assert isinstance(var.frequency, list)
233
+ frequency = var.frequency[0]
234
+ else:
235
+ link = var.frequency[0]
236
+ freq = Frequency.get_record(link)
237
+ frequency = freq.name
238
+
239
+ link = var.temporal_shape[0]
240
+ temporal_shape = TemporalShape.get_record(link)
241
+
242
+ if hasattr(var, 'cell_methods'):
243
+ assert len(var.cell_methods) == 1
244
+ link = var.cell_methods[0]
245
+ cell_methods = CellMethods.get_record(link).cell_methods
246
+ else:
247
+ cell_methods = ''
248
+
249
+ # get the 'Spatial Shape' record, which contains info about dimensions
250
+ assert len(var.spatial_shape) == 1
251
+ link = var.spatial_shape[0]
252
+ spatial_shape = SpatialShape.get_record(link)
253
+
254
+ if not hasattr(spatial_shape, 'dimensions'):
255
+ # not all variables have dimensions info
256
+ continue
257
+ levels = ''
258
+ var_dims = []
259
+ for link in spatial_shape.dimensions:
260
+ dims = Dimensions.get_record(link)
261
+ var_dims.append(dims.name)
262
+ if hasattr(dims, 'axis_flag') and dims.axis_flag == 'Z':
263
+ assert levels == '', 'found more than one vertical dimension'
264
+ levels = dims.name
265
+ if 'plev' in dims.name:
266
+ if var_name not in var_plev:
267
+ # record the plev set used by this variable
268
+ var_plev[var_name] = dims.name
269
+ else:
270
+ # or, if we already found the pressure levels, make sure they're consistent with what we previously found
271
+ assert dims.name == var_plev[var_name]
272
+
273
+ # also record, in a separate dict, what these pressure levels actually are
274
+ if dims.name not in plev_info:
275
+ # get list of pressure values for this plev set
276
+ plev_info[dims.name] = [float(s) for s in dims.requested_values.split()]
277
+ assert dims.units == 'Pa'
278
+ assert dims.stored_direction == 'decreasing'
279
+
280
+ # Get CF standard name, if it exists
281
+ # record_id = var.cf_standard_name_from_physical_parameter[0] # not a real link!
282
+ # phys_param = PhysicalParameter.get_record(record_id)
283
+ link = var.physical_parameter[0]
284
+ phys_param = PhysicalParameter.get_record(link)
285
+ if hasattr(phys_param, 'cf_standard_name'):
286
+ if isinstance(phys_param.cf_standard_name, str):
287
+ # retain this option for non-consolidated raw export?
288
+ var_info[var_name].update({
289
+ 'CF standard name' : phys_param.cf_standard_name,
290
+ })
291
+ else:
292
+ link = phys_param.cf_standard_name[0]
293
+ cfsn = CFStandardName.get_record(link)
294
+ var_info[var_name].update({
295
+ 'CF standard name' : cfsn.name,
296
+ })
297
+ else:
298
+ var_info[var_name].update({
299
+ 'CF standard name (proposed)' : phys_param.proposed_cf_standard_name,
300
+ })
301
+
302
+ var_info[var_name].update({
303
+ 'units' : phys_param.units,
304
+ 'cell_methods' : cell_methods,
305
+ 'dimensions' : ' '.join(var_dims),
306
+ 'frequency' : frequency,
307
+ 'spatial_shape' : spatial_shape.name,
308
+ 'temporal_shape' : temporal_shape.name,
309
+ 'vertical_levels' : levels,
310
+ # 'hor_label_dd' : spatial_shape.hor_label_dd,
311
+ # 'vertical_label_dd' : spatial_shape.vertical_label_dd,
312
+ # 'temporal_brand' : temporal_shape.brand,
313
+ })
314
+
315
+ if var_name not in all_var_info:
316
+ all_var_info[var_name] = var_info[var_name]
317
+
318
+ # Store the same var_info in another dict that groups variables by their variable groups
319
+ # (in case this is more convenient for reviewing variable groups)
320
+ # opp_var_info_by_group[opp_title] = {var_group_name : {} for var_group_name in opp_vars_by_group[opp_title]}
321
+ opp_var_info_by_group[opp_title] = {var_group_name : OrderedDict() for var_group_name in opp_vars_by_group[opp_title]}
322
+ for var_group_name, var_names in opp_vars_by_group[opp_title].items():
323
+ for var_name in var_names:
324
+ opp_var_info_by_group[opp_title][var_group_name][var_name] = var_info[var_name]
325
+
326
+ # Sort the all-variables dict
327
+ d = OrderedDict()
328
+ for var_name in sorted(all_var_info.keys(), key=str.lower):
329
+ d[var_name] = all_var_info[var_name]
330
+ all_var_info = d
331
+ del d
332
+
333
+
334
+ # For each levels set, list all the variables requested on it
335
+ requests = {}
336
+ for var_name, var_info in all_var_info.items():
337
+ levs = var_info['vertical_levels']
338
+ if levs == '':
339
+ continue
340
+ if levs not in requests:
341
+ requests[levs] = set()
342
+ requests[levs].add(var_name)
343
+ d = OrderedDict()
344
+ for levs in sorted(requests.keys(), key=str.lower):
345
+ d[levs] = sorted(requests[levs], key=str.lower)
346
+ requests = d
347
+ del d
348
+
349
+
350
+
351
+
352
+
353
+ ###############################################################################
354
+ # write various kinds json files summarizing the info
355
+
356
+ # write file that says what each plev grid is
357
+ filepath = 'plev_info.json'
358
+ order = [t[1] for t in sorted([(len(v),k) for k,v in plev_info.items()])]
359
+ d = OrderedDict()
360
+ for s in order:
361
+ d[s] = plev_info[s]
362
+ plev_info = d
363
+ del d
364
+ with open(filepath, 'w') as f:
365
+ # json.dump(plev_info, f, indent=4, sort_keys=True)
366
+ json.dump(plev_info, f, indent=4)
367
+ print('wrote ' + filepath)
368
+
369
+ # write file giving plevs for all variables in an opportunity on plevs
370
+ filepath = 'opp_var_plev.json'
371
+ with open(filepath, 'w') as f:
372
+ json.dump(opp_var_plev, f, indent=4, sort_keys=True)
373
+ # json.dump(opp_var_plev, f, indent=4)
374
+ print('wrote ' + filepath)
375
+
376
+ # write file giving selected info on all variables in an opportunity (including their vertical levels)
377
+ filepath = 'opp_var_info.json'
378
+ with open(filepath, 'w') as f:
379
+ json.dump(opp_var_info, f, indent=4, sort_keys=True)
380
+ # json.dump(opp_var_info, f, indent=4)
381
+ print('wrote ' + filepath)
382
+
383
+ # write another file giving selected info on all variables in an opportunity,
384
+ # but this one groups the variables by their variable groups
385
+ filepath = 'opp_var_info_by_group.json'
386
+ with open(filepath, 'w') as f:
387
+ json.dump(opp_var_info_by_group, f, indent=4)
388
+ print('wrote ' + filepath)
389
+
390
+ # write yet another file giving the same variable info, but this one doesn't group anything
391
+ # by opportunity, so each variable appears only once
392
+ filepath = 'all_var_info.json'
393
+ with open(filepath, 'w') as f:
394
+ json.dump(all_var_info, f, indent=4)
395
+ print('wrote ' + filepath)
396
+
397
+ if len(opp_vars_at_multiple_priorities) > 0:
398
+ # Write file indicating which opportunities have requested the same variable at multiple priority levels
399
+ filepath = 'opp_vars_at_multiple_priorities.json'
400
+ opp_vars_at_multiple_priorities = {k : sorted(v, key=str.lower) for k,v in opp_vars_at_multiple_priorities.items()}
401
+ with open(filepath, 'w') as f:
402
+ json.dump(opp_vars_at_multiple_priorities, f, indent=4, sort_keys=True)
403
+ print('wrote ' + filepath)
404
+
405
+ filepath = 'requested_by_level.json'
406
+ with open(filepath, 'w') as f:
407
+ json.dump(requests, f, indent=4)
408
+ print('wrote ' + filepath)
409
+
410
+ filepath = 'requested_by_plev.json'
411
+ requests2 = {k:v for k,v in requests.items() if k.startswith('plev')}
412
+ with open(filepath, 'w') as f:
413
+ json.dump(requests2, f, indent=4)
414
+ print('wrote ' + filepath)
415
+
416
+
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env python
2
+ '''
3
+ Helper utility to read data request "feedback" spreadsheet and produce a json file summarizing
4
+ which Opportunities are supported, and which aren't. Also provides priority levels of variables
5
+ that are supported for each opportunity.
6
+
7
+ Produces a json file that can be read by scripts using the data request API to generate lists
8
+ of variables by experiment, given supported opportunities as input.
9
+ '''
10
+
11
+ import argparse
12
+ import os
13
+ import openpyxl as xp
14
+ from collections import OrderedDict
15
+ import json
16
+
17
+ description = '''
18
+ Command-line tool to read a spreadsheet (the "data request feedback" spreadsheet) that specifies
19
+ which data request Opportunities are supported by a modelling centre.
20
+
21
+ Writes machine-readable summary to json file indicating:
22
+ 1) whether each Opportunity is supported (yes/no)
23
+ 2) the priority levels of variables to produce for each Opportunity
24
+ '''
25
+ parser = argparse.ArgumentParser(description=description, formatter_class=argparse.RawDescriptionHelpFormatter)
26
+
27
+ parser.add_argument('filepath', type=str, help=\
28
+ f'input spreadsheet indicating supported Opportunities')
29
+ parser.add_argument('-o', '--outfile', type=str, default='opportunity_support.json', help=\
30
+ 'output file path (optional)')
31
+ args = parser.parse_args()
32
+
33
+ filepath = args.filepath
34
+ if not os.path.exists(filepath):
35
+ raise Exception('Input file not found: ' + filepath)
36
+ workbook = xp.load_workbook(filepath, read_only=True, data_only=True)
37
+ print('Loaded spreadsheet: {}'.format(filepath))
38
+
39
+ sheet_name = 'Opportunities'
40
+ sheet = workbook[sheet_name]
41
+ use_columns = []
42
+ use_columns.append('CMIP7 production intent')
43
+
44
+ def sanitize_column_values(cols):
45
+ if not isinstance(cols, list):
46
+ raise TypeError('expected list of column values')
47
+ for k,v in enumerate(cols):
48
+ if isinstance(v, str):
49
+ v = v.replace('\n','')
50
+ elif v is None:
51
+ v = str(None)
52
+ cols[k] = v
53
+
54
+ Opps_intention = {}
55
+ for k, row in enumerate(sheet.rows):
56
+ cols = [c.value for c in row]
57
+ if k == 0:
58
+ column_names = cols
59
+ if None in column_names:
60
+ # Remove empty columns
61
+ m = column_names.index(None)
62
+ column_names = column_names[:m]
63
+ assert all([isinstance(s, str) for s in column_names]), 'column names should be string'
64
+ sanitize_column_values(column_names)
65
+ ncol = len(column_names)
66
+ else:
67
+ cols = cols[:ncol]
68
+ sanitize_column_values(cols)
69
+ d = dict(zip(column_names, cols))
70
+
71
+ opp_title = d['Title of Opportunity']
72
+ if opp_title in [None, 'None']:
73
+ continue
74
+
75
+ assert opp_title not in Opps_intention, 'Opportunity title is not unique: ' + opp_title
76
+ opp_info = OrderedDict({key : d[key] for key in use_columns})
77
+ Opps_intention[opp_title] = opp_info
78
+
79
+ del d
80
+
81
+ Opps_intention = OrderedDict({key : Opps_intention[key] for key in sorted(Opps_intention, key=str.lower)})
82
+
83
+ # Parse the production intent response strings
84
+ priority_levels = ['High', 'Medium', 'Low']
85
+ intention2maxpriority = {
86
+ 'Yes, HIGH(will aim to definitely produce)' : 'Low',
87
+ 'Yes. MEDIUM (would like to produce, will depend on other factors)' : 'Medium',
88
+ 'Yes, LOW(will only produce if time/volume allows)' : 'High',
89
+ }
90
+ for opp_title, opp_info in Opps_intention.items():
91
+ intent = opp_info['CMIP7 production intent'].strip()
92
+ if intent.lower().startswith('yes'):
93
+ p = intention2maxpriority[intent]
94
+ m = priority_levels.index(p)
95
+ opp_info['supporting'] = 'yes'
96
+ opp_info['priority_levels'] = priority_levels[:m+1]
97
+ else:
98
+ opp_info['supporting'] = 'no'
99
+ opp_info['priority_levels'] = []
100
+
101
+ supported_opps = sorted([opp_title for opp_title,opp_info in Opps_intention.items() if opp_info['supporting'] == 'yes'], key=str.lower)
102
+ unsupported_opps = sorted([opp_title for opp_title,opp_info in Opps_intention.items() if opp_info['supporting'] == 'no'], key=str.lower)
103
+
104
+ indent = ' '*2
105
+ n = len(unsupported_opps)
106
+ if n > 0:
107
+ print(f'\nOpportunities that are NOT being supported ({n}):')
108
+ for opp_title in unsupported_opps:
109
+ print(indent + opp_title)
110
+ n = len(supported_opps)
111
+ if n > 0:
112
+ print(f'\nOpportunities that ARE being supported ({n}) and their supported priority levels for variables:')
113
+ for opp_title in supported_opps:
114
+ print(indent + opp_title)
115
+ opp_info = Opps_intention[opp_title]
116
+ for key in sorted(opp_info, key=str.lower):
117
+ if key == 'supporting':
118
+ continue
119
+ print(indent*2 + f'{key} : {opp_info[key]}')
120
+
121
+ # Write json file summarizing the info
122
+ Header = OrderedDict()
123
+ Header['Opportunities supported'] = len(supported_opps)
124
+ opp_count = OrderedDict()
125
+ Header['Opportunities supported, by priority level of variables'] = opp_count
126
+ for p in priority_levels:
127
+ count = len([opp_info for opp_info in Opps_intention.values() if p in opp_info['priority_levels']])
128
+ opp_count.update({
129
+ p : count
130
+ })
131
+ Header['Opportunities not supported'] = len(unsupported_opps)
132
+ Header['Input spreadsheet'] = filepath
133
+
134
+ output = OrderedDict()
135
+ output['Header'] = Header
136
+ output['Title of Opportunity'] = Opps_intention
137
+
138
+ outfile = args.outfile
139
+ with open(outfile, 'w') as f:
140
+ json.dump(output, f, indent=4)
141
+ print('\nWrote ' + outfile)