openstudio-metadata 0.0.1

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.
@@ -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