openstudio-extension 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +9 -0
- data/.rubocop.yml +9 -0
- data/Gemfile +3 -1
- data/Jenkinsfile +10 -0
- data/README.md +230 -12
- data/Rakefile +88 -3
- data/bin/console +3 -3
- data/doc_templates/LICENSE.md +27 -0
- data/doc_templates/README.md.erb +42 -0
- data/doc_templates/copyright_erb.txt +36 -0
- data/doc_templates/copyright_js.txt +4 -0
- data/doc_templates/copyright_ruby.txt +34 -0
- data/init_templates/README.md +37 -0
- data/init_templates/gemspec.txt +32 -0
- data/init_templates/openstudio_module.rb +50 -0
- data/init_templates/spec.rb +47 -0
- data/init_templates/spec_helper.rb +49 -0
- data/init_templates/template_gemfile.txt +17 -0
- data/init_templates/template_rakefile.txt +15 -0
- data/init_templates/version.rb +40 -0
- data/lib/files/openstudio-extension-gem-test.ddy +536 -0
- data/lib/files/openstudio-extension-gem-test.epw +8768 -0
- data/lib/files/openstudio-extension-gem-test.stat +554 -0
- data/lib/measures/openstudio_extension_test_measure/LICENSE.md +27 -0
- data/lib/measures/openstudio_extension_test_measure/README.md +26 -0
- data/lib/measures/openstudio_extension_test_measure/README.md.erb +42 -0
- data/lib/measures/openstudio_extension_test_measure/measure.rb +72 -0
- data/lib/measures/openstudio_extension_test_measure/measure.xml +83 -0
- data/lib/measures/openstudio_extension_test_measure/resources/os_lib_helper_methods.rb +399 -0
- data/lib/measures/openstudio_extension_test_measure/tests/OpenStudioExtensionTestMeasure_Test.rb +75 -0
- data/lib/openstudio/extension.rb +220 -0
- data/lib/openstudio/extension/core/CreateResults.rb +879 -0
- data/lib/openstudio/extension/core/check_air_sys_temps.rb +190 -0
- data/lib/openstudio/extension/core/check_calibration.rb +155 -0
- data/lib/openstudio/extension/core/check_cond_zns.rb +84 -0
- data/lib/openstudio/extension/core/check_domestic_hot_water.rb +334 -0
- data/lib/openstudio/extension/core/check_envelope_conductance.rb +453 -0
- data/lib/openstudio/extension/core/check_eui_by_end_use.rb +162 -0
- data/lib/openstudio/extension/core/check_eui_reasonableness.rb +135 -0
- data/lib/openstudio/extension/core/check_fan_pwr.rb +98 -0
- data/lib/openstudio/extension/core/check_internal_loads.rb +393 -0
- data/lib/openstudio/extension/core/check_mech_sys_capacity.rb +226 -0
- data/lib/openstudio/extension/core/check_mech_sys_efficiency.rb +326 -0
- data/lib/openstudio/extension/core/check_mech_sys_part_load_eff.rb +464 -0
- data/lib/openstudio/extension/core/check_mech_sys_type.rb +139 -0
- data/lib/openstudio/extension/core/check_part_loads.rb +451 -0
- data/lib/openstudio/extension/core/check_placeholder.rb +75 -0
- data/lib/openstudio/extension/core/check_plant_cap.rb +123 -0
- data/lib/openstudio/extension/core/check_plant_temps.rb +159 -0
- data/lib/openstudio/extension/core/check_plenum_loads.rb +87 -0
- data/lib/openstudio/extension/core/check_pump_pwr.rb +108 -0
- data/lib/openstudio/extension/core/check_sch_coord.rb +241 -0
- data/lib/openstudio/extension/core/check_schedules.rb +311 -0
- data/lib/openstudio/extension/core/check_simultaneous_heating_and_cooling.rb +158 -0
- data/lib/openstudio/extension/core/check_supply_air_and_thermostat_temp_difference.rb +148 -0
- data/lib/openstudio/extension/core/check_weather_files.rb +132 -0
- data/lib/openstudio/extension/core/deer_vintages.rb +311 -0
- data/lib/openstudio/extension/core/os_lib_aedg_measures.rb +491 -0
- data/lib/openstudio/extension/core/os_lib_cofee.rb +259 -0
- data/lib/openstudio/extension/core/os_lib_constructions.rb +378 -0
- data/lib/openstudio/extension/core/os_lib_geometry.rb +1022 -0
- data/lib/openstudio/extension/core/os_lib_helper_methods.rb +399 -0
- data/lib/openstudio/extension/core/os_lib_hvac.rb +2171 -0
- data/lib/openstudio/extension/core/os_lib_lighting_and_equipment.rb +214 -0
- data/lib/openstudio/extension/core/os_lib_model_generation.rb +817 -0
- data/lib/openstudio/extension/core/os_lib_model_simplification.rb +1049 -0
- data/lib/openstudio/extension/core/os_lib_outdoorair_and_infiltration.rb +165 -0
- data/lib/openstudio/extension/core/os_lib_reporting.rb +4652 -0
- data/lib/openstudio/extension/core/os_lib_reporting_qaqc.rb +200 -0
- data/lib/openstudio/extension/core/os_lib_schedules.rb +963 -0
- data/lib/openstudio/extension/rake_task.rb +149 -0
- data/lib/openstudio/extension/runner.rb +644 -0
- data/lib/openstudio/extension/version.rb +40 -0
- data/openstudio-extension.gemspec +20 -15
- metadata +150 -14
- data/.travis.yml +0 -7
- data/lib/OpenStudio/Extension/rake_task.rb +0 -84
- data/lib/OpenStudio/Extension/version.rb +0 -33
- data/lib/OpenStudio/extension.rb +0 -65
@@ -0,0 +1,158 @@
|
|
1
|
+
# *******************************************************************************
|
2
|
+
# OpenStudio(R), Copyright (c) 2008-2019, Alliance for Sustainable Energy, LLC.
|
3
|
+
# All rights reserved.
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are met:
|
6
|
+
#
|
7
|
+
# (1) Redistributions of source code must retain the above copyright notice,
|
8
|
+
# this list of conditions and the following disclaimer.
|
9
|
+
#
|
10
|
+
# (2) Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
#
|
14
|
+
# (3) Neither the name of the copyright holder nor the names of any contributors
|
15
|
+
# may be used to endorse or promote products derived from this software without
|
16
|
+
# specific prior written permission from the respective party.
|
17
|
+
#
|
18
|
+
# (4) Other than as required in clauses (1) and (2), distributions in any form
|
19
|
+
# of modifications or other derivative works may not use the "OpenStudio"
|
20
|
+
# trademark, "OS", "os", or any other confusingly similar designation without
|
21
|
+
# specific prior written permission from Alliance for Sustainable Energy, LLC.
|
22
|
+
#
|
23
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
|
24
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
25
|
+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
26
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
|
27
|
+
# UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
|
28
|
+
# THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
29
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
30
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
31
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
32
|
+
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
33
|
+
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
34
|
+
# *******************************************************************************
|
35
|
+
|
36
|
+
module OsLib_QAQC
|
37
|
+
# include any general notes about QAQC method here
|
38
|
+
|
39
|
+
# checks the number of unmet hours in the model
|
40
|
+
def check_simultaneous_heating_and_cooling(category, max_pass, name_only = false)
|
41
|
+
# summary of the check
|
42
|
+
check_elems = OpenStudio::AttributeVector.new
|
43
|
+
check_elems << OpenStudio::Attribute.new('name', 'Simultaneous Heating and Cooling')
|
44
|
+
check_elems << OpenStudio::Attribute.new('category', category)
|
45
|
+
check_elems << OpenStudio::Attribute.new('description', 'Check for simultaneous heating and cooling by looping through all Single Duct VAV Reheat Air Terminals and analyzing hourly data when there is a cooling load. ')
|
46
|
+
|
47
|
+
# stop here if only name is requested this is used to populate display name for arguments
|
48
|
+
if name_only == true
|
49
|
+
results = []
|
50
|
+
check_elems.each do |elem|
|
51
|
+
results << elem.valueAsString
|
52
|
+
end
|
53
|
+
return results
|
54
|
+
end
|
55
|
+
|
56
|
+
begin
|
57
|
+
# get the weather file run period (as opposed to design day run period)
|
58
|
+
ann_env_pd = nil
|
59
|
+
@sql.availableEnvPeriods.each do |env_pd|
|
60
|
+
env_type = @sql.environmentType(env_pd)
|
61
|
+
if env_type.is_initialized
|
62
|
+
if env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod')
|
63
|
+
ann_env_pd = env_pd
|
64
|
+
break
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# only try to get the annual timeseries if an annual simulation was run
|
70
|
+
if ann_env_pd.nil?
|
71
|
+
check_elems << OpenStudio::Attribute.new('flag', 'Cannot find the annual simulation run period, cannot determine simultaneous heating and cooling.')
|
72
|
+
return check_elem
|
73
|
+
end
|
74
|
+
|
75
|
+
# For each VAV reheat terminal, calculate
|
76
|
+
# the annual total % reheat hours.
|
77
|
+
@model.getAirTerminalSingleDuctVAVReheats.each do |term|
|
78
|
+
# Reheat coil heating rate
|
79
|
+
rht_coil = term.reheatCoil
|
80
|
+
key_value = rht_coil.name.get.to_s.upcase # must be in all caps.
|
81
|
+
time_step = 'Hourly' # "Zone Timestep", "Hourly", "HVAC System Timestep"
|
82
|
+
variable_name = 'Heating Coil Heating Rate'
|
83
|
+
variable_name_alt = 'Heating Coil Air Heating Rate'
|
84
|
+
rht_rate_ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) # key value would go at the end if we used it.
|
85
|
+
|
86
|
+
# try and alternate variable name
|
87
|
+
if rht_rate_ts.empty?
|
88
|
+
rht_rate_ts = @sql.timeSeries(ann_env_pd, time_step, variable_name_alt, key_value) # key value would go at the end if we used it.
|
89
|
+
end
|
90
|
+
|
91
|
+
if rht_rate_ts.empty?
|
92
|
+
check_elems << OpenStudio::Attribute.new('flag', "Heating Coil (Air) Heating Rate Timeseries not found for #{key_value}.")
|
93
|
+
else
|
94
|
+
|
95
|
+
rht_rate_ts = rht_rate_ts.get.values
|
96
|
+
# Put timeseries into array
|
97
|
+
rht_rate_vals = []
|
98
|
+
for i in 0..(rht_rate_ts.size - 1)
|
99
|
+
rht_rate_vals << rht_rate_ts[i]
|
100
|
+
end
|
101
|
+
|
102
|
+
# Zone Air Terminal Sensible Heating Rate
|
103
|
+
key_value = "ADU #{term.name.get.to_s.upcase}" # must be in all caps.
|
104
|
+
time_step = 'Hourly' # "Zone Timestep", "Hourly", "HVAC System Timestep"
|
105
|
+
variable_name = 'Zone Air Terminal Sensible Cooling Rate'
|
106
|
+
clg_rate_ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) # key value would go at the end if we used it.
|
107
|
+
if clg_rate_ts.empty?
|
108
|
+
check_elems << OpenStudio::Attribute.new('flag', "Zone Air Terminal Sensible Cooling Rate Timeseries not found for #{key_value}.")
|
109
|
+
else
|
110
|
+
|
111
|
+
clg_rate_ts = clg_rate_ts.get.values
|
112
|
+
# Put timeseries into array
|
113
|
+
clg_rate_vals = []
|
114
|
+
for i in 0..(clg_rate_ts.size - 1)
|
115
|
+
clg_rate_vals << clg_rate_ts[i]
|
116
|
+
end
|
117
|
+
|
118
|
+
# Loop through each timestep and calculate the hourly
|
119
|
+
# % reheat value.
|
120
|
+
ann_rht_hrs = 0
|
121
|
+
ann_clg_hrs = 0
|
122
|
+
ann_pcts = []
|
123
|
+
rht_rate_vals.zip(clg_rate_vals).each do |rht_w, clg_w|
|
124
|
+
# Skip hours with no cooling (in heating mode)
|
125
|
+
next if clg_w == 0
|
126
|
+
pct_overcool_rht = rht_w / (rht_w + clg_w)
|
127
|
+
ann_rht_hrs += pct_overcool_rht # implied * 1hr b/c hrly results
|
128
|
+
ann_clg_hrs += 1
|
129
|
+
ann_pcts << pct_overcool_rht.round(3)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Calculate annual % reheat hours
|
133
|
+
ann_pct_reheat = ((ann_rht_hrs / ann_clg_hrs) * 100).round(1)
|
134
|
+
|
135
|
+
# Compare to limit
|
136
|
+
if ann_pct_reheat > max_pass * 100.0
|
137
|
+
check_elems << OpenStudio::Attribute.new('flag', "#{term.name} has #{ann_pct_reheat}% overcool-reheat, which is greater than the limit of #{max_pass * 100.0}%. This terminal is in cooling mode for #{ann_clg_hrs} hours of the year.")
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|
144
|
+
rescue StandardError => e
|
145
|
+
# brief description of ruby error
|
146
|
+
check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
|
147
|
+
|
148
|
+
# backtrace of ruby error for diagnostic use
|
149
|
+
if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
|
150
|
+
end
|
151
|
+
|
152
|
+
# add check_elms to new attribute
|
153
|
+
check_elem = OpenStudio::Attribute.new('check', check_elems)
|
154
|
+
|
155
|
+
return check_elem
|
156
|
+
# note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# *******************************************************************************
|
2
|
+
# OpenStudio(R), Copyright (c) 2008-2019, Alliance for Sustainable Energy, LLC.
|
3
|
+
# All rights reserved.
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are met:
|
6
|
+
#
|
7
|
+
# (1) Redistributions of source code must retain the above copyright notice,
|
8
|
+
# this list of conditions and the following disclaimer.
|
9
|
+
#
|
10
|
+
# (2) Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
#
|
14
|
+
# (3) Neither the name of the copyright holder nor the names of any contributors
|
15
|
+
# may be used to endorse or promote products derived from this software without
|
16
|
+
# specific prior written permission from the respective party.
|
17
|
+
#
|
18
|
+
# (4) Other than as required in clauses (1) and (2), distributions in any form
|
19
|
+
# of modifications or other derivative works may not use the "OpenStudio"
|
20
|
+
# trademark, "OS", "os", or any other confusingly similar designation without
|
21
|
+
# specific prior written permission from Alliance for Sustainable Energy, LLC.
|
22
|
+
#
|
23
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
|
24
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
25
|
+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
26
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
|
27
|
+
# UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
|
28
|
+
# THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
29
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
30
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
31
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
32
|
+
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
33
|
+
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
34
|
+
# *******************************************************************************
|
35
|
+
|
36
|
+
module OsLib_QAQC
|
37
|
+
# include any general notes about QAQC method here
|
38
|
+
|
39
|
+
# checks the number of unmet hours in the model
|
40
|
+
def check_supply_air_and_thermostat_temp_difference(category, target_standard, max_delta, name_only = false)
|
41
|
+
# G3.1.2.9 requires a 20 degree F delta between supply air temperature and zone temperature.
|
42
|
+
target_clg_delta = 20.0
|
43
|
+
|
44
|
+
# summary of the check
|
45
|
+
check_elems = OpenStudio::AttributeVector.new
|
46
|
+
check_elems << OpenStudio::Attribute.new('name', 'Supply and Zone Air Temperature')
|
47
|
+
check_elems << OpenStudio::Attribute.new('category', category)
|
48
|
+
if @utility_name.nil?
|
49
|
+
check_elems << OpenStudio::Attribute.new('description', "Check if fans modeled to ASHRAE 90.1 2013 Section G3.1.2.9 requirements. Compare the supply air temperature for each thermal zone against the thermostat setpoints. Throw flag if temperature difference excedes threshold of #{target_clg_delta}F plus the selected tolerance.")
|
50
|
+
else
|
51
|
+
check_elems << OpenStudio::Attribute.new('description', "Check if fans modeled to ASHRAE 90.1 2013 Section G3.1.2.9 requirements. Compare the supply air temperature for each thermal zone against the thermostat setpoints. Throw flag if temperature difference excedes threshold set by #{@utility_name}.")
|
52
|
+
end
|
53
|
+
|
54
|
+
# stop here if only name is requested this is used to populate display name for arguments
|
55
|
+
if name_only == true
|
56
|
+
results = []
|
57
|
+
check_elems.each do |elem|
|
58
|
+
results << elem.valueAsString
|
59
|
+
end
|
60
|
+
return results
|
61
|
+
end
|
62
|
+
|
63
|
+
# Versions of OpenStudio greater than 2.4.0 use a modified version of
|
64
|
+
# openstudio-standards with different method calls. These methods
|
65
|
+
# require a "Standard" object instead of the standard being passed into method calls.
|
66
|
+
# This Standard object is used throughout the QAQC check.
|
67
|
+
if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.3')
|
68
|
+
use_old_gem_code = true
|
69
|
+
else
|
70
|
+
use_old_gem_code = false
|
71
|
+
std = Standard.build(target_standard)
|
72
|
+
end
|
73
|
+
|
74
|
+
begin
|
75
|
+
# loop through thermal zones
|
76
|
+
@model.getThermalZones.sort.each do |thermal_zone|
|
77
|
+
model_clg_min = nil
|
78
|
+
|
79
|
+
# populate thermostat ranges
|
80
|
+
if thermal_zone.thermostatSetpointDualSetpoint.is_initialized
|
81
|
+
|
82
|
+
thermostat = thermal_zone.thermostatSetpointDualSetpoint.get
|
83
|
+
if thermostat.coolingSetpointTemperatureSchedule.is_initialized
|
84
|
+
|
85
|
+
clg_sch = thermostat.coolingSetpointTemperatureSchedule.get
|
86
|
+
schedule_values = nil
|
87
|
+
if clg_sch.to_ScheduleRuleset.is_initialized
|
88
|
+
if use_old_gem_code
|
89
|
+
schedule_values = clg_sch.to_ScheduleRuleset.get.annual_min_max_value
|
90
|
+
else
|
91
|
+
schedule_values = std.schedule_ruleset_annual_min_max_value(clg_sch.to_ScheduleRuleset.get)
|
92
|
+
end
|
93
|
+
elsif clg_sch.to_ScheduleConstant.is_initialized
|
94
|
+
if use_old_gem_code
|
95
|
+
schedule_values = clg_sch.to_ScheduleConstant.get.annual_min_max_value
|
96
|
+
else
|
97
|
+
schedule_values = std.schedule_constant_annual_min_max_value(clg_sch.to_ScheduleConstant.get)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
unless schedule_values.nil?
|
102
|
+
model_clg_min = schedule_values['min']
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
else
|
107
|
+
# go to next zone if not conditioned
|
108
|
+
next
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
# flag if there is setpoint schedule can't be inspected (isn't ruleset)
|
113
|
+
if model_clg_min.nil?
|
114
|
+
check_elems << OpenStudio::Attribute.new('flag', "Can't inspect thermostat schedules for #{thermal_zone.name}")
|
115
|
+
else
|
116
|
+
|
117
|
+
# get supply air temps from thermal zone sizing
|
118
|
+
sizing_zone = thermal_zone.sizingZone
|
119
|
+
clg_supply_air_temp = sizing_zone.zoneCoolingDesignSupplyAirTemperature
|
120
|
+
|
121
|
+
# convert model values to IP
|
122
|
+
model_clg_min_ip = OpenStudio.convert(model_clg_min, 'C', 'F').get
|
123
|
+
clg_supply_air_temp_ip = OpenStudio.convert(clg_supply_air_temp, 'C', 'F').get
|
124
|
+
|
125
|
+
# check supply air against zone temperature (only check against min setpoint, assume max is night setback)
|
126
|
+
if model_clg_min_ip - clg_supply_air_temp_ip > target_clg_delta + max_delta
|
127
|
+
check_elems << OpenStudio::Attribute.new('flag', "For #{thermal_zone.name} the delta temp between the cooling supply air temp of #{clg_supply_air_temp_ip.round(2)} (F) and the minimum thermostat cooling temp of #{model_clg_min_ip.round(2)} (F) is more than #{max_delta} (F) larger than the expected delta of #{target_clg_delta} (F)")
|
128
|
+
elsif model_clg_min_ip - clg_supply_air_temp_ip < target_clg_delta - max_delta
|
129
|
+
check_elems << OpenStudio::Attribute.new('flag', "For #{thermal_zone.name} the delta temp between the cooling supply air temp of #{clg_supply_air_temp_ip.round(2)} (F) and the minimum thermostat cooling temp of #{model_clg_min_ip.round(2)} (F) is more than #{max_delta} (F) smaller than the expected delta of #{target_clg_delta} (F)")
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
134
|
+
rescue StandardError => e
|
135
|
+
# brief description of ruby error
|
136
|
+
check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
|
137
|
+
|
138
|
+
# backtrace of ruby error for diagnostic use
|
139
|
+
if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
|
140
|
+
end
|
141
|
+
|
142
|
+
# add check_elms to new attribute
|
143
|
+
check_elem = OpenStudio::Attribute.new('check', check_elems)
|
144
|
+
|
145
|
+
return check_elem
|
146
|
+
# note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# *******************************************************************************
|
2
|
+
# OpenStudio(R), Copyright (c) 2008-2019, Alliance for Sustainable Energy, LLC.
|
3
|
+
# All rights reserved.
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are met:
|
6
|
+
#
|
7
|
+
# (1) Redistributions of source code must retain the above copyright notice,
|
8
|
+
# this list of conditions and the following disclaimer.
|
9
|
+
#
|
10
|
+
# (2) Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
#
|
14
|
+
# (3) Neither the name of the copyright holder nor the names of any contributors
|
15
|
+
# may be used to endorse or promote products derived from this software without
|
16
|
+
# specific prior written permission from the respective party.
|
17
|
+
#
|
18
|
+
# (4) Other than as required in clauses (1) and (2), distributions in any form
|
19
|
+
# of modifications or other derivative works may not use the "OpenStudio"
|
20
|
+
# trademark, "OS", "os", or any other confusingly similar designation without
|
21
|
+
# specific prior written permission from Alliance for Sustainable Energy, LLC.
|
22
|
+
#
|
23
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
|
24
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
25
|
+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
26
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
|
27
|
+
# UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
|
28
|
+
# THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
29
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
30
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
31
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
32
|
+
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
33
|
+
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
34
|
+
# *******************************************************************************
|
35
|
+
|
36
|
+
module OsLib_QAQC
|
37
|
+
# include any general notes about QAQC method here
|
38
|
+
|
39
|
+
# checks the number of unmet hours in the model
|
40
|
+
def check_weather_files(category, options, name_only = false)
|
41
|
+
# summary of the check
|
42
|
+
check_elems = OpenStudio::AttributeVector.new
|
43
|
+
check_elems << OpenStudio::Attribute.new('name', 'Weather Files')
|
44
|
+
check_elems << OpenStudio::Attribute.new('category', category)
|
45
|
+
check_elems << OpenStudio::Attribute.new('description', "Check weather file, design days, and climate zone against #{@utility_name} list of allowable options.")
|
46
|
+
|
47
|
+
# stop here if only name is requested this is used to populate display name for arguments
|
48
|
+
if name_only == true
|
49
|
+
results = []
|
50
|
+
check_elems.each do |elem|
|
51
|
+
results << elem.valueAsString
|
52
|
+
end
|
53
|
+
return results
|
54
|
+
end
|
55
|
+
|
56
|
+
begin
|
57
|
+
# get weather file
|
58
|
+
model_epw = nil
|
59
|
+
if @model.getWeatherFile.url.is_initialized
|
60
|
+
raw_epw = @model.getWeatherFile.url.get
|
61
|
+
end_path_index = raw_epw.rindex('/')
|
62
|
+
model_epw = raw_epw.slice!(end_path_index + 1, raw_epw.length) # everything right of last forward slash
|
63
|
+
end
|
64
|
+
|
65
|
+
# check design days (model must have one or more of the required summer and winter design days)
|
66
|
+
# get design days names from model
|
67
|
+
model_summer_dd_names = []
|
68
|
+
model_winter_dd_names = []
|
69
|
+
@model.getDesignDays.each do |design_day|
|
70
|
+
if design_day.dayType == 'SummerDesignDay'
|
71
|
+
model_summer_dd_names << design_day.name.to_s
|
72
|
+
elsif design_day.dayType == 'WinterDesignDay'
|
73
|
+
model_winter_dd_names << design_day.name.to_s
|
74
|
+
else
|
75
|
+
puts "unexpected day type of #{design_day.dayType} wont' be included in check"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# find matching weather file from options, as well as design days and climate zone
|
80
|
+
if options.key?(model_epw)
|
81
|
+
required_summer_dd = options[model_epw]['summer']
|
82
|
+
required_winter_dd = options[model_epw]['winter']
|
83
|
+
valid_climate_zones = [options[model_epw]['climate_zone']]
|
84
|
+
|
85
|
+
# check for intersection betwen model valid design days
|
86
|
+
summer_intersection = (required_summer_dd & model_summer_dd_names)
|
87
|
+
winter_intersection = (required_winter_dd & model_winter_dd_names)
|
88
|
+
if summer_intersection.empty? && !required_summer_dd.empty?
|
89
|
+
check_elems << OpenStudio::Attribute.new('flag', "Didn't find any of the expected summer design days for #{model_epw}")
|
90
|
+
end
|
91
|
+
if winter_intersection.empty? && !required_winter_dd.empty?
|
92
|
+
check_elems << OpenStudio::Attribute.new('flag', "Didn't find any of the expected winter design days for #{model_epw}")
|
93
|
+
end
|
94
|
+
|
95
|
+
else
|
96
|
+
check_elems << OpenStudio::Attribute.new('flag', "#{model_epw} is not a an expected weather file.")
|
97
|
+
check_elems << OpenStudio::Attribute.new('flag', "Model doesn't have expected epw file, as a result can't validate design days.")
|
98
|
+
valid_climate_zones = []
|
99
|
+
options.each do |lookup_epw, value|
|
100
|
+
valid_climate_zones << value['climate_zone']
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# get ashrae climate zone from model
|
105
|
+
model_climate_zone = nil
|
106
|
+
climateZones = @model.getClimateZones
|
107
|
+
climateZones.climateZones.each do |climateZone|
|
108
|
+
if climateZone.institution == 'ASHRAE'
|
109
|
+
model_climate_zone = climateZone.value
|
110
|
+
next
|
111
|
+
end
|
112
|
+
end
|
113
|
+
if model_climate_zone == ''
|
114
|
+
check_elems << OpenStudio::Attribute.new('flag', "The model's ASHRAE climate zone has not been defined. Expected climate zone was #{valid_climate_zones.uniq.join(',')}.")
|
115
|
+
elsif !valid_climate_zones.include?(model_climate_zone)
|
116
|
+
check_elems << OpenStudio::Attribute.new('flag', "The model's ASHRAE climate zone was #{model_climate_zone}. Expected climate zone was #{valid_climate_zones.uniq.join(',')}.")
|
117
|
+
end
|
118
|
+
rescue StandardError => e
|
119
|
+
# brief description of ruby error
|
120
|
+
check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).")
|
121
|
+
|
122
|
+
# backtrace of ruby error for diagnostic use
|
123
|
+
if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end
|
124
|
+
end
|
125
|
+
|
126
|
+
# add check_elms to new attribute
|
127
|
+
check_elem = OpenStudio::Attribute.new('check', check_elems)
|
128
|
+
|
129
|
+
return check_elem
|
130
|
+
# note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,311 @@
|
|
1
|
+
# *******************************************************************************
|
2
|
+
# OpenStudio(R), Copyright (c) 2008-2019, Alliance for Sustainable Energy, LLC.
|
3
|
+
# All rights reserved.
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are met:
|
6
|
+
#
|
7
|
+
# (1) Redistributions of source code must retain the above copyright notice,
|
8
|
+
# this list of conditions and the following disclaimer.
|
9
|
+
#
|
10
|
+
# (2) Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
#
|
14
|
+
# (3) Neither the name of the copyright holder nor the names of any contributors
|
15
|
+
# may be used to endorse or promote products derived from this software without
|
16
|
+
# specific prior written permission from the respective party.
|
17
|
+
#
|
18
|
+
# (4) Other than as required in clauses (1) and (2), distributions in any form
|
19
|
+
# of modifications or other derivative works may not use the "OpenStudio"
|
20
|
+
# trademark, "OS", "os", or any other confusingly similar designation without
|
21
|
+
# specific prior written permission from Alliance for Sustainable Energy, LLC.
|
22
|
+
#
|
23
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
|
24
|
+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
25
|
+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
26
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
|
27
|
+
# UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
|
28
|
+
# THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
29
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
30
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
31
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
32
|
+
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
33
|
+
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
34
|
+
# *******************************************************************************
|
35
|
+
|
36
|
+
# Maps the DEER vintages to year ranges
|
37
|
+
module DEERVintages
|
38
|
+
# Building type abbreviation to long name map
|
39
|
+
def building_type_to_long
|
40
|
+
return {
|
41
|
+
'Asm' => 'Assembly',
|
42
|
+
'DMo' => 'Residential Mobile Home',
|
43
|
+
'ECC' => 'Education - Community College',
|
44
|
+
'EPr' => 'Education - Primary School',
|
45
|
+
'ERC' => 'Education - Relocatable Classroom',
|
46
|
+
'ESe' => 'Education - Secondary School',
|
47
|
+
'EUn' => 'Education - University',
|
48
|
+
'GHs' => 'Greenhouse',
|
49
|
+
'Gro' => 'Grocery',
|
50
|
+
'Hsp' => 'Health/Medical - Hospital',
|
51
|
+
'Htl' => 'Lodging - Hotel',
|
52
|
+
'MBT' => 'Manufacturing Biotech',
|
53
|
+
'MFm' => 'Residential Multi-family',
|
54
|
+
'MLI' => 'Manufacturing Light Industrial',
|
55
|
+
'Mtl' => 'Lodging - Motel',
|
56
|
+
'Nrs' => 'Health/Medical - Nursing Home',
|
57
|
+
'OfL' => 'Office - Large',
|
58
|
+
'OfS' => 'Office - Small',
|
59
|
+
'RFF' => 'Restaurant - Fast-Food',
|
60
|
+
'RSD' => 'Restaurant - Sit-Down',
|
61
|
+
'Rt3' => 'Retail - Multistory Large',
|
62
|
+
'RtL' => 'Retail - Single-Story Large',
|
63
|
+
'RtS' => 'Retail - Small',
|
64
|
+
'SCn' => 'Storage - Conditioned',
|
65
|
+
'SFm' => 'Residential Single Family',
|
66
|
+
'SUn' => 'Storage - Unconditioned',
|
67
|
+
'WRf' => 'Warehouse - Refrigerated'
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
# HVAC type abbreviation to long name map
|
72
|
+
def hvac_sys_to_long
|
73
|
+
return {
|
74
|
+
'DXGF' => 'Split or Packaged DX Unit with Gas Furnace',
|
75
|
+
'DXEH' => 'Split or Packaged DX Unit with Electric Heat',
|
76
|
+
'DXHP' => 'Split or Packaged DX Unit with Heat Pump',
|
77
|
+
'WLHP' => 'Water Loop Heat Pump',
|
78
|
+
'NCEH' => 'No Cooling with Electric Heat',
|
79
|
+
'NCGF' => 'No Cooling with Gas Furnace',
|
80
|
+
'PVVG' => 'Packaged VAV System with Gas Boiler',
|
81
|
+
'PVVE' => 'Packaged VAV System with Electric Heat',
|
82
|
+
'SVVG' => 'Built-Up VAV System with Gas Boiler',
|
83
|
+
'SVVE' => 'Built-Up VAV System with Electric Reheat',
|
84
|
+
'Unc' => 'No HVAC (Unconditioned)',
|
85
|
+
'PTAC' => 'Packaged Terminal Air Conditioner',
|
86
|
+
'PTHP' => 'Packaged Terminal Heat Pump',
|
87
|
+
'FPFC' => 'Four Pipe Fan Coil',
|
88
|
+
'DDCT' => 'Dual Duct System',
|
89
|
+
'EVAP' => 'Evaporative Cooling with Separate Gas Furnace'
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
# Valid building type/hvac type combos
|
94
|
+
def building_type_to_hvac_systems
|
95
|
+
return {
|
96
|
+
'Asm' => [
|
97
|
+
'DXEH',
|
98
|
+
'DXGF',
|
99
|
+
'DXHP',
|
100
|
+
'NCEH',
|
101
|
+
'NCGF'
|
102
|
+
],
|
103
|
+
'ECC' => [
|
104
|
+
'DXEH',
|
105
|
+
'DXGF',
|
106
|
+
'DXHP',
|
107
|
+
'NCEH',
|
108
|
+
'NCGF',
|
109
|
+
'PVVE',
|
110
|
+
'PVVG',
|
111
|
+
'SVVE',
|
112
|
+
'SVVG',
|
113
|
+
'WLHP'
|
114
|
+
],
|
115
|
+
'EPr' => [
|
116
|
+
'DXEH',
|
117
|
+
'DXGF',
|
118
|
+
'DXHP',
|
119
|
+
'NCEH',
|
120
|
+
'NCGF',
|
121
|
+
'WLHP'
|
122
|
+
],
|
123
|
+
'ERC' => [
|
124
|
+
'DXEH',
|
125
|
+
'DXGF',
|
126
|
+
'DXHP',
|
127
|
+
'NCEH',
|
128
|
+
'NCGF'
|
129
|
+
],
|
130
|
+
'ESe' => [
|
131
|
+
'DXEH',
|
132
|
+
'DXGF',
|
133
|
+
'DXHP',
|
134
|
+
'NCEH',
|
135
|
+
'NCGF',
|
136
|
+
'PVVE',
|
137
|
+
'PVVG',
|
138
|
+
'SVVE',
|
139
|
+
'SVVG',
|
140
|
+
'WLHP'
|
141
|
+
],
|
142
|
+
'EUn' => [
|
143
|
+
'DXEH',
|
144
|
+
'DXGF',
|
145
|
+
'DXHP',
|
146
|
+
'NCEH',
|
147
|
+
'NCGF',
|
148
|
+
'PVVE',
|
149
|
+
'PVVG',
|
150
|
+
'SVVE',
|
151
|
+
'SVVG'
|
152
|
+
],
|
153
|
+
'Gro' => [
|
154
|
+
'DXEH',
|
155
|
+
'DXGF',
|
156
|
+
'DXHP',
|
157
|
+
'NCEH',
|
158
|
+
'NCGF'
|
159
|
+
],
|
160
|
+
'Hsp' => [
|
161
|
+
'DXEH',
|
162
|
+
'DXGF',
|
163
|
+
'DXHP',
|
164
|
+
'NCEH',
|
165
|
+
'NCGF',
|
166
|
+
'PVVE',
|
167
|
+
'PVVG',
|
168
|
+
'SVVE',
|
169
|
+
'SVVG'
|
170
|
+
],
|
171
|
+
'Nrs' => [
|
172
|
+
'DXEH',
|
173
|
+
'DXGF',
|
174
|
+
'DXHP',
|
175
|
+
'FPFC',
|
176
|
+
'NCEH',
|
177
|
+
'NCGF',
|
178
|
+
'PVVE',
|
179
|
+
'PVVG',
|
180
|
+
'SVVE',
|
181
|
+
'SVVG'
|
182
|
+
],
|
183
|
+
'Htl' => [
|
184
|
+
'DXEH',
|
185
|
+
'DXGF',
|
186
|
+
'DXHP',
|
187
|
+
'NCEH',
|
188
|
+
'NCGF',
|
189
|
+
'PVVE',
|
190
|
+
'PVVG',
|
191
|
+
'SVVE',
|
192
|
+
'SVVG',
|
193
|
+
'WLHP'
|
194
|
+
],
|
195
|
+
'Mtl' => [
|
196
|
+
'DXEH',
|
197
|
+
'DXGF',
|
198
|
+
'DXHP',
|
199
|
+
'NCEH',
|
200
|
+
'NCGF'
|
201
|
+
],
|
202
|
+
'MBT' => [
|
203
|
+
'DXEH',
|
204
|
+
'DXGF',
|
205
|
+
'DXHP',
|
206
|
+
'NCEH',
|
207
|
+
'NCGF',
|
208
|
+
'PVVE',
|
209
|
+
'PVVG',
|
210
|
+
'SVVE',
|
211
|
+
'SVVG',
|
212
|
+
'WLHP'
|
213
|
+
],
|
214
|
+
'MLI' => [
|
215
|
+
'DXEH',
|
216
|
+
'DXGF',
|
217
|
+
'DXHP',
|
218
|
+
'NCEH',
|
219
|
+
'NCGF'
|
220
|
+
],
|
221
|
+
'OfL' => [
|
222
|
+
'DXEH',
|
223
|
+
'DXGF',
|
224
|
+
'DXHP',
|
225
|
+
'NCEH',
|
226
|
+
'NCGF',
|
227
|
+
'PVVE',
|
228
|
+
'PVVG',
|
229
|
+
'SVVE',
|
230
|
+
'SVVG',
|
231
|
+
'WLHP'
|
232
|
+
],
|
233
|
+
'OfS' => [
|
234
|
+
'DXEH',
|
235
|
+
'DXGF',
|
236
|
+
'DXHP',
|
237
|
+
'NCEH',
|
238
|
+
'NCGF',
|
239
|
+
'PVVE',
|
240
|
+
'PVVG',
|
241
|
+
'SVVE',
|
242
|
+
'SVVG',
|
243
|
+
'WLHP'
|
244
|
+
],
|
245
|
+
'RFF' => [
|
246
|
+
'DXEH',
|
247
|
+
'DXGF',
|
248
|
+
'DXHP',
|
249
|
+
'NCEH',
|
250
|
+
'NCGF'
|
251
|
+
],
|
252
|
+
'RSD' => [
|
253
|
+
'DXEH',
|
254
|
+
'DXGF',
|
255
|
+
'DXHP',
|
256
|
+
'NCEH',
|
257
|
+
'NCGF'
|
258
|
+
],
|
259
|
+
'Rt3' => [
|
260
|
+
'DXEH',
|
261
|
+
'DXGF',
|
262
|
+
'DXHP',
|
263
|
+
'NCEH',
|
264
|
+
'NCGF',
|
265
|
+
'PVVE',
|
266
|
+
'PVVG',
|
267
|
+
'SVVE',
|
268
|
+
'SVVG',
|
269
|
+
'WLHP'
|
270
|
+
],
|
271
|
+
'RtL' => [
|
272
|
+
'DXEH',
|
273
|
+
'DXGF',
|
274
|
+
'DXHP',
|
275
|
+
'NCEH',
|
276
|
+
'NCGF'
|
277
|
+
],
|
278
|
+
'RtS' => [
|
279
|
+
'DXEH',
|
280
|
+
'DXGF',
|
281
|
+
'DXHP',
|
282
|
+
'NCEH',
|
283
|
+
'NCGF'
|
284
|
+
],
|
285
|
+
'SCn' => [
|
286
|
+
'DXEH',
|
287
|
+
'DXGF',
|
288
|
+
'DXHP',
|
289
|
+
'NCEH',
|
290
|
+
'NCGF'
|
291
|
+
],
|
292
|
+
'SUn' => ['Unc'],
|
293
|
+
'WRf' => ['DXGF']
|
294
|
+
}
|
295
|
+
end
|
296
|
+
|
297
|
+
# Age range to DEER template
|
298
|
+
def template_to_age_range
|
299
|
+
return {
|
300
|
+
'DEER Pre-1975' => 'Before 1978',
|
301
|
+
'DEER 1985' => '1978-1992',
|
302
|
+
'DEER 1996' => '1993-2001',
|
303
|
+
'DEER 2003' => '2002-2005',
|
304
|
+
'DEER 2007' => '2006-2009',
|
305
|
+
'DEER 2011' => '2010-2013',
|
306
|
+
'DEER 2014' => '2014',
|
307
|
+
'DEER 2015' => '2015-2016',
|
308
|
+
'DEER 2017' => '2017 or Later'
|
309
|
+
}
|
310
|
+
end
|
311
|
+
end
|