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.
- checksums.yaml +7 -0
- data/.gitignore +49 -0
- data/.rakeTasks +7 -0
- data/.rspec +1 -0
- data/.rubocop.yml +9 -0
- data/.travis.yml +20 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +23 -0
- data/README.md +66 -0
- data/Rakefile +22 -0
- data/doc_templates/LICENSE.md +27 -0
- data/doc_templates/README.md.erb +42 -0
- data/doc_templates/copyright_erb.txt +36 -0
- data/doc_templates/copyright_js.txt +4 -0
- data/doc_templates/copyright_ruby.txt +34 -0
- data/lib/.DS_Store +0 -0
- data/lib/files/brick/1.1/Brick.ttl +11144 -0
- data/lib/files/haystack/3.9.9/defs.ttl +5205 -0
- data/lib/files/mappings.json +388 -0
- data/lib/files/templates.yaml +361 -0
- data/lib/measures/haystack/measure.rb +574 -0
- data/lib/measures/haystack/measure.xml +83 -0
- data/lib/openstudio/metadata/controls.rb +220 -0
- data/lib/openstudio/metadata/creator.rb +432 -0
- data/lib/openstudio/metadata/helpers.rb +184 -0
- data/lib/openstudio/metadata/serializer.rb +141 -0
- data/lib/openstudio/metadata/version.rb +40 -0
- data/lib/openstudio/metadata/writer.rb +107 -0
- data/lib/openstudio/metadata.rb +55 -0
- data/lib/openstudio-metadata.rb +1 -0
- data/openstudio-metadata.gemspec +32 -0
- data/test.rb +15 -0
- metadata +165 -0
@@ -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
|