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