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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -0
  3. data/.rubocop.yml +9 -0
  4. data/Gemfile +3 -1
  5. data/Jenkinsfile +10 -0
  6. data/README.md +230 -12
  7. data/Rakefile +88 -3
  8. data/bin/console +3 -3
  9. data/doc_templates/LICENSE.md +27 -0
  10. data/doc_templates/README.md.erb +42 -0
  11. data/doc_templates/copyright_erb.txt +36 -0
  12. data/doc_templates/copyright_js.txt +4 -0
  13. data/doc_templates/copyright_ruby.txt +34 -0
  14. data/init_templates/README.md +37 -0
  15. data/init_templates/gemspec.txt +32 -0
  16. data/init_templates/openstudio_module.rb +50 -0
  17. data/init_templates/spec.rb +47 -0
  18. data/init_templates/spec_helper.rb +49 -0
  19. data/init_templates/template_gemfile.txt +17 -0
  20. data/init_templates/template_rakefile.txt +15 -0
  21. data/init_templates/version.rb +40 -0
  22. data/lib/files/openstudio-extension-gem-test.ddy +536 -0
  23. data/lib/files/openstudio-extension-gem-test.epw +8768 -0
  24. data/lib/files/openstudio-extension-gem-test.stat +554 -0
  25. data/lib/measures/openstudio_extension_test_measure/LICENSE.md +27 -0
  26. data/lib/measures/openstudio_extension_test_measure/README.md +26 -0
  27. data/lib/measures/openstudio_extension_test_measure/README.md.erb +42 -0
  28. data/lib/measures/openstudio_extension_test_measure/measure.rb +72 -0
  29. data/lib/measures/openstudio_extension_test_measure/measure.xml +83 -0
  30. data/lib/measures/openstudio_extension_test_measure/resources/os_lib_helper_methods.rb +399 -0
  31. data/lib/measures/openstudio_extension_test_measure/tests/OpenStudioExtensionTestMeasure_Test.rb +75 -0
  32. data/lib/openstudio/extension.rb +220 -0
  33. data/lib/openstudio/extension/core/CreateResults.rb +879 -0
  34. data/lib/openstudio/extension/core/check_air_sys_temps.rb +190 -0
  35. data/lib/openstudio/extension/core/check_calibration.rb +155 -0
  36. data/lib/openstudio/extension/core/check_cond_zns.rb +84 -0
  37. data/lib/openstudio/extension/core/check_domestic_hot_water.rb +334 -0
  38. data/lib/openstudio/extension/core/check_envelope_conductance.rb +453 -0
  39. data/lib/openstudio/extension/core/check_eui_by_end_use.rb +162 -0
  40. data/lib/openstudio/extension/core/check_eui_reasonableness.rb +135 -0
  41. data/lib/openstudio/extension/core/check_fan_pwr.rb +98 -0
  42. data/lib/openstudio/extension/core/check_internal_loads.rb +393 -0
  43. data/lib/openstudio/extension/core/check_mech_sys_capacity.rb +226 -0
  44. data/lib/openstudio/extension/core/check_mech_sys_efficiency.rb +326 -0
  45. data/lib/openstudio/extension/core/check_mech_sys_part_load_eff.rb +464 -0
  46. data/lib/openstudio/extension/core/check_mech_sys_type.rb +139 -0
  47. data/lib/openstudio/extension/core/check_part_loads.rb +451 -0
  48. data/lib/openstudio/extension/core/check_placeholder.rb +75 -0
  49. data/lib/openstudio/extension/core/check_plant_cap.rb +123 -0
  50. data/lib/openstudio/extension/core/check_plant_temps.rb +159 -0
  51. data/lib/openstudio/extension/core/check_plenum_loads.rb +87 -0
  52. data/lib/openstudio/extension/core/check_pump_pwr.rb +108 -0
  53. data/lib/openstudio/extension/core/check_sch_coord.rb +241 -0
  54. data/lib/openstudio/extension/core/check_schedules.rb +311 -0
  55. data/lib/openstudio/extension/core/check_simultaneous_heating_and_cooling.rb +158 -0
  56. data/lib/openstudio/extension/core/check_supply_air_and_thermostat_temp_difference.rb +148 -0
  57. data/lib/openstudio/extension/core/check_weather_files.rb +132 -0
  58. data/lib/openstudio/extension/core/deer_vintages.rb +311 -0
  59. data/lib/openstudio/extension/core/os_lib_aedg_measures.rb +491 -0
  60. data/lib/openstudio/extension/core/os_lib_cofee.rb +259 -0
  61. data/lib/openstudio/extension/core/os_lib_constructions.rb +378 -0
  62. data/lib/openstudio/extension/core/os_lib_geometry.rb +1022 -0
  63. data/lib/openstudio/extension/core/os_lib_helper_methods.rb +399 -0
  64. data/lib/openstudio/extension/core/os_lib_hvac.rb +2171 -0
  65. data/lib/openstudio/extension/core/os_lib_lighting_and_equipment.rb +214 -0
  66. data/lib/openstudio/extension/core/os_lib_model_generation.rb +817 -0
  67. data/lib/openstudio/extension/core/os_lib_model_simplification.rb +1049 -0
  68. data/lib/openstudio/extension/core/os_lib_outdoorair_and_infiltration.rb +165 -0
  69. data/lib/openstudio/extension/core/os_lib_reporting.rb +4652 -0
  70. data/lib/openstudio/extension/core/os_lib_reporting_qaqc.rb +200 -0
  71. data/lib/openstudio/extension/core/os_lib_schedules.rb +963 -0
  72. data/lib/openstudio/extension/rake_task.rb +149 -0
  73. data/lib/openstudio/extension/runner.rb +644 -0
  74. data/lib/openstudio/extension/version.rb +40 -0
  75. data/openstudio-extension.gemspec +20 -15
  76. metadata +150 -14
  77. data/.travis.yml +0 -7
  78. data/lib/OpenStudio/Extension/rake_task.rb +0 -84
  79. data/lib/OpenStudio/Extension/version.rb +0 -33
  80. data/lib/OpenStudio/extension.rb +0 -65
@@ -0,0 +1,75 @@
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
+ require 'openstudio'
37
+ require 'openstudio/measure/ShowRunnerOutput'
38
+
39
+ require_relative '../measure.rb'
40
+ require 'minitest/autorun'
41
+
42
+ class OpenStudioExtensionTestMeasure_Test < Minitest::Test
43
+ # def setup
44
+ # end
45
+
46
+ # def teardown
47
+ # end
48
+
49
+ def test_OpenStudioExtensionTestMeasure
50
+ # create an instance of the measure
51
+ measure = OpenStudioExtensionTestMeasure.new
52
+
53
+ # create an instance of a runner
54
+ runner = OpenStudio::Measure::OSRunner.new(OpenStudio::WorkflowJSON.new)
55
+
56
+ # make an empty model
57
+ model = OpenStudio::Model::Model.new
58
+
59
+ # get arguments and test that they are what we are expecting
60
+ arguments = measure.arguments(model)
61
+ assert_equal(0, arguments.size)
62
+
63
+ # set argument values to good values and run the measure on model with spaces
64
+ arguments = measure.arguments(model)
65
+ argument_map = OpenStudio::Measure.convertOSArgumentVectorToMap(arguments)
66
+
67
+ measure.run(model, runner, argument_map)
68
+ result = runner.result
69
+ # show_output(result)
70
+ assert(result.value.valueName == 'Success')
71
+ assert(result.warnings.size == 0)
72
+ assert(result.info.size == 0)
73
+ end
74
+
75
+ end
@@ -0,0 +1,220 @@
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
+ require 'openstudio/extension/version'
37
+ require 'openstudio/extension/runner'
38
+
39
+ module OpenStudio
40
+ module Extension
41
+ class Extension
42
+ attr_accessor :root_dir
43
+
44
+ def initialize
45
+ @root_dir = File.absolute_path(File.join(File.dirname(__FILE__), '..', '..'))
46
+ end
47
+
48
+ # Return the absolute path of the measures or nil if there is none, used when configuring OSWs
49
+ def measures_dir
50
+ return File.absolute_path(File.join(@root_dir, 'lib', 'measures'))
51
+ end
52
+
53
+ # Relevant files such as weather data, design days, etc.
54
+ # Return the absolute path of the files or nil if there is none, used when configuring OSWs
55
+ def files_dir
56
+ return File.absolute_path(File.join(@root_dir, 'lib', 'files'))
57
+ end
58
+
59
+ # Doc templates are common files like copyright files which are used to update measures and other code
60
+ # Doc templates will only be applied to measures in the current repository
61
+ # Return the absolute path of the doc templates dir or nil if there is none
62
+ def doc_templates_dir
63
+ return File.absolute_path(File.join(@root_dir, 'doc_templates'))
64
+ end
65
+
66
+ # Do not override
67
+ # Files in the core directory are copied into measure resource folders to build standalone measures
68
+ # Files will be copied into the resources folder of measures which have files of the same name
69
+ # Return the absolute path of the core dir or nil if there is none
70
+ def core_dir
71
+ return File.absolute_path(File.join(@root_dir, 'lib', 'openstudio', 'extension', 'core'))
72
+ end
73
+ end
74
+
75
+ ##
76
+ # Module method to return all classes derived from OpenStudio::Extension::Extension
77
+ # Note all extension classes must be loaded before calling this method
78
+ ##
79
+ # @return [Array]: Array of classes
80
+ def self.all_extensions
81
+ # DLM: consider calling Bundler.require in this method
82
+ # do not call Bundler.require when requiring this file, only when calling this method
83
+ result = []
84
+ ObjectSpace.each_object(::Class) do |obj|
85
+ next if !obj.ancestors.include?(OpenStudio::Extension::Extension)
86
+ result << obj
87
+ end
88
+ return result.uniq
89
+ end
90
+
91
+ ##
92
+ # Module method to return measure directories from all extensions
93
+ ##
94
+ # @return [Array]: Array of measure directories
95
+ def self.all_measure_dirs
96
+ result = []
97
+ all_extensions.each do |obj|
98
+ begin
99
+ dir = obj.new.measures_dir
100
+ result << dir if dir
101
+ rescue StandardError
102
+ end
103
+ end
104
+ return result.uniq
105
+ end
106
+
107
+ ##
108
+ # Module method to return file directories from all extensions
109
+ ##
110
+ # @return [Array] Array of measure resource directories
111
+ def self.all_file_dirs
112
+ result = []
113
+ all_extensions.each do |obj|
114
+ begin
115
+ dir = obj.new.files_dir
116
+ result << dir if dir
117
+ rescue StandardError
118
+ end
119
+ end
120
+ return result.uniq
121
+ end
122
+
123
+ ##
124
+ # Module method to check for duplicate file, measure, or measure resource names across all extensions
125
+ #
126
+ # Will raise an error if conflicting file names are found.
127
+ # Note that file names in measure_files_dir names (e.g. License.md) are expected to be the same across all extensions.
128
+ ##
129
+ def self.check_for_name_conflicts
130
+ measure_dirs = all_measure_dirs
131
+ file_dirs = all_file_dirs
132
+ conflicts = []
133
+
134
+ measure_dir_names = {}
135
+ measure_dirs.each do |dir|
136
+ Dir.glob(File.join(dir, '*')).each do |file|
137
+ next if !File.directory?(file)
138
+ next if !File.exist?(File.join(file, 'measure.rb'))
139
+
140
+ # puts file
141
+ file_name = File.basename(file).downcase
142
+ if measure_dir_names[file_name]
143
+ conflicts << "Measure '#{file}' conflicts with '#{measure_dir_names[file_name]}'"
144
+ else
145
+ measure_dir_names[file_name] = file
146
+ end
147
+ end
148
+ end
149
+
150
+ file_names = {}
151
+ file_dirs.each do |dir|
152
+ Dir.glob(File.join(dir, '*')).each do |file|
153
+ next if !File.file?(file)
154
+
155
+ # puts file
156
+ file_name = File.basename(file).downcase
157
+ if file_names[file_name]
158
+ conflicts << "File '#{file}' conflicts with '#{file_names[file_name]}'"
159
+ else
160
+ file_names[file_name] = file
161
+ end
162
+ end
163
+ end
164
+
165
+ if !conflicts.empty?
166
+ conflicts.each do |conflict|
167
+ puts conflict
168
+ end
169
+ raise 'Conflicting file names found'
170
+ end
171
+
172
+ return false
173
+ end
174
+
175
+ ##
176
+ # Module method used to configure an input OSW with paths to all OpenStudio::Extension measure and file directories
177
+ ##
178
+ # @param [Hash] in_osw Initial OSW object as a Hash, keys should be symbolized
179
+ #
180
+ # @return [Hash] Output OSW with measure and file paths configured
181
+ def self.configure_osw(in_osw)
182
+ check_for_name_conflicts
183
+
184
+ measure_dirs = all_measure_dirs
185
+ file_dirs = all_file_dirs
186
+
187
+ in_osw[:measure_paths] = [] if in_osw[:measure_paths].nil?
188
+ in_osw[:file_paths] = [] if in_osw[:file_paths].nil?
189
+
190
+ in_osw[:measure_paths] = in_osw[:measure_paths].concat(measure_dirs).uniq
191
+ in_osw[:file_paths] = in_osw[:file_paths].concat(file_dirs).uniq
192
+
193
+ return in_osw
194
+ end
195
+
196
+ ##
197
+ # Module method used to set the measure argument for measure_dir_name to argument_value
198
+ # argument_name must appear in the OSW or exception will be raised
199
+ ##
200
+ # @param [Hash] in_osw Initial OSW object as a Hash, keys should be symbolized
201
+ #
202
+ # @return [Hash] Output OSW with measure argument set
203
+ #
204
+ def self.set_measure_argument(osw, measure_dir_name, argument_name, argument_value)
205
+ result = false
206
+ osw[:steps].each do |step|
207
+ if step[:measure_dir_name] == measure_dir_name
208
+ step[:arguments][argument_name.to_sym] = argument_value
209
+ result = true
210
+ end
211
+ end
212
+
213
+ if !result
214
+ raise "Could not set '#{argument_name}' to '#{argument_value}' for measure '#{measure_dir_name}'"
215
+ end
216
+
217
+ return osw
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,879 @@
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_CreateResults
37
+ # Reports out the detailed simulation results needed by EDAPT.
38
+ # Results are output as both OpenStudio::Attributes (for OpenStudio 1.X)
39
+ # and runner.registerValue (for OpenStudio 2.X).
40
+ # @param skip_weekends [Bool] if true, weekends will not be included in the peak demand window
41
+ # @param skip_holidays [Bool] if true, holidays will not be included in the peak demand window
42
+ # @param start_mo [String] the start month for the peak demand window
43
+ # @param start_day [Integer] the start day for the peak demand window
44
+ # @param start_hr [Integer] the start hour for the peak demand window, using 24-hr clock
45
+ # @param end_mo [String] the end month for the peak demand window
46
+ # @param end_day [Integer] the end day for the peak demand window
47
+ # @param end_hr [Integer] the end hour for the peak demand window, using 24-hr clock
48
+ # @return [OpenStudio::AttributeVector] a vector of results needed by EDAPT
49
+ def create_results(skip_weekends = true,
50
+ skip_holidays = true,
51
+ start_mo = 'June',
52
+ start_day = 1,
53
+ start_hr = 14,
54
+ end_mo = 'September',
55
+ end_day = 30,
56
+ end_hr = 18)
57
+
58
+ # get the current version of OS being used to determine if sql query
59
+ # changes are needed (for when E+ changes).
60
+ os_version = OpenStudio::VersionString.new(OpenStudio.openStudioVersion)
61
+
62
+ # create an attribute vector to hold results
63
+ result_elems = OpenStudio::AttributeVector.new
64
+
65
+ # floor_area
66
+ floor_area_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='Building Area' AND RowName='Net Conditioned Building Area' AND ColumnName='Area' AND Units='m2'"
67
+ floor_area = @sql.execAndReturnFirstDouble(floor_area_query)
68
+ if floor_area.is_initialized
69
+ result_elems << OpenStudio::Attribute.new('floor_area', floor_area.get, 'm^2')
70
+ @runner.registerValue('charsfloor_area', floor_area.get, 'm^2')
71
+ else
72
+ @runner.registerWarning('Building floor area not found')
73
+ return false
74
+ end
75
+
76
+ # inflation approach
77
+ inf_appr_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Life-Cycle Cost Parameters' AND RowName='Inflation Approach' AND ColumnName='Value'"
78
+ inf_appr = @sql.execAndReturnFirstString(inf_appr_query)
79
+ if inf_appr.is_initialized
80
+ if inf_appr.get == 'ConstantDollar'
81
+ inf_appr = 'Constant Dollar'
82
+ elsif inf_appr.get == 'CurrentDollar'
83
+ inf_appr = 'Current Dollar'
84
+ else
85
+ @runner.registerError("Inflation approach: #{inf_appr.get} not recognized")
86
+ return OpenStudio::Attribute.new('report', result_elems)
87
+ end
88
+ @runner.registerInfo("Inflation approach = #{inf_appr}")
89
+ else
90
+ @runner.registerError('Could not determine inflation approach used')
91
+ return OpenStudio::Attribute.new('report', result_elems)
92
+ end
93
+
94
+ # base year
95
+ base_yr_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Life-Cycle Cost Parameters' AND RowName='Base Date' AND ColumnName='Value'"
96
+ base_yr = @sql.execAndReturnFirstString(base_yr_query)
97
+ if base_yr.is_initialized
98
+ if base_yr.get =~ /\d\d\d\d/
99
+ base_yr = base_yr.get.match(/\d\d\d\d/)[0].to_f
100
+ else
101
+ @runner.registerError("Could not determine the analysis start year from #{base_yr.get}")
102
+ return OpenStudio::Attribute.new('report', result_elems)
103
+ end
104
+ else
105
+ @runner.registerError('Could not determine analysis start year')
106
+ return OpenStudio::Attribute.new('report', result_elems)
107
+ end
108
+
109
+ # analysis length
110
+ length_yrs_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Life-Cycle Cost Parameters' AND RowName='Length of Study Period in Years' AND ColumnName='Value'"
111
+ length_yrs = @sql.execAndReturnFirstInt(length_yrs_query)
112
+ if length_yrs.is_initialized
113
+ @runner.registerInfo "Analysis length = #{length_yrs.get} yrs"
114
+ length_yrs = length_yrs.get
115
+ else
116
+ @runner.registerError('Could not determine analysis length')
117
+ return OpenStudio::Attribute.new('report', result_elems)
118
+ end
119
+
120
+ # cash flows
121
+ cash_flow_elems = OpenStudio::AttributeVector.new
122
+
123
+ # setup a vector for each type of cash flow
124
+ cap_cash_flow_elems = OpenStudio::AttributeVector.new
125
+ om_cash_flow_elems = OpenStudio::AttributeVector.new
126
+ energy_cash_flow_elems = OpenStudio::AttributeVector.new
127
+ water_cash_flow_elems = OpenStudio::AttributeVector.new
128
+ tot_cash_flow_elems = OpenStudio::AttributeVector.new
129
+
130
+ # add the type to the element
131
+ cap_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Capital Costs")
132
+ om_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Operating Costs")
133
+ energy_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Energy Costs")
134
+ water_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Water Costs")
135
+ tot_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Total Costs")
136
+
137
+ @runner.registerValue('cash_flows_capital_type', "#{inf_appr} Capital Costs")
138
+ @runner.registerValue('cash_flows_operating_type', "#{inf_appr} Operating Costs")
139
+ @runner.registerValue('cash_flows_energy_type', "#{inf_appr} Energy Costs")
140
+ @runner.registerValue('cash_flows_water_type', "#{inf_appr} Water Costs")
141
+ @runner.registerValue('cash_flows_total_type', "#{inf_appr} Total Costs")
142
+
143
+ # record the cash flow in these hashes
144
+ cap_cash_flow = {}
145
+ om_cash_flow = {}
146
+ energy_cash_flow = {}
147
+ water_cash_flow = {}
148
+ tot_cash_flow = {}
149
+
150
+ # loop through each year and record the cash flow
151
+ for i in 0..(length_yrs - 1) do
152
+ new_yr = base_yr + i
153
+
154
+ yr = nil
155
+ if os_version > OpenStudio::VersionString.new('1.5.3')
156
+ yr = "January #{new_yr.round}"
157
+ else
158
+ yr = "January #{new_yr.round}"
159
+ end
160
+
161
+ ann_cap_cash = 0.0
162
+ ann_om_cash = 0.0
163
+ ann_energy_cash = 0.0
164
+ ann_water_cash = 0.0
165
+ ann_tot_cash = 0.0
166
+
167
+ # capital cash flow
168
+ cap_cash_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Capital Cash Flow by Category (Without Escalation)' AND RowName='#{yr}' AND ColumnName='Total'"
169
+ cap_cash = @sql.execAndReturnFirstDouble(cap_cash_query)
170
+ if cap_cash.is_initialized
171
+ ann_cap_cash += cap_cash.get
172
+ ann_tot_cash += cap_cash.get
173
+ end
174
+
175
+ # o&m cash flow (excluding utility costs)
176
+ om_types = ['Maintenance', 'Repair', 'Operation', 'Replacement', 'MinorOverhaul', 'MajorOverhaul', 'OtherOperational']
177
+ om_types.each do |om_type|
178
+ om_cash_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Operating Cash Flow by Category (Without Escalation)' AND RowName='#{yr}' AND ColumnName='#{om_type}'"
179
+ om_cash = @sql.execAndReturnFirstDouble(om_cash_query)
180
+ if om_cash.is_initialized
181
+ ann_om_cash += om_cash.get
182
+ ann_tot_cash += om_cash.get
183
+ end
184
+ end
185
+
186
+ # energy cash flow
187
+ energy_cash_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Operating Cash Flow by Category (Without Escalation)' AND RowName='#{yr}' AND ColumnName='Energy'"
188
+ energy_cash = @sql.execAndReturnFirstDouble(energy_cash_query)
189
+ if energy_cash.is_initialized
190
+ ann_energy_cash += energy_cash.get
191
+ ann_tot_cash += energy_cash.get
192
+ end
193
+
194
+ # water cash flow
195
+ water_cash_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Operating Cash Flow by Category (Without Escalation)' AND RowName='#{yr}' AND ColumnName='Water'"
196
+ water_cash = @sql.execAndReturnFirstDouble(water_cash_query)
197
+ if water_cash.is_initialized
198
+ ann_water_cash += water_cash.get
199
+ ann_tot_cash += water_cash.get
200
+ end
201
+
202
+ # log the values for this year
203
+ cap_cash_flow[yr] = ann_cap_cash
204
+ om_cash_flow[yr] = ann_om_cash
205
+ energy_cash_flow[yr] = ann_energy_cash
206
+ water_cash_flow[yr] = ann_water_cash
207
+ tot_cash_flow[yr] = ann_tot_cash
208
+
209
+ cap_cash_flow_elems << OpenStudio::Attribute.new('year', ann_cap_cash, 'dollars')
210
+ om_cash_flow_elems << OpenStudio::Attribute.new('year', ann_om_cash, 'dollars')
211
+ energy_cash_flow_elems << OpenStudio::Attribute.new('year', ann_energy_cash, 'dollars')
212
+ water_cash_flow_elems << OpenStudio::Attribute.new('year', ann_water_cash, 'dollars')
213
+ tot_cash_flow_elems << OpenStudio::Attribute.new('year', ann_tot_cash, 'dollars')
214
+
215
+ @runner.registerValue("cash_flows_capital_year_#{i + 1}", ann_cap_cash, 'dollars')
216
+ @runner.registerValue("cash_flows_operating_year_#{i + 1}", ann_om_cash, 'dollars')
217
+ @runner.registerValue("cash_flows_energy_year_#{i + 1}", ann_energy_cash, 'dollars')
218
+ @runner.registerValue("cash_flows_water_year_#{i + 1}", ann_water_cash, 'dollars')
219
+ @runner.registerValue("cash_flows_total_year_#{i + 1}", ann_tot_cash, 'dollars')
220
+
221
+ end # next year
222
+
223
+ # end cash flows
224
+ cash_flow_elems << OpenStudio::Attribute.new('cash_flow', cap_cash_flow_elems)
225
+ cash_flow_elems << OpenStudio::Attribute.new('cash_flow', om_cash_flow_elems)
226
+ cash_flow_elems << OpenStudio::Attribute.new('cash_flow', energy_cash_flow_elems)
227
+ cash_flow_elems << OpenStudio::Attribute.new('cash_flow', water_cash_flow_elems)
228
+ cash_flow_elems << OpenStudio::Attribute.new('cash_flow', tot_cash_flow_elems)
229
+ result_elems << OpenStudio::Attribute.new('cash_flows', cash_flow_elems)
230
+
231
+ # list of all end uses in OpenStudio
232
+ end_use_cat_types = []
233
+ OpenStudio::EndUseCategoryType.getValues.each do |end_use_val|
234
+ end_use_cat_types << OpenStudio::EndUseCategoryType.new(end_use_val)
235
+ end
236
+
237
+ # list of all end use fule types in OpenStudio
238
+ end_use_fuel_types = []
239
+ OpenStudio::EndUseFuelType.getValues.each do |end_use_fuel_type_val|
240
+ end_use_fuel_types << OpenStudio::EndUseFuelType.new(end_use_fuel_type_val)
241
+ end
242
+
243
+ # list of the 12 months of the year in OpenStudio
244
+ months = []
245
+ OpenStudio::MonthOfYear.getValues.each do |month_of_year_val|
246
+ if (month_of_year_val >= 1) && (month_of_year_val <= 12)
247
+ months << OpenStudio::MonthOfYear.new(month_of_year_val)
248
+ end
249
+ end
250
+
251
+ # map each end use category type to the name that will be used in the xml
252
+ end_use_map = {
253
+ OpenStudio::EndUseCategoryType.new('Heating').value => 'heating',
254
+ OpenStudio::EndUseCategoryType.new('Cooling').value => 'cooling',
255
+ OpenStudio::EndUseCategoryType.new('InteriorLights').value => 'lighting_interior',
256
+ OpenStudio::EndUseCategoryType.new('ExteriorLights').value => 'lighting_exterior',
257
+ OpenStudio::EndUseCategoryType.new('InteriorEquipment').value => 'equipment_interior',
258
+ OpenStudio::EndUseCategoryType.new('ExteriorEquipment').value => 'equipment_exterior',
259
+ OpenStudio::EndUseCategoryType.new('Fans').value => 'fans',
260
+ OpenStudio::EndUseCategoryType.new('Pumps').value => 'pumps',
261
+ OpenStudio::EndUseCategoryType.new('HeatRejection').value => 'heat_rejection',
262
+ OpenStudio::EndUseCategoryType.new('Humidifier').value => 'humidification',
263
+ OpenStudio::EndUseCategoryType.new('HeatRecovery').value => 'heat_recovery',
264
+ OpenStudio::EndUseCategoryType.new('WaterSystems').value => 'water_systems',
265
+ OpenStudio::EndUseCategoryType.new('Refrigeration').value => 'refrigeration',
266
+ OpenStudio::EndUseCategoryType.new('Generators').value => 'generators'
267
+ }
268
+
269
+ # map each fuel type in EndUseFuelTypes to a specific FuelTypes
270
+ fuel_type_map = {
271
+ OpenStudio::EndUseFuelType.new('Electricity').value => OpenStudio::FuelType.new('Electricity'),
272
+ OpenStudio::EndUseFuelType.new('Gas').value => OpenStudio::FuelType.new('Gas'),
273
+ OpenStudio::EndUseFuelType.new('AdditionalFuel').value => OpenStudio::FuelType.new('Diesel'), # TODO: add other fuel types
274
+ OpenStudio::EndUseFuelType.new('DistrictCooling').value => OpenStudio::FuelType.new('DistrictCooling'),
275
+ OpenStudio::EndUseFuelType.new('DistrictHeating').value => OpenStudio::FuelType.new('DistrictHeating'),
276
+ OpenStudio::EndUseFuelType.new('Water').value => OpenStudio::FuelType.new('Water')
277
+ }
278
+
279
+ # map each fuel type in EndUseFuelTypes to a specific FuelTypes
280
+ fuel_type_alias_map = {
281
+ OpenStudio::EndUseFuelType.new('Electricity').value => 'electricity',
282
+ OpenStudio::EndUseFuelType.new('Gas').value => 'gas',
283
+ OpenStudio::EndUseFuelType.new('AdditionalFuel').value => 'other_energy',
284
+ OpenStudio::EndUseFuelType.new('DistrictCooling').value => 'district_cooling',
285
+ OpenStudio::EndUseFuelType.new('DistrictHeating').value => 'district_heating',
286
+ OpenStudio::EndUseFuelType.new('Water').value => 'water'
287
+ }
288
+
289
+ # annual "annual"
290
+ annual_elems = OpenStudio::AttributeVector.new
291
+
292
+ # consumption "consumption"
293
+ cons_elems = OpenStudio::AttributeVector.new
294
+
295
+ # electricity
296
+ electricity = @sql.electricityTotalEndUses
297
+ if electricity.is_initialized
298
+ cons_elems << OpenStudio::Attribute.new('electricity', electricity.get, 'GJ')
299
+ @runner.registerValue('annual_consumption_electricity', electricity.get, 'GJ')
300
+ else
301
+ cons_elems << OpenStudio::Attribute.new('electricity', 0.0, 'GJ')
302
+ @runner.registerValue('annual_consumption_electricity', 0.0, 'GJ')
303
+ end
304
+
305
+ # gas
306
+ gas = @sql.naturalGasTotalEndUses
307
+ if gas.is_initialized
308
+ cons_elems << OpenStudio::Attribute.new('gas', gas.get, 'GJ')
309
+ @runner.registerValue('annual_consumption_gas', gas.get, 'GJ')
310
+ else
311
+ cons_elems << OpenStudio::Attribute.new('gas', 0.0, 'GJ')
312
+ @runner.registerValue('annual_consumption_gas', 0.0, 'GJ')
313
+ end
314
+
315
+ # other_energy
316
+ other_energy = @sql.otherFuelTotalEndUses
317
+ if other_energy.is_initialized
318
+ cons_elems << OpenStudio::Attribute.new('other_energy', other_energy.get, 'GJ')
319
+ @runner.registerValue('annual_consumption_other_energy', other_energy.get, 'GJ')
320
+ else
321
+ cons_elems << OpenStudio::Attribute.new('other_energy', 0.0, 'GJ')
322
+ @runner.registerValue('annual_consumption_other_energy', 0.0, 'GJ')
323
+ end
324
+
325
+ # district_cooling
326
+ district_cooling = @sql.districtCoolingTotalEndUses
327
+ if district_cooling.is_initialized
328
+ cons_elems << OpenStudio::Attribute.new('district_cooling', district_cooling.get, 'GJ')
329
+ @runner.registerValue('annual_consumption_district_cooling', district_cooling.get, 'GJ')
330
+ else
331
+ cons_elems << OpenStudio::Attribute.new('district_cooling', 0.0, 'GJ')
332
+ @runner.registerValue('annual_consumption_district_cooling', 0.0, 'GJ')
333
+ end
334
+
335
+ # district_heating
336
+ district_heating = @sql.districtHeatingTotalEndUses
337
+ if district_heating.is_initialized
338
+ cons_elems << OpenStudio::Attribute.new('district_heating', district_heating.get, 'GJ')
339
+ @runner.registerValue('annual_consumption_district_heating', district_heating.get, 'GJ')
340
+ else
341
+ cons_elems << OpenStudio::Attribute.new('district_heating', 0.0, 'GJ')
342
+ @runner.registerValue('annual_consumption_district_heating', 0.0, 'GJ')
343
+ end
344
+
345
+ # water
346
+ water = @sql.waterTotalEndUses
347
+ if water.is_initialized
348
+ cons_elems << OpenStudio::Attribute.new('water', water.get, 'm^3')
349
+ @runner.registerValue('annual_consumption_water', water.get, 'm^3')
350
+ else
351
+ cons_elems << OpenStudio::Attribute.new('water', 0.0, 'm^3')
352
+ @runner.registerValue('annual_consumption_water', 0.0, 'm^3')
353
+ end
354
+
355
+ # end consumption
356
+ annual_elems << OpenStudio::Attribute.new('consumption', cons_elems)
357
+
358
+ # demand "demand"
359
+ demand_elems = OpenStudio::AttributeVector.new
360
+
361
+ # get the weather file run period (as opposed to design day run period)
362
+ ann_env_pd = nil
363
+ @sql.availableEnvPeriods.each do |env_pd|
364
+ env_type = @sql.environmentType(env_pd)
365
+ if env_type.is_initialized
366
+ if env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod')
367
+ ann_env_pd = env_pd
368
+ end
369
+ end
370
+ end
371
+
372
+ # only try to get the annual peak demand if an annual simulation was run
373
+ if ann_env_pd
374
+
375
+ # create some units to use
376
+ joule_unit = OpenStudio.createUnit('J').get
377
+ gigajoule_unit = OpenStudio.createUnit('GJ').get
378
+ hrs_unit = OpenStudio.createUnit('h').get
379
+ kilowatt_unit = OpenStudio.createUnit('kW').get
380
+
381
+ # get the annual hours simulated
382
+ hrs_sim = '(0 - no partial annual simulation)'
383
+ if @sql.hoursSimulated.is_initialized
384
+ hrs_sim = @sql.hoursSimulated.get
385
+ if hrs_sim != 8760
386
+ @runner.registerError("Simulation was only #{hrs_sim} hrs; EDA requires an annual simulation (8760 hrs)")
387
+ return OpenStudio::Attribute.new('report', result_elems)
388
+ end
389
+ end
390
+
391
+ # Setup the peak demand time window based on input arguments.
392
+ # Note that holidays and weekends are not excluded because
393
+ # of a bug in EnergyPlus dates.
394
+ # This will only impact corner-case buildings that have
395
+ # peak demand on weekends or holidays, which is unusual.
396
+ @runner.registerInfo("Peak Demand window is #{start_mo} #{start_day} to #{end_mo} #{end_day} from #{start_hr}:00 to #{end_hr}:00.")
397
+ start_date = OpenStudio::DateTime.new(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(start_mo), start_day), OpenStudio::Time.new(0, 0, 0, 0))
398
+ end_date = OpenStudio::DateTime.new(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(end_mo), end_day), OpenStudio::Time.new(0, 24, 0, 0))
399
+ start_time = OpenStudio::Time.new(0, start_hr, 0, 0)
400
+ end_time = OpenStudio::Time.new(0, end_hr, 0, 0)
401
+
402
+ # Get the day type timeseries.
403
+ day_types = nil
404
+ day_type_indices = @sql.timeSeries(ann_env_pd, 'Zone Timestep', 'Site Day Type Index', 'Environment')
405
+ if day_type_indices.is_initialized
406
+ # Put values into array
407
+ day_types = []
408
+ day_type_vals = day_type_indices.get.values
409
+ for i in 0..(day_type_vals.size - 1)
410
+ day_types << day_type_vals[i]
411
+ end
412
+ else
413
+ @runner.registerError('Day Type timeseries (Site Day Type Index at zone timestep) could not be found, cannot accurately determine the peak demand.')
414
+ end
415
+
416
+ # electricity_peak_demand
417
+ electricity_peak_demand = -1.0
418
+ electricity_peak_demand_time = nil
419
+ elec = @sql.timeSeries(ann_env_pd, 'Zone Timestep', 'Electricity:Facility', '')
420
+ # deduce the timestep based on the hours simulated and the number of datapoints in the timeseries
421
+ if elec.is_initialized && day_types
422
+ elec = elec.get
423
+ num_int = elec.values.size
424
+ int_len_hrs = OpenStudio::Quantity.new(hrs_sim / num_int, hrs_unit)
425
+
426
+ # Put timeseries into array
427
+ elec_vals = []
428
+ ann_elec_vals = elec.values
429
+ for i in 0..(ann_elec_vals.size - 1)
430
+ elec_vals << ann_elec_vals[i]
431
+ end
432
+
433
+ # Put values into array
434
+ elec_times = []
435
+ ann_elec_times = elec.dateTimes
436
+ for i in 0..(ann_elec_times.size - 1)
437
+ elec_times << ann_elec_times[i]
438
+ end
439
+
440
+ # Loop through the time/value pairs and find the peak
441
+ # excluding the times outside of the Xcel peak demand window
442
+ elec_times.zip(elec_vals).each_with_index do |vs, ind|
443
+ date_time = vs[0]
444
+ val = vs[1]
445
+ day_type = day_types[ind]
446
+ time = date_time.time
447
+ date = date_time.date
448
+ day_of_week = date.dayOfWeek
449
+ # Convert the peak demand to kW
450
+ val_J_per_hr = val / int_len_hrs.value
451
+ val_kW = OpenStudio.convert(val_J_per_hr, 'J/h', 'kW').get
452
+
453
+ # puts("#{val_kW}kW; #{date}; #{time}; #{day_of_week.valueName}")
454
+
455
+ # Skip times outside of the correct months
456
+ next if date_time < start_date || date_time > end_date
457
+ # Skip times before 2pm and after 6pm
458
+ next if time < start_time || time > end_time
459
+ # Skip weekends if asked
460
+ if skip_weekends
461
+ # Sunday = 1, Saturday = 7
462
+ next if day_type == 1 || day_type == 7
463
+ end
464
+ # Skip holidays if asked
465
+ if skip_holidays
466
+ # Holiday = 8
467
+ next if day_type == 8
468
+ end
469
+
470
+ # puts("VALID #{val_kW}kW; #{date}; #{time}; #{day_of_week.valueName}")
471
+
472
+ # Check peak demand against this timestep
473
+ # and update if this timestep is higher.
474
+ if val > electricity_peak_demand
475
+ electricity_peak_demand = val
476
+ electricity_peak_demand_time = date_time
477
+ end
478
+ end
479
+ elec_peak_demand_timestep_J = OpenStudio::Quantity.new(electricity_peak_demand, joule_unit)
480
+ num_int = elec.values.size
481
+ int_len_hrs = OpenStudio::Quantity.new(hrs_sim / num_int, hrs_unit)
482
+ elec_peak_demand_hourly_J_per_hr = elec_peak_demand_timestep_J / int_len_hrs
483
+ electricity_peak_demand = OpenStudio.convert(elec_peak_demand_hourly_J_per_hr, kilowatt_unit).get.value
484
+ demand_elems << OpenStudio::Attribute.new('electricity_peak_demand', electricity_peak_demand, 'kW')
485
+ @runner.registerValue('annual_demand_electricity_peak_demand', electricity_peak_demand, 'kW')
486
+ @runner.registerInfo("Peak Demand = #{electricity_peak_demand.round(2)}kW on #{electricity_peak_demand_time}")
487
+ else
488
+ @runner.registerError('Peak Demand timeseries (Electricity:Facility at zone timestep) could not be found, cannot determine the informatino needed to calculate savings or incentives.')
489
+ demand_elems << OpenStudio::Attribute.new('electricity_peak_demand', 0.0, 'kW')
490
+ @runner.registerValue('annual_demand_electricity_peak_demand', 0.0, 'kW')
491
+ end
492
+
493
+ # electricity_annual_avg_peak_demand
494
+ val = @sql.electricityTotalEndUses
495
+ if val.is_initialized
496
+ ann_elec_gj = OpenStudio::Quantity.new(val.get, gigajoule_unit)
497
+ ann_hrs = OpenStudio::Quantity.new(hrs_sim, hrs_unit)
498
+ elec_ann_avg_peak_demand_hourly_GJ_per_hr = ann_elec_gj / ann_hrs
499
+ electricity_annual_avg_peak_demand = OpenStudio.convert(elec_ann_avg_peak_demand_hourly_GJ_per_hr, kilowatt_unit).get.value
500
+ demand_elems << OpenStudio::Attribute.new('electricity_annual_avg_peak_demand', electricity_annual_avg_peak_demand, 'kW')
501
+ @runner.registerValue('annual_demand_electricity_annual_avg_peak_demand', electricity_annual_avg_peak_demand, 'kW')
502
+ else
503
+ demand_elems << OpenStudio::Attribute.new('electricity_annual_avg_peak_demand', 0.0, 'kW')
504
+ @runner.registerValue('annual_demand_electricity_annual_avg_peak_demand', 0.0, 'kW')
505
+ end
506
+
507
+ # district_cooling_peak_demand
508
+ district_cooling_peak_demand = -1.0
509
+ ann_dist_clg_peak_demand_time = nil
510
+ dist_clg = @sql.timeSeries(ann_env_pd, 'Zone Timestep', 'DistrictCooling:Facility', '')
511
+ # deduce the timestep based on the hours simulated and the number of datapoints in the timeseries
512
+ if dist_clg.is_initialized && day_types
513
+ dist_clg = dist_clg.get
514
+ num_int = dist_clg.values.size
515
+ int_len_hrs = OpenStudio::Quantity.new(hrs_sim / num_int, hrs_unit)
516
+
517
+ # Put timeseries into array
518
+ dist_clg_vals = []
519
+ ann_dist_clg_vals = dist_clg.values
520
+ for i in 0..(ann_dist_clg_vals.size - 1)
521
+ dist_clg_vals << ann_dist_clg_vals[i]
522
+ end
523
+
524
+ # Put values into array
525
+ dist_clg_times = []
526
+ ann_dist_clg_times = dist_clg.dateTimes
527
+ for i in 0..(ann_dist_clg_times.size - 1)
528
+ dist_clg_times << ann_dist_clg_times[i]
529
+ end
530
+
531
+ # Loop through the time/value pairs and find the peak
532
+ # excluding the times outside of the Xcel peak demand window
533
+ dist_clg_times.zip(dist_clg_vals).each_with_index do |vs, ind|
534
+ date_time = vs[0]
535
+ val = vs[1]
536
+ day_type = day_types[ind]
537
+ time = date_time.time
538
+ date = date_time.date
539
+ day_of_week = date.dayOfWeek
540
+ # Convert the peak demand to kW
541
+ val_J_per_hr = val / int_len_hrs.value
542
+ val_kW = OpenStudio.convert(val_J_per_hr, 'J/h', 'kW').get
543
+
544
+ # puts("#{val_kW}kW; #{date}; #{time}; #{day_of_week.valueName}")
545
+
546
+ # Skip times outside of the correct months
547
+ next if date_time < start_date || date_time > end_date
548
+ # Skip times before 2pm and after 6pm
549
+ next if time < start_time || time > end_time
550
+ # Skip weekends if asked
551
+ if skip_weekends
552
+ # Sunday = 1, Saturday = 7
553
+ next if day_type == 1 || day_type == 7
554
+ end
555
+ # Skip holidays if asked
556
+ if skip_holidays
557
+ # Holiday = 8
558
+ next if day_type == 8
559
+ end
560
+
561
+ # puts("VALID #{val_kW}kW; #{date}; #{time}; #{day_of_week.valueName}")
562
+
563
+ # Check peak demand against this timestep
564
+ # and update if this timestep is higher.
565
+ if val > district_cooling_peak_demand
566
+ district_cooling_peak_demand = val
567
+ ann_dist_clg_peak_demand_time = date_time
568
+ end
569
+ end
570
+ dist_clg_peak_demand_timestep_J = OpenStudio::Quantity.new(district_cooling_peak_demand, joule_unit)
571
+ num_int = dist_clg.values.size
572
+ int_len_hrs = OpenStudio::Quantity.new(hrs_sim / num_int, hrs_unit)
573
+ dist_clg_peak_demand_hourly_J_per_hr = dist_clg_peak_demand_timestep_J / int_len_hrs
574
+ district_cooling_peak_demand = OpenStudio.convert(dist_clg_peak_demand_hourly_J_per_hr, kilowatt_unit).get.value
575
+ demand_elems << OpenStudio::Attribute.new('district_cooling_peak_demand', district_cooling_peak_demand, 'kW')
576
+ @runner.registerValue('annual_demand_district_cooling_peak_demand', district_cooling_peak_demand, 'kW')
577
+ @runner.registerInfo("District Cooling Peak Demand = #{district_cooling_peak_demand.round(2)}kW on #{ann_dist_clg_peak_demand_time}")
578
+ else
579
+ demand_elems << OpenStudio::Attribute.new('district_cooling_peak_demand', 0.0, 'kW')
580
+ @runner.registerValue('annual_demand_district_cooling_peak_demand', 0.0, 'kW')
581
+ end
582
+
583
+ else
584
+ @runner.registerError('Could not find an annual run period')
585
+ return OpenStudio::Attribute.new('report', result_elems)
586
+ end
587
+
588
+ # end demand
589
+ annual_elems << OpenStudio::Attribute.new('demand', demand_elems)
590
+
591
+ # utility_cost
592
+ utility_cost_elems = OpenStudio::AttributeVector.new
593
+ annual_utility_cost_map = {}
594
+
595
+ # electricity
596
+ electricity = @sql.annualTotalCost(OpenStudio::FuelType.new('Electricity'))
597
+ if electricity.is_initialized
598
+ utility_cost_elems << OpenStudio::Attribute.new('electricity', electricity.get, 'dollars')
599
+ @runner.registerValue('annual_utility_cost_electricity', electricity.get, 'dollars')
600
+ annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Electricity').valueName] = electricity.get
601
+ else
602
+ utility_cost_elems << OpenStudio::Attribute.new('electricity', 0.0, 'dollars')
603
+ @runner.registerValue('annual_utility_cost_electricity', 0.0, 'dollars')
604
+ annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Electricity').valueName] = 0.0
605
+ end
606
+
607
+ # electricity_consumption_charge and electricity_demand_charge
608
+ electric_consumption_charge = 0.0
609
+ electric_demand_charge = 0.0
610
+
611
+ electric_rate_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-3. Energy Type Summary' AND RowName='Electricity' AND ColumnName='Utility Rate'"
612
+ electric_rate_name = @sql.execAndReturnFirstString(electric_rate_query)
613
+ if electric_rate_name.is_initialized
614
+ electric_rate_name = electric_rate_name.get.strip
615
+
616
+ # electricity_consumption_charge
617
+ electric_consumption_charge_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='Tariff Report' AND ReportForString='#{electric_rate_name}' AND TableName='Categories' AND RowName='EnergyCharges (~~$~~)' AND ColumnName='Sum'"
618
+ val = @sql.execAndReturnFirstDouble(electric_consumption_charge_query)
619
+ if val.is_initialized
620
+ electric_consumption_charge = val.get
621
+ end
622
+
623
+ # electricity_demand_charge
624
+ electric_demand_charge_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='Tariff Report' AND ReportForString='#{electric_rate_name}' AND TableName='Categories' AND RowName='DemandCharges (~~$~~)' AND ColumnName='Sum'"
625
+ val = @sql.execAndReturnFirstDouble(electric_demand_charge_query)
626
+ if val.is_initialized
627
+ electric_demand_charge = val.get
628
+ end
629
+
630
+ end
631
+ utility_cost_elems << OpenStudio::Attribute.new('electricity_consumption_charge', electric_consumption_charge, 'dollars')
632
+ @runner.registerValue('annual_utility_cost_electricity_consumption_charge', electric_consumption_charge, 'dollars')
633
+ utility_cost_elems << OpenStudio::Attribute.new('electricity_demand_charge', electric_demand_charge, 'dollars')
634
+ @runner.registerValue('annual_utility_cost_electricity_demand_charge', electric_demand_charge, 'dollars')
635
+
636
+ # gas
637
+ gas = @sql.annualTotalCost(OpenStudio::FuelType.new('Gas'))
638
+ if gas.is_initialized
639
+ annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Gas').valueName] = gas.get
640
+ else
641
+ annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Gas').valueName] = 0.0
642
+ end
643
+
644
+ # district_cooling
645
+ district_cooling_charge = 0.0
646
+
647
+ district_cooling_rate_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-3. Energy Type Summary' AND RowName='District Cooling' AND ColumnName='Utility Rate'"
648
+ district_cooling_rate_name = @sql.execAndReturnFirstString(district_cooling_rate_query)
649
+ if district_cooling_rate_name.is_initialized
650
+ district_cooling_rate_name = district_cooling_rate_name.get.strip
651
+
652
+ # district_cooling_charge
653
+ district_cooling_charge_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='Tariff Report' AND ReportForString='#{district_cooling_rate_name}' AND TableName='Categories' AND RowName='Basis (~~$~~)' AND ColumnName='Sum'"
654
+ val = @sql.execAndReturnFirstDouble(district_cooling_charge_query)
655
+ if val.is_initialized
656
+ district_cooling_charge = val.get
657
+ end
658
+
659
+ end
660
+ annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictCooling').valueName] = district_cooling_charge
661
+
662
+ # district_heating
663
+ district_heating_charge = 0.0
664
+
665
+ district_heating_rate_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-3. Energy Type Summary' AND RowName='District Heating' AND ColumnName='Utility Rate'"
666
+ district_heating_rate_name = @sql.execAndReturnFirstString(district_heating_rate_query)
667
+ if district_heating_rate_name.is_initialized
668
+ district_heating_rate_name = district_heating_rate_name.get.strip
669
+
670
+ # district_heating_charge
671
+ district_heating_charge_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='Tariff Report' AND ReportForString='#{district_heating_rate_name}' AND TableName='Categories' AND RowName='Basis (~~$~~)' AND ColumnName='Sum'"
672
+ val = @sql.execAndReturnFirstDouble(district_heating_charge_query)
673
+ if val.is_initialized
674
+ district_heating_charge = val.get
675
+ end
676
+
677
+ end
678
+ annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictHeating').valueName] = district_heating_charge
679
+
680
+ # water
681
+ water = @sql.annualTotalCost(OpenStudio::FuelType.new('Water'))
682
+ if water.is_initialized
683
+ annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Water').valueName] = water.get
684
+ else
685
+ annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Water').valueName] = 0.0
686
+ end
687
+
688
+ # total
689
+ total_query = "SELECT Value from tabulardatawithstrings where (reportname = 'Economics Results Summary Report') and (ReportForString = 'Entire Facility') and (TableName = 'Annual Cost') and (ColumnName ='Total') and (((RowName = 'Cost') and (Units = '~~$~~')) or (RowName = 'Cost (~~$~~)'))"
690
+ total = @sql.execAndReturnFirstDouble(total_query)
691
+
692
+ # other_energy
693
+ # Subtract off the already accounted for fuel types from the total
694
+ # to account for fuels on custom meters where the fuel type is not known.
695
+ prev_tot = 0.0
696
+ annual_utility_cost_map.each do |fuel, val|
697
+ prev_tot += val
698
+ end
699
+ if total.is_initialized
700
+ other_val = total.get - prev_tot
701
+ annual_utility_cost_map[OpenStudio::EndUseFuelType.new('AdditionalFuel').valueName] = other_val
702
+ else
703
+ annual_utility_cost_map[OpenStudio::EndUseFuelType.new('AdditionalFuel').valueName] = 0.0
704
+ end
705
+
706
+ # export remaining costs in the correct order
707
+ # gas
708
+ utility_cost_elems << OpenStudio::Attribute.new('gas', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Gas').valueName], 'dollars')
709
+ @runner.registerValue('annual_utility_cost_gas', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Gas').valueName], 'dollars')
710
+ # other_energy
711
+ utility_cost_elems << OpenStudio::Attribute.new('other_energy', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('AdditionalFuel').valueName], 'dollars')
712
+ @runner.registerValue('annual_utility_cost_other_energy', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('AdditionalFuel').valueName], 'dollars')
713
+ # district_cooling
714
+ utility_cost_elems << OpenStudio::Attribute.new('district_cooling', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictCooling').valueName], 'dollars')
715
+ @runner.registerValue('annual_utility_cost_district_cooling', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictCooling').valueName], 'dollars')
716
+ # district_heating
717
+ utility_cost_elems << OpenStudio::Attribute.new('district_heating', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictHeating').valueName], 'dollars')
718
+ @runner.registerValue('annual_utility_cost_district_heating', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictHeating').valueName], 'dollars')
719
+ # water
720
+ utility_cost_elems << OpenStudio::Attribute.new('water', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Water').valueName], 'dollars')
721
+ @runner.registerValue('annual_utility_cost_water', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Water').valueName], 'dollars')
722
+ # total
723
+ if total.is_initialized
724
+ utility_cost_elems << OpenStudio::Attribute.new('total', total.get, 'dollars')
725
+ @runner.registerValue('annual_utility_cost_total', total.get, 'dollars')
726
+ else
727
+ utility_cost_elems << OpenStudio::Attribute.new('total', 0.0, 'dollars')
728
+ @runner.registerValue('annual_utility_cost_total', 0.0, 'dollars')
729
+ end
730
+
731
+ # end_uses - utility costs by end use using average blended cost
732
+ end_uses_elems = OpenStudio::AttributeVector.new
733
+ # map to store the costs by end use
734
+ cost_by_end_use = {}
735
+
736
+ # fill the map with 0.0's to start
737
+ end_use_cat_types.each do |end_use_cat_type|
738
+ cost_by_end_use[end_use_cat_type] = 0.0
739
+ end
740
+
741
+ # only attempt to get monthly data if enduses table is available
742
+ if @sql.endUses.is_initialized
743
+ end_uses_table = @sql.endUses.get
744
+ # loop through all the fuel types
745
+ end_use_fuel_types.each do |end_use_fuel_type|
746
+ # get the annual total cost for this fuel type
747
+ ann_cost = annual_utility_cost_map[end_use_fuel_type.valueName]
748
+ # get the total annual usage for this fuel type in all end use categories
749
+ # loop through all end uses, adding the annual usage value to the aggregator
750
+ ann_usg = 0.0
751
+ end_use_cat_types.each do |end_use_cat_type|
752
+ ann_usg += end_uses_table.getEndUse(end_use_fuel_type, end_use_cat_type)
753
+ end
754
+ # figure out the annual blended rate for this fuel type
755
+ avg_ann_rate = 0.0
756
+ if ann_cost > 0 && ann_usg > 0
757
+ avg_ann_rate = ann_cost / ann_usg
758
+ end
759
+ # for each end use category, figure out the cost if using
760
+ # the avg ann rate; add this cost to the map
761
+ end_use_cat_types.each do |end_use_cat_type|
762
+ cost_by_end_use[end_use_cat_type] += end_uses_table.getEndUse(end_use_fuel_type, end_use_cat_type) * avg_ann_rate
763
+ end
764
+ end
765
+ # loop through the end uses and record the annual total cost based on the avg annual rate
766
+ end_use_cat_types.each do |end_use_cat_type|
767
+ # record the value
768
+ end_uses_elems << OpenStudio::Attribute.new(end_use_map[end_use_cat_type.value], cost_by_end_use[end_use_cat_type], 'dollars')
769
+ @runner.registerValue("annual_utility_cost_end_uses_#{end_use_map[end_use_cat_type.value]}", cost_by_end_use[end_use_cat_type], 'dollars')
770
+ end
771
+ else
772
+ @runner.registerError('End-Use table not available in results; could not retrieve monthly costs by end use')
773
+ return OpenStudio::Attribute.new('report', result_elems)
774
+ end
775
+
776
+ # end end_uses
777
+ utility_cost_elems << OpenStudio::Attribute.new('end_uses', end_uses_elems)
778
+
779
+ # end utility_costs
780
+ annual_elems << OpenStudio::Attribute.new('utility_cost', utility_cost_elems)
781
+
782
+ # end annual
783
+ result_elems << OpenStudio::Attribute.new('annual', annual_elems)
784
+
785
+ # monthly
786
+ monthly_elems = OpenStudio::AttributeVector.new
787
+
788
+ # consumption
789
+ cons_elems = OpenStudio::AttributeVector.new
790
+ # loop through all end uses
791
+ end_use_cat_types.each do |end_use_cat|
792
+ end_use_elems = OpenStudio::AttributeVector.new
793
+ end_use_name = end_use_map[end_use_cat.value]
794
+ # in each end use, loop through all fuel types
795
+ end_use_fuel_types.each do |end_use_fuel_type|
796
+ fuel_type_elems = OpenStudio::AttributeVector.new
797
+ fuel_type_name = fuel_type_alias_map[end_use_fuel_type.value]
798
+ ann_energy_cons = 0.0
799
+ # in each end use, loop through months and get monthly enedy consumption
800
+ months.each_with_index do |month, i|
801
+ mon_energy_cons = 0.0
802
+ val = @sql.energyConsumptionByMonth(end_use_fuel_type, end_use_cat, month)
803
+ if val.is_initialized
804
+ monthly_consumption_J = OpenStudio::Quantity.new(val.get, joule_unit)
805
+ monthly_consumption_GJ = OpenStudio.convert(monthly_consumption_J, gigajoule_unit).get.value
806
+ mon_energy_cons = monthly_consumption_GJ
807
+ ann_energy_cons += monthly_consumption_GJ
808
+ end
809
+ # record the monthly value
810
+ if end_use_fuel_type == OpenStudio::EndUseFuelType.new('Water')
811
+ fuel_type_elems << OpenStudio::Attribute.new('month', mon_energy_cons, 'm^3')
812
+ @runner.registerValue("monthly_consumption_#{end_use_name}_#{fuel_type_name}_month_#{i + 1}", mon_energy_cons, 'm^3')
813
+ else
814
+ fuel_type_elems << OpenStudio::Attribute.new('month', mon_energy_cons, 'GJ')
815
+ @runner.registerValue("monthly_consumption_#{end_use_name}_#{fuel_type_name}_month_#{i + 1}", mon_energy_cons, 'GJ')
816
+ end
817
+ end
818
+ # record the annual total
819
+ fuel_type_elems << OpenStudio::Attribute.new('year', ann_energy_cons, 'GJ')
820
+ @runner.registerValue("monthly_consumption_#{end_use_name}_#{fuel_type_name}_year", ann_energy_cons, 'GJ')
821
+ # add this fuel type
822
+ end_use_elems << OpenStudio::Attribute.new(fuel_type_alias_map[end_use_fuel_type.value], fuel_type_elems)
823
+ end
824
+ # add this end use
825
+ cons_elems << OpenStudio::Attribute.new(end_use_map[end_use_cat.value], end_use_elems)
826
+ end
827
+ # end consumption
828
+ monthly_elems << OpenStudio::Attribute.new('consumption', cons_elems)
829
+
830
+ # create a unit to use
831
+ watt_unit = OpenStudio.createUnit('W').get
832
+ kilowatt_unit = OpenStudio.createUnit('kW').get
833
+
834
+ # demand
835
+ demand_elems = OpenStudio::AttributeVector.new
836
+ # loop through all end uses
837
+ end_use_cat_types.each do |end_use_cat|
838
+ end_use_elems = OpenStudio::AttributeVector.new
839
+ end_use_name = end_use_map[end_use_cat.value]
840
+ # in each end use, loop through all fuel types
841
+ end_use_fuel_types.each do |end_use_fuel_type|
842
+ fuel_type_elems = OpenStudio::AttributeVector.new
843
+ fuel_type_name = fuel_type_alias_map[end_use_fuel_type.value]
844
+ ann_peak_demand = 0.0
845
+ # in each end use, loop through months and get monthly enedy consumption
846
+ months.each_with_index do |month, i|
847
+ mon_peak_demand = 0.0
848
+ val = @sql.peakEnergyDemandByMonth(end_use_fuel_type, end_use_cat, month)
849
+ if val.is_initialized
850
+ mon_peak_demand_W = OpenStudio::Quantity.new(val.get, watt_unit)
851
+ mon_peak_demand = OpenStudio.convert(mon_peak_demand_W, kilowatt_unit).get.value
852
+ end
853
+ # record the monthly value
854
+ fuel_type_elems << OpenStudio::Attribute.new('month', mon_peak_demand, 'kW')
855
+ @runner.registerValue("monthly_demand_#{end_use_name}_#{fuel_type_name}_month_#{i + 1}", mon_peak_demand, 'kW')
856
+ # if month peak demand > ann peak demand make this new ann peak demand
857
+ if mon_peak_demand > ann_peak_demand
858
+ ann_peak_demand = mon_peak_demand
859
+ end
860
+ end
861
+ # record the annual peak demand
862
+ fuel_type_elems << OpenStudio::Attribute.new('year', ann_peak_demand, 'kW')
863
+ @runner.registerValue("monthly_demand_#{end_use_name}_#{fuel_type_name}_year", ann_peak_demand, 'kW')
864
+ # add this fuel type
865
+ end_use_elems << OpenStudio::Attribute.new(fuel_type_alias_map[end_use_fuel_type.value], fuel_type_elems)
866
+ end
867
+ # add this end use
868
+ demand_elems << OpenStudio::Attribute.new(end_use_map[end_use_cat.value], end_use_elems)
869
+ end
870
+ # end demand
871
+ monthly_elems << OpenStudio::Attribute.new('demand', demand_elems)
872
+
873
+ # end monthly
874
+ result_elems << OpenStudio::Attribute.new('monthly', monthly_elems)
875
+
876
+ result_elem = OpenStudio::Attribute.new('results', result_elems)
877
+ return result_elem
878
+ end # end create_results
879
+ end