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.
- CMIP7_data_request_api-1.1.2.dist-info/LICENSE +21 -0
- CMIP7_data_request_api-1.1.2.dist-info/METADATA +210 -0
- CMIP7_data_request_api-1.1.2.dist-info/RECORD +36 -0
- CMIP7_data_request_api-1.1.2.dist-info/WHEEL +5 -0
- CMIP7_data_request_api-1.1.2.dist-info/entry_points.txt +2 -0
- CMIP7_data_request_api-1.1.2.dist-info/top_level.txt +1 -0
- data_request_api/__init__.py +1 -0
- data_request_api/command_line/__init__.py +0 -0
- data_request_api/command_line/export_dreq_lists_json.py +136 -0
- data_request_api/dev/JA/__init__.py +0 -0
- data_request_api/dev/JA/check_plev_requests.py +416 -0
- data_request_api/dev/JA/read_feedback_spreadsheet.py +141 -0
- data_request_api/dev/JA/workflow_example_GRtest.py +275 -0
- data_request_api/dev/JA/workflow_example_test.py +222 -0
- data_request_api/dev/MM/checksum.py +68 -0
- data_request_api/dev/MM/walking_data_request.ipynb +380 -0
- data_request_api/dev/MS/dreq_content_and_walking_data_request.ipynb +3755 -0
- data_request_api/dev/__init__.py +0 -0
- data_request_api/stable/__init__.py +0 -0
- data_request_api/stable/content/README.MD +106 -0
- data_request_api/stable/content/__init__.py +0 -0
- data_request_api/stable/content/dreq_api/__init__.py +0 -0
- data_request_api/stable/content/dreq_api/consolidate_export.py +488 -0
- data_request_api/stable/content/dreq_api/dreq_content.py +593 -0
- data_request_api/stable/content/dreq_api/mapping_table.py +335 -0
- data_request_api/stable/content/dreq_api/test_dreq_content.py +194 -0
- data_request_api/stable/content/dump_transformation.py +550 -0
- data_request_api/stable/query/__init__.py +0 -0
- data_request_api/stable/query/data_request.py +1120 -0
- data_request_api/stable/query/dreq_classes.py +372 -0
- data_request_api/stable/query/dreq_query.py +981 -0
- data_request_api/stable/query/vocabulary_server.py +208 -0
- data_request_api/stable/utilities/__init__.py +0 -0
- data_request_api/stable/utilities/logger.py +71 -0
- data_request_api/stable/utilities/tools.py +49 -0
- data_request_api/version.py +16 -0
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Functions to extract information from the data request.
|
|
3
|
+
E.g., get variables requested for each experiment.
|
|
4
|
+
|
|
5
|
+
The module has two basic sections:
|
|
6
|
+
|
|
7
|
+
1) Functions that take the data request content and convert it to python objects.
|
|
8
|
+
2) Functions that interrogate the data request, usually using output from (1) as their input.
|
|
9
|
+
|
|
10
|
+
'''
|
|
11
|
+
import os
|
|
12
|
+
import hashlib
|
|
13
|
+
import json
|
|
14
|
+
from collections import OrderedDict
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
from data_request_api.stable.query.dreq_classes import (
|
|
18
|
+
dreq_table, expt_request, UNIQUE_VAR_NAME, PRIORITY_LEVELS)
|
|
19
|
+
|
|
20
|
+
# Version of data request content:
|
|
21
|
+
DREQ_VERSION = '' # if a tagged version is being used, set this in calling script
|
|
22
|
+
|
|
23
|
+
# Version of software (python API):
|
|
24
|
+
from data_request_api import version as api_version
|
|
25
|
+
|
|
26
|
+
###############################################################################
|
|
27
|
+
# Functions to manage data request content input and use it to create python
|
|
28
|
+
# objects representing the tables.
|
|
29
|
+
|
|
30
|
+
def get_content_type(content):
|
|
31
|
+
'''
|
|
32
|
+
Internal function to distinguish the type of airtable export we are working with, based on the input dict.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
content : dict
|
|
37
|
+
Dict containing data request content exported from airtable.
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
str indicating type of content:
|
|
42
|
+
|
|
43
|
+
'working' : 3 bases containing the latest working version of data request content,
|
|
44
|
+
or 4 bases if the Schema table has been added to the export.
|
|
45
|
+
|
|
46
|
+
'version' : 1 base containing the content of a tagged data request version.
|
|
47
|
+
'''
|
|
48
|
+
n = len(content)
|
|
49
|
+
if n in [3,4]:
|
|
50
|
+
content_type = 'working'
|
|
51
|
+
elif n == 1:
|
|
52
|
+
content_type = 'version'
|
|
53
|
+
else:
|
|
54
|
+
raise ValueError('Unable to determine type of data request content in the exported json file')
|
|
55
|
+
return content_type
|
|
56
|
+
|
|
57
|
+
def version_base_name():
|
|
58
|
+
return f'Data Request {DREQ_VERSION}'
|
|
59
|
+
|
|
60
|
+
def get_priority_levels():
|
|
61
|
+
'''
|
|
62
|
+
Return list of all valid priority levels (str) in the data request.
|
|
63
|
+
List is ordered from highest to lowest priority.
|
|
64
|
+
'''
|
|
65
|
+
priority_levels = [s.capitalize() for s in PRIORITY_LEVELS]
|
|
66
|
+
|
|
67
|
+
# The priorities are specified in PRIORITY_LEVELS from dreq_classes.
|
|
68
|
+
# Check here that 'Core' is highest priority.
|
|
69
|
+
# The 'Core' priority represents the Baseline Climate Variables (BCVs, https://doi.org/10.5194/egusphere-2024-2363).
|
|
70
|
+
# It should be highest priority unless something has been mistakenly modified in dreq_classes.py.
|
|
71
|
+
# Hence this check should NEVER fail, and is done here only to be EXTRA safe.
|
|
72
|
+
assert priority_levels[0] == 'Core', 'error in PRIORITY_LEVELS: highest priority should be Core (BCVs)'
|
|
73
|
+
|
|
74
|
+
return priority_levels
|
|
75
|
+
|
|
76
|
+
def get_table_id2name(base, base_name):
|
|
77
|
+
'''
|
|
78
|
+
Get a mapping from table id to table name
|
|
79
|
+
'''
|
|
80
|
+
table_id2name = {}
|
|
81
|
+
for table_name, table in base.items():
|
|
82
|
+
# assert table['name'] == table_name
|
|
83
|
+
# assert table['base_name'] == base_name, table['base_name'] + ', ' + base_name
|
|
84
|
+
table_id2name.update({
|
|
85
|
+
table['id'] : table['name']
|
|
86
|
+
})
|
|
87
|
+
assert len(table_id2name) == len(base), 'table ids are not unique!'
|
|
88
|
+
return table_id2name
|
|
89
|
+
|
|
90
|
+
def create_dreq_tables_for_request(content, consolidated=True):
|
|
91
|
+
'''
|
|
92
|
+
For the "request" part of the data request content (Opportunities, Variable Groups, etc),
|
|
93
|
+
render raw airtable export content as dreq_table objects.
|
|
94
|
+
|
|
95
|
+
For the "data" part of the data request, the corresponding function is create_dreq_tables_for_variables().
|
|
96
|
+
|
|
97
|
+
Parameters
|
|
98
|
+
----------
|
|
99
|
+
content : dict
|
|
100
|
+
Raw airtable export. Dict is keyed by base name, for example:
|
|
101
|
+
{'Data Request Opportunities (Public)' : {
|
|
102
|
+
'Opportunity' : {...},
|
|
103
|
+
...
|
|
104
|
+
},
|
|
105
|
+
'Data Request Variables (Public)' : {
|
|
106
|
+
'Variables' : {...}
|
|
107
|
+
...
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
Returns
|
|
112
|
+
-------
|
|
113
|
+
Dict whose keys are table names and values are dreq_table objects.
|
|
114
|
+
(The base name from the input 'content' dict no longer appears.)
|
|
115
|
+
'''
|
|
116
|
+
if not isinstance(content, dict):
|
|
117
|
+
raise TypeError('Input should be dict from raw airtable export json file')
|
|
118
|
+
|
|
119
|
+
# Content is dict loaded from raw airtable export json file
|
|
120
|
+
if consolidated:
|
|
121
|
+
base_name = 'Data Request'
|
|
122
|
+
content_type = 'consolidated'
|
|
123
|
+
else:
|
|
124
|
+
# for backward compatibility
|
|
125
|
+
content_type = get_content_type(content)
|
|
126
|
+
if content_type == 'working':
|
|
127
|
+
base_name = 'Data Request Opportunities (Public)'
|
|
128
|
+
elif content_type == 'version':
|
|
129
|
+
base_name = version_base_name()
|
|
130
|
+
else:
|
|
131
|
+
raise ValueError('Unknown content type: ' + content_type)
|
|
132
|
+
# base_name = 'Data Request'
|
|
133
|
+
base = content[base_name]
|
|
134
|
+
|
|
135
|
+
# Create objects representing data request tables
|
|
136
|
+
table_id2name = get_table_id2name(base, base_name)
|
|
137
|
+
for table_name, table in base.items():
|
|
138
|
+
# print('Creating table object for table: ' + table_name)
|
|
139
|
+
base[table_name] = dreq_table(table, table_id2name)
|
|
140
|
+
|
|
141
|
+
# Change names of tables if needed
|
|
142
|
+
# (insulates downstream code from upstream name changes that don't affect functionality)
|
|
143
|
+
change_table_names = {}
|
|
144
|
+
if content_type == 'working':
|
|
145
|
+
change_table_names = {
|
|
146
|
+
# old name : new name
|
|
147
|
+
'Experiment' : 'Experiments',
|
|
148
|
+
'Priority level' : 'Priority Level'
|
|
149
|
+
}
|
|
150
|
+
for old,new in change_table_names.items():
|
|
151
|
+
assert new not in base, 'New table name already exists: ' + new
|
|
152
|
+
if old not in base:
|
|
153
|
+
# print(f'Unavailable table {old}, skipping name change')
|
|
154
|
+
continue
|
|
155
|
+
base[new] = base[old]
|
|
156
|
+
base.pop(old)
|
|
157
|
+
|
|
158
|
+
# Make some adjustments that are specific to the Opportunity table
|
|
159
|
+
Opps = base['Opportunity']
|
|
160
|
+
Opps.rename_attr('title_of_opportunity', 'title') # rename title attribute for brevity in downstream code
|
|
161
|
+
for opp in Opps.records.values():
|
|
162
|
+
opp.title = opp.title.strip()
|
|
163
|
+
if content_type == 'working':
|
|
164
|
+
if 'variable_groups' not in Opps.attr2field:
|
|
165
|
+
# Try alternate names for the latest variable groups
|
|
166
|
+
try_vg_attr = []
|
|
167
|
+
try_vg_attr.append('working_updated_variable_groups') # takes precendence over originally requested groups
|
|
168
|
+
try_vg_attr.append('originally_requested_variable_groups')
|
|
169
|
+
for vg_attr in try_vg_attr:
|
|
170
|
+
if vg_attr in Opps.attr2field:
|
|
171
|
+
Opps.rename_attr(vg_attr, 'variable_groups')
|
|
172
|
+
break
|
|
173
|
+
assert 'variable_groups' in Opps.attr2field, f'unable to determine variable groups attribute for opportunity: {opp.title}'
|
|
174
|
+
exclude_opps = set()
|
|
175
|
+
for opp_id, opp in Opps.records.items():
|
|
176
|
+
if not hasattr(opp, 'experiment_groups'):
|
|
177
|
+
print(f' * WARNING * no experiment groups found for Opportunity: {opp.title}')
|
|
178
|
+
exclude_opps.add(opp_id)
|
|
179
|
+
if not hasattr(opp, 'variable_groups'):
|
|
180
|
+
print(f' * WARNING * no variable groups found for Opportunity: {opp.title}')
|
|
181
|
+
exclude_opps.add(opp_id)
|
|
182
|
+
if len(exclude_opps) > 0:
|
|
183
|
+
print('Quality control check is excluding these Opportunities:')
|
|
184
|
+
for opp_id in exclude_opps:
|
|
185
|
+
opp = Opps.records[opp_id]
|
|
186
|
+
print(f' {opp.title}')
|
|
187
|
+
Opps.delete_record(opp_id)
|
|
188
|
+
print()
|
|
189
|
+
if len(Opps.records) == 0:
|
|
190
|
+
# If there are no opportunities left, there's no point in continuing!
|
|
191
|
+
# This check is here because if something changes upstream in Airtable, it might cause
|
|
192
|
+
# the above code to erroneously remove all opportunities.
|
|
193
|
+
raise Exception(' * ERROR * All Opportunities were removed!')
|
|
194
|
+
|
|
195
|
+
return base
|
|
196
|
+
|
|
197
|
+
def create_dreq_tables_for_variables(content, consolidated=True):
|
|
198
|
+
'''
|
|
199
|
+
For the "data" part of the data request content (Variables, Cell Methods etc),
|
|
200
|
+
render raw airtable export content as dreq_table objects.
|
|
201
|
+
|
|
202
|
+
For the "request" part of the data request, the corresponding function is create_dreq_tables_for_request().
|
|
203
|
+
|
|
204
|
+
'''
|
|
205
|
+
if not isinstance(content, dict):
|
|
206
|
+
raise TypeError('Input should be dict from raw airtable export json file')
|
|
207
|
+
|
|
208
|
+
# Content is dict loaded from raw airtable export json file
|
|
209
|
+
if consolidated:
|
|
210
|
+
base_name = 'Data Request'
|
|
211
|
+
content_type = 'consolidated'
|
|
212
|
+
else:
|
|
213
|
+
# for backward compatibility
|
|
214
|
+
content_type = get_content_type(content)
|
|
215
|
+
if content_type == 'working':
|
|
216
|
+
base_name = 'Data Request Variables (Public)'
|
|
217
|
+
elif content_type == 'version':
|
|
218
|
+
base_name = version_base_name()
|
|
219
|
+
else:
|
|
220
|
+
raise ValueError('Unknown content type: ' + content_type)
|
|
221
|
+
base = content[base_name]
|
|
222
|
+
|
|
223
|
+
# Create objects representing data request tables
|
|
224
|
+
table_id2name = get_table_id2name(base, base_name)
|
|
225
|
+
for table_name, table in base.items():
|
|
226
|
+
# print('Creating table object for table: ' + table_name)
|
|
227
|
+
base[table_name] = dreq_table(table, table_id2name)
|
|
228
|
+
|
|
229
|
+
# Change names of tables if needed
|
|
230
|
+
# (insulates downstream code from upstream name changes that don't affect functionality)
|
|
231
|
+
change_table_names = {}
|
|
232
|
+
if content_type == 'working':
|
|
233
|
+
change_table_names = {
|
|
234
|
+
# old name : new name
|
|
235
|
+
'Variable' : 'Variables',
|
|
236
|
+
'Coordinate or Dimension' : 'Coordinates and Dimensions',
|
|
237
|
+
'Physical Parameter' : 'Physical Parameters',
|
|
238
|
+
}
|
|
239
|
+
for old,new in change_table_names.items():
|
|
240
|
+
assert new not in base, 'New table name already exists: ' + new
|
|
241
|
+
base[new] = base[old]
|
|
242
|
+
base.pop(old)
|
|
243
|
+
|
|
244
|
+
return base
|
|
245
|
+
|
|
246
|
+
def _create_dreq_table_objects(content, working_base='Opportunities'):
|
|
247
|
+
'''
|
|
248
|
+
******************
|
|
249
|
+
*** DEPRECATED ***
|
|
250
|
+
Replaced by two functions:
|
|
251
|
+
create_dreq_tables_for_request()
|
|
252
|
+
create_dreq_tables_for_variables()
|
|
253
|
+
******************
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
Render raw airtable export content as dreq_table objects.
|
|
257
|
+
|
|
258
|
+
The exported content (input dict 'content') has a slightly different
|
|
259
|
+
structure depending on the content type, determined here by:
|
|
260
|
+
get_content_type(content)
|
|
261
|
+
If any finicky details need to be adjusted based on the content type,
|
|
262
|
+
this function handles them. For example, if the experiments table is
|
|
263
|
+
named "Experiments" in a versioned release but is named "Experiment"
|
|
264
|
+
in the 'working' content type. Ideally there would be no such differences,
|
|
265
|
+
but sometimes they happen. They are resolved here, insulating
|
|
266
|
+
downstream code from having to deal with them. That is, downstream code
|
|
267
|
+
should be independent of the content type.
|
|
268
|
+
|
|
269
|
+
Parameters
|
|
270
|
+
----------
|
|
271
|
+
content : dict
|
|
272
|
+
Raw airtable export, keyed by base name:
|
|
273
|
+
{ base 1 name : {
|
|
274
|
+
table 1 name : {...}
|
|
275
|
+
table 2 name : {...}
|
|
276
|
+
}
|
|
277
|
+
base 2 name : ...
|
|
278
|
+
}
|
|
279
|
+
For further details see "Structure of the exported content" in
|
|
280
|
+
scripts/README_airtable_export.md in the content repo:
|
|
281
|
+
https://github.com/CMIP-Data-Request/CMIP7_DReq_Content/
|
|
282
|
+
or equivalently for release versions:
|
|
283
|
+
https://github.com/CMIP-CMIP/CMIP7_DReq_Content/
|
|
284
|
+
|
|
285
|
+
working_base : str
|
|
286
|
+
If content dict has more than one base, as for the "working version",
|
|
287
|
+
this specifies which one to convert and return.
|
|
288
|
+
|
|
289
|
+
Returns
|
|
290
|
+
-------
|
|
291
|
+
base : dict
|
|
292
|
+
Dict keys are table names, values are dreq_table objects.
|
|
293
|
+
'''
|
|
294
|
+
if not isinstance(content, dict):
|
|
295
|
+
raise TypeError('Input should be dict from raw airtable export json file')
|
|
296
|
+
|
|
297
|
+
# Content is dict loaded from raw airtable export json file
|
|
298
|
+
content_type = get_content_type(content)
|
|
299
|
+
|
|
300
|
+
if content_type == 'working':
|
|
301
|
+
if working_base == 'Opportunities':
|
|
302
|
+
base_name = 'Data Request Opportunities (Public)'
|
|
303
|
+
elif working_base == 'Variables':
|
|
304
|
+
base_name = 'Data Request Variables (Public)'
|
|
305
|
+
else:
|
|
306
|
+
raise ValueError('Which working base to use? Unknown type: ' + working_base)
|
|
307
|
+
elif content_type == 'version':
|
|
308
|
+
base_name = version_base_name()
|
|
309
|
+
else:
|
|
310
|
+
raise ValueError('Unknown content type: ' + content_type)
|
|
311
|
+
base = content[base_name]
|
|
312
|
+
|
|
313
|
+
# Get a mapping from table id to table name
|
|
314
|
+
table_id2name = {}
|
|
315
|
+
for table_name, table in base.items():
|
|
316
|
+
assert table['name'] == table_name
|
|
317
|
+
assert table['base_name'] == base_name
|
|
318
|
+
table_id2name.update({
|
|
319
|
+
table['id'] : table['name']
|
|
320
|
+
})
|
|
321
|
+
assert len(table_id2name) == len(base)
|
|
322
|
+
# Create objects representing data request tables
|
|
323
|
+
for table_name, table in base.items():
|
|
324
|
+
# print('Creating table object for table: ' + table_name)
|
|
325
|
+
base[table_name] = dreq_table(table, table_id2name)
|
|
326
|
+
|
|
327
|
+
if 'Opportunity' in base and working_base == 'Opportunities':
|
|
328
|
+
# Make some adjustments that are specific to the Opportunity table
|
|
329
|
+
Opps = base['Opportunity']
|
|
330
|
+
Opps.rename_attr('title_of_opportunity', 'title') # rename title attribute for brevity in downstream code
|
|
331
|
+
if content_type == 'working':
|
|
332
|
+
if 'variable_groups' not in Opps.attr2field:
|
|
333
|
+
if 'originally_requested_variable_groups' in Opps.attr2field:
|
|
334
|
+
Opps.rename_attr('originally_requested_variable_groups', 'variable_groups')
|
|
335
|
+
exclude_opps = set()
|
|
336
|
+
for opp_id, opp in Opps.records.items():
|
|
337
|
+
if not hasattr(opp, 'experiment_groups'):
|
|
338
|
+
print(f' * WARNING * no experiment groups found for Opportunity {opp.title}')
|
|
339
|
+
exclude_opps.add(opp_id)
|
|
340
|
+
if not hasattr(opp, 'variable_groups'):
|
|
341
|
+
print(f' * WARNING * no variable groups found for Opportunity {opp.title}')
|
|
342
|
+
exclude_opps.add(opp_id)
|
|
343
|
+
if len(exclude_opps) > 0:
|
|
344
|
+
print('Excluding Opportunities:')
|
|
345
|
+
for opp_id in exclude_opps:
|
|
346
|
+
opp = Opps.records[opp_id]
|
|
347
|
+
print(f' {opp.title}')
|
|
348
|
+
Opps.delete_record(opp_id)
|
|
349
|
+
if len(Opps.records) == 0:
|
|
350
|
+
# If there are no opportunities left, there's no point in continuing!
|
|
351
|
+
# This check is here because if something changes upstream in Airtable, it might cause
|
|
352
|
+
# the above code to erroneously remove all opportunities.
|
|
353
|
+
raise Exception(' * ERROR * All Opportunities were removed!')
|
|
354
|
+
|
|
355
|
+
# Other adjustments
|
|
356
|
+
if content_type == 'working':
|
|
357
|
+
|
|
358
|
+
if working_base == 'Opportunities':
|
|
359
|
+
change_table_names = {
|
|
360
|
+
# old name : new name
|
|
361
|
+
'Experiment' : 'Experiments',
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
# if 'Experiments' not in base:
|
|
365
|
+
# # Unfortunately the 'working' bases have a different table name for experiments
|
|
366
|
+
# # than the official releases (as of Oct 2024)
|
|
367
|
+
# base['Experiments'] = base['Experiment']
|
|
368
|
+
# base.pop('Experiment')
|
|
369
|
+
# assert 'Experiment' not in base
|
|
370
|
+
|
|
371
|
+
elif working_base == 'Variables':
|
|
372
|
+
change_table_names = {
|
|
373
|
+
# old name : new name
|
|
374
|
+
'Variable' : 'Variables',
|
|
375
|
+
'Coordinate or Dimension' : 'Coordinates and Dimensions',
|
|
376
|
+
'Physical Parameter' : 'Physical Parameters',
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
# if 'Variables' not in base:
|
|
380
|
+
# base['Variables'] = base['Variable']
|
|
381
|
+
# base.pop('Variable')
|
|
382
|
+
# assert 'Variable' not in base
|
|
383
|
+
|
|
384
|
+
for old,new in change_table_names.items():
|
|
385
|
+
assert new not in base, 'New table name already exists: ' + new
|
|
386
|
+
base[new] = base[old]
|
|
387
|
+
base.pop(old)
|
|
388
|
+
|
|
389
|
+
return base
|
|
390
|
+
|
|
391
|
+
###############################################################################
|
|
392
|
+
# Functions to interrogate the data request, e.g. get variables requested for
|
|
393
|
+
# each experiment.
|
|
394
|
+
|
|
395
|
+
def get_opp_ids(use_opps, Opps, verbose=False, quality_control=True):
|
|
396
|
+
'''
|
|
397
|
+
Return list of unique opportunity identifiers.
|
|
398
|
+
|
|
399
|
+
Parameters
|
|
400
|
+
----------
|
|
401
|
+
use_opps : str or list
|
|
402
|
+
"all" : return all available ids
|
|
403
|
+
list of str : return ids for with the listed opportunity titles
|
|
404
|
+
Opps : dreq_table
|
|
405
|
+
table object representing the opportunities table
|
|
406
|
+
'''
|
|
407
|
+
opp_ids = []
|
|
408
|
+
records = Opps.records
|
|
409
|
+
if use_opps == 'all':
|
|
410
|
+
# Include all opportunities
|
|
411
|
+
opp_ids = list(records.keys())
|
|
412
|
+
elif isinstance(use_opps, list):
|
|
413
|
+
use_opps = sorted(set(use_opps))
|
|
414
|
+
if all([isinstance(s, str) for s in use_opps]):
|
|
415
|
+
# opp_ids = [opp_id for opp_id,opp in records.items() if opp.title in use_opps]
|
|
416
|
+
title2id = {opp.title : opp_id for opp_id,opp in records.items()}
|
|
417
|
+
assert len(records) == len(title2id), 'Opportunity titles are not unique'
|
|
418
|
+
for title in use_opps:
|
|
419
|
+
if title in title2id:
|
|
420
|
+
opp_ids.append(title2id[title])
|
|
421
|
+
else:
|
|
422
|
+
# print(f'\n* WARNING * Opportunity not found: {title}\n')
|
|
423
|
+
raise Exception(f'\n* ERROR * The specified Opportunity is not found: {title}\n')
|
|
424
|
+
|
|
425
|
+
assert len(set(opp_ids)) == len(opp_ids), 'found repeated opportunity ids'
|
|
426
|
+
|
|
427
|
+
if quality_control:
|
|
428
|
+
valid_opp_status = ['Accepted', 'Under review']
|
|
429
|
+
discard_opp_id = set()
|
|
430
|
+
for opp_id in opp_ids:
|
|
431
|
+
opp = Opps.get_record(opp_id)
|
|
432
|
+
# print(opp)
|
|
433
|
+
# if len(opp) == 0:
|
|
434
|
+
# # discard empty opportunities
|
|
435
|
+
# discard_opp_id.add(opp_id)
|
|
436
|
+
if hasattr(opp, 'status') and opp.status not in valid_opp_status:
|
|
437
|
+
discard_opp_id.add(opp_id)
|
|
438
|
+
for opp_id in discard_opp_id:
|
|
439
|
+
Opps.delete_record(opp_id)
|
|
440
|
+
opp_ids.remove(opp_id)
|
|
441
|
+
del discard_opp_id
|
|
442
|
+
|
|
443
|
+
if verbose:
|
|
444
|
+
if len(opp_ids) > 0:
|
|
445
|
+
print('Found {} Opportunities:'.format(len(opp_ids)))
|
|
446
|
+
for opp_id in opp_ids:
|
|
447
|
+
opp = records[opp_id]
|
|
448
|
+
print(' ' + opp.title)
|
|
449
|
+
else:
|
|
450
|
+
print('No Opportunities found')
|
|
451
|
+
|
|
452
|
+
return opp_ids
|
|
453
|
+
|
|
454
|
+
def get_var_group_priority(var_group, PriorityLevel=None):
|
|
455
|
+
'''
|
|
456
|
+
Returns string stating the priorty level of variable group.
|
|
457
|
+
|
|
458
|
+
Parameters
|
|
459
|
+
----------
|
|
460
|
+
var_group : dreq_record
|
|
461
|
+
Object representing a variable group
|
|
462
|
+
Its "priority_level" attribute specifies the priority as either string or link to PriorityLevel table
|
|
463
|
+
PriorityLevel : dreq_table
|
|
464
|
+
Required if var_group.priority_level is link to PriorityLevel table
|
|
465
|
+
|
|
466
|
+
Returns
|
|
467
|
+
-------
|
|
468
|
+
str that states the priority level, e.g. "High"
|
|
469
|
+
'''
|
|
470
|
+
if not hasattr(var_group, 'priority_level'):
|
|
471
|
+
return 'Undefined'
|
|
472
|
+
|
|
473
|
+
if isinstance(var_group.priority_level, list):
|
|
474
|
+
assert len(var_group.priority_level) == 1, 'Variable group should have one specified priority level'
|
|
475
|
+
link = var_group.priority_level[0]
|
|
476
|
+
assert isinstance(PriorityLevel, dreq_table)
|
|
477
|
+
rec = PriorityLevel.records[link.record_id]
|
|
478
|
+
priority_level = rec.name
|
|
479
|
+
elif isinstance(var_group.priority_level, str):
|
|
480
|
+
priority_level = var_group.priority_level
|
|
481
|
+
else:
|
|
482
|
+
raise Exception('Unable to determine variable group priority level')
|
|
483
|
+
if not isinstance(priority_level, str):
|
|
484
|
+
raise TypeError('Priority level should be str, instead got {}'.format(type(priority_level)))
|
|
485
|
+
return priority_level
|
|
486
|
+
|
|
487
|
+
def get_unique_var_name(var):
|
|
488
|
+
'''
|
|
489
|
+
Return name that uniquely identifies a variable.
|
|
490
|
+
Reason to make this a function is to control this choice in one place.
|
|
491
|
+
E.g., if compound_name is used initially, but something else chosen later.
|
|
492
|
+
|
|
493
|
+
Parameters
|
|
494
|
+
----------
|
|
495
|
+
var : dreq_record
|
|
496
|
+
Object representing a variable
|
|
497
|
+
|
|
498
|
+
Returns
|
|
499
|
+
-------
|
|
500
|
+
str that uniquely identifes a variable in the data request
|
|
501
|
+
'''
|
|
502
|
+
if UNIQUE_VAR_NAME == 'compound name':
|
|
503
|
+
return var.compound_name
|
|
504
|
+
else:
|
|
505
|
+
raise ValueError('Unknown identifier for UNIQUE_VAR_NAME: ' + UNIQUE_VAR_NAME +
|
|
506
|
+
'\nHow should the unique variable name be determined?')
|
|
507
|
+
|
|
508
|
+
def get_opp_expts(opp, ExptGroups, Expts, verbose=False):
|
|
509
|
+
'''
|
|
510
|
+
For one Opportunity, get its requested experiments.
|
|
511
|
+
Input parameters are not modified.
|
|
512
|
+
|
|
513
|
+
Parameters
|
|
514
|
+
----------
|
|
515
|
+
opp : dreq_record
|
|
516
|
+
One record from the Opportunity table
|
|
517
|
+
ExptGroups : dreq_table
|
|
518
|
+
Experiment Group table
|
|
519
|
+
Expts : dreq_table
|
|
520
|
+
Experiments table
|
|
521
|
+
|
|
522
|
+
Returns
|
|
523
|
+
-------
|
|
524
|
+
Set giving names of experiments from which the Opportunity requests output.
|
|
525
|
+
Example: {'historical', 'piControl'}
|
|
526
|
+
'''
|
|
527
|
+
# Follow links to experiment groups to find the names of requested experiments
|
|
528
|
+
opp_expts = set() # list to store names of experiments requested by this Opportunity
|
|
529
|
+
if verbose:
|
|
530
|
+
print(' Experiment Groups ({}):'.format(len(opp.experiment_groups)))
|
|
531
|
+
for link in opp.experiment_groups:
|
|
532
|
+
# expt_group = base[link.table_name].records[link.record_id]
|
|
533
|
+
expt_group = ExptGroups.records[link.record_id]
|
|
534
|
+
|
|
535
|
+
if not hasattr(expt_group, 'experiments'):
|
|
536
|
+
continue
|
|
537
|
+
|
|
538
|
+
if verbose:
|
|
539
|
+
print(f' {expt_group.name} ({len(expt_group.experiments)} experiments)')
|
|
540
|
+
|
|
541
|
+
for link in expt_group.experiments:
|
|
542
|
+
expt = Expts.records[link.record_id]
|
|
543
|
+
# print(f' {expt.experiment}')
|
|
544
|
+
opp_expts.add(expt.experiment)
|
|
545
|
+
return opp_expts
|
|
546
|
+
|
|
547
|
+
def get_opp_vars(opp, priority_levels, VarGroups, Vars, PriorityLevel=None, verbose=False):
|
|
548
|
+
'''
|
|
549
|
+
For one Opportunity, get its requested variables grouped by priority level.
|
|
550
|
+
Input parameters are not modified.
|
|
551
|
+
|
|
552
|
+
Parameters
|
|
553
|
+
----------
|
|
554
|
+
opp : dreq_record
|
|
555
|
+
One record from the Opportunity table
|
|
556
|
+
priority_levels : list[str]
|
|
557
|
+
Priority levels to get, example: ['High', 'Medium']
|
|
558
|
+
VarGroups : dreq_table
|
|
559
|
+
Variable Group table
|
|
560
|
+
Vars : dreq_table
|
|
561
|
+
Variables table
|
|
562
|
+
PriorityLevel : dreq_table
|
|
563
|
+
Required if var_group.priority_level is link to PriorityLevel table
|
|
564
|
+
|
|
565
|
+
Returns
|
|
566
|
+
-------
|
|
567
|
+
Dict giving set of variables requested at each specified priority level
|
|
568
|
+
Example: {'High' : {'Amon.tas', 'day.tas'}, 'Medium' : {'day.ua'}}
|
|
569
|
+
'''
|
|
570
|
+
# Follow links to variable groups to find names of requested variables
|
|
571
|
+
opp_vars = {p : set() for p in priority_levels}
|
|
572
|
+
if verbose:
|
|
573
|
+
print(' Variable Groups ({}):'.format(len(opp.variable_groups)))
|
|
574
|
+
for link in opp.variable_groups:
|
|
575
|
+
var_group = VarGroups.records[link.record_id]
|
|
576
|
+
|
|
577
|
+
priority_level = get_var_group_priority(var_group, PriorityLevel)
|
|
578
|
+
if priority_level not in priority_levels:
|
|
579
|
+
continue
|
|
580
|
+
|
|
581
|
+
if verbose:
|
|
582
|
+
print(f' {var_group.name} ({len(var_group.variables)} variables, {priority_level} priority)')
|
|
583
|
+
|
|
584
|
+
for link in var_group.variables:
|
|
585
|
+
var = Vars.records[link.record_id]
|
|
586
|
+
var_name = get_unique_var_name(var)
|
|
587
|
+
# Add this variable to the list of requested variables at the specified priority
|
|
588
|
+
opp_vars[priority_level].add(var_name)
|
|
589
|
+
return opp_vars
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def get_requested_variables(content, use_opps='all', priority_cutoff='Low', verbose=True, consolidated=True, check_core_variables=True):
|
|
594
|
+
'''
|
|
595
|
+
Return variables requested for each experiment, as a function of opportunities supported and priority level of variables.
|
|
596
|
+
|
|
597
|
+
Parameters
|
|
598
|
+
----------
|
|
599
|
+
content : dict
|
|
600
|
+
Dict containing either:
|
|
601
|
+
- data request content as exported from airtable
|
|
602
|
+
OR
|
|
603
|
+
- dreq_table objects representing tables (dict keys are table names)
|
|
604
|
+
use_opp : str or list of str/int
|
|
605
|
+
Identifies the opportunities being supported. Options:
|
|
606
|
+
'all' : include all available opportunities
|
|
607
|
+
integers : include opportunities identified by their integer IDs
|
|
608
|
+
strings : include opportunities identified by their titles
|
|
609
|
+
priority_cutoff : str
|
|
610
|
+
Only return variables of equal or higher priority level than priority_cutoff.
|
|
611
|
+
E.g., priority_cutoff='Low' means all priority levels are returned.
|
|
612
|
+
check_core_variables : bool
|
|
613
|
+
True ==> check that all experiments contain a non-empty list of Core variables,
|
|
614
|
+
and that it's the same list for all experiments.
|
|
615
|
+
|
|
616
|
+
Returns
|
|
617
|
+
-------
|
|
618
|
+
Dict keyed by experiment name, giving prioritized variables for each experiment.
|
|
619
|
+
Example:
|
|
620
|
+
{ 'Header' : ... (Header contains info about where this request comes from)
|
|
621
|
+
'experiment' : {
|
|
622
|
+
'historical' :
|
|
623
|
+
'High' : ['Amon.tas', 'day.tas', ...],
|
|
624
|
+
'Medium' : ...
|
|
625
|
+
}
|
|
626
|
+
...
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
'''
|
|
630
|
+
if isinstance(content, dict):
|
|
631
|
+
if all([isinstance(table, dreq_table) for table in content.values()]):
|
|
632
|
+
# tables have already been rendered as dreq_table objects
|
|
633
|
+
base = content
|
|
634
|
+
else:
|
|
635
|
+
# render tables as dreq_table objects
|
|
636
|
+
base = create_dreq_tables_for_request(content, consolidated=consolidated)
|
|
637
|
+
else:
|
|
638
|
+
raise TypeError('Expect dict as input')
|
|
639
|
+
|
|
640
|
+
Opps = base['Opportunity']
|
|
641
|
+
opp_ids = get_opp_ids(use_opps, Opps, verbose=verbose)
|
|
642
|
+
|
|
643
|
+
ExptGroups = base['Experiment Group']
|
|
644
|
+
Expts = base['Experiments']
|
|
645
|
+
VarGroups = base['Variable Group']
|
|
646
|
+
Vars = base['Variables']
|
|
647
|
+
|
|
648
|
+
# all_priority_levels = ['Core', 'High', 'Medium', 'Low']
|
|
649
|
+
# all_priority_levels = [s.capitalize() for s in PRIORITY_LEVELS]
|
|
650
|
+
all_priority_levels = get_priority_levels()
|
|
651
|
+
|
|
652
|
+
if 'Priority Level' in base:
|
|
653
|
+
PriorityLevel = base['Priority Level']
|
|
654
|
+
priority_levels_from_table = [rec.name for rec in PriorityLevel.records.values()]
|
|
655
|
+
assert set(all_priority_levels) == set(priority_levels_from_table), \
|
|
656
|
+
'inconsistent priority levels:\n ' + str(all_priority_levels) + '\n ' + str(priority_levels_from_table)
|
|
657
|
+
else:
|
|
658
|
+
PriorityLevel = None
|
|
659
|
+
priority_cutoff = priority_cutoff.capitalize()
|
|
660
|
+
if priority_cutoff not in all_priority_levels:
|
|
661
|
+
raise ValueError('Invalid priority level cutoff: ' + priority_cutoff + '\nCould not determine priority levels to include.')
|
|
662
|
+
m = all_priority_levels.index(priority_cutoff)
|
|
663
|
+
priority_levels = all_priority_levels[:m+1]
|
|
664
|
+
del priority_cutoff
|
|
665
|
+
|
|
666
|
+
# Loop over Opportunities to get prioritized lists of variables
|
|
667
|
+
request = {} # dict to hold aggregated request
|
|
668
|
+
for opp_id in opp_ids:
|
|
669
|
+
opp = Opps.records[opp_id] # one record from the Opportunity table
|
|
670
|
+
|
|
671
|
+
if verbose:
|
|
672
|
+
print(f'Opportunity: {opp.title}')
|
|
673
|
+
|
|
674
|
+
opp_expts = get_opp_expts(opp, ExptGroups, Expts, verbose=verbose)
|
|
675
|
+
opp_vars = get_opp_vars(opp, priority_levels, VarGroups, Vars, PriorityLevel, verbose=verbose)
|
|
676
|
+
|
|
677
|
+
# Aggregate this Opportunity's request into the master list of requests
|
|
678
|
+
for expt_name in opp_expts:
|
|
679
|
+
if expt_name not in request:
|
|
680
|
+
# If we haven't encountered this experiment yet, initialize an expt_request object for it
|
|
681
|
+
request[expt_name] = expt_request(expt_name)
|
|
682
|
+
|
|
683
|
+
# Add this Opportunity's variables request to the expt_request object
|
|
684
|
+
for priority_level, var_names in opp_vars.items():
|
|
685
|
+
request[expt_name].add_vars(var_names, priority_level)
|
|
686
|
+
|
|
687
|
+
opp_titles = sorted([Opps.get_record(opp_id).title for opp_id in opp_ids])
|
|
688
|
+
requested_vars = {
|
|
689
|
+
'Header' : {
|
|
690
|
+
'Opportunities' : opp_titles,
|
|
691
|
+
'dreq version' : DREQ_VERSION,
|
|
692
|
+
},
|
|
693
|
+
'experiment' : {},
|
|
694
|
+
}
|
|
695
|
+
for expt_name, expt_req in request.items():
|
|
696
|
+
requested_vars['experiment'].update(expt_req.to_dict())
|
|
697
|
+
|
|
698
|
+
if check_core_variables:
|
|
699
|
+
# Confirm that 'Core' priority level variables are included, and identical for each experiment.
|
|
700
|
+
# The setting of priority_levels list, above, should guarantee this.
|
|
701
|
+
# Putting this extra check here just to be extra sure.
|
|
702
|
+
core_vars = set()
|
|
703
|
+
for expt_name, expt_req in requested_vars['experiment'].items():
|
|
704
|
+
assert 'Core' in expt_req, 'Missing Core variables for experiment: ' + expt_name
|
|
705
|
+
vars = set(expt_req['Core'])
|
|
706
|
+
assert len(vars) > 0, 'Empty Core variables list for experiment: ' + expt_name
|
|
707
|
+
if len(core_vars) == 0:
|
|
708
|
+
core_vars = vars
|
|
709
|
+
assert vars == core_vars, 'Inconsistent Core variables for experiment: ' + expt_name
|
|
710
|
+
|
|
711
|
+
return requested_vars
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def _get_requested_variables(content, use_opp='all', priority_cutoff='Low', verbose=True):
|
|
716
|
+
'''
|
|
717
|
+
******************
|
|
718
|
+
*** DEPRECATED ***
|
|
719
|
+
This is an initial version of the search function that uses python dicts from the airtable export directly.
|
|
720
|
+
It may be useful to keep in the module for testing, i.e. to validate other codes.
|
|
721
|
+
******************
|
|
722
|
+
|
|
723
|
+
Return variables requested for each experiment, as a function of opportunities supported and priority level of variables.
|
|
724
|
+
|
|
725
|
+
Parameters
|
|
726
|
+
----------
|
|
727
|
+
content : dict
|
|
728
|
+
Dict containing data request content exported from airtable.
|
|
729
|
+
use_opp : str or list of str/int
|
|
730
|
+
Identifies the opportunities being supported. Options:
|
|
731
|
+
'all' : include all available opportunities
|
|
732
|
+
integers : include opportunities identified by their integer IDs
|
|
733
|
+
strings : include opportunities identified by their titles
|
|
734
|
+
priority_cutoff : str
|
|
735
|
+
Only return variables of equal or higher priority level than priority_cutoff.
|
|
736
|
+
E.g., priority_cutoff='Low' means all priority levels are returned.
|
|
737
|
+
|
|
738
|
+
Returns
|
|
739
|
+
-------
|
|
740
|
+
Dict keyed by experiment name, giving prioritized variables for each experiment.
|
|
741
|
+
Example:
|
|
742
|
+
{ 'Header' : ... (Header contains info about where this request comes from)
|
|
743
|
+
'experiment' : {
|
|
744
|
+
'historical' :
|
|
745
|
+
'High' : ['Amon.tas', 'day.tas', ...],
|
|
746
|
+
'Medium' : ...
|
|
747
|
+
}
|
|
748
|
+
...
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
'''
|
|
752
|
+
|
|
753
|
+
if not isinstance(content, dict):
|
|
754
|
+
raise TypeError('Input should be dict from raw airtable export json file')
|
|
755
|
+
|
|
756
|
+
content_type = get_content_type(content)
|
|
757
|
+
if content_type == 'working':
|
|
758
|
+
base_name = 'Data Request Opportunities (Public)'
|
|
759
|
+
elif content_type == 'version':
|
|
760
|
+
base_name = version_base_name()
|
|
761
|
+
|
|
762
|
+
tables = content[base_name]
|
|
763
|
+
|
|
764
|
+
all_opps = tables['Opportunity'] # Opportunities table, specifying all defined data request opportunities
|
|
765
|
+
|
|
766
|
+
filter_opps = True
|
|
767
|
+
if filter_opps:
|
|
768
|
+
# somehow empty opportunities are in the v1.0alpha base
|
|
769
|
+
# this will cause problems below
|
|
770
|
+
# discard them
|
|
771
|
+
discard_opp_id = set()
|
|
772
|
+
for opp_id, opp in all_opps['records'].items():
|
|
773
|
+
if len(opp) == 0:
|
|
774
|
+
# discard empty opportunities
|
|
775
|
+
discard_opp_id.add(opp_id)
|
|
776
|
+
if 'Status' in opp and opp['Status'] not in ['Accepted', 'Under review']:
|
|
777
|
+
discard_opp_id.add(opp_id)
|
|
778
|
+
for opp_id in discard_opp_id:
|
|
779
|
+
all_opps['records'].pop(opp_id)
|
|
780
|
+
del discard_opp_id
|
|
781
|
+
|
|
782
|
+
if use_opp == 'all':
|
|
783
|
+
# Include all opportunities
|
|
784
|
+
use_opp = [opp_id for opp_id in all_opps['records']]
|
|
785
|
+
elif isinstance(use_opp, list):
|
|
786
|
+
if all([isinstance(m, int) for m in use_opp]):
|
|
787
|
+
# Opportunity IDs have been given as input
|
|
788
|
+
use_opp = [opp_id for opp_id,opp in all_opps['records'].items() if int(opp['Opportunity ID']) in use_opp]
|
|
789
|
+
elif all([isinstance(s, str) for s in use_opp]):
|
|
790
|
+
# Opportunity titles have been given as input
|
|
791
|
+
use_opp = [opp_id for opp_id,opp in all_opps['records'].items() if opp['Title of Opportunity'] in use_opp]
|
|
792
|
+
use_opp = list(set(use_opp))
|
|
793
|
+
if len(use_opp) == 0:
|
|
794
|
+
print('No opportunities found')
|
|
795
|
+
return
|
|
796
|
+
if verbose:
|
|
797
|
+
n = len(use_opp)
|
|
798
|
+
print(f'Finding requested variables for {n} Opportunities:')
|
|
799
|
+
for opp_id in use_opp:
|
|
800
|
+
opp = all_opps['records'][opp_id]
|
|
801
|
+
print(' ' + opp['Title of Opportunity'])
|
|
802
|
+
|
|
803
|
+
# Loop over the opportunities
|
|
804
|
+
expt_vars = {}
|
|
805
|
+
priority_levels = get_priority_levels()
|
|
806
|
+
for opp_id in use_opp:
|
|
807
|
+
opp = all_opps['records'][opp_id] # one record from the Opportunity table
|
|
808
|
+
|
|
809
|
+
if 'Experiment Groups' not in opp:
|
|
810
|
+
print('No experiment groups defined for opportunity: ' + opp['Title of Opportunity'])
|
|
811
|
+
continue
|
|
812
|
+
opp_expts = set() # will hold names of experiments requested by this opportunity
|
|
813
|
+
for expt_group_id in opp['Experiment Groups']: # Loop over experiment groups in this opportunity
|
|
814
|
+
expt_group = tables['Experiment Group']['records'][expt_group_id]
|
|
815
|
+
# Get names of experiments in this experiment group
|
|
816
|
+
for expt_id in expt_group['Experiments']:
|
|
817
|
+
|
|
818
|
+
# cluge
|
|
819
|
+
if content_type == 'working':
|
|
820
|
+
expt_table_name = 'Experiment'
|
|
821
|
+
elif content_type == 'version':
|
|
822
|
+
expt_table_name = 'Experiments'
|
|
823
|
+
|
|
824
|
+
expt = tables[expt_table_name]['records'][expt_id]
|
|
825
|
+
expt_key = expt[' Experiment'].strip() # Name of experiment, e.g "historical"
|
|
826
|
+
opp_expts.add(expt_key)
|
|
827
|
+
if expt_key not in expt_vars:
|
|
828
|
+
expt_vars[expt_key] = {p : set() for p in priority_levels}
|
|
829
|
+
|
|
830
|
+
try_vg_fields = []
|
|
831
|
+
try_vg_fields.append('Variable Groups')
|
|
832
|
+
try_vg_fields.append('Working/Updated Variable Groups')
|
|
833
|
+
try_vg_fields.append('Originally Requested Variable Groups')
|
|
834
|
+
vg_key = None
|
|
835
|
+
for vg_key in try_vg_fields:
|
|
836
|
+
if vg_key in opp:
|
|
837
|
+
break
|
|
838
|
+
if vg_key not in opp:
|
|
839
|
+
print('No variable groups defined for opportunity: ' + opp['Title of Opportunity'])
|
|
840
|
+
continue
|
|
841
|
+
for var_group_id in opp[vg_key]: # Loop over variable groups in this opportunity
|
|
842
|
+
var_group = tables['Variable Group']['records'][var_group_id]
|
|
843
|
+
priority = var_group['Priority Level']
|
|
844
|
+
|
|
845
|
+
if isinstance(priority, list): # True if priority is a link to a Priority Level record (instead of just a string)
|
|
846
|
+
assert len(priority) == 1, 'Variable Group should have one specified priority level'
|
|
847
|
+
prilev_id = priority[0]
|
|
848
|
+
|
|
849
|
+
# prilev = tables['Priority Level']['records'][prilev_id]
|
|
850
|
+
# cluge for testing with latest working bases
|
|
851
|
+
pl_table = None
|
|
852
|
+
pl_try = ['Priority Level', 'Priority level']
|
|
853
|
+
for s in pl_try:
|
|
854
|
+
if s in tables:
|
|
855
|
+
pl_table = tables[s]
|
|
856
|
+
break
|
|
857
|
+
prilev = pl_table['records'][prilev_id]
|
|
858
|
+
priority = prilev['Name']
|
|
859
|
+
assert priority in priority_levels, 'Unrecognized priority level: ' + priority
|
|
860
|
+
del prilev
|
|
861
|
+
|
|
862
|
+
# Get names of variables in this variable group
|
|
863
|
+
for var_id in var_group['Variables']: # Loop over variables in this variable group
|
|
864
|
+
var = tables['Variables']['records'][var_id]
|
|
865
|
+
var_key = var['Compound Name'] # Name of variable, e.g. "Amon.tas"
|
|
866
|
+
for expt_key in opp_expts:
|
|
867
|
+
# Add this variable to the experiment's output set, at the priority level specified by the variable group
|
|
868
|
+
expt_vars[expt_key][priority].add(var_key)
|
|
869
|
+
|
|
870
|
+
# Remove overlaps between priority levels
|
|
871
|
+
assert priority_levels == ['Core', 'High', 'Medium', 'Low']
|
|
872
|
+
for expt_key, expt_var in expt_vars.items():
|
|
873
|
+
# remove any Core priority variables from other groups
|
|
874
|
+
for p in ['High', 'Medium', 'Low']:
|
|
875
|
+
expt_var[p] = expt_var[p].difference(expt_var['Core'])
|
|
876
|
+
# remove any High priority variables from lower priority groups
|
|
877
|
+
for p in ['Medium', 'Low']:
|
|
878
|
+
expt_var[p] = expt_var[p].difference(expt_var['High'])
|
|
879
|
+
# remove any Medium priority variables from lower priority groups
|
|
880
|
+
for p in ['Low']:
|
|
881
|
+
expt_var[p] = expt_var[p].difference(expt_var['Medium'])
|
|
882
|
+
# Remove unwanted priority levels
|
|
883
|
+
for expt_key, expt_var in expt_vars.items():
|
|
884
|
+
if priority_cutoff.lower() == 'core':
|
|
885
|
+
expt_var.pop('High')
|
|
886
|
+
expt_var.pop('Medium')
|
|
887
|
+
expt_var.pop('Low')
|
|
888
|
+
elif priority_cutoff.lower() == 'high':
|
|
889
|
+
expt_var.pop('Medium')
|
|
890
|
+
expt_var.pop('Low')
|
|
891
|
+
elif priority_cutoff.lower() == 'medium':
|
|
892
|
+
expt_var.pop('Low')
|
|
893
|
+
|
|
894
|
+
for expt, req in expt_vars.items():
|
|
895
|
+
# Change sets to lists
|
|
896
|
+
for p in req:
|
|
897
|
+
req[p] = sorted(req[p], key=str.lower)
|
|
898
|
+
|
|
899
|
+
opp_titles = sorted([all_opps['records'][opp_id]['Title of Opportunity'] for opp_id in use_opp])
|
|
900
|
+
requested_vars = {
|
|
901
|
+
'Header' : {
|
|
902
|
+
'Opportunities' : opp_titles,
|
|
903
|
+
'dreq version' : DREQ_VERSION,
|
|
904
|
+
},
|
|
905
|
+
'experiment' : expt_vars,
|
|
906
|
+
}
|
|
907
|
+
return requested_vars
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
def show_requested_vars_summary(expt_vars, use_dreq_version):
|
|
911
|
+
'''
|
|
912
|
+
Display quick summary to stdout of variables requested.
|
|
913
|
+
expt_vars is the output dict from dq.get_requested_variables().
|
|
914
|
+
'''
|
|
915
|
+
print(f'\nFor data request version {use_dreq_version}, number of requested variables found by experiment:')
|
|
916
|
+
priority_levels=get_priority_levels()
|
|
917
|
+
for expt, req in sorted(expt_vars['experiment'].items()):
|
|
918
|
+
d = {p : 0 for p in priority_levels}
|
|
919
|
+
for p in priority_levels:
|
|
920
|
+
if p in req:
|
|
921
|
+
d[p] = len(req[p])
|
|
922
|
+
n_total = sum(d.values())
|
|
923
|
+
print(f' {expt} : ' + ' ,'.join(['{p}={n}'.format(p=p,n=d[p]) for p in priority_levels]) + f', TOTAL={n_total}')
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
def write_requested_vars_json(outfile, expt_vars, use_dreq_version, priority_cutoff, content_path):
|
|
927
|
+
'''
|
|
928
|
+
Write a nicely formatted json file with lists of requested variables by experiment.
|
|
929
|
+
expt_vars is the output dict from dq.get_requested_variables().
|
|
930
|
+
'''
|
|
931
|
+
|
|
932
|
+
Header = OrderedDict({
|
|
933
|
+
'Description' : 'This file gives the names of output variables that are requested from CMIP experiments by the supported Opportunities. The variables requested from each experiment are listed under each experiment name, grouped according to the priority level at which they are requested. For each experiment, the prioritized list of variables was determined by compiling together all requests made by the supported Opportunities for output from that experiment.',
|
|
934
|
+
'Opportunities supported' : sorted(expt_vars['Header']['Opportunities'], key=str.lower)
|
|
935
|
+
})
|
|
936
|
+
|
|
937
|
+
# List supported priority levels
|
|
938
|
+
priority_levels=get_priority_levels()
|
|
939
|
+
priority_cutoff = priority_cutoff.capitalize()
|
|
940
|
+
m = priority_levels.index(priority_cutoff)+1
|
|
941
|
+
Header.update({
|
|
942
|
+
'Priority levels supported' : priority_levels[:m]
|
|
943
|
+
})
|
|
944
|
+
for req in expt_vars['experiment'].values():
|
|
945
|
+
for p in priority_levels[m:]:
|
|
946
|
+
assert req[p] == []
|
|
947
|
+
req.pop(p) # remove empty lists of unsupported priorities from the output
|
|
948
|
+
|
|
949
|
+
# List included experiments
|
|
950
|
+
Header.update({
|
|
951
|
+
'Experiments included' : sorted(expt_vars['experiment'].keys(), key=str.lower)
|
|
952
|
+
})
|
|
953
|
+
|
|
954
|
+
# Get provenance of content to include in the Header
|
|
955
|
+
# content_path = dc._dreq_content_loaded['json_path']
|
|
956
|
+
with open(content_path, 'rb') as f:
|
|
957
|
+
content_hash = hashlib.sha256(f.read()).hexdigest()
|
|
958
|
+
Header.update({
|
|
959
|
+
'dreq content version' : use_dreq_version,
|
|
960
|
+
'dreq content file' : os.path.basename(os.path.normpath(content_path)),
|
|
961
|
+
'dreq content sha256 hash' : content_hash,
|
|
962
|
+
'dreq api version' : api_version,
|
|
963
|
+
})
|
|
964
|
+
|
|
965
|
+
out = {
|
|
966
|
+
'Header' : Header,
|
|
967
|
+
'experiment' : OrderedDict(),
|
|
968
|
+
}
|
|
969
|
+
expt_names = sorted(expt_vars['experiment'].keys(), key=str.lower)
|
|
970
|
+
for expt_name in expt_names:
|
|
971
|
+
out['experiment'][expt_name] = OrderedDict()
|
|
972
|
+
req = expt_vars['experiment'][expt_name]
|
|
973
|
+
for p in priority_levels:
|
|
974
|
+
if p in req:
|
|
975
|
+
out['experiment'][expt_name][p] = req[p]
|
|
976
|
+
|
|
977
|
+
# Write the results to json
|
|
978
|
+
with open(outfile, 'w') as f:
|
|
979
|
+
# json.dump(expt_vars, f, indent=4, sort_keys=True)
|
|
980
|
+
json.dump(out, f, indent=4)
|
|
981
|
+
print('\nWrote requested variables to ' + outfile)
|