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,55 @@
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
+
40
+ module BuildingSync
41
+ # ResourceUse class
42
+ class ResourceUse
43
+ include BuildingSync::Helper
44
+ include BuildingSync::XmlGetSet
45
+ # initialize
46
+ # @param @base_xml [REXML::Element]
47
+ # @param ns [String]
48
+ def initialize(base_xml, ns)
49
+ @base_xml = base_xml
50
+ @ns = ns
51
+
52
+ help_element_class_type_check(base_xml, 'ResourceUse')
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,622 @@
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/element'
40
+ require 'securerandom'
41
+
42
+ require 'buildingsync/helpers/helper'
43
+ require 'buildingsync/helpers/xml_get_set'
44
+ require 'buildingsync/resource_use'
45
+ require 'buildingsync/all_resource_total'
46
+ require 'buildingsync/time_series'
47
+
48
+ module BuildingSync
49
+ # Scenario class
50
+ class Scenario
51
+ include BuildingSync::Helper
52
+ include BuildingSync::XmlGetSet
53
+
54
+ def initialize(base_xml, ns)
55
+ @base_xml = base_xml
56
+ @ns = ns
57
+
58
+ help_element_class_type_check(base_xml, 'Scenario')
59
+
60
+ # Helpful
61
+ @site_eui_xpath = "#{@ns}:AllResourceTotals/#{@ns}:AllResourceTotal/#{@ns}:SiteEnergyUseIntensity"
62
+ @g = BuildingSync::Generator.new(@ns)
63
+
64
+ # linked fields
65
+ @resource_uses = [] # Array[<BuildingSync::ResourceUse>]
66
+ @time_series_data = [] # Array[<BuildingSync::TimeSeries]
67
+ @all_resource_totals = [] # Array[<REXML::Element>] of AllResourceTotal
68
+
69
+ # Simulation relevant fields
70
+ @main_output_dir = nil
71
+ @osw_dir = nil
72
+ @workflow = {} # Hash to hold the workflow, see set_workflow
73
+ @results_file_name = 'results.json' # holds annual and monthly results
74
+ @eplustbl_file_name = 'eplustbl.htm' # holds source energy results
75
+ @out_osw_file_name = 'out.osw' # holds the completion status of the simulation
76
+ @out_osw_json = nil # Hash to hold the read in of out.osw
77
+ @results_json = nil # Hash to hold the read in of results.json
78
+
79
+ # Define mappings for native units by Resource Use
80
+ @native_units_map = {
81
+ 'Electricity' => 'kWh',
82
+ 'Natural gas' => 'kBtu'
83
+ }
84
+
85
+ # Define a mapping between BuildingSync concepts to openstudio concepts
86
+ # available in the results.json file
87
+ @bsync_openstudio_resources_map = {
88
+ 'IP' => {
89
+ 'ResourceUse' => [
90
+ {
91
+ 'EnergyResource' => 'Electricity',
92
+ 'EndUse' => 'All end uses',
93
+ 'fields' => [
94
+ {
95
+ # AnnualFuelUseConsistentUnits is in MMBtu/yr
96
+ 'bsync_element_name' => 'AnnualFuelUseConsistentUnits',
97
+ 'bsync_element_units' => 'MMBtu',
98
+ 'os_results_key' => 'fuel_electricity',
99
+ 'os_results_unit' => 'kBtu'
100
+ },
101
+ {
102
+ 'bsync_element_name' => 'AnnualPeakConsistentUnits',
103
+ 'bsync_element_units' => 'kW',
104
+ 'os_results_key' => 'annual_peak_electric_demand',
105
+ 'os_results_unit' => 'kW'
106
+ }
107
+ ],
108
+ 'monthly' => {
109
+ # [bracket text] is replaced when processed
110
+ 'text' => 'electricity_ip_[month]',
111
+ 'os_results_unit' => 'kWh'
112
+ }
113
+ },
114
+ {
115
+ 'EnergyResource' => 'Natural gas',
116
+ 'EndUse' => 'All end uses',
117
+ 'fields' => [
118
+ {
119
+ # AnnualFuelUseConsistentUnits is in MMBtu/yr
120
+ 'bsync_element_name' => 'AnnualFuelUseConsistentUnits',
121
+ 'bsync_element_units' => 'MMBtu',
122
+ 'os_results_key' => 'fuel_natural_gas',
123
+ 'os_results_unit' => 'kBtu'
124
+ }
125
+ ],
126
+ 'monthly' => {
127
+ # [bracket text] is replaced when processed
128
+ 'text' => 'natural_gas_ip_[month]',
129
+ 'os_results_unit' => 'MMBtu'
130
+ }
131
+ }
132
+ ],
133
+ 'AllResourceTotal' => [
134
+ {
135
+ 'EndUse' => 'All end uses',
136
+ 'fields' => [
137
+ {
138
+ 'bsync_element_name' => 'SiteEnergyUse',
139
+ 'bsync_element_units' => 'kBtu',
140
+ 'os_results_key' => 'total_site_energy',
141
+ 'os_results_unit' => 'kBtu'
142
+ },
143
+ {
144
+ 'bsync_element_name' => 'SiteEnergyUseIntensity',
145
+ 'bsync_element_units' => 'kBtu/ft^2',
146
+ 'os_results_key' => 'total_site_eui',
147
+ 'os_results_unit' => 'kBtu/ft^2'
148
+ }
149
+ ]
150
+ }
151
+ ]
152
+ }
153
+ }
154
+
155
+ read_xml
156
+
157
+ # Removes data from POM and CB Modeled on import
158
+ if !get_scenario_type_child_element.nil? && (pom? || cb_modeled?)
159
+ delete_previous_results
160
+ end
161
+ end
162
+
163
+ def read_xml
164
+ # Read in data about ResourceUses, AllResourceTotals, and TimeSeriesData
165
+ read_resource_uses
166
+ read_all_resource_totals
167
+ read_time_series_data
168
+ end
169
+
170
+ # @return [REXML::Element]
171
+ def get_scenario_type_child_element
172
+ scenario_type = xget_element('ScenarioType')
173
+ if !scenario_type.nil?
174
+ scenario_type.get_elements('*')[0]
175
+ end
176
+ end
177
+
178
+ # @return [Array<String>]
179
+ def get_measure_ids
180
+ return xget_idrefs('MeasureID')
181
+ end
182
+
183
+ # @return [Array<BuildingSync::ResourceUse>]
184
+ def get_resource_uses
185
+ return @resource_uses
186
+ end
187
+
188
+ def get_all_end_use_resource_uses
189
+ return @resource_uses.each.select { |ru| ru.xget_text('EndUse') == 'All end uses' }
190
+ end
191
+
192
+ # @return [Array<REXML::Element>]
193
+ def get_all_resource_totals
194
+ return @all_resource_totals
195
+ end
196
+
197
+ # @return [Array<BuildingSync::TimeSeries>]
198
+ def get_time_series_data
199
+ return @time_series_data
200
+ end
201
+
202
+ # @return [Hash]
203
+ def get_workflow
204
+ return @workflow
205
+ end
206
+
207
+ # @return [String]
208
+ def get_main_output_dir
209
+ return @main_output_dir
210
+ end
211
+
212
+ # get osw dir
213
+ # @return [String] directory to the new osw_dir
214
+ def get_osw_dir
215
+ return @osw_dir
216
+ end
217
+
218
+ def get_benchmark_tool
219
+ child = get_scenario_type_child_element
220
+ return help_get_text_value(child.elements["#{@ns}:BenchmarkTool"])
221
+ end
222
+
223
+ # @param workflow [Hash] a hash of the openstudio workflow
224
+ def set_workflow(workflow)
225
+ if !workflow.is_a?(Hash)
226
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Scenario.set_workflow', "Scenario ID: #{xget_id}. Cannot set_workflow, argument must be a Hash.")
227
+ raise StandardError, "BuildingSync.Scenario.set_workflow Scenario ID: #{xget_id}. Cannot set_workflow, argument must be a Hash, not a #{workflow.class}"
228
+ else
229
+ @workflow = workflow
230
+ end
231
+ end
232
+
233
+ def set_main_output_dir(main_output_dir)
234
+ @main_output_dir = main_output_dir
235
+ return @main_output_dir
236
+ end
237
+
238
+ def set_osw_dir(main_output_dir = @main_output_dir)
239
+ if !xget_name.nil?
240
+ to_use = xget_name
241
+ elsif !xget_id.nil?
242
+ to_use = xget_id
243
+ end
244
+ @osw_dir = File.join(main_output_dir, to_use)
245
+ return @osw_dir
246
+ end
247
+
248
+ # Create the @osw_dir
249
+ def osw_mkdir_p
250
+ if !@osw_dir.nil?
251
+ FileUtils.mkdir_p(@osw_dir)
252
+ else
253
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Scenario.osw_mkdir_p', "Scenario ID: #{xget_id}. @osw_dir must be set first")
254
+ raise StandardError, "BuildingSync.Scenario.osw_mkdir_p Scenario ID: #{xget_id}. @osw_dir must be set first"
255
+ end
256
+ end
257
+
258
+ # Use the @workflow definition to write a new ' in.osw ' file.
259
+ # The main_output_dir and osw_dir are set and created if not existing.
260
+ # @param main_output_dir [String] path to the main output directory to use
261
+ def write_osw(main_output_dir = @main_output_dir)
262
+ set_main_output_dir(main_output_dir)
263
+ set_osw_dir(main_output_dir)
264
+ osw_mkdir_p
265
+ # write the osw
266
+ path = File.join(@osw_dir, 'in.osw')
267
+ File.open(path, 'w') do |file|
268
+ file << JSON.pretty_generate(@workflow)
269
+ end
270
+ end
271
+
272
+ # delete previous results from the Scenario. This only affects POM or cb_modeled scenarios,
273
+ # unless all = true is passed
274
+ def delete_previous_results(all = false)
275
+ if pom?
276
+ get_scenario_type_child_element.elements.delete("#{@ns}:AnnualSavingsSiteEnergy")
277
+ get_scenario_type_child_element.elements.delete("#{@ns}:AnnualSavingsCost")
278
+ get_scenario_type_child_element.elements.delete("#{@ns}:CalculationMethod")
279
+ get_scenario_type_child_element.elements.delete("#{@ns}AnnualSavingsByFuels")
280
+ end
281
+
282
+ # Delete elements from the xml and reset the attributes to empty
283
+ if pom? || cb_modeled? || all
284
+ get_scenario_type_child_element.elements.delete("#{@ns}AllResourceTotals")
285
+ get_scenario_type_child_element.elements.delete("#{@ns}ResourceUses")
286
+ @resource_uses = []
287
+ @all_resource_totals = []
288
+ end
289
+ end
290
+
291
+ # Check that the simulation was completed successfully. We check:
292
+ # - out.osw completed_status == 'Success'
293
+ # - finished.job file exists
294
+ # - failed.job file doesn't exist
295
+ # - eplusout.end and eplusout.err files
296
+ def simulation_success?
297
+ success = true
298
+
299
+ # Check out.osw
300
+ out_osw_file = File.join(get_osw_dir, @out_osw_file_name)
301
+ if !File.exist?(out_osw_file)
302
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Scenario.simulation_success?', "Scenario ID: #{xget_id}. #{out_osw_file} does not exist.")
303
+ else
304
+ File.open(out_osw_file, 'r') do |file|
305
+ @out_osw_json = JSON.parse(file.read)
306
+ end
307
+ if @out_osw_json['completed_status'] == 'Success'
308
+ OpenStudio.logFree(OpenStudio::Info, 'BuildingSync.Scenario.simulation_success?', "Scenario ID: #{xget_id} successfully completed.")
309
+ else
310
+ success = false
311
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Scenario.simulation_success?', "Scenario ID: #{xget_id} unsuccessful.")
312
+ end
313
+
314
+ end
315
+
316
+ # Check for finished.job
317
+ finished_job = File.join(get_osw_dir, 'finished.job')
318
+ if !File.exist?(finished_job)
319
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Scenario.simulation_success?', "Scenario ID: #{xget_id}: finished.job does not exist, simulation unsuccessful.")
320
+ success = false
321
+ end
322
+
323
+ # Check for failed.job
324
+ failed_job = File.join(get_osw_dir, 'failed.job')
325
+ if File.exist?(failed_job)
326
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Scenario.simulation_success?', "Scenario ID: #{xget_id}: failed.job exists, simulation unsuccessful.")
327
+ success = false
328
+ end
329
+
330
+ # Check eplusout.end and eplusout.err files
331
+ end_file = File.join(get_osw_dir, 'eplusout.end')
332
+ if File.exist?(end_file)
333
+ # we open the .end file to determine if EnergyPlus was successful or not
334
+ energy_plus_string = File.open(end_file, &:readline)
335
+ if energy_plus_string.include? 'Fatal Error Detected'
336
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Scenario.simulation_success?', "Scenario ID: #{xget_id}: eplusout.end detected error, simulation unsuccessful: #{energy_plus_string}")
337
+ success = false
338
+ # if we found out that there was a fatal error we search the err file for the first error.
339
+ File.open(File.join(scenario.get_osw_dir, 'eplusout.err')).each do |line|
340
+ if line.include? '** Severe **'
341
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Scenario.simulation_success?', "Scenario ID: #{xget_id}: Severe error occurred! #{line}")
342
+ elsif line.include? '** Fatal **'
343
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Scenario.simulation_success?', "Scenario ID: #{xget_id}: Fatal error occurred! #{line}")
344
+ end
345
+ end
346
+ end
347
+ end
348
+
349
+ return success
350
+ end
351
+
352
+ def results_available_and_correct_units?(results = @results_json)
353
+ results_available = true
354
+
355
+ if !results.nil?
356
+ if @results_json['units'] == 'SI'
357
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Scenario.results_available_and_correct_units?', "Scenario ID: #{xget_id}. Only able to process IP results.")
358
+ results_available = false
359
+ end
360
+ elsif !File.exist?(File.join(get_osw_dir, @results_file_name))
361
+ results_available = false
362
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Scenario.results_available_and_correct_units?', "Scenario ID: #{xget_id}. Unable to gather results: #{results_file} does not exist.")
363
+ else
364
+ results_file = File.join(get_osw_dir, @results_file_name)
365
+ File.open(results_file, 'r') do |file|
366
+ @results_json = JSON.parse(file.read)
367
+ end
368
+ if @results_json['units'] == 'SI'
369
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Scenario.results_available_and_correct_units?', "Scenario ID: #{xget_id}. Only able to process IP results.")
370
+ results_available = false
371
+ end
372
+ end
373
+ return results_available
374
+ end
375
+
376
+ def os_gather_results(year_val)
377
+ if simulation_success? && results_available_and_correct_units?
378
+ os_parse_annual_results
379
+ os_parse_monthly_all_end_uses_results(year_val)
380
+ elsif !simulation_success?
381
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Scenario.os_gather_results', "Scenario ID: #{xget_id}. Unable to gather results as simulation was unsuccessful.")
382
+ elsif !results_available_and_correct_units?
383
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Scenario.os_gather_results', "Scenario ID: #{xget_id}. Unable to gather results as results are not available.")
384
+ end
385
+ end
386
+
387
+ def os_parse_annual_results(results = @results_json)
388
+ os_add_resource_uses(results)
389
+ os_add_all_resource_totals(results)
390
+ end
391
+
392
+ def os_parse_monthly_all_end_uses_results(year_val = Date.today.year, results = @results_json)
393
+ if results_available_and_correct_units?(results)
394
+ time_series_data_xml = xget_or_create('TimeSeriesData')
395
+ resource_use_map = @bsync_openstudio_resources_map['IP']['ResourceUse']
396
+ os_results = results['OpenStudioResults']
397
+ get_all_end_use_resource_uses.each do |resource_use|
398
+ resource_use_hash = resource_use_map.each.find { |h| h['EnergyResource'] == resource_use.xget_text('EnergyResource') && h['EndUse'] == 'All end uses' }
399
+ if resource_use_hash.nil?
400
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Scenario.os_parse_monthly_all_end_uses_results', "Scenario ID: #{xget_id}: Unable to find mapping for ResourceUse: #{resource_use.xget_id} and 'All end uses'. Cannot parse monthly results")
401
+ else
402
+ monthly_text = resource_use_hash['monthly']['text']
403
+ monthly_units = resource_use_hash['monthly']['os_results_unit']
404
+ native_units = @native_units_map[resource_use.xget_text('EnergyResource')]
405
+ (1..12).each do |month|
406
+ start_date_time = DateTime.new(year_val, month, 1)
407
+
408
+ # substitues [month] with oct, for example, so we get electricity_ip_oct
409
+ key_to_find = monthly_text.gsub('[month]', start_date_time.strftime('%b').downcase)
410
+ if os_results.key?(key_to_find)
411
+ # We always use the first day of the month as the start day
412
+ time_series_xml = REXML::Element.new("#{@ns}:TimeSeries", time_series_data_xml)
413
+ time_series_xml.add_attribute('ID', "TS-#{start_date_time.strftime('%b').upcase}-#{resource_use.xget_id}")
414
+
415
+ # Convert value to correct units
416
+ interval_reading_value = help_convert(os_results[key_to_find], monthly_units, native_units)
417
+
418
+ # Create new TimeSeries element
419
+ ts = BuildingSync::TimeSeries.new(time_series_xml, @ns)
420
+ ts.set_monthly_energy_reading(start_date_time.dup, interval_reading_value, resource_use.xget_id)
421
+
422
+ else
423
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Scenario.os_parse_monthly_all_end_uses_results', "Scenario ID: #{xget_id}: Key #{key_to_find} not found in results['OpenStudioResults']. Make sure monthly data is being output by os_results measure")
424
+ end
425
+ end
426
+ end
427
+ end
428
+ else
429
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.WorkflowMaker.get_timeseries_element', 'Cannot add monthly report values to the BldgSync file since it is missing.')
430
+ end
431
+ end
432
+
433
+ # Use the bsync to openstudio resources map to add results from the openstudio
434
+ # simulations as new ResourceUse elements and objects
435
+ # @param results [Hash] a hash of the results as directly read in from a results.json file
436
+ def os_add_resource_uses(results)
437
+ @results_json = results
438
+ ip_map = @bsync_openstudio_resources_map['IP']
439
+ os_results = @results_json['OpenStudioResults']
440
+
441
+ # Loop through ResourceUses in the resource_use_map
442
+ ip_map['ResourceUse'].each do |resource_use_map|
443
+ ru_type = resource_use_map['EnergyResource']
444
+ end_use = resource_use_map['EndUse']
445
+ native_units = @native_units_map[ru_type]
446
+
447
+ # Check if a ResourceUse of the desired type already exists
448
+ resource_use_element = @base_xml.get_elements("./#{@ns}:ResourceUses/#{@ns}:ResourceUse[#{@ns}:EnergyResource/text() = '#{ru_type}' and #{@ns}:EndUse/text() = '#{end_use}']")
449
+
450
+ # Add a new ResourceUse xml to the Scenario. This also adds ResourceUses if not defined
451
+ if resource_use_element.nil? || resource_use_element.empty?
452
+ ru_id = "#{xget_id}-ResourceUse-#{ru_type.split.map(&:capitalize).join('')}-#{end_use.split.map(&:capitalize).join('')}"
453
+ resource_use_xml = @g.add_energy_resource_use_to_scenario(@base_xml, ru_type, end_use, ru_id, native_units)
454
+ else
455
+ OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.Scenario.parse_annual_results', "Scenario ID: #{xget_id}. Resource Use of type: #{ru_type} and end use: #{end_use} already exists")
456
+ resource_use_xml = resource_use_element.first
457
+ end
458
+
459
+ # Map in the fields for each ResourceUse element into the xml
460
+ add_fields_from_map(resource_use_map['fields'], os_results, resource_use_xml)
461
+
462
+ # Add ResourceUse to array
463
+ @resource_uses << BuildingSync::ResourceUse.new(resource_use_xml, @ns)
464
+ end
465
+ end
466
+
467
+ def os_add_all_resource_totals(results)
468
+ ip_map = @bsync_openstudio_resources_map['IP']
469
+ os_results = results['OpenStudioResults']
470
+
471
+ # Loop through ResourceUses in the resource_use_map
472
+ ip_map['AllResourceTotal'].each do |map|
473
+ end_use = map['EndUse']
474
+
475
+ # Check if an AllResourceTotal of the desired type already exists
476
+ element = @base_xml.get_elements("./#{@ns}:AllResourceTotals/#{@ns}:AllResourceTotal[#{@ns}:EndUse/text() = '#{end_use}']")
477
+
478
+ # Add a new ResourceUse xml to the Scenario
479
+ if element.nil? || element.empty?
480
+ art_id = "#{xget_id}-AllResourceTotal-#{end_use.split.map(&:capitalize).join('')}"
481
+ all_resource_total_xml = @g.add_all_resource_total_to_scenario(@base_xml, end_use, art_id)
482
+ else
483
+ OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.Scenario.parse_annual_results', "Scenario ID: #{xget_id}. Resource Use of type: #{ru_type} and end use: #{end_use} already exists")
484
+ all_resource_total_xml = element.first
485
+ end
486
+
487
+ add_fields_from_map(map['fields'], os_results, all_resource_total_xml)
488
+ # add_source_energy(all_resource_total_xml)
489
+
490
+ @all_resource_totals << BuildingSync::AllResourceTotal.new(all_resource_total_xml, @ns)
491
+ end
492
+ end
493
+
494
+ def add_source_energy(all_resource_total_xml)
495
+ eplustbl_file = File.join(get_osw_dir, @eplustbl_file_name)
496
+ if !File.exist?(eplustbl_file)
497
+ OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.Scenario.add_source_energy', "Scenario ID: #{xget_id}. #{@eplustbl_file_name} does not exist, cannot add source energy results")
498
+ else
499
+ source_energy, source_eui = get_source_energy_array(eplustbl_file)
500
+ source_energy_xml = REXML::Element.new("#{@ns}:SourceEnergyUse", all_resource_total_xml)
501
+ source_energy_xml.text = source_energy
502
+ source_eui_xml = REXML::Element.new("#{@ns}:SourceEnergyUseIntensity", all_resource_total_xml)
503
+ source_eui_xml.text = source_eui
504
+ end
505
+ end
506
+
507
+ # Get source energy and source EUI from
508
+ # @param eplustbl_path [String]
509
+ # @return [Array] [total_source_energy_kbtu, total_source_eui_kbtu_ft2]
510
+ def get_source_energy_array(eplustbl_path)
511
+ # DLM: total hack because these are not reported in the out.osw
512
+ # output is array of [source_energy, source_eui] in kBtu and kBtu/ft2
513
+ result = []
514
+ File.open(eplustbl_path, 'r') do |f|
515
+ while line = f.gets
516
+ if /\<td align=\"right\"\>Total Source Energy\<\/td\>/.match?(line)
517
+ result << /\<td align=\"right\"\>(.*?)<\/td\>/.match(f.gets)[1].to_f
518
+ result << /\<td align=\"right\"\>(.*?)<\/td\>/.match(f.gets)[1].to_f
519
+ break
520
+ end
521
+ end
522
+ end
523
+
524
+ result[0] = result[0] * 947.8171203133 # GJ to kBtu
525
+ result[1] = result[1] * 0.947817120313 * 0.092903 # MJ/m2 to kBtu/ft2
526
+
527
+ return result[0], result[1]
528
+ end
529
+
530
+ def add_fields_from_map(fields, os_results, parent_xml)
531
+ fields.each do |field|
532
+ os_results_val = os_results[field['os_results_key']]
533
+ if os_results_val.nil?
534
+ OpenStudio.logFree(OpenStudio::Error, 'BuildingSync.Scenario.parse_annual_results', "Scenario ID: #{xget_id}. Unable to find result for #{field['os_results_key']}")
535
+ else
536
+ if field['os_results_unit'] == field['bsync_element_units']
537
+ # Parent element
538
+ new_element = REXML::Element.new("#{@ns}:#{field['bsync_element_name']}", parent_xml)
539
+ new_element.text = os_results_val
540
+ else
541
+ converted = help_convert(os_results_val, field['os_results_unit'], field['bsync_element_units'])
542
+ if !converted.nil?
543
+ new_element = REXML::Element.new("#{@ns}:#{field['bsync_element_name']}", parent_xml)
544
+ new_element.text = converted
545
+ end
546
+ end
547
+ end
548
+ end
549
+ end
550
+
551
+ def read_resource_uses
552
+ resource_use = @base_xml.get_elements("./#{@ns}:ResourceUses/#{@ns}:ResourceUse")
553
+ if !resource_use.nil? && !resource_use.empty?
554
+ resource_use.each do |ru|
555
+ @resource_uses << BuildingSync::ResourceUse.new(ru, @ns)
556
+ end
557
+ end
558
+ end
559
+
560
+ def read_all_resource_totals
561
+ all_resource_total = @base_xml.get_elements("./#{@ns}:AllResourceTotals/#{@ns}:AllResourceTotal")
562
+ if !all_resource_total.nil? && !all_resource_total.empty?
563
+ all_resource_total.each do |art|
564
+ @all_resource_totals << BuildingSync::AllResourceTotal.new(art, @ns)
565
+ end
566
+ end
567
+ end
568
+
569
+ def read_time_series_data
570
+ time_series = @base_xml.get_elements("./#{@ns}:TimeSeriesData/#{@ns}:TimeSeries")
571
+ if !time_series.nil? && !time_series.empty?
572
+ time_series.each do |ts|
573
+ @time_series_data << BuildingSync::TimeSeries.new(ts, @ns)
574
+ end
575
+ end
576
+ end
577
+
578
+ def check_scenario_type(path)
579
+ to_check = xget_element('ScenarioType').get_elements(path)
580
+ if !to_check.nil? && !to_check.empty?
581
+ return true
582
+ else
583
+ return false
584
+ end
585
+ end
586
+
587
+ def cb_measured?
588
+ if xget_element('ScenarioType').nil?
589
+ return false
590
+ end
591
+ return check_scenario_type("./#{@ns}:CurrentBuilding/#{@ns}:CalculationMethod/#{@ns}:Measured")
592
+ end
593
+
594
+ def cb_modeled?
595
+ if xget_element('ScenarioType').nil?
596
+ return false
597
+ end
598
+ return check_scenario_type("./#{@ns}:CurrentBuilding/#{@ns}:CalculationMethod/#{@ns}:Modeled")
599
+ end
600
+
601
+ def pom?
602
+ if xget_element('ScenarioType').nil?
603
+ return false
604
+ end
605
+ return check_scenario_type("./#{@ns}:PackageOfMeasures")
606
+ end
607
+
608
+ def target?
609
+ if xget_element('ScenarioType').nil?
610
+ return false
611
+ end
612
+ return check_scenario_type("./#{@ns}:Target")
613
+ end
614
+
615
+ def benchmark?
616
+ if xget_element('ScenarioType').nil?
617
+ return false
618
+ end
619
+ return check_scenario_type("./#{@ns}:Benchmark")
620
+ end
621
+ end
622
+ end