buildingsync 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/continuous_integration.yml +146 -0
  3. data/.gitignore +33 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +10 -0
  6. data/CHANGELOG.md +50 -0
  7. data/Gemfile +31 -0
  8. data/Jenkinsfile +10 -0
  9. data/LICENSE.md +29 -0
  10. data/README.md +105 -0
  11. data/Rakefile +77 -0
  12. data/bin/console +15 -0
  13. data/bin/setup +8 -0
  14. data/buildingsync.gemspec +37 -0
  15. data/config.rb.in +26 -0
  16. data/doc_templates/LICENSE.md +29 -0
  17. data/doc_templates/README.md.erb +42 -0
  18. data/doc_templates/copyright_erb.txt +38 -0
  19. data/doc_templates/copyright_js.txt +5 -0
  20. data/doc_templates/copyright_ruby.txt +36 -0
  21. data/lib/buildingsync.rb +43 -0
  22. data/lib/buildingsync/all_resource_total.rb +54 -0
  23. data/lib/buildingsync/audit_date.rb +54 -0
  24. data/lib/buildingsync/constants.rb +49 -0
  25. data/lib/buildingsync/contact.rb +54 -0
  26. data/lib/buildingsync/extension.rb +57 -0
  27. data/lib/buildingsync/generator.rb +584 -0
  28. data/lib/buildingsync/get_bcl_weather_file.rb +326 -0
  29. data/lib/buildingsync/helpers/Model.hvac.rb +216 -0
  30. data/lib/buildingsync/helpers/helper.rb +494 -0
  31. data/lib/buildingsync/helpers/xml_get_set.rb +215 -0
  32. data/lib/buildingsync/makers/phase_zero_base.osw +178 -0
  33. data/lib/buildingsync/makers/workflow_maker.json +811 -0
  34. data/lib/buildingsync/makers/workflow_maker.rb +581 -0
  35. data/lib/buildingsync/makers/workflow_maker_base.rb +167 -0
  36. data/lib/buildingsync/model_articulation/building.rb +1119 -0
  37. data/lib/buildingsync/model_articulation/building_and_system_types.json +121 -0
  38. data/lib/buildingsync/model_articulation/building_section.rb +190 -0
  39. data/lib/buildingsync/model_articulation/building_system.rb +49 -0
  40. data/lib/buildingsync/model_articulation/envelope_system.rb +102 -0
  41. data/lib/buildingsync/model_articulation/exterior_floor_system_type.rb +64 -0
  42. data/lib/buildingsync/model_articulation/facility.rb +439 -0
  43. data/lib/buildingsync/model_articulation/foundation_system_type.rb +64 -0
  44. data/lib/buildingsync/model_articulation/hvac_system.rb +395 -0
  45. data/lib/buildingsync/model_articulation/lighting_system.rb +102 -0
  46. data/lib/buildingsync/model_articulation/loads_system.rb +287 -0
  47. data/lib/buildingsync/model_articulation/location_element.rb +129 -0
  48. data/lib/buildingsync/model_articulation/measure.rb +57 -0
  49. data/lib/buildingsync/model_articulation/roof_system_type.rb +64 -0
  50. data/lib/buildingsync/model_articulation/service_hot_water_system.rb +87 -0
  51. data/lib/buildingsync/model_articulation/site.rb +242 -0
  52. data/lib/buildingsync/model_articulation/spatial_element.rb +343 -0
  53. data/lib/buildingsync/model_articulation/wall_system_type.rb +64 -0
  54. data/lib/buildingsync/report.rb +217 -0
  55. data/lib/buildingsync/resource_use.rb +55 -0
  56. data/lib/buildingsync/scenario.rb +622 -0
  57. data/lib/buildingsync/selection_tool.rb +98 -0
  58. data/lib/buildingsync/time_series.rb +85 -0
  59. data/lib/buildingsync/translator.rb +167 -0
  60. data/lib/buildingsync/utility.rb +67 -0
  61. data/lib/buildingsync/version.rb +45 -0
  62. metadata +223 -0
@@ -0,0 +1,581 @@
1
+ # frozen_string_literal: true
2
+
3
+ # *******************************************************************************
4
+ # OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC.
5
+ # BuildingSync(R), Copyright (c) 2015-2020, Alliance for Sustainable Energy, LLC.
6
+ # All rights reserved.
7
+ #
8
+ # Redistribution and use in source and binary forms, with or without
9
+ # modification, are permitted provided that the following conditions are met:
10
+ #
11
+ # (1) Redistributions of source code must retain the above copyright notice,
12
+ # this list of conditions and the following disclaimer.
13
+ #
14
+ # (2) Redistributions in binary form must reproduce the above copyright notice,
15
+ # this list of conditions and the following disclaimer in the documentation
16
+ # and/or other materials provided with the distribution.
17
+ #
18
+ # (3) Neither the name of the copyright holder nor the names of any contributors
19
+ # may be used to endorse or promote products derived from this software without
20
+ # specific prior written permission from the respective party.
21
+ #
22
+ # (4) Other than as required in clauses (1) and (2), distributions in any form
23
+ # of modifications or other derivative works may not use the "OpenStudio"
24
+ # trademark, "OS", "os", or any other confusingly similar designation without
25
+ # specific prior written permission from Alliance for Sustainable Energy, LLC.
26
+ #
27
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
28
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
29
+ # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
31
+ # UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
32
+ # THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
33
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
34
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
35
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
36
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37
+ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
+ # *******************************************************************************
39
+ require 'rexml/document'
40
+
41
+ require 'openstudio/common_measures'
42
+ require 'openstudio/model_articulation'
43
+ require 'openstudio/ee_measures'
44
+
45
+ require 'buildingsync/extension'
46
+ require 'buildingsync/constants'
47
+ require 'buildingsync/scenario'
48
+ require 'buildingsync/makers/workflow_maker_base'
49
+ require 'buildingsync/model_articulation/facility'
50
+
51
+ module BuildingSync
52
+ # base class for objects that will configure workflows based on building sync files
53
+ class WorkflowMaker < WorkflowMakerBase
54
+ # initialize - load workflow json file and add necessary measure paths
55
+ # @param doc [REXML::Document]
56
+ # @param ns [String]
57
+ def initialize(doc, ns)
58
+ super(doc, ns)
59
+
60
+ @facility_xml = nil
61
+ @facility = nil
62
+
63
+ # TODO: Be consistent in symbolizing names in hashes or not
64
+ File.open(PHASE_0_BASE_OSW_FILE_PATH, 'r') do |file|
65
+ @workflow = JSON.parse(file.read)
66
+ end
67
+
68
+ File.open(WORKFLOW_MAKER_JSON_FILE_PATH, 'r') do |file|
69
+ @workflow_maker_json = JSON.parse(file.read, symbolize_names: true)
70
+ end
71
+
72
+ # Add all of the measure directories from the extension gems
73
+ # into the @workflow, then check they exist
74
+ set_measure_paths(get_measure_directories_array)
75
+ measures_exist?
76
+ read_xml
77
+ end
78
+
79
+ def read_xml
80
+ facility_xml_temp = @doc.get_elements("#{get_prefix}BuildingSync/#{get_prefix}Facilities/#{get_prefix}Facility")
81
+
82
+ # Raise errors for zero or multiple Facilities. Not supported at this time.
83
+ if facility_xml_temp.nil? || facility_xml_temp.empty?
84
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.populate_facility_report_and_scenarios', 'There are no Facility elements in your BuildingSync file.')
85
+ raise StandardError, 'There are no Facility elements in your BuildingSync file.'
86
+ elsif facility_xml_temp.size > 1
87
+ @facility_xml = facility_xml_temp.first
88
+ OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.WorkflowMaker.populate_facility_report_and_scenarios', "There are more than one (#{facility_xml_temp.size}) Facility elements in your BuildingSync file. Only the first Facility will be considered (ID: #{@facility_xml.attributes['ID']}")
89
+ else
90
+ @facility_xml = facility_xml_temp.first
91
+ end
92
+
93
+ OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.WorkflowMaker.read_xml', "Setting up workflow for Facility ID: #{@facility_xml.attributes['ID']}")
94
+
95
+ # Initialize Facility object
96
+ @facility = BuildingSync::Facility.new(@facility_xml, @ns)
97
+ end
98
+
99
+ # get the facility object from this workflow
100
+ # @return [BuildingSync::Facility] facility
101
+ def get_facility
102
+ return @facility
103
+ end
104
+
105
+ # get the space types of the facility
106
+ # @return [Vector<OpenStudio::Model::SpaceType>] vector of space types
107
+ def get_space_types
108
+ return @facility.get_space_types
109
+ end
110
+
111
+ # get model
112
+ # @return [OpenStudio::Model] model
113
+ def get_model
114
+ return @facility.get_model
115
+ end
116
+
117
+ # get the current workflow
118
+ # @return [Hash]
119
+ def get_workflow
120
+ return @workflow
121
+ end
122
+
123
+ # get scenario elements
124
+ # @return [Array<BuildingSync::Scenario>]
125
+ def get_scenarios
126
+ return @facility.report.scenarios
127
+ end
128
+
129
+ # generate the baseline model as osm model
130
+ # @param dir [String]
131
+ # @param epw_file_path [String]
132
+ # @param standard_to_be_used [String] 'ASHRAE90.1' or 'CaliforniaTitle24' are supported options
133
+ # @param ddy_file [String] path to the ddy file
134
+ # @return @see BuildingSync::Facility.write_osm
135
+ def setup_and_sizing_run(dir, epw_file_path, standard_to_be_used, ddy_file = nil)
136
+ @facility.set_all
137
+ @facility.determine_open_studio_standard(standard_to_be_used)
138
+ @facility.generate_baseline_osm(epw_file_path, dir, standard_to_be_used, ddy_file)
139
+ @facility.write_osm(dir)
140
+ end
141
+
142
+ # writes the parameters determined during processing back to the BldgSync XML file
143
+ def prepare_final_xml
144
+ @facility.prepare_final_xml
145
+ end
146
+
147
+ # # write osm
148
+ # # @param dir [String]
149
+ # def write_osm(dir)
150
+ # @scenario_types = @facility.write_osm(dir)
151
+ # end
152
+
153
+ # iterate over the current measure list in the workflow and check if they are available at the referenced measure directories
154
+ # @return [Boolean]
155
+ def measures_exist?
156
+ all_measures_found = true
157
+ number_measures_found = 0
158
+ @workflow['steps'].each do |step|
159
+ measure_is_valid = false
160
+ measure_dir_name = step['measure_dir_name']
161
+ get_measure_directories_array.each do |potential_measure_path|
162
+ measure_dir_full_path = "#{potential_measure_path}/#{measure_dir_name}"
163
+ if Dir.exist?(measure_dir_full_path)
164
+ measure_is_valid = true
165
+ OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.WorkflowMaker.measures_exist?', "Measure: #{measure_dir_name} found at: #{measure_dir_full_path}")
166
+ number_measures_found += 1
167
+ break
168
+ end
169
+ end
170
+ if !measure_is_valid
171
+ all_measures_found = false
172
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.measures_exist?', "CANNOT find measure with name (#{measure_dir_name}) in any of the measure paths ")
173
+ end
174
+ end
175
+ if all_measures_found
176
+ OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.WorkflowMaker.measures_exist?', "Total measures found: #{number_measures_found}. All measures defined by @workflow found.")
177
+ puts "Total measures found: #{number_measures_found}. All measures defined by @workflow found."
178
+ end
179
+ return all_measures_found
180
+ end
181
+
182
+ # gets all available measures across all measure directories
183
+ # @return [hash] Looks as follows {path_to_measure_dir: [measure_name1, mn2, etc.], path_to_measure_dir_2: [...]}
184
+ def get_available_measures_hash
185
+ measures_hash = {}
186
+ get_measure_directories_array.each do |potential_measure_path|
187
+ Dir.chdir(potential_measure_path) do
188
+ measures_hash[potential_measure_path] = Dir.glob('*').select { |f| File.directory? f }
189
+ end
190
+ end
191
+ return measures_hash
192
+ end
193
+
194
+ # collect all measure directories that contain measures needed for BldgSync
195
+ # @return [array] of measure dirs
196
+ def get_measure_directories_array
197
+ common_measures_instance = OpenStudio::CommonMeasures::Extension.new
198
+ model_articulation_instance = OpenStudio::ModelArticulation::Extension.new
199
+ ee_measures_instance = OpenStudio::EeMeasures::Extension.new
200
+ bldg_sync_instance = BuildingSync::Extension.new
201
+ return [common_measures_instance.measures_dir, model_articulation_instance.measures_dir, bldg_sync_instance.measures_dir, ee_measures_instance.measures_dir]
202
+ end
203
+
204
+ # inserts any measure. traverses through the measures available in the included extensions
205
+ # (common measures, model articulation, etc.) to find the lib/measures/[measure_dir] specified.
206
+ # It is inserted at the relative position according to its type
207
+ # @param measure_goal_type [String] one of: 'EnergyPlusMeasure', 'ReportingMeasure', or 'ModelMeasure'
208
+ # @param measure_dir_name [String] the directory name for the measure, as it appears
209
+ # in any of the gems, i.e. openstudio-common-measures-gem/lib/measures/[measure_dir_name]
210
+ # @param relative_position [Integer] the position where the measure should be inserted with respect to the measure_goal_type
211
+ # @param args_hash [hash]
212
+ def insert_measure_into_workflow(measure_goal_type, measure_dir_name, relative_position = 0, args_hash = {})
213
+ successfully_added = false
214
+ count = 0 # count for all of the measures, regardless of the type
215
+ measure_type_count = 0 # count of measures specific to the measure_goal_type
216
+ measure_type_found = false
217
+ new_step = {}
218
+ new_step['measure_dir_name'] = measure_dir_name
219
+ new_step['arguments'] = args_hash
220
+ if @workflow['steps'].empty?
221
+ @workflow['steps'].insert(count, new_step)
222
+ successfully_added = true
223
+ else
224
+ @workflow['steps'].each do |step|
225
+ measure_dir_name = step['measure_dir_name']
226
+ measure_type = get_measure_type(measure_dir_name)
227
+ OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.WorkflowMaker.insert_measure_into_workflow', "measure: #{measure_dir_name} with type: #{measure_type} found")
228
+ if measure_type == measure_goal_type
229
+ measure_type_found = true
230
+ if measure_type_count == relative_position
231
+ # insert measure here
232
+ OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.WorkflowMaker.insert_measure_into_workflow', "inserting measure with type (#{measure_goal_type}) at position #{count} and dir: #{measure_dir_name} and type: #{get_measure_type(measure_dir_name)}")
233
+ puts "inserting measure with type (#{measure_goal_type}) at position #{count} and dir: #{measure_dir_name} and type: #{get_measure_type(measure_dir_name)}"
234
+ @workflow['steps'].insert(count, new_step)
235
+ successfully_added = true
236
+ break
237
+ end
238
+ measure_type_count += 1
239
+ elsif measure_type_found
240
+ OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.WorkflowMaker.insert_measure_into_workflow', "inserting measure with type (#{measure_goal_type})at position #{count} and dir: #{measure_dir_name} and type: #{get_measure_type(measure_dir_name)}")
241
+ puts "inserting measure with type (#{measure_goal_type}) at position #{count} and dir: #{measure_dir_name} and type: #{get_measure_type(measure_dir_name)}"
242
+ @workflow['steps'].insert(count - 1, new_step)
243
+ successfully_added = true
244
+ break
245
+ end
246
+ count += 1
247
+ end
248
+ end
249
+ if !successfully_added
250
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMakerPhaseZero.insert_measure_into_workflow', "CANNOT insert measure with type (#{measure_goal_type}) at position #{count} and dir: #{measure_dir_name} and type: #{get_measure_type(measure_dir_name)}")
251
+ end
252
+ return successfully_added
253
+ end
254
+
255
+ # gets the measure type of a measure given its directory - looking up the measure type in the measure.xml file
256
+ # @param measure_dir_name [String] the directory name for the measure, as it appears
257
+ # in any of the gems, i.e. openstudio-common-measures-gem/lib/measures/[measure_dir_name]
258
+ # @return [String]
259
+ def get_measure_type(measure_dir_name)
260
+ measure_type = nil
261
+ get_measure_directories_array.each do |potential_measure_path|
262
+ measure_dir_full_path = "#{potential_measure_path}/#{measure_dir_name}"
263
+ if Dir.exist?(measure_dir_full_path)
264
+ measure_xml_doc = nil
265
+ File.open(measure_dir_full_path + '/measure.xml', 'r') do |file|
266
+ measure_xml_doc = REXML::Document.new(file)
267
+ end
268
+ measure_xml_doc.elements.each('/measure/attributes/attribute') do |attribute|
269
+ attribute_name = attribute.elements['name'].text
270
+ if attribute_name == 'Measure Type'
271
+ measure_type = attribute.elements['value'].text
272
+ end
273
+ end
274
+ end
275
+ end
276
+ return measure_type
277
+ end
278
+
279
+ # Based on the MeasureIDs defined by the Scenario, configure the workflow provided
280
+ # using the default measure arguments defined by the lib/buildingsync/makers/workflow_maker.json
281
+ # @param base_workflow [Hash] a Hash map of the @workflow. DO NOT use @workflow directly, should be a deep clone
282
+ # @param scenario [BuildingSync::Scenario] a Scenario object
283
+ def configure_workflow_for_scenario(base_workflow, scenario)
284
+ successful = true
285
+
286
+ num_measures = 0
287
+ scenario.get_measure_ids.each do |measure_id|
288
+ measure = @facility.measures.find { |m| m.xget_id == measure_id }
289
+ current_num_measure = num_measures
290
+
291
+ sym_to_find = measure.xget_text('SystemCategoryAffected')
292
+ if sym_to_find.nil? || sym_to_find.empty?
293
+ OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.WorkflowMaker.configure_workflow_for_scenario', "Measure ID: #{measure.xget_id} does not define a SystemCategoryAffected.")
294
+ successful = false
295
+ else
296
+ sym_to_find = sym_to_find.to_s.to_sym
297
+ end
298
+
299
+ # 'Other HVAC' or 'Cooling System' as examples
300
+ categories_found = @workflow_maker_json.key?(sym_to_find)
301
+ if categories_found
302
+ m_name = measure.xget_name
303
+
304
+ if m_name.nil? || m_name.empty?
305
+ OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.WorkflowMaker.configure_workflow_for_scenario', "Measure ID: #{measure.xget_id} does not have a MeasureName defined.")
306
+ successful = false
307
+ else
308
+ m_name = m_name.to_sym
309
+ end
310
+
311
+ # Where standardized measure names have not been adopted as enumerations
312
+ # in the BuildingSync Schema, a <MeasureName>Other</MeasureName> is used
313
+ # and the actual measure name added
314
+ if m_name == :Other
315
+ m_name = measure.xget_text('CustomMeasureName')
316
+ if m_name.nil? || m_name.empty?
317
+ OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.WorkflowMaker.configure_workflow_for_scenario', "Measure ID: #{measure.xget_id} has a MeasureName of 'Other' but does not have a CustomMeasureName defined.")
318
+ successful = false
319
+ else
320
+ m_name = m_name.to_sym
321
+ end
322
+ end
323
+ measure_found = false
324
+ @workflow_maker_json[sym_to_find].each do |category|
325
+ # m_name is, for example: 'Replace HVAC system type to VRF'
326
+
327
+ if !category[m_name].nil?
328
+ measure_found = true
329
+ measure_dir_name = category[m_name][:measure_dir_name]
330
+ num_measures += 1
331
+ category[m_name][:arguments].each do |argument|
332
+ # Certain arguments are only applied under specific conditions
333
+ #
334
+ if !argument[:condition].nil? && !argument[:condition].empty?
335
+ set_argument_detail(base_workflow, argument, measure_dir_name, m_name.to_s)
336
+ else
337
+ set_measure_argument(base_workflow, measure_dir_name, argument[:name], argument[:value])
338
+ end
339
+ end
340
+ end
341
+ end
342
+ if !measure_found
343
+ OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.WorkflowMaker.configure_workflow_for_scenario', "Could not find measure '#{m_name}' under category #{sym_to_find} in workflow_maker.json.")
344
+ end
345
+ else
346
+ OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.WorkflowMaker.configure_workflow_for_scenario', "Category: #{measure.xget_text('SystemCategoryAffected')} not found in workflow_maker.json.")
347
+ end
348
+
349
+ if current_num_measure == num_measures
350
+ OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.WorkflowMaker.configure_workflow_for_scenario', "Measure ID: #{measure.xget_id} could not be processed!")
351
+ successful = false
352
+ end
353
+ end
354
+
355
+ # ensure that we didn't miss any measures by accident
356
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.configure_workflow_for_scenario', "#{scenario.get_measure_ids.size} measures expected, #{num_measures} resolved, expected measure_ids = #{scenario.get_measure_ids}") if num_measures != scenario.get_measure_ids.size
357
+ return successful
358
+ end
359
+
360
+ # TODO: Update this as I believe no longer will work as expected, keys being searched for
361
+ # by the @facility_xml['key'] don't make sense.
362
+ # set argument details, used when the condition
363
+ # @param workflow [Hash] a hash of the openstudio workflow
364
+ # @param argument [Hash]
365
+ # @param measure_dir_name [String] the directory name for the measure, as it appears
366
+ # in any of the gems, i.e. openstudio-common-measures-gem/lib/measures/[measure_dir_name]
367
+ # @param measure_name [String]
368
+ def set_argument_detail(workflow, argument, measure_dir_name, measure_name)
369
+ argument_name = ''
370
+ argument_value = ''
371
+
372
+ if measure_name == 'Add daylight controls' || measure_name == 'Replace HVAC system type to PZHP'
373
+ # For these measures, the condition is based on the standards building type determined
374
+ if argument[:condition] == @facility.site.get_building_type
375
+ argument_name = argument[:name]
376
+
377
+ # This is a really terrible way to do this. It fails
378
+ # in many scenarios
379
+ argument_value = "#{argument[:value]} #{@facility.site.get_standard_template}"
380
+ end
381
+ elsif measure_name == 'Replace burner'
382
+ if argument[:condition] == @facility.site.get_system_type
383
+ argument_name = argument[:name]
384
+ argument_value = argument[:value]
385
+ end
386
+ elsif measure_name == 'Replace boiler'
387
+ if argument[:condition] == @facility.site.get_system_type
388
+ argument_name = argument[:name]
389
+ argument_value = argument[:value]
390
+ end
391
+ elsif measure_name == 'Replace package units'
392
+ if argument[:condition] == @facility.site.get_system_type
393
+ argument_name = argument[:name]
394
+ argument_value = argument[:value]
395
+ end
396
+ elsif measure_name == 'Replace HVAC system type to VRF' || measure_name == 'Replace HVAC with GSHP and DOAS' || measure_name == 'Replace AC and heating units with ground coupled heat pump systems'
397
+ if argument[:condition] == @facility.site.get_building_type
398
+ argument_name = (argument[:name]).to_s
399
+ argument_value = argument[:value]
400
+ end
401
+ else
402
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.set_argument_detail', "measure dir name not found #{measure_name}.")
403
+ puts "BuildingSync.WorkflowMaker.set_argument_detail: Measure dir name not found #{measure_name}."
404
+ end
405
+
406
+ set_measure_argument(workflow, measure_dir_name, argument_name, argument_value) if !argument_name.nil? && !argument_name.empty?
407
+ end
408
+
409
+ # write workflows for scenarios into osw files. This includes:
410
+ # - Package of Measure Scenarios
411
+ # - Current Building Modeled (Baseline) Scenario
412
+ # @param main_output_dir [String] main output path, not scenario specific. i.e. SR should be a subdirectory
413
+ # @return [Boolean] whether writing of all the new workflows was successful
414
+ def write_osws(main_output_dir, only_cb_modeled = false)
415
+ # make sure paths exist
416
+ FileUtils.mkdir_p(main_output_dir)
417
+
418
+ if @facility.report.cb_modeled.nil?
419
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.write_osws', 'OSW cannot be written since no current building modeled scenario is defined. One can be added after file import using the add_cb_modeled method')
420
+ raise StandardError, 'BuildingSync.WorkflowMaker.write_osws: OSW cannot be written since no current building modeled scenario is defined. One can be added after file import using the add_cb_modeled method'
421
+ end
422
+
423
+ # Write a workflow for the current building modeled scenario
424
+ cb_modeled_success = write_osw(main_output_dir, @facility.report.cb_modeled)
425
+
426
+ if !cb_modeled_success
427
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.write_osws', 'A workflow was not successfully written for the cb_modeled (Current Building Modeled) Scenario.')
428
+ raise StandardError, 'BuildingSync.WorkflowMaker.write_osws: A workflow was not successfully written for the cb_modeled (Current Building Modeled) Scenario.'
429
+ end
430
+
431
+ number_successful = cb_modeled_success ? 1 : 0
432
+
433
+ if !only_cb_modeled
434
+ # write an osw for each Package Of Measures scenario
435
+ @facility.report.poms.each do |scenario|
436
+ successful = write_osw(main_output_dir, scenario)
437
+ if successful
438
+ number_successful += 1
439
+ else
440
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.write_osws', "Scenario ID: #{scenario.xget_id}. Unsuccessful write_osw")
441
+ end
442
+ end
443
+ end
444
+
445
+ # Compare the total number of potential successes to the number of actual successes
446
+ if only_cb_modeled
447
+ # In this case we should have only 1 success
448
+ expected_successes = 1
449
+ really_successful = number_successful == expected_successes
450
+ else
451
+ # In this case, all pom scenarios should be run + the cb_modeled scenario
452
+ expected_successes = @facility.report.poms.size + 1
453
+ really_successful = number_successful == expected_successes
454
+ end
455
+
456
+ if !really_successful
457
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.write_osws', "Facility ID: #{@facility.xget_id}. Expected #{expected_successes}, Got #{number_successful} OSWs")
458
+ end
459
+
460
+ return really_successful
461
+ end
462
+
463
+ # Write an OSW for the provided scenario
464
+ # @param main_output_dir [String] main output path, not scenario specific. i.e. SR should be a subdirectory
465
+ # @param [BuildingSync::Scenario]
466
+ # @return [Boolean] whether the writing was successful
467
+ def write_osw(main_output_dir, scenario)
468
+ successful = true
469
+ # deep clone
470
+ base_workflow = deep_copy_workflow
471
+
472
+ # configure the workflow based on measures in this scenario
473
+ begin
474
+ # The workflow is updated by configure_workflow, put with pass by reference
475
+ # we are ok to use it later without returning
476
+ if !configure_workflow_for_scenario(base_workflow, scenario)
477
+ successful = false
478
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.write_osw', "Could not configure workflow for scenario #{scenario.xget_name}")
479
+ else
480
+ purge_skipped_from_workflow(base_workflow)
481
+ scenario.set_workflow(base_workflow)
482
+ scenario.write_osw(main_output_dir)
483
+ end
484
+ rescue StandardError => e
485
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.write_osw', "Could not configure for scenario #{scenario.xget_name}. Error: #{e}")
486
+ puts "Could not configure for scenario #{scenario.xget_name}"
487
+ puts e.backtrace.join("\n\t")
488
+ successful = false
489
+ end
490
+ return successful
491
+ end
492
+
493
+ # run osws - running all scenario simulations
494
+ # @param only_cb_modeled [Boolean] used to only run the simulations for the cb_modeled (baseline) scenario
495
+ # @param runner_options [hash]
496
+ def run_osws(output_dir, only_cb_modeled = false, runner_options = { run_simulations: true, verbose: false, num_parallel: 7, max_to_run: Float::INFINITY })
497
+ osw_files = []
498
+ osw_sr_files = []
499
+ if only_cb_modeled
500
+ osw_files << "#{@facility.report.cb_modeled.get_osw_dir}/in.osw"
501
+ else
502
+ Dir.glob("#{output_dir}/**/in.osw") { |osw| osw_files << osw }
503
+ end
504
+ Dir.glob("#{output_dir}/SR/in.osw") { |osw| osw_sr_files << osw }
505
+
506
+ runner = OpenStudio::Extension::Runner.new(dirname = Dir.pwd, bundle_without = [], options = runner_options)
507
+
508
+ # This doesn't run the workflow defined by the Sizing Run
509
+ return runner.run_osws(osw_files - osw_sr_files)
510
+ end
511
+
512
+ # Creates a deep copy of the @workflow be serializing and reloading with JSON
513
+ # @return [Hash] a new workflow object
514
+ def deep_copy_workflow
515
+ return JSON.load(JSON.generate(@workflow))
516
+ end
517
+
518
+ # Removes unused measures from a workflow, where __SKIP__ == true
519
+ # @param workflow [Hash] a hash of the openstudio workflow, typically after a deep
520
+ # copy is made and the measures are configured for the specific scenario
521
+ def purge_skipped_from_workflow(workflow)
522
+ non_skipped = []
523
+ if !workflow.nil? && !workflow['steps'].nil? && workflow.key?('steps')
524
+ workflow['steps'].each do |step|
525
+ if !step.nil? && step.key?('arguments') && !step['arguments'].nil?
526
+ if step['arguments'].key?('__SKIP__') && step['arguments']['__SKIP__'] == false
527
+ non_skipped << step
528
+ end
529
+ end
530
+ end
531
+ workflow['steps'] = non_skipped
532
+ end
533
+ end
534
+
535
+ # get failed scenarios
536
+ # @return [Array<BuildingSync::Scenario>]
537
+ def get_failed_scenarios
538
+ failed = []
539
+ @facility.report.scenarios.each do |scenario|
540
+ failed << scenario if !scenario.simulation_success?
541
+ end
542
+ return failed
543
+ end
544
+
545
+ # cleanup larger files
546
+ # @param osw_dir [String]
547
+ def cleanup_larger_files(osw_dir)
548
+ path = File.join(osw_dir, 'eplusout.sql')
549
+ FileUtils.rm_f(path) if File.exist?(path)
550
+ path = File.join(osw_dir, 'data_point.zip')
551
+ FileUtils.rm_f(path) if File.exist?(path)
552
+ path = File.join(osw_dir, 'eplusout.eso')
553
+ FileUtils.rm_f(path) if File.exist?(path)
554
+ Dir.glob(File.join(osw_dir, '*create_typical_building_from_model*')).each do |path|
555
+ FileUtils.rm_rf(path) if File.exist?(path)
556
+ end
557
+ Dir.glob(File.join(osw_dir, '*create_typical_building_from_model*')).each do |path|
558
+ FileUtils.rm_rf(path) if File.exist?(path)
559
+ end
560
+ end
561
+
562
+ # gather results for all CB Modeled and POM Scenarios, including both annual and monthly results
563
+ # - ResourceUse and AllResourceTotal elements are added to the Scenario as part of this process
564
+ # - ResourceUse - holds consumption information about a specific resource / fuel (Electricity, Natural gas, etc.)
565
+ # - AllResourceTotal - holds total site and source energy consumption information
566
+ # @param year_val [Integer]
567
+ # @param baseline_only [Boolean]
568
+ # @return [Boolean]
569
+ def gather_results(year_val = Date.today.year, baseline_only = false)
570
+ # Gather results for the Current Building Modeled (Baseline) Scenario
571
+ @facility.report.cb_modeled.os_gather_results(year_val)
572
+
573
+ if !baseline_only
574
+ # Gather results for the Package of Measures scenarios
575
+ @facility.report.poms.each do |scenario|
576
+ scenario.os_gather_results(year_val)
577
+ end
578
+ end
579
+ end
580
+ end
581
+ end