buildingsync 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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