openstudio-metadata 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,220 @@
1
+ # *******************************************************************************
2
+ # OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC.
3
+ # All rights reserved.
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # (1) Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # (2) Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # (3) Neither the name of the copyright holder nor the names of any contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission from the respective party.
17
+ #
18
+ # (4) Other than as required in clauses (1) and (2), distributions in any form
19
+ # of modifications or other derivative works may not use the "OpenStudio"
20
+ # trademark, "OS", "os", or any other confusingly similar designation without
21
+ # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
24
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25
+ # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
27
+ # UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
28
+ # THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
30
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33
+ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ # *******************************************************************************
35
+ require 'rexml/document'
36
+ module OpenStudio
37
+ module Metadata
38
+ class BCVTBControlsSetup
39
+
40
+ ##
41
+ # Returns new BCVTBControlsSetup
42
+ ##
43
+ # @param model [OpenStudio::Model::Model] openstudio model
44
+ def initialize(model)
45
+ @model = model
46
+ @output_variables = @model.getOutputVariables.sort_by{ |m| [ m.keyValue.to_s, m.name.to_s.downcase]}
47
+
48
+ @ems_output_variables = @model.getEnergyManagementSystemOutputVariables.sort_by{ |m| m.name.to_s.downcase }
49
+ @ems_programs = @model.getEnergyManagementSystemPrograms
50
+ @ems_subroutines = @model.getEnergyManagementSystemSubroutines
51
+ @ems_global_variables = @model.getEnergyManagementSystemGlobalVariables
52
+
53
+ @global_variables_swapped = false
54
+ @ext_int_variables = nil # set after replace_ems_globals_with_ext_variables function is called
55
+ @ext_int_schedules = @model.getExternalInterfaceSchedules.sort_by{ |m| m.name.to_s.downcase }
56
+ @ext_int_actuators = @model.getExternalInterfaceActuators.sort_by{ |m| m.name.to_s.downcase }
57
+
58
+ @bcvtb_output_file = nil # set by initialize_bcvtb_output_file
59
+ initialize_bcvtb_output_file
60
+
61
+ @xml_doc = REXML::Document.new
62
+ @bcvtb = REXML::Element.new "BCVTB-variables"
63
+ end
64
+
65
+ ##
66
+ # Add xml declaration and doctyp to output file
67
+ ##
68
+ # @param file_path [String] Path to file to save bcvtb data
69
+ def initialize_bcvtb_output_file(file_path: File.join(File.dirname(__FILE__ ) , 'report_variables.cfg'))
70
+ @bcvtb_output_file = file_path
71
+ File.open(@bcvtb_output_file, 'w') do |fo|
72
+ fo.puts '<?xml version="1.0" encoding="ISO-8859-1"?>'
73
+ fo.puts '<!DOCTYPE BCVTB-variables SYSTEM "variables.dtd">'
74
+ end
75
+ end
76
+
77
+ ##
78
+ # Add bcvtb element to xml doc, pretty format, and write to disk
79
+ ##
80
+ def write_bcvtb_to_output_file
81
+ @xml_doc.add_element @bcvtb
82
+ formatter = REXML::Formatters::Pretty.new
83
+ formatter.compact = true
84
+ File.open(@bcvtb_output_file,"a"){|file| file.puts formatter.write(@xml_doc.root,"")}
85
+ end
86
+
87
+ ##
88
+ # Add either an OutputVariable or an EnergyManagementSystemOutputVariable to the bcvtb xml file.
89
+ # The source attribute is set as 'EnergyPlus'.
90
+ ##
91
+ # @param variable_name [String] variable_name OutputVariable.variableName or EMSOutputVariable.nameString
92
+ # @param key_value [String] OutputVariable.keyValue or 'EMS'
93
+ def add_xml_output(variable_name, key_value)
94
+ variable = REXML::Element.new "variable"
95
+ variable.attributes["source"] = "EnergyPlus"
96
+ energyplus = REXML::Element.new "EnergyPlus"
97
+ energyplus.attributes["name"] = key_value
98
+ energyplus.attributes["type"] = variable_name
99
+ variable.add_element energyplus
100
+ @bcvtb.add_element variable
101
+ end
102
+
103
+ ##
104
+ # Add an ExternalInterface:* object to the bcvtb xml file.
105
+ # The source attribute is set as 'Ptolemy'.
106
+ ##
107
+ # @param type [String] Depending on the type of ExternalInterface, this is one of: ['variable', 'schedule', 'actuator']
108
+ # @param name [String] Value of the '.name' method called on the ExternalInterface object
109
+ def add_xml_ptolemy(type, name)
110
+ valid_types = ['variable', 'schedule', 'actuator']
111
+ raise "type must be one of #{valid_types}" unless valid_types.include? type
112
+ variable = REXML::Element.new "variable"
113
+ variable.attributes["source"] = "Ptolemy"
114
+ energyplus = REXML::Element.new "EnergyPlus"
115
+ energyplus.attributes[type] = name
116
+ variable.add_element energyplus
117
+ @bcvtb.add_element variable
118
+ end
119
+
120
+ ##
121
+ # Loops through all EMSGlobalVariables. If exportToBCVTB, removes the variable from the
122
+ # model and creates a new ExternalInterfaceVariable to replace it. Handles of the old
123
+ # EMSGlobalVariable and the new ExternalInterfaceVariable are swapped in programs and subroutines.
124
+ #
125
+ # Initial values for all are set to 0.
126
+ #
127
+ # After, the @ext_int_variables attribute is populated.
128
+ ##
129
+ def replace_ems_globals_with_ext_variables
130
+ @ems_global_variables.each do |ems_var|
131
+ if ( ems_var.exportToBCVTB )
132
+ ems_global_name = ems_var.nameString
133
+ ems_global_handle = ems_var.handle.to_s
134
+ ems_var.remove
135
+
136
+ ext_int_var = OpenStudio::Model::ExternalInterfaceVariable.new(@model, ems_global_name, 0)
137
+ ext_int_var_handle = ext_int_var.handle.to_s
138
+
139
+ @ems_programs.each do |prog|
140
+ body = prog.body
141
+ body.gsub!(ems_global_handle, ext_int_var_handle)
142
+ prog.setBody(body)
143
+ end
144
+
145
+ @ems_subroutines.each do |prog|
146
+ body = prog.body
147
+ body.gsub!(ems_global_handle, ext_int_var_handle)
148
+ prog.setBody(body)
149
+ end
150
+ end
151
+ end
152
+ @global_variables_swapped = true
153
+ @ext_int_variables = @model.getExternalInterfaceVariables.sort_by{ |m| m.name.to_s.downcase }
154
+ end
155
+
156
+ ##
157
+ # Adds all OpenStudio OutputVariable variables to the bcvtb xml
158
+ # @note These are added as sourcing from 'EnergyPlus'
159
+ ##
160
+ def add_output_variables_to_bcvtb
161
+ @output_variables.each do |outvar|
162
+ if (outvar.exportToBCVTB && (outvar.keyValue != "*"))
163
+ @bcvtb.add_element add_xml_output(outvar.variableName, outvar.keyValue)
164
+ end
165
+ end
166
+ end
167
+
168
+ ##
169
+ # Adds all EMSOutputVariable variables to the bcvtb xml
170
+ # @note These are added as sourcing from 'EnergyPlus'
171
+ ##
172
+ def add_ems_output_variables_to_bcvtb
173
+ @ems_output_variables.each do |outvar|
174
+ if (outvar.exportToBCVTB)
175
+ @bcvtb.add_element add_xml_output(outvar.nameString, "EMS")
176
+ end
177
+ end
178
+ end
179
+
180
+ ##
181
+ # Adds all ExternalInterface:Variable variables to the bcvtb xml
182
+ # @note These are added as sourcing from 'Ptolemy'
183
+ ##
184
+ def add_ext_int_variables_to_bcvtb
185
+ if !@global_variables_swapped
186
+ replace_ems_globals_with_ext_variables
187
+ end
188
+ @ext_int_variables.each do |outvar|
189
+ if (outvar.exportToBCVTB)
190
+ @bcvtb.add_element add_xml_ptolemy("variable", outvar.name)
191
+ end
192
+ end
193
+ end
194
+
195
+ ##
196
+ # Adds all ExternalInterface:Schedule variables to the bcvtb xml.
197
+ # @note These are added as sourcing from 'Ptolemy'
198
+ ##
199
+ def add_ext_int_schedules_to_bcvtb
200
+ @ext_int_schedules.each do |schedule|
201
+ if (schedule.exportToBCVTB)
202
+ @bcvtb.add_element add_xml_ptolemy("schedule", schedule.name)
203
+ end
204
+ end
205
+ end
206
+
207
+ ##
208
+ # Adds all ExternalInterface:Actuator variables to the bcvtb xml
209
+ # @note These are added as sourcing from 'Ptolemy'
210
+ ##
211
+ def add_ext_int_actuators_to_bcvtb
212
+ @ext_int_actuators.each do |actuator|
213
+ if (actuator.exportToBCVTB)
214
+ @bcvtb.add_element add_xml_ptolemy("actuator", actuator.name)
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,432 @@
1
+ # *******************************************************************************
2
+ # OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC.
3
+ # All rights reserved.
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # (1) Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # (2) Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # (3) Neither the name of the copyright holder nor the names of any contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission from the respective party.
17
+ #
18
+ # (4) Other than as required in clauses (1) and (2), distributions in any form
19
+ # of modifications or other derivative works may not use the "OpenStudio"
20
+ # trademark, "OS", "os", or any other confusingly similar designation without
21
+ # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
24
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25
+ # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
27
+ # UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
28
+ # THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
30
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33
+ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ # *******************************************************************************
35
+ require 'json'
36
+ require 'yaml'
37
+
38
+ require 'linkeddata'
39
+ require 'sparql/client'
40
+ require 'openstudio'
41
+
42
+ require_relative 'helpers'
43
+
44
+ module OpenStudio
45
+ module Metadata
46
+ ##
47
+ # Class to map OpenStudio models to haystack and brick
48
+ ##
49
+ # @example Instantiate creator with model
50
+ # path_to_model = "path/to/model.osm"
51
+ # creator = OpenStudio::Metadata::Creator.new(path_to_model)
52
+ class Creator
53
+ attr_accessor :entities, :model
54
+ attr_reader :mappings, :templates, :haystack_repo, :brick_repo, :phiot_vocab, :brick_vocab, :metadata_type
55
+ include OpenStudio::Metadata::Helpers
56
+ ##
57
+ # @param [String] path_to_model
58
+ def initialize(path_to_model)
59
+ @model = OpenStudio::Model::Model.load(path_to_model).get
60
+ @path_to_model = path_to_model
61
+ @phiot_vocab = RDF::Vocabulary.new('https://project-haystack.org/def/phIoT/3.9.9#')
62
+ @ph_vocab = RDF::Vocabulary.new('https://project-haystack.org/def/ph/3.9.9#')
63
+ @brick_vocab = RDF::Vocabulary.new('https://brickschema.org/schema/1.1/Brick#')
64
+ @templates = nil
65
+ @mappings = nil
66
+ @haystack_repo = nil
67
+ @brick_repo = nil
68
+ @current_repo = nil # pointer to either haystack_repo or brick_repo
69
+ @current_vocab = nil # pointer to either @phiot_vocab or @brick_vocab
70
+ @metadata_type = nil # set by apply_mappings
71
+ @entities = []
72
+ @files_path = File.join(File.dirname(__FILE__), '../../files')
73
+ @brick_version = nil
74
+ @haystack_version = nil
75
+ end
76
+
77
+ ##
78
+ # Add nodes defined in mapping document as entities
79
+ ##
80
+ # @param obj [OpenStudio parent object] obj
81
+ # @param nodes [Hash] nodes
82
+ def add_nodes(obj, nodes)
83
+ if obj.to_ThermalZone.is_initialized
84
+ if !obj.airLoopHVAC.is_initialized && obj.zoneConditioningEquipmentListName.empty?
85
+ return
86
+ end
87
+ end
88
+ relationship_to_parent = nodes['relationship_to_parent']
89
+ nodes.each do |node_method, node_properties|
90
+ next unless node_method != 'relationship_to_parent'
91
+ found_node = obj.send(node_method)
92
+ found_node = found_node.get unless found_node.is_a?(OpenStudio::Model::Node)
93
+ next unless found_node.initialized
94
+ node_properties.each do |system_node_property, map|
95
+ name = "#{obj.name} #{map['brick']}" # Brick names are prettier / consistent
96
+ name = create_ems_str(name)
97
+
98
+ # Else recreates variable every time
99
+ output_variable = @model.getOutputVariableByName(name)
100
+ if output_variable.is_initialized
101
+ output_variable = output_variable.get
102
+ else
103
+ output_variable = create_output_variable_and_ems_sensor(system_node_property: system_node_property, node: found_node, ems_name: name, model: @model)
104
+ end
105
+ entity_info = resolve_template(map[@metadata_type.downcase])
106
+ entity_info = add_node_relationship_to_parent(obj, relationship_to_parent, entity_info) unless relationship_to_parent.nil?
107
+ add_specific_info(output_variable, entity_info)
108
+ end
109
+ end
110
+ end
111
+
112
+ ##
113
+ # Apply mappings for all of the Hash objects in the mappings.json.
114
+ # Applying a mapping consists of:
115
+ # 1. Resolving the OpenStudio class to a template type
116
+ # 2. Iterating through all objects of a certain OpenStudio class and adding metadata to @entities
117
+ # 3. Adding relationships and nodes
118
+ #
119
+ # @note meter mappings are handled via apply_meter_mappings
120
+ ##
121
+ # @param metadata_type [String] One of: ['Brick', 'Haystack']
122
+ def apply_mappings(metadata_type)
123
+ types = ['Brick', 'Haystack']
124
+ raise "metadata_type must be one of #{types}" unless types.include? metadata_type
125
+ if metadata_type == 'Brick'
126
+ @current_repo = @brick_repo
127
+ @current_vocab = @brick_vocab
128
+ elsif metadata_type == 'Haystack'
129
+ @current_repo = @haystack_repo
130
+ @current_vocab = @phiot_vocab
131
+ end
132
+ @metadata_type = metadata_type
133
+
134
+ # Let mappings run through once to 'create' entities
135
+ @mappings.each do |mapping|
136
+ if mapping['openstudio_class'] == "OS:Output:Meter"
137
+ raise "Primary meter mapping must have key: submeter_relationships" unless mapping.key? 'submeter_relationships'
138
+ apply_meter_mappings(mapping['meters'], mapping['relationships'],
139
+ mapping['submeter_relationships'], mapping['point_to_meter_relationship'])
140
+ else
141
+ cls_info = resolve_template_from_mapping(mapping)
142
+ cls = mapping['openstudio_class']
143
+ objs = @model.getObjectsByType(cls)
144
+ objs.each do |obj|
145
+ # rescue objects from the clutches of boost
146
+ conv_meth = 'to_' << cls.gsub(/^OS/, '').gsub(':', '').gsub('_', '')
147
+ obj = obj.send(conv_meth)
148
+ break if obj.empty?
149
+ obj = obj.get
150
+
151
+ obj_info = cls_info.deep_dup
152
+ add_relationship_info(obj, mapping['relationships'], obj_info) if mapping.key? 'relationships'
153
+ add_specific_info(obj, obj_info)
154
+ add_nodes(obj, mapping['nodes']) if mapping.key? 'nodes'
155
+ end
156
+ end
157
+ end
158
+
159
+ resolve_unitary_and_air_loops_overlap
160
+
161
+ # Check that relationships point somewhere
162
+ ids = @entities.flat_map { |entity| entity['id'] }
163
+ @entities.select { |entity| entity.key? 'relationships' }.each do |entity|
164
+ relationships = entity['relationships']
165
+ relationships.keys.each do |key|
166
+ if !ids.include? relationships[key]
167
+ relationships.delete(key)
168
+ entity.delete('relationships') if relationships.empty?
169
+ end
170
+ end
171
+ end
172
+ save_model
173
+ end
174
+
175
+ ##
176
+ # Necessary when adding additional output / EMS variables
177
+ # so they get stored in OSM
178
+ def save_model
179
+ @model.save(@path_to_model, true)
180
+ end
181
+
182
+ ##
183
+ # Reads templates and mappings into memory
184
+ # @note Must do before applying mappings
185
+ def read_templates_and_mappings
186
+ templates_path = File.join(@files_path, 'templates.yaml')
187
+ mappings_path = File.join(@files_path, 'mappings.json')
188
+ raise "File '#{templates_path}' does not exist" unless File.exist?(templates_path)
189
+ raise "File '#{mappings_path}' does not exist" unless File.exist?(mappings_path)
190
+ @templates = YAML.load_file(templates_path)
191
+ @mappings = JSON.parse(File.read(mappings_path))
192
+ end
193
+
194
+ ##
195
+ # Reads Brick and Haystack metadata into memory
196
+ # @note Must do before applying mappings
197
+ def read_metadata(brick_version = '1.1', haystack_version = '3.9.9')
198
+ @brick_version = brick_version
199
+ @haystack_version = haystack_version
200
+ read_brick_ttl_as_repository_object(brick_version)
201
+ read_haystack_ttl_as_repository_object(haystack_version)
202
+ end
203
+
204
+ private
205
+
206
+ def read_haystack_ttl_as_repository_object(version)
207
+ path = File.join(@files_path, "haystack/#{version}/defs.ttl")
208
+ raise "File '#{path}' does not exist" unless File.exist?(path)
209
+ @haystack_repo = RDF::Repository.load(path)
210
+ end
211
+
212
+ def read_brick_ttl_as_repository_object(version)
213
+ path = File.join(@files_path, "brick/#{version}/Brick.ttl")
214
+ raise "File '#{path}' does not exist" unless File.exist?(path)
215
+ @brick_repo = RDF::Repository.load(path)
216
+ end
217
+
218
+ def create_base_info_hash(openstudio_object)
219
+ temp = {}
220
+ temp['id'] = OpenStudio.removeBraces(openstudio_object.handle)
221
+ temp['dis'] = openstudio_object.name.instance_of?(String) ? openstudio_object.name : openstudio_object.name.get
222
+ return temp
223
+ end
224
+
225
+ def create_meter_base_info_hash(meter_name)
226
+ temp = {}
227
+ temp['id'] = OpenStudio.removeBraces(OpenStudio.createUUID).to_s
228
+ temp['dis'] = "#{meter_name} Meter Equipment"
229
+ return temp
230
+ end
231
+
232
+ def add_meter_specific_info(meter_object, term_info)
233
+ temp = {}
234
+ temp['id'] = OpenStudio.removeBraces(meter_object.handle)
235
+ temp['dis'] = "#{meter_object.name} Sensor"
236
+ temp = temp.merge(term_info)
237
+ @entities << temp
238
+ end
239
+
240
+ def add_specific_info(openstudio_object, term_info)
241
+ temp = create_base_info_hash(openstudio_object)
242
+ temp = temp.merge(term_info)
243
+ @entities << temp
244
+ end
245
+
246
+ def resolve_mandatory_tags(term)
247
+ q = "SELECT ?m WHERE { <#{@current_vocab[term]}> <#{RDF::RDFS.subClassOf}>* ?m . ?m <#{@ph_vocab.mandatory}> <#{@ph_vocab.marker}> }"
248
+ s = SPARQL::Client.new(@haystack_repo)
249
+ results = s.query(q)
250
+ necessary_tags = []
251
+ results.each do |r|
252
+ necessary_tags << r[:m].to_h[:fragment]
253
+ end
254
+ necessary_tags = necessary_tags.to_set
255
+ term_tags = term.split('-').to_set
256
+ difference = necessary_tags.difference(term_tags)
257
+ difference = difference.to_a
258
+ to_return = {'type' => term}
259
+ if !difference.empty?
260
+ to_return = to_return.merge('add_tags' => difference)
261
+ end
262
+ return to_return
263
+ end
264
+
265
+ def find_template(template)
266
+ @templates.each do |t|
267
+ if t['id'] == template
268
+ return t
269
+ end
270
+ end
271
+ return false
272
+ end
273
+
274
+ def resolve_template_from_mapping(mapping)
275
+ template = mapping[@metadata_type.downcase]['template']
276
+ return resolve_template(template)
277
+ end
278
+
279
+ def resolve_template(template)
280
+ if @current_repo.has_term? @current_vocab[template]
281
+ if @metadata_type == 'Haystack'
282
+ return resolve_mandatory_tags(template)
283
+ else
284
+ return {'type' => template}
285
+ end
286
+ else
287
+ template = find_template(template)
288
+ if template
289
+ type = template['base_type']
290
+ if @metadata_type == 'Haystack'
291
+ to_return = resolve_mandatory_tags(type)
292
+ else
293
+ to_return = {'type' => type}
294
+ end
295
+ if template.key? 'properties'
296
+ if to_return.key? 'add_tags'
297
+ to_return['add_tags'] += template['properties']
298
+ else
299
+ to_return['add_tags'] = template['properties']
300
+ end
301
+ end
302
+ return to_return
303
+ else
304
+ return {'type' => nil}
305
+ end
306
+ end
307
+ end
308
+
309
+ def add_relationship_info(obj, relationships, info)
310
+ relationships.each do |relationship|
311
+ info['relationships'] = {} unless info['relationships']
312
+ # Default to `this`
313
+ scope = 'this'
314
+ if relationship.key? 'method_scope'
315
+ scope = relationship['method_scope']
316
+ end
317
+ if scope == 'model'
318
+ obj = @model
319
+ ref = relationship['openstudio_method'].map { |method| obj.send(method) }.find(&:initialized)
320
+ break if ref.nil?
321
+ info['relationships'][relationship[@metadata_type.downcase]] = OpenStudio.removeBraces(ref.handle)
322
+ elsif scope == 'this'
323
+ ref = relationship['openstudio_method'].map { |method| obj.send(method) }.find(&:is_initialized)
324
+ break if ref.nil?
325
+ info['relationships'][relationship[@metadata_type.downcase]] = OpenStudio.removeBraces(ref.get.handle)
326
+ end
327
+ end
328
+ end
329
+
330
+ def add_meter_relationship_to_parent(parent_id, relationship, entity_info)
331
+ entity_info['relationships'] = {} unless entity_info['relationships']
332
+ entity_info['relationships'][relationship[@metadata_type.downcase]] = parent_id
333
+ return entity_info
334
+ end
335
+
336
+ def add_node_relationship_to_parent(parent_obj, relationship, entity_info)
337
+ entity_info['relationships'] = {} unless entity_info['relationships']
338
+ entity_info['relationships'][relationship[@metadata_type.downcase]] = OpenStudio.removeBraces(parent_obj.handle)
339
+ return entity_info
340
+ end
341
+
342
+ ##
343
+ # @return [Boolean or One of AirLoopHVACUnitary* objects]
344
+ def check_if_component_is_unitary(sc)
345
+ r = false
346
+ if sc.to_AirLoopHVACUnitaryHeatPumpAirToAir.is_initialized
347
+ r = sc.to_AirLoopHVACUnitaryHeatPumpAirToAir
348
+ elsif sc.to_AirLoopHVACUnitarySystem.is_initialized
349
+ r = sc.to_AirLoopHVACUnitarySystem
350
+ elsif sc.to_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed.is_initialized
351
+ r = sc.to_AirLoopHVACUnitaryHeatPumpAirToAirMultiSpeed
352
+ end
353
+ if r
354
+ r = r.get
355
+ end
356
+ return r
357
+ end
358
+
359
+ ##
360
+ # Deletes all of the AirLoopHVAC entities in favor of the contained unitary system
361
+ # and replaces the unitary entity id with the airloop id.
362
+ def resolve_unitary_and_air_loops_overlap
363
+ handles_to_swap = {}
364
+ @model.getAirLoopHVACs.each do |air_loop|
365
+ air_loop.supplyComponents.each do |sc|
366
+ unitary_system = check_if_component_is_unitary(sc)
367
+ if unitary_system
368
+ if unitary_system.airLoopHVAC.is_initialized
369
+ al = unitary_system.airLoopHVAC.get
370
+ al_handle = OpenStudio.removeBraces(al.handle).to_s
371
+ us_handle = OpenStudio.removeBraces(unitary_system.handle).to_s
372
+ @entities.delete_if { |entity| handles_to_swap[us_handle] = al_handle; entity['id'] == al_handle }
373
+ end
374
+ end
375
+ end
376
+ end
377
+
378
+ @entities.each do |entity|
379
+ if handles_to_swap.key? entity['id']
380
+ entity['id'] = handles_to_swap[entity['id']]
381
+ end
382
+ if entity.key? 'relationships'
383
+ entity['relationships'].each do |rel_type, ref|
384
+ if handles_to_swap.key? ref
385
+ entity['relationships'][rel_type] = handles_to_swap[ref]
386
+ end
387
+ end
388
+ end
389
+ end
390
+ end
391
+
392
+ ##
393
+ #
394
+ def apply_meter_mappings(meters, relationships, submeter_relationship, point_to_meter_relationship, parent_meter_id = nil)
395
+ meters.each do |k, v|
396
+ # next unless !meters.key? == 'meters'
397
+
398
+ # A new 'meter' entity is created, since no meter as an equipment exists in OpenStudio.
399
+ meter_equip_entity_info = resolve_template(v[@metadata_type.downcase]['equip_template'])
400
+ temp_info = create_meter_base_info_hash(k)
401
+ meter_equip_entity_info = meter_equip_entity_info.merge(temp_info)
402
+ obj_info = meter_equip_entity_info.deep_dup
403
+
404
+ # Can pass nil since only relationship should have method_scope = 'model'
405
+ # relationships will always stay the same
406
+ add_relationship_info(nil, relationships, obj_info)
407
+ if !parent_meter_id.nil?
408
+ obj_info['relationships'] = {} unless obj_info['relationships']
409
+ obj_info['relationships'][submeter_relationship[@metadata_type.downcase]] = parent_meter_id
410
+ end
411
+
412
+ @entities << obj_info
413
+
414
+ # Add actual point variable
415
+ meter_variable = @model.getOutputMeterByName(k)
416
+ if meter_variable.is_initialized
417
+ meter_variable = meter_variable.get
418
+ else
419
+ meter_variable = create_output_meter(@model, k)
420
+ end
421
+ point_info = resolve_template(v[@metadata_type.downcase]['point_template'])
422
+ point_info = add_meter_relationship_to_parent(obj_info['id'], point_to_meter_relationship, point_info)
423
+ add_meter_specific_info(meter_variable, point_info)
424
+
425
+ if v.key? 'meters'
426
+ apply_meter_mappings(v['meters'], relationships, submeter_relationship, point_to_meter_relationship, obj_info['id'])
427
+ end
428
+ end
429
+ end
430
+ end
431
+ end
432
+ end