From f8bf82e569e30cf00fde53338b592d0068133dda Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Wed, 26 Jan 2022 17:09:25 -0700 Subject: [PATCH 01/10] Per #1247, added a unit test that currently fails but should pass when the fix is properly implemented --- internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py b/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py index 80c7393c8..9ba9d8b17 100644 --- a/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py +++ b/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py @@ -423,6 +423,9 @@ def test_handle_climo_file_variables(metplus_config, config_overrides, '["/some/climo_mean/file.txt"];}'), 'CLIMO_MEAN_FILE': '"/some/climo_mean/file.txt"'}), + ({'GRID_STAT_CLIMO_MEAN_FIELD': '{name="UGRD"; level=["P850","P500","P250"];}', }, + {'METPLUS_CLIMO_MEAN_DICT': 'climo_mean = {field = [{name="UGRD"; level=["P850","P500","P250"];}];}'}), + ({'GRID_STAT_CLIMO_MEAN_FIELD': '{name="CLM_NAME"; level="(0,0,*,*)";}', }, {'METPLUS_CLIMO_MEAN_DICT': 'climo_mean = {field = [{name="CLM_NAME"; level="(0,0,*,*)";}];}'}), From a659427bf0833263c23c8991319747fdc19354e2 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Thu, 27 Jan 2022 11:05:36 -0700 Subject: [PATCH 02/10] clean up tests --- .../string_manip/test_util_string_manip.py | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/internal_tests/pytests/util/string_manip/test_util_string_manip.py b/internal_tests/pytests/util/string_manip/test_util_string_manip.py index 9ceab10ed..3858378fa 100644 --- a/internal_tests/pytests/util/string_manip/test_util_string_manip.py +++ b/internal_tests/pytests/util/string_manip/test_util_string_manip.py @@ -7,26 +7,28 @@ from metplus.util.string_manip import * from metplus.util.string_manip import _fix_list -def test_getlist(): - string_list = 'gt2.7, >3.6, eq42' +@pytest.mark.parametrize( + 'string_list, output_list', [ + # 0: list of strings + ('gt2.7, >3.6, eq42', + ['gt2.7', '>3.6', 'eq42']), + # 1: one string has commas within quotes + ('gt2.7, >3.6, eq42, "has,commas,in,it"', + ['gt2.7', '>3.6', 'eq42', 'has,commas,in,it']), + # 2: empty string + ('', + []), + ] +) +def test_getlist(string_list, output_list): test_list = getlist(string_list) - assert test_list == ['gt2.7', '>3.6', 'eq42'] + assert test_list == output_list def test_getlist_int(): string_list = '6, 7, 42' test_list = getlistint(string_list) assert test_list == [6, 7, 42] -def test_getlist_has_commas(): - string_list = 'gt2.7, >3.6, eq42, "has,commas,in,it"' - test_list = getlist(string_list) - assert test_list == ['gt2.7', '>3.6', 'eq42', 'has,commas,in,it'] - -def test_getlist_empty(): - string_list = '' - test_list = getlist(string_list) - assert test_list == [] - @pytest.mark.parametrize( 'list_string, output_list', [ ('begin_end_incr(3,12,3)', From 310470ca31f411c89528aa2a4077d3f14831e950 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Thu, 27 Jan 2022 11:07:28 -0700 Subject: [PATCH 03/10] test skipping _fix_list logic to see if anything fails to see if it is still needed, ci-run-all-diff --- metplus/util/string_manip.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/metplus/util/string_manip.py b/metplus/util/string_manip.py index f6326d50f..c7695ceb6 100644 --- a/metplus/util/string_manip.py +++ b/metplus/util/string_manip.py @@ -40,7 +40,7 @@ def getlist(list_str, expand_begin_end_incr=True): # (which is the whole list) item_list = list(reader([list_str], escapechar='\\'))[0] - item_list = _fix_list(item_list) + #item_list = _fix_list(item_list) return item_list @@ -139,6 +139,9 @@ def _fix_list_helper(item_list, type): elif type == '[': close_regex = r"[^\[]+\].*" open_regex = r".*\[[^\]]*$" + elif type == '{': + close_regex = r"[^\{]+\}.*" + open_regex = r".*\{[^\}]*$" else: return item_list From c5608721598ae4b2f685f361ec27f641179e88be Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:04:46 -0700 Subject: [PATCH 04/10] Per #1247, moved remove_quotes function to string_manip utility from met_util --- internal_tests/pytests/met_util/test_met_util.py | 12 ------------ .../util/string_manip/test_util_string_manip.py | 12 ++++++++++++ metplus/util/config_metplus.py | 4 ++-- metplus/util/met_config.py | 2 +- metplus/util/met_util.py | 8 -------- metplus/util/string_manip.py | 8 ++++++++ metplus/wrappers/command_builder.py | 5 +++-- metplus/wrappers/plot_data_plane_wrapper.py | 4 ++-- metplus/wrappers/regrid_data_plane_wrapper.py | 3 ++- metplus/wrappers/stat_analysis_wrapper.py | 6 +++--- metplus/wrappers/tcmpr_plotter_wrapper.py | 3 ++- 11 files changed, 35 insertions(+), 32 deletions(-) diff --git a/internal_tests/pytests/met_util/test_met_util.py b/internal_tests/pytests/met_util/test_met_util.py index 7ff9bc2b7..ccc7d6bcd 100644 --- a/internal_tests/pytests/met_util/test_met_util.py +++ b/internal_tests/pytests/met_util/test_met_util.py @@ -11,18 +11,6 @@ from metplus.util import time_util from metplus.util.config_metplus import parse_var_list -@pytest.mark.parametrize( - 'before, after', [ - ('string', 'string'), - ('"string"', 'string'), - ('', ''), - ('""', ''), - (None, ''), - ] -) -def test_remove_quotes(before, after): - assert(util.remove_quotes(before) == after) - @pytest.mark.parametrize( 'key, value', [ ({"gt2.3", "gt5.5"}, True), diff --git a/internal_tests/pytests/util/string_manip/test_util_string_manip.py b/internal_tests/pytests/util/string_manip/test_util_string_manip.py index 3858378fa..1283e159d 100644 --- a/internal_tests/pytests/util/string_manip/test_util_string_manip.py +++ b/internal_tests/pytests/util/string_manip/test_util_string_manip.py @@ -7,6 +7,18 @@ from metplus.util.string_manip import * from metplus.util.string_manip import _fix_list +@pytest.mark.parametrize( + 'before, after', [ + ('string', 'string'), + ('"string"', 'string'), + ('', ''), + ('""', ''), + (None, ''), + ] +) +def test_remove_quotes(before, after): + assert(remove_quotes(before) == after) + @pytest.mark.parametrize( 'string_list, output_list', [ # 0: list of strings diff --git a/metplus/util/config_metplus.py b/metplus/util/config_metplus.py index e2cc3981c..353eed0d3 100644 --- a/metplus/util/config_metplus.py +++ b/metplus/util/config_metplus.py @@ -23,7 +23,7 @@ from . import met_util as util from .string_template_substitution import get_tags, do_string_sub from .met_util import is_python_script, format_var_items -from .string_manip import getlist +from .string_manip import getlist, remove_quotes from .doc_util import get_wrapper_name """!Creates the initial METplus directory structure, @@ -793,7 +793,7 @@ def getbool(self, sec, name, default=None, badtypeok=False, morevars=None, return False # check if value is y/Y/n/N and return True/False if so - value_string = util.remove_quotes(value_string) + value_string = remove_quotes(value_string) if value_string.lower() == 'y': return True if value_string.lower() == 'n': diff --git a/metplus/util/met_config.py b/metplus/util/met_config.py index e8ef186e9..55e753883 100644 --- a/metplus/util/met_config.py +++ b/metplus/util/met_config.py @@ -7,7 +7,7 @@ from .string_manip import getlist from .met_util import get_threshold_via_regex, MISSING_DATA_VALUE -from .met_util import remove_quotes as util_remove_quotes +from .string_manip import remove_quotes as util_remove_quotes from .config_metplus import find_indices_in_config_section class METConfig: diff --git a/metplus/util/met_util.py b/metplus/util/met_util.py index 0949726a9..91e4593b9 100644 --- a/metplus/util/met_util.py +++ b/metplus/util/met_util.py @@ -1052,14 +1052,6 @@ def split_level(level): return '', '' -def remove_quotes(input_string): - """!Remove quotes from string""" - if not input_string: - return '' - - # strip off double and single quotes - return input_string.strip('"').strip("'") - def get_filetype(filepath, logger=None): """!This function determines if the filepath is a NETCDF or GRIB file based on the first eight bytes of the file. diff --git a/metplus/util/string_manip.py b/metplus/util/string_manip.py index c7695ceb6..644843fc4 100644 --- a/metplus/util/string_manip.py +++ b/metplus/util/string_manip.py @@ -7,6 +7,14 @@ import re from csv import reader +def remove_quotes(input_string): + """!Remove quotes from string""" + if not input_string: + return '' + + # strip off double and single quotes + return input_string.strip('"').strip("'") + def getlist(list_str, expand_begin_end_incr=True): """! Returns a list of string elements from a comma separated string of values. diff --git a/metplus/wrappers/command_builder.py b/metplus/wrappers/command_builder.py index 4eccbb92d..c9b36ddd9 100755 --- a/metplus/wrappers/command_builder.py +++ b/metplus/wrappers/command_builder.py @@ -27,6 +27,7 @@ from ..util import MISSING_DATA_VALUE from ..util import get_custom_string_list from ..util import get_wrapped_met_config_file, add_met_config_item, format_met_config +from ..util import remove_quotes from ..util.met_config import add_met_config_dict # pylint:disable=pointless-string-statement @@ -1215,7 +1216,7 @@ def get_field_info(self, d_type='', v_name='', v_level='', v_thresh=None, field = f'name="{v_name}";' if v_level: - field += f' level="{util.remove_quotes(v_level)}";' + field += f' level="{remove_quotes(v_level)}";' if self.c_dict.get(d_type + '_IS_PROB', False): field += " prob=TRUE;" @@ -1474,7 +1475,7 @@ def handle_description(self): # if the value is set, set the DESC c_dict if conf_value: self.env_var_dict['METPLUS_DESC'] = ( - f'desc = "{util.remove_quotes(conf_value)}";' + f'desc = "{remove_quotes(conf_value)}";' ) def get_output_prefix(self, time_info=None, set_env_vars=True): diff --git a/metplus/wrappers/plot_data_plane_wrapper.py b/metplus/wrappers/plot_data_plane_wrapper.py index 2ed866c54..3eb642037 100755 --- a/metplus/wrappers/plot_data_plane_wrapper.py +++ b/metplus/wrappers/plot_data_plane_wrapper.py @@ -15,7 +15,7 @@ from ..util import met_util as util from ..util import time_util from . import CommandBuilder -from ..util import do_string_sub +from ..util import do_string_sub, remove_quotes '''!@namespace PlotDataPlaneWrapper @brief Wraps the PlotDataPlane tool to plot data @@ -187,7 +187,7 @@ def set_command_line_arguments(self, time_info): **time_info) field_info = f" 'name=\"{field_name}\";" if self.c_dict['FIELD_LEVEL']: - field_level = util.remove_quotes(self.c_dict['FIELD_LEVEL']) + field_level = remove_quotes(self.c_dict['FIELD_LEVEL']) field_info += f" level=\"{field_level}\";" if self.c_dict['FIELD_EXTRA']: diff --git a/metplus/wrappers/regrid_data_plane_wrapper.py b/metplus/wrappers/regrid_data_plane_wrapper.py index a3d544a0d..58588c4ce 100755 --- a/metplus/wrappers/regrid_data_plane_wrapper.py +++ b/metplus/wrappers/regrid_data_plane_wrapper.py @@ -17,6 +17,7 @@ from ..util import do_string_sub from ..util import parse_var_list from ..util import get_process_list +from ..util import remove_quotes from . import ReformatGriddedWrapper # pylint:disable=pointless-string-statement @@ -383,7 +384,7 @@ def set_field_command_line_arguments(self, field_info, data_type): field_name = field_info[f'{data_type.lower()}_name'] # strip off quotes around input_level if found - input_level = util.remove_quotes(field_info[f'{data_type.lower()}_level']) + input_level = remove_quotes(field_info[f'{data_type.lower()}_level']) field_text = f"-field 'name=\"{field_name}\";" diff --git a/metplus/wrappers/stat_analysis_wrapper.py b/metplus/wrappers/stat_analysis_wrapper.py index d95bb3230..5c8fc8225 100755 --- a/metplus/wrappers/stat_analysis_wrapper.py +++ b/metplus/wrappers/stat_analysis_wrapper.py @@ -21,7 +21,7 @@ from ..util import getlist from ..util import met_util as util from ..util import do_string_sub, find_indices_in_config_section -from ..util import parse_var_list +from ..util import parse_var_list, remove_quotes from . import CommandBuilder class StatAnalysisWrapper(CommandBuilder): @@ -1325,7 +1325,7 @@ def get_level_list(self, data_type): for level in level_input: level = level.strip('(').strip(')') - level = f'{util.remove_quotes(level)}' + level = f'{remove_quotes(level)}' level_list.append(level) return level_list @@ -1778,7 +1778,7 @@ def run_stat_analysis_job(self, runtime_settings_dict_list): for mp_item in mp_items: if not runtime_settings_dict.get(mp_item, ''): continue - value = util.remove_quotes(runtime_settings_dict.get(mp_item, + value = remove_quotes(runtime_settings_dict.get(mp_item, '')) value = (f"{mp_item.lower()} = \"{value}\";") self.env_var_dict[f'METPLUS_{mp_item}'] = value diff --git a/metplus/wrappers/tcmpr_plotter_wrapper.py b/metplus/wrappers/tcmpr_plotter_wrapper.py index 3a3b6fb6e..a1186c43f 100755 --- a/metplus/wrappers/tcmpr_plotter_wrapper.py +++ b/metplus/wrappers/tcmpr_plotter_wrapper.py @@ -11,6 +11,7 @@ from ..util import time_util from ..util import do_string_sub from ..util import time_generator +from ..util import remove_quotes from . import CommandBuilder class TCMPRPlotterWrapper(CommandBuilder): @@ -160,7 +161,7 @@ def read_optional_args(self): if value: # add quotes around value if they are not already there if 'quotes' in data_type: - value = f'"{util.remove_quotes(value)}"' + value = f'"{remove_quotes(value)}"' # don't add value for boolean if data_type == 'bool': From 66a83ee8ae38c559c1f8c5c6b4b87add4a0c44a2 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:09:03 -0700 Subject: [PATCH 05/10] Per #1247, improve getlist logic to properly handle climo examples that are not formatted properly --- metplus/util/string_manip.py | 101 ++++++++++++++--------------------- 1 file changed, 39 insertions(+), 62 deletions(-) diff --git a/metplus/util/string_manip.py b/metplus/util/string_manip.py index 644843fc4..af1d71901 100644 --- a/metplus/util/string_manip.py +++ b/metplus/util/string_manip.py @@ -43,12 +43,13 @@ def getlist(list_str, expand_begin_end_incr=True): if expand_begin_end_incr: list_str = _handle_begin_end_incr(list_str) - # use csv reader to divide comma list while preserving strings with comma - # convert the csv reader to a list and get first item - # (which is the whole list) - item_list = list(reader([list_str], escapechar='\\'))[0] + # use regex split to split list string by commas that are not + # found within []s or ()s + item_list = re.split(r',\s*(?![^\[\]]*\]|[^()]*\))', list_str) - #item_list = _fix_list(item_list) + # regex split will still split by commas that are found between + # quotation marks, so call function to put them back together properly + item_list = _fix_list(item_list) return item_list @@ -136,65 +137,41 @@ def _begin_end_incr_evaluate(item): return None def _fix_list(item_list): - item_list = _fix_list_helper(item_list, '(') - item_list = _fix_list_helper(item_list, '[') - return item_list + """! The logic that calls this function may have incorrectly split up + a string that contains commas within quotation marks. This function + looks through the list and finds items that appear to have been split up + incorrectly and puts them back together properly. -def _fix_list_helper(item_list, type): - if type == '(': - close_regex = r"[^(]+\).*" - open_regex = r".*\([^)]*$" - elif type == '[': - close_regex = r"[^\[]+\].*" - open_regex = r".*\[[^\]]*$" - elif type == '{': - close_regex = r"[^\{]+\}.*" - open_regex = r".*\{[^\}]*$" - else: - return item_list - - # combine items that had a comma between ()s or []s + @param item_list list of items to be corrected + @returns corrected list + """ fixed_list = [] - incomplete_item = None - found_close = False - for index, item in enumerate(item_list): - # if we have found an item that ends with ( but - if incomplete_item: - # check if item has ) before ( - match = re.match(close_regex, item) - if match: - # add rest of text, add it to output list, - # then reset incomplete_item - incomplete_item += ',' + item - found_close = True - else: - # if not ) before (, add text and continue - incomplete_item += ',' + item - - match = re.match(open_regex, item) - # if we find ( without ) after it - if match: - # if we are still putting together an item, - # append comma and new item - if incomplete_item: - if not found_close: - incomplete_item += ',' + item - # if not, start new incomplete item to put together + list_buffer = [] + for item in item_list: + quote_count = item.count('"') + if not list_buffer: + # if there are an even number of quotation marks, add to list + if quote_count % 2 == 0: + fixed_list.append(item) + # otherwise add it to the list buffer else: - incomplete_item = item - - found_close = False - # if we don't find ( without ) + list_buffer.append(item) else: - # if we are putting together item, we can add to the - # output list and reset incomplete_item - if incomplete_item: - if found_close: - fixed_list.append(incomplete_item) - incomplete_item = None - # if we are not within brackets and we found no brackets, - # add item to output list - else: - fixed_list.append(item) + list_buffer.append(item) + if quote_count == 1: + fixed_list.append(','.join(list_buffer)) + list_buffer.clear() + + # if there are still items in the buffer, add them to end of list + if list_buffer: + fixed_list.append(','.join(list_buffer)) + + # remove extra quotation marks around string + out_list = [] + for item in fixed_list: + if item[0] == '"' and item[-1] == '"': + out_list.append(item.strip('"')) + else: + out_list.append(item) - return fixed_list + return out_list From 6e9dd25d218a038e527f2bf586659d0574102294 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:09:42 -0700 Subject: [PATCH 06/10] Per #1247, added unit tests that demonstrate expected behavior --- .../string_manip/test_util_string_manip.py | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/internal_tests/pytests/util/string_manip/test_util_string_manip.py b/internal_tests/pytests/util/string_manip/test_util_string_manip.py index 1283e159d..1c6f2dfc3 100644 --- a/internal_tests/pytests/util/string_manip/test_util_string_manip.py +++ b/internal_tests/pytests/util/string_manip/test_util_string_manip.py @@ -27,10 +27,35 @@ def test_remove_quotes(before, after): # 1: one string has commas within quotes ('gt2.7, >3.6, eq42, "has,commas,in,it"', ['gt2.7', '>3.6', 'eq42', 'has,commas,in,it']), - # 2: empty string + # 2: one string has commas and spaces within quotes + ('gt2.7, >3.6, eq42, "has some,commas,in,it"', + ['gt2.7', '>3.6', 'eq42', 'has some,commas,in,it']), + # 3: empty string ('', []), - ] + # 4: string with commas between ()s + ('name="CLM_NAME"; level="(0,0,*,*)"', + ['name="CLM_NAME"; level="(0,0,*,*)"']), + # 5: string with commas between ()s and commas not between ()s + ('name="CLM_NAME"; level="(0,0,*,*)";, name="OTHER"; level="A06"', + ['name="CLM_NAME"; level="(0,0,*,*)";', 'name="OTHER"; level="A06"']), + # 6: string with commas between ()s within {}s + ('{name="CLM_NAME"; level="(0,0,*,*)";}', + ['{name="CLM_NAME"; level="(0,0,*,*)";}']), + # 7: multiple {}s with string with commas between ()s + ('{name="CLM_NAME"; level="(0,0,*,*)";},{name="CLM_NAME"; level="(0,0,*,*)";}', + ['{name="CLM_NAME"; level="(0,0,*,*)";}', + '{name="CLM_NAME"; level="(0,0,*,*)";}']), + # 8: read example with commas beween ()s + ('-input_field \'name="TEC"; level="({valid?fmt=%Y%m%d_%H%M%S},*,*)"; file_type=NETCDF_NCCF;\'', + ['-input_field \'name="TEC"; level="({valid?fmt=%Y%m%d_%H%M%S},*,*)"; file_type=NETCDF_NCCF;\'']), + # 9: read example commas separating quotes within []s + ('{name="UGRD"; level=["P850","P500","P250"];}', + ['{name="UGRD"; level=["P850","P500","P250"];}']), + # 10: multiples {}s with commas separating quotes within []s + ('{name="UGRD"; level=["P850","P500","P250"];}, {name="UGRD"; level=["P750","P600"];}', + ['{name="UGRD"; level=["P850","P500","P250"];}', '{name="UGRD"; level=["P750","P600"];}']), + ] ) def test_getlist(string_list, output_list): test_list = getlist(string_list) @@ -98,29 +123,3 @@ def test_getlist_int(): ) def test_getlist_begin_end_incr(list_string, output_list): assert getlist(list_string) == output_list - -@pytest.mark.parametrize( - 'list_str, expected_fixed_list', [ - ('some,items,here', ['some', - 'items', - 'here']), - ('(*,*)', ['(*,*)']), - ("-type solar_alt -thresh 'ge45' -name solar_altitude_ge_45_mask -input_field 'name=\"TEC\"; level=\"(0,*,*)\"; file_type=NETCDF_NCCF;' -mask_field 'name=\"TEC\"; level=\"(0,*,*)\"; file_type=NETCDF_NCCF;\'", - ["-type solar_alt -thresh 'ge45' -name solar_altitude_ge_45_mask -input_field 'name=\"TEC\"; level=\"(0,*,*)\"; file_type=NETCDF_NCCF;' -mask_field 'name=\"TEC\"; level=\"(0,*,*)\"; file_type=NETCDF_NCCF;\'"]), - ("(*,*),'level=\"(0,*,*)\"' -censor_thresh [lt12.3,gt8.8],other", ['(*,*)', - "'level=\"(0,*,*)\"' -censor_thresh [lt12.3,gt8.8]", - 'other']), - ] -) -def test_fix_list(list_str, expected_fixed_list): - item_list = list(reader([list_str]))[0] - fixed_list = _fix_list(item_list) - print("FIXED LIST:") - for fixed in fixed_list: - print(f"ITEM: {fixed}") - - print("EXPECTED LIST") - for expected in expected_fixed_list: - print(f"ITEM: {expected}") - - assert(fixed_list == expected_fixed_list) From 513be81091e127d6601b53e0c0dcb0f2f1e6c496 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:10:34 -0700 Subject: [PATCH 07/10] Per #1247, changed unit test results because new getlist logic no longer adds unnecessary spaces after commas within quotation marks, ci-run-all-diff --- internal_tests/pytests/ioda2nc/test_ioda2nc_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal_tests/pytests/ioda2nc/test_ioda2nc_wrapper.py b/internal_tests/pytests/ioda2nc/test_ioda2nc_wrapper.py index d07460055..7effa04e7 100644 --- a/internal_tests/pytests/ioda2nc/test_ioda2nc_wrapper.py +++ b/internal_tests/pytests/ioda2nc/test_ioda2nc_wrapper.py @@ -39,7 +39,7 @@ def set_minimum_config_settings(config): {'METPLUS_MESSAGE_TYPE_MAP': 'message_type_map = [{ key = “AIRCAR”; val = “AIRCAR_PROFILES”; }];'}, ''), # 2 ({'IODA2NC_MESSAGE_TYPE_GROUP_MAP': '{ key = "SURFACE"; val = "ADPSFC,SFCSHP,MSONET";},{ key = "ANYAIR"; val = "AIRCAR,AIRCFT";}', }, - {'METPLUS_MESSAGE_TYPE_GROUP_MAP': 'message_type_group_map = [{ key = "SURFACE"; val = "ADPSFC, SFCSHP, MSONET";}, { key = "ANYAIR"; val = "AIRCAR, AIRCFT";}];'}, ''), + {'METPLUS_MESSAGE_TYPE_GROUP_MAP': 'message_type_group_map = [{ key = "SURFACE"; val = "ADPSFC,SFCSHP,MSONET";}, { key = "ANYAIR"; val = "AIRCAR,AIRCFT";}];'}, ''), # 3 ({'IODA2NC_STATION_ID': 'value1, value2', }, {'METPLUS_STATION_ID': 'station_id = ["value1", "value2"];'}, ''), From 1a5c8f9fb5ba5c223da21c455ffeffaaf28d52c4 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Thu, 27 Jan 2022 15:11:48 -0700 Subject: [PATCH 08/10] Per #1247, changed unit test results because new getlist logic no longer adds unnecessary spaces after commas within quotation marks, ci-run-all-diff --- internal_tests/pytests/point_stat/test_point_stat_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal_tests/pytests/point_stat/test_point_stat_wrapper.py b/internal_tests/pytests/point_stat/test_point_stat_wrapper.py index 118a6f7dc..53aefbf19 100755 --- a/internal_tests/pytests/point_stat/test_point_stat_wrapper.py +++ b/internal_tests/pytests/point_stat/test_point_stat_wrapper.py @@ -442,7 +442,7 @@ def test_met_dictionary_in_var_options(metplus_config): 'shape = SQUARE;' 'prob_cat_thresh = [>1, <=2];}')}), ({'POINT_STAT_MESSAGE_TYPE_GROUP_MAP': '{ key = "SURFACE"; val = "ADPSFC,SFCSHP,MSONET";},{ key = "ANYAIR"; val = "AIRCAR,AIRCFT";}', }, - {'METPLUS_MESSAGE_TYPE_GROUP_MAP': 'message_type_group_map = [{ key = "SURFACE"; val = "ADPSFC, SFCSHP, MSONET";}, { key = "ANYAIR"; val = "AIRCAR, AIRCFT";}];'}), + {'METPLUS_MESSAGE_TYPE_GROUP_MAP': 'message_type_group_map = [{ key = "SURFACE"; val = "ADPSFC,SFCSHP,MSONET";}, { key = "ANYAIR"; val = "AIRCAR,AIRCFT";}];'}), ] ) From d971cb03640a12acaa2aa331dad5357ac0d488cb Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Thu, 27 Jan 2022 17:17:37 -0700 Subject: [PATCH 09/10] Per #1247, added support for using the fcst or obs field information for climo_mean or climo_stdev, added unit tests to ensure correct value is set --- .../grid_stat/test_grid_stat_wrapper.py | 48 +++++++++++++++++++ metplus/wrappers/command_builder.py | 35 ++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py b/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py index 9ba9d8b17..72b234ea7 100644 --- a/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py +++ b/internal_tests/pytests/grid_stat/test_grid_stat_wrapper.py @@ -428,6 +428,28 @@ def test_handle_climo_file_variables(metplus_config, config_overrides, ({'GRID_STAT_CLIMO_MEAN_FIELD': '{name="CLM_NAME"; level="(0,0,*,*)";}', }, {'METPLUS_CLIMO_MEAN_DICT': 'climo_mean = {field = [{name="CLM_NAME"; level="(0,0,*,*)";}];}'}), + # ignore USE_FCST because FIELD is set + ({'GRID_STAT_CLIMO_MEAN_FIELD': '{name="CLM_NAME"; level="(0,0,*,*)";}', + 'GRID_STAT_CLIMO_MEAN_USE_FCST': 'TRUE'}, + {'METPLUS_CLIMO_MEAN_DICT': 'climo_mean = {field = [{name="CLM_NAME"; level="(0,0,*,*)";}];}'}), + # ignore USE_OBS because FIELD is set + ({'GRID_STAT_CLIMO_MEAN_FIELD': '{name="CLM_NAME"; level="(0,0,*,*)";}', + 'GRID_STAT_CLIMO_MEAN_USE_OBS': 'TRUE'}, + {'METPLUS_CLIMO_MEAN_DICT': 'climo_mean = {field = [{name="CLM_NAME"; level="(0,0,*,*)";}];}'}), + # use fcst no other climo_mean + ({'GRID_STAT_CLIMO_MEAN_USE_FCST': 'TRUE'}, + {'METPLUS_CLIMO_MEAN_DICT': 'climo_mean = fcst;'}), + # use obs no other climo_mean + ({'GRID_STAT_CLIMO_MEAN_USE_OBS': 'TRUE'}, + {'METPLUS_CLIMO_MEAN_DICT': 'climo_mean = obs;'}), + # use fcst with other climo_mean + ({'GRID_STAT_CLIMO_MEAN_REGRID_METHOD': 'NEAREST', + 'GRID_STAT_CLIMO_MEAN_USE_FCST': 'TRUE'}, + {'METPLUS_CLIMO_MEAN_DICT': 'climo_mean = {regrid = {method = NEAREST;}}climo_mean = fcst;'}), + # use obs with other climo_mean + ({'GRID_STAT_CLIMO_MEAN_REGRID_METHOD': 'NEAREST', + 'GRID_STAT_CLIMO_MEAN_USE_OBS': 'TRUE'}, + {'METPLUS_CLIMO_MEAN_DICT': 'climo_mean = {regrid = {method = NEAREST;}}climo_mean = obs;'}), ({'GRID_STAT_CLIMO_MEAN_REGRID_METHOD': 'NEAREST', }, {'METPLUS_CLIMO_MEAN_DICT': 'climo_mean = {regrid = {method = NEAREST;}}'}), @@ -535,6 +557,32 @@ def test_handle_climo_file_variables(metplus_config, config_overrides, 'match_month = TRUE;day_interval = 30;' 'hour_interval = 12;}'), 'CLIMO_STDEV_FILE': '"/some/climo_stdev/file.txt"'}), + # ignore USE_FCST because FIELD is set + ( + {'GRID_STAT_CLIMO_STDEV_FIELD': '{name="CLM_NAME"; level="(0,0,*,*)";}', + 'GRID_STAT_CLIMO_STDEV_USE_FCST': 'TRUE'}, + { + 'METPLUS_CLIMO_STDEV_DICT': 'climo_stdev = {field = [{name="CLM_NAME"; level="(0,0,*,*)";}];}'}), + # ignore USE_OBS because FIELD is set + ( + {'GRID_STAT_CLIMO_STDEV_FIELD': '{name="CLM_NAME"; level="(0,0,*,*)";}', + 'GRID_STAT_CLIMO_STDEV_USE_OBS': 'TRUE'}, + { + 'METPLUS_CLIMO_STDEV_DICT': 'climo_stdev = {field = [{name="CLM_NAME"; level="(0,0,*,*)";}];}'}), + # use fcst no other climo_stdev + ({'GRID_STAT_CLIMO_STDEV_USE_FCST': 'TRUE'}, + {'METPLUS_CLIMO_STDEV_DICT': 'climo_stdev = fcst;'}), + # use obs no other climo_stdev + ({'GRID_STAT_CLIMO_STDEV_USE_OBS': 'TRUE'}, + {'METPLUS_CLIMO_STDEV_DICT': 'climo_stdev = obs;'}), + # use fcst with other climo_stdev + ({'GRID_STAT_CLIMO_STDEV_REGRID_METHOD': 'NEAREST', + 'GRID_STAT_CLIMO_STDEV_USE_FCST': 'TRUE'}, + {'METPLUS_CLIMO_STDEV_DICT': 'climo_stdev = {regrid = {method = NEAREST;}}climo_stdev = fcst;'}), + # use obs with other climo_stdev + ({'GRID_STAT_CLIMO_STDEV_REGRID_METHOD': 'NEAREST', + 'GRID_STAT_CLIMO_STDEV_USE_OBS': 'TRUE'}, + {'METPLUS_CLIMO_STDEV_DICT': 'climo_stdev = {regrid = {method = NEAREST;}}climo_stdev = obs;'}), ({'GRID_STAT_GRID_WEIGHT_FLAG': 'COS_LAT', }, {'METPLUS_GRID_WEIGHT_FLAG': 'grid_weight_flag = COS_LAT;'}), diff --git a/metplus/wrappers/command_builder.py b/metplus/wrappers/command_builder.py index c9b36ddd9..05e498016 100755 --- a/metplus/wrappers/command_builder.py +++ b/metplus/wrappers/command_builder.py @@ -1537,6 +1537,9 @@ def handle_climo_dict(self): self.add_met_config_dict(dict_name, items) + # handle use_fcst or use_obs options for setting field list + self.climo_use_fcst_or_obs_fields(dict_name) + # handle deprecated env vars CLIMO_MEAN_FILE and CLIMO_STDEV_FILE # that are used by pre v4.0.0 wrapped MET config files env_var_name = f'METPLUS_{dict_name.upper()}_DICT' @@ -1596,6 +1599,38 @@ def read_climo_file_name(self, climo_type): self.config.set('config', f'{prefix}FILE_NAME', template_list_string) + def climo_use_fcst_or_obs_fields(self, dict_name): + """! If climo field is not explicitly set, check if config is set + to use forecast or observation fields. + + @param dict_name name of climo to check: climo_mean or climo_stdev + """ + # if {APP}_CLIMO_[MEAN/STDEV]_FIELD is set, do nothing + field_conf = f'{self.app_name}_{dict_name}_FIELD'.upper() + if self.config.has_option('config', field_conf): + return + + use_fcst_conf = f'{self.app_name}_{dict_name}_USE_FCST'.upper() + use_obs_conf = f'{self.app_name}_{dict_name}_USE_OBS'.upper() + + use_fcst = self.config.getbool('config', use_fcst_conf, False) + use_obs = self.config.getbool('config', use_obs_conf, False) + + # if both are set, report an error + if use_fcst and use_obs: + self.log_error(f'Cannot set both {use_fcst_conf} and ' + f'{use_obs_conf} in config.') + return + + # if neither are set, do nothing + if not use_fcst and not use_obs: + return + + env_var_name = f'METPLUS_{dict_name.upper()}_DICT' + rvalue = 'fcst' if use_fcst else 'obs' + + self.env_var_dict[env_var_name] += f'{dict_name} = {rvalue};' + def get_wrapper_or_generic_config(self, generic_config_name): """! Check for config variable with _ prepended first. If set use that value. If not, check for config without prefix. From 290ac47cea091e5c6fe0f390afea4983c2bc6ce8 Mon Sep 17 00:00:00 2001 From: George McCabe <23407799+georgemccabe@users.noreply.github.com> Date: Thu, 27 Jan 2022 17:31:06 -0700 Subject: [PATCH 10/10] Per #1247, add documentation for new config variables --- docs/Users_Guide/glossary.rst | 180 ++++++++++++++++++++++++++++++++++ docs/Users_Guide/wrappers.rst | 20 ++++ 2 files changed, 200 insertions(+) diff --git a/docs/Users_Guide/glossary.rst b/docs/Users_Guide/glossary.rst index 3c71c5b9a..959e1d99b 100644 --- a/docs/Users_Guide/glossary.rst +++ b/docs/Users_Guide/glossary.rst @@ -8775,3 +8775,183 @@ METplus Configuration Glossary See :term:`FCST_PCP_COMBINE_USE_ZERO_ACCUM` for more information. | *Used by:* PCPCombine + + ENSEMBLE_STAT_CLIMO_MEAN_USE_FCST + If set to True, use the field array from the fcst dictionary for the + climo_mean fields for EnsembleStat. + Sets "climo_mean = fcst;" in the wrapped MET config file. + Only used if :term:`ENSEMBLE_STAT_CLIMO_MEAN_FIELD` is unset. + See also :term:`ENSEMBLE_STAT_CLIMO_MEAN_USE_OBS`. + + | *Used by:* EnsembleStat + + ENSEMBLE_STAT_CLIMO_MEAN_USE_OBS + If set to True, use the field array from the obs dictionary for the + climo_mean fields for EnsembleStat. + Sets "climo_mean = obs;" in the wrapped MET config file. + Only used if :term:`ENSEMBLE_STAT_CLIMO_MEAN_FIELD` is unset. + See also :term:`ENSEMBLE_STAT_CLIMO_MEAN_USE_FCST`. + + | *Used by:* EnsembleStat + + ENSEMBLE_STAT_CLIMO_STDEV_USE_FCST + If set to True, use the field array from the fcst dictionary for the + climo_stdev fields for EnsembleStat. + Sets "climo_stdev = fcst;" in the wrapped MET config file. + Only used if :term:`ENSEMBLE_STAT_CLIMO_STDEV_FIELD` is unset. + See also :term:`ENSEMBLE_STAT_CLIMO_STDEV_USE_OBS`. + + | *Used by:* EnsembleStat + + ENSEMBLE_STAT_CLIMO_STDEV_USE_OBS + If set to True, use the field array from the obs dictionary for the + climo_stdev fields for EnsembleStat. + Sets "climo_stdev = obs;" in the wrapped MET config file. + Only used if :term:`ENSEMBLE_STAT_CLIMO_STDEV_FIELD` is unset. + See also :term:`ENSEMBLE_STAT_CLIMO_STDEV_USE_FCST`. + + | *Used by:* EnsembleStat + + GEN_ENS_PROD_CLIMO_MEAN_USE_FCST + If set to True, use the field array from the fcst dictionary for the + climo_mean fields for GenEnsProd. + Sets "climo_mean = fcst;" in the wrapped MET config file. + Only used if :term:`GEN_ENS_PROD_CLIMO_MEAN_FIELD` is unset. + See also :term:`GEN_ENS_PROD_CLIMO_MEAN_USE_OBS`. + + | *Used by:* GenEnsProd + + GEN_ENS_PROD_CLIMO_MEAN_USE_OBS + If set to True, use the field array from the obs dictionary for the + climo_mean fields for GenEnsProd. + Sets "climo_mean = obs;" in the wrapped MET config file. + Only used if :term:`GEN_ENS_PROD_CLIMO_MEAN_FIELD` is unset. + See also :term:`GEN_ENS_PROD_CLIMO_MEAN_USE_FCST`. + + | *Used by:* GenEnsProd + + GEN_ENS_PROD_CLIMO_STDEV_USE_FCST + If set to True, use the field array from the fcst dictionary for the + climo_stdev fields for GenEnsProd. + Sets "climo_stdev = fcst;" in the wrapped MET config file. + Only used if :term:`GEN_ENS_PROD_CLIMO_STDEV_FIELD` is unset. + See also :term:`GEN_ENS_PROD_CLIMO_STDEV_USE_OBS`. + + | *Used by:* GenEnsProd + + GEN_ENS_PROD_CLIMO_STDEV_USE_OBS + If set to True, use the field array from the obs dictionary for the + climo_stdev fields for GenEnsProd. + Sets "climo_stdev = obs;" in the wrapped MET config file. + Only used if :term:`GEN_ENS_PROD_CLIMO_STDEV_FIELD` is unset. + See also :term:`GEN_ENS_PROD_CLIMO_STDEV_USE_FCST`. + + | *Used by:* GenEnsProd + + GRID_STAT_CLIMO_MEAN_USE_FCST + If set to True, use the field array from the fcst dictionary for the + climo_mean fields for GridStat. + Sets "climo_mean = fcst;" in the wrapped MET config file. + Only used if :term:`GRID_STAT_CLIMO_MEAN_FIELD` is unset. + See also :term:`GRID_STAT_CLIMO_MEAN_USE_OBS`. + + | *Used by:* GridStat + + GRID_STAT_CLIMO_MEAN_USE_OBS + If set to True, use the field array from the obs dictionary for the + climo_mean fields for GridStat. + Sets "climo_mean = obs;" in the wrapped MET config file. + Only used if :term:`GRID_STAT_CLIMO_MEAN_FIELD` is unset. + See also :term:`GRID_STAT_CLIMO_MEAN_USE_FCST`. + + | *Used by:* GridStat + + GRID_STAT_CLIMO_STDEV_USE_FCST + If set to True, use the field array from the fcst dictionary for the + climo_stdev fields for GridStat. + Sets "climo_stdev = fcst;" in the wrapped MET config file. + Only used if :term:`GRID_STAT_CLIMO_STDEV_FIELD` is unset. + See also :term:`GRID_STAT_CLIMO_STDEV_USE_OBS`. + + | *Used by:* GridStat + + GRID_STAT_CLIMO_STDEV_USE_OBS + If set to True, use the field array from the obs dictionary for the + climo_stdev fields for GridStat. + Sets "climo_stdev = obs;" in the wrapped MET config file. + Only used if :term:`GRID_STAT_CLIMO_STDEV_FIELD` is unset. + See also :term:`GRID_STAT_CLIMO_STDEV_USE_FCST`. + + | *Used by:* GridStat + + POINT_STAT_CLIMO_MEAN_USE_FCST + If set to True, use the field array from the fcst dictionary for the + climo_mean fields for PointStat. + Sets "climo_mean = fcst;" in the wrapped MET config file. + Only used if :term:`POINT_STAT_CLIMO_MEAN_FIELD` is unset. + See also :term:`POINT_STAT_CLIMO_MEAN_USE_OBS`. + + | *Used by:* PointStat + + POINT_STAT_CLIMO_MEAN_USE_OBS + If set to True, use the field array from the obs dictionary for the + climo_mean fields for PointStat. + Sets "climo_mean = obs;" in the wrapped MET config file. + Only used if :term:`POINT_STAT_CLIMO_MEAN_FIELD` is unset. + See also :term:`POINT_STAT_CLIMO_MEAN_USE_FCST`. + + | *Used by:* PointStat + + POINT_STAT_CLIMO_STDEV_USE_FCST + If set to True, use the field array from the fcst dictionary for the + climo_stdev fields for PointStat. + Sets "climo_stdev = fcst;" in the wrapped MET config file. + Only used if :term:`POINT_STAT_CLIMO_STDEV_FIELD` is unset. + See also :term:`POINT_STAT_CLIMO_STDEV_USE_OBS`. + + | *Used by:* PointStat + + POINT_STAT_CLIMO_STDEV_USE_OBS + If set to True, use the field array from the obs dictionary for the + climo_stdev fields for PointStat. + Sets "climo_stdev = obs;" in the wrapped MET config file. + Only used if :term:`POINT_STAT_CLIMO_STDEV_FIELD` is unset. + See also :term:`POINT_STAT_CLIMO_STDEV_USE_FCST`. + + | *Used by:* PointStat + + SERIES_ANALYSIS_CLIMO_MEAN_USE_FCST + If set to True, use the field array from the fcst dictionary for the + climo_mean fields for SeriesAnalysis. + Sets "climo_mean = fcst;" in the wrapped MET config file. + Only used if :term:`SERIES_ANALYSIS_CLIMO_MEAN_FIELD` is unset. + See also :term:`SERIES_ANALYSIS_CLIMO_MEAN_USE_OBS`. + + | *Used by:* SeriesAnalysis + + SERIES_ANALYSIS_CLIMO_MEAN_USE_OBS + If set to True, use the field array from the obs dictionary for the + climo_mean fields for SeriesAnalysis. + Sets "climo_mean = obs;" in the wrapped MET config file. + Only used if :term:`SERIES_ANALYSIS_CLIMO_MEAN_FIELD` is unset. + See also :term:`SERIES_ANALYSIS_CLIMO_MEAN_USE_FCST`. + + | *Used by:* SeriesAnalysis + + SERIES_ANALYSIS_CLIMO_STDEV_USE_FCST + If set to True, use the field array from the fcst dictionary for the + climo_stdev fields for SeriesAnalysis. + Sets "climo_stdev = fcst;" in the wrapped MET config file. + Only used if :term:`SERIES_ANALYSIS_CLIMO_STDEV_FIELD` is unset. + See also :term:`SERIES_ANALYSIS_CLIMO_STDEV_USE_OBS`. + + | *Used by:* SeriesAnalysis + + SERIES_ANALYSIS_CLIMO_STDEV_USE_OBS + If set to True, use the field array from the obs dictionary for the + climo_stdev fields for SeriesAnalysis. + Sets "climo_stdev = obs;" in the wrapped MET config file. + Only used if :term:`SERIES_ANALYSIS_CLIMO_STDEV_FIELD` is unset. + See also :term:`SERIES_ANALYSIS_CLIMO_STDEV_USE_FCST`. + + | *Used by:* SeriesAnalysis diff --git a/docs/Users_Guide/wrappers.rst b/docs/Users_Guide/wrappers.rst index 68c73be4d..68af4b7c5 100644 --- a/docs/Users_Guide/wrappers.rst +++ b/docs/Users_Guide/wrappers.rst @@ -238,6 +238,8 @@ METplus Configuration | :term:`ENSEMBLE_STAT_CLIMO_MEAN_MATCH_MONTH` | :term:`ENSEMBLE_STAT_CLIMO_MEAN_DAY_INTERVAL` | :term:`ENSEMBLE_STAT_CLIMO_MEAN_HOUR_INTERVAL` +| :term:`ENSEMBLE_STAT_CLIMO_MEAN_USE_FCST` +| :term:`ENSEMBLE_STAT_CLIMO_MEAN_USE_OBS` | :term:`ENSEMBLE_STAT_CLIMO_STDEV_FILE_NAME` | :term:`ENSEMBLE_STAT_CLIMO_STDEV_FIELD` | :term:`ENSEMBLE_STAT_CLIMO_STDEV_REGRID_METHOD` @@ -248,6 +250,8 @@ METplus Configuration | :term:`ENSEMBLE_STAT_CLIMO_STDEV_MATCH_MONTH` | :term:`ENSEMBLE_STAT_CLIMO_STDEV_DAY_INTERVAL` | :term:`ENSEMBLE_STAT_CLIMO_STDEV_HOUR_INTERVAL` +| :term:`ENSEMBLE_STAT_CLIMO_STDEV_USE_FCST` +| :term:`ENSEMBLE_STAT_CLIMO_STDEV_USE_OBS` | :term:`ENSEMBLE_STAT_MASK_GRID` | :term:`ENSEMBLE_STAT_CI_ALPHA` | :term:`ENSEMBLE_STAT_INTERP_FIELD` @@ -1051,6 +1055,8 @@ METplus Configuration | :term:`GEN_ENS_PROD_CLIMO_MEAN_MATCH_MONTH` | :term:`GEN_ENS_PROD_CLIMO_MEAN_DAY_INTERVAL` | :term:`GEN_ENS_PROD_CLIMO_MEAN_HOUR_INTERVAL` +| :term:`GEN_ENS_PROD_CLIMO_MEAN_USE_FCST` +| :term:`GEN_ENS_PROD_CLIMO_MEAN_USE_OBS` | :term:`GEN_ENS_PROD_CLIMO_STDEV_FILE_NAME` | :term:`GEN_ENS_PROD_CLIMO_STDEV_FIELD` | :term:`GEN_ENS_PROD_CLIMO_STDEV_REGRID_METHOD` @@ -1061,6 +1067,8 @@ METplus Configuration | :term:`GEN_ENS_PROD_CLIMO_STDEV_MATCH_MONTH` | :term:`GEN_ENS_PROD_CLIMO_STDEV_DAY_INTERVAL` | :term:`GEN_ENS_PROD_CLIMO_STDEV_HOUR_INTERVAL` +| :term:`GEN_ENS_PROD_CLIMO_STDEV_USE_FCST` +| :term:`GEN_ENS_PROD_CLIMO_STDEV_USE_OBS` | :term:`GEN_ENS_PROD_ENSEMBLE_FLAG_LATLON` | :term:`GEN_ENS_PROD_ENSEMBLE_FLAG_MEAN` | :term:`GEN_ENS_PROD_ENSEMBLE_FLAG_STDEV` @@ -2834,6 +2842,8 @@ METplus Configuration | :term:`GRID_STAT_CLIMO_MEAN_MATCH_MONTH` | :term:`GRID_STAT_CLIMO_MEAN_DAY_INTERVAL` | :term:`GRID_STAT_CLIMO_MEAN_HOUR_INTERVAL` +| :term:`GRID_STAT_CLIMO_MEAN_USE_FCST` +| :term:`GRID_STAT_CLIMO_MEAN_USE_OBS` | :term:`GRID_STAT_CLIMO_STDEV_FILE_NAME` | :term:`GRID_STAT_CLIMO_STDEV_FIELD` | :term:`GRID_STAT_CLIMO_STDEV_REGRID_METHOD` @@ -2844,6 +2854,8 @@ METplus Configuration | :term:`GRID_STAT_CLIMO_STDEV_MATCH_MONTH` | :term:`GRID_STAT_CLIMO_STDEV_DAY_INTERVAL` | :term:`GRID_STAT_CLIMO_STDEV_HOUR_INTERVAL` +| :term:`GRID_STAT_CLIMO_STDEV_USE_FCST` +| :term:`GRID_STAT_CLIMO_STDEV_USE_OBS` | :term:`GRID_STAT_HSS_EC_VALUE` | :term:`GRID_STAT_DISTANCE_MAP_BADDELEY_P` | :term:`GRID_STAT_DISTANCE_MAP_BADDELEY_MAX_DIST` @@ -5321,6 +5333,8 @@ Configuration | :term:`POINT_STAT_CLIMO_MEAN_MATCH_MONTH` | :term:`POINT_STAT_CLIMO_MEAN_DAY_INTERVAL` | :term:`POINT_STAT_CLIMO_MEAN_HOUR_INTERVAL` +| :term:`POINT_STAT_CLIMO_MEAN_USE_FCST` +| :term:`POINT_STAT_CLIMO_MEAN_USE_OBS` | :term:`POINT_STAT_CLIMO_STDEV_FILE_NAME` | :term:`POINT_STAT_CLIMO_STDEV_FIELD` | :term:`POINT_STAT_CLIMO_STDEV_REGRID_METHOD` @@ -5331,6 +5345,8 @@ Configuration | :term:`POINT_STAT_CLIMO_STDEV_MATCH_MONTH` | :term:`POINT_STAT_CLIMO_STDEV_DAY_INTERVAL` | :term:`POINT_STAT_CLIMO_STDEV_HOUR_INTERVAL` +| :term:`POINT_STAT_CLIMO_STDEV_USE_FCST` +| :term:`POINT_STAT_CLIMO_STDEV_USE_OBS` | :term:`POINT_STAT_HSS_EC_VALUE` | :term:`POINT_STAT_HIRA_FLAG` | :term:`POINT_STAT_HIRA_WIDTH` @@ -5924,6 +5940,8 @@ METplus Configuration | :term:`SERIES_ANALYSIS_CLIMO_MEAN_DAY_INTERVAL` | :term:`SERIES_ANALYSIS_CLIMO_MEAN_HOUR_INTERVAL` | :term:`SERIES_ANALYSIS_CLIMO_MEAN_FILE_TYPE` +| :term:`SERIES_ANALYSIS_CLIMO_MEAN_USE_FCST` +| :term:`SERIES_ANALYSIS_CLIMO_MEAN_USE_OBS` | :term:`SERIES_ANALYSIS_CLIMO_STDEV_FILE_NAME` | :term:`SERIES_ANALYSIS_CLIMO_STDEV_FIELD` | :term:`SERIES_ANALYSIS_CLIMO_STDEV_REGRID_METHOD` @@ -5935,6 +5953,8 @@ METplus Configuration | :term:`SERIES_ANALYSIS_CLIMO_STDEV_DAY_INTERVAL` | :term:`SERIES_ANALYSIS_CLIMO_STDEV_HOUR_INTERVAL` | :term:`SERIES_ANALYSIS_CLIMO_STDEV_FILE_TYPE` +| :term:`SERIES_ANALYSIS_CLIMO_STDEV_USE_FCST` +| :term:`SERIES_ANALYSIS_CLIMO_STDEV_USE_OBS` | :term:`SERIES_ANALYSIS_HSS_EC_VALUE` | :term:`SERIES_ANALYSIS_OUTPUT_STATS_FHO` | :term:`SERIES_ANALYSIS_OUTPUT_STATS_CTC`