diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2211171..d69b87f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -25,5 +25,6 @@ jobs:
gem install bundler -v 2.4.10
bundle config set --local path "$GITHUB_WORKSPACE/.bundle/install"
bundle install
+ bundle exec rake measures:install_external
- name: Run Tests
run: bundle exec rspec
diff --git a/lib/BOSS/boss.rb b/lib/BOSS/boss.rb
index e562777..f998e7f 100644
--- a/lib/BOSS/boss.rb
+++ b/lib/BOSS/boss.rb
@@ -48,6 +48,8 @@ def write_baseline_osw(xml_file_path, output_dir, epw_file_path, standard_to_be_
OSWArgPopulator::populate_set_lighting_loads_by_LPD_args(baseline_osw, bsync_reader)
OSWArgPopulator::populate_set_electric_equipment_loads_by_epd_args(baseline_osw, bsync_reader)
+ OSWArgPopulator::populate_replace_baseline_windows_args(baseline_osw, bsync_reader)
+
OSWArgPopulator::populate_openstudio_results_args(baseline_osw, bsync_reader)
# Gather extension-derived paths (typically gem-based measures/files) from ObjectSpace.
diff --git a/lib/BOSS/buildingsync_reader/buildingsync_reader.rb b/lib/BOSS/buildingsync_reader/buildingsync_reader.rb
index 028a0b6..524ea73 100644
--- a/lib/BOSS/buildingsync_reader/buildingsync_reader.rb
+++ b/lib/BOSS/buildingsync_reader/buildingsync_reader.rb
@@ -212,11 +212,11 @@ def _get_total_number_floors
# 1. /YearOfLastMajorRemodel of building
# 2. /YearOfConstruction of building
def get_built_year
- year_of_major_remodel = @building_xml.elements["#{@ns}YearOfLastMajorRemodel"]&.text.to_f
- return year_of_major_remodel if !year_of_major_remodel.nil?
+ year_of_major_remodel = @building_xml.elements["#{@ns}YearOfLastMajorRemodel"]&.text
+ return year_of_major_remodel.to_f if !year_of_major_remodel.nil?
- year_of_construction = @building_xml.elements["#{@ns}YearOfConstruction"]&.text.to_f
- return year_of_construction if !year_of_construction.nil?
+ year_of_construction = @building_xml.elements["#{@ns}YearOfConstruction"]&.text
+ return year_of_construction.to_f if !year_of_construction.nil?
end
# map year built and standard_to_be_used to a standard_template
@@ -310,5 +310,52 @@ def get_total_weighted_average_load
return nil if !all_weighted_average_loads.all?
return all_weighted_average_loads.map {|s| s.to_f}.sum
end
+
+ # first valid window that has all mappable fields
+ def get_window_data
+ # if no windows, return nil
+ fenestration_systems = @facility_xml.elements.each("#{@ns}Systems/#{@ns}FenestrationSystems/#{@ns}FenestrationSystem") {|s| s}
+ return nil if fenestration_systems.nil?
+
+ # get all windows
+ windows = fenestration_systems.select {|fs| fs.elements["#{@ns}FenestrationType/#{@ns}Window"] }
+ return nil if windows.empty?
+
+ # iter through windows until we get one with all the required fields
+ windows.each do |window|
+ # get data
+ fenestration_frame_material = window.elements["#{@ns}FenestrationFrameMaterial"]&.text
+ glass_type = window.elements["#{@ns}GlassType"]&.text
+ fenestration_glass_layers = window.elements["#{@ns}FenestrationGlassLayers"]&.text
+ solar_heat_gain_coefficient = window.elements["#{@ns}SolarHeatGainCoefficient"]&.text
+ visible_transmittance = window.elements["#{@ns}VisibleTransmittance"]&.text
+
+ # We only need one of fenestration_u_factor/fenestration_r_value
+ fenestration_u_factor = window.elements["#{@ns}FenestrationUFactor"]&.text
+ if fenestration_u_factor.nil?
+ fenestration_r_value = window.elements["#{@ns}FenestrationRValue"]&.text
+ if !fenestration_r_value.nil? then fenestration_u_factor = (1.0 / fenestration_r_value.to_f).to_s end
+ end
+
+ # map data
+ os_fenestration_frame_material = BuildingSyncToOSSystemMaps.get_frame_material_map[fenestration_frame_material.to_s]
+ os_glass_type = BuildingSyncToOSSystemMaps.get_glass_type_map[glass_type.to_s]
+ os_fenestration_glass_layers = BuildingSyncToOSSystemMaps.get_glass_layers_map[fenestration_glass_layers.to_s]
+
+ # return if we can use this one
+ if (
+ !os_fenestration_frame_material.nil? &&
+ !os_glass_type.nil? &&
+ !os_fenestration_glass_layers.nil? &&
+ !fenestration_u_factor.nil? &&
+ !solar_heat_gain_coefficient.nil? &&
+ !visible_transmittance.nil?
+ )
+ window_pane_type = [os_fenestration_glass_layers, os_glass_type, os_fenestration_frame_material].join(' - ')
+ return window_pane_type, fenestration_u_factor, solar_heat_gain_coefficient, visible_transmittance
+ end
+ end
+ return nil
end
end
+end
diff --git a/lib/BOSS/buildingsync_reader/systems_map.rb b/lib/BOSS/buildingsync_reader/systems_map.rb
index 32989a5..8ac2fde 100644
--- a/lib/BOSS/buildingsync_reader/systems_map.rb
+++ b/lib/BOSS/buildingsync_reader/systems_map.rb
@@ -26,4 +26,42 @@ def self.get_hvac_map
"Other" => ""
}
end
+
+ def self.get_glass_layers_map
+ return {
+ "Single pane" => "Single",
+ "Double pane" => "Double",
+ "Triple pane" => "Triple",
+ "Single paned with storm panel" => "Single",
+ }
+ end
+
+ def self.get_glass_type_map
+ return {
+ "Clear uncoated" => "No LowE - Clear",
+ "Low e" => "LowE - Clear",
+ "Tinted" => "No LowE - Tinted/Reflective",
+ "Tinted plus low e" => "LowE - Tinted/Reflective",
+ "Reflective" => "No LowE - Tinted/Reflective",
+ "Reflective on tint" => "No LowE - Tinted/Reflective",
+ "High performance tint" => "LowE - Tinted/Reflective",
+ "Sunbelt low E low SHGC" => "LowE - Tinted/Reflective",
+ # "Suspended film" => "",
+ # "Plastic" => "",
+ }
+ end
+
+ def self.get_frame_material_map
+ return {
+ "Aluminum uncategorized" => "Aluminum",
+ "Aluminum no thermal break" => "Aluminum",
+ "Aluminum thermal break" => "Thermally Broken Aluminum",
+ # "Clad" => "",
+ # "Composite" => "",
+ # "Fiberglass" => "",
+ # "Steel" => "",
+ # "Vinyl" => "",
+ "Wood" => "Wood",
+ }
+ end
end
diff --git a/lib/BOSS/osw_arg_populator.rb b/lib/BOSS/osw_arg_populator.rb
index 2d899d0..070eea5 100644
--- a/lib/BOSS/osw_arg_populator.rb
+++ b/lib/BOSS/osw_arg_populator.rb
@@ -183,6 +183,28 @@ def self.populate_set_electric_equipment_loads_by_epd_args(osw, bsync_reader)
# om_frequency
end
+ def self.populate_replace_baseline_windows_args(osw, bsync_reader)
+ osw[:steps].append({"measure_dir_name": "replace_baseline_windows", "arguments": {}})
+ set_measure_argument = lambda {| key, value | OpenStudio::Extension.set_measure_argument(osw, "replace_baseline_windows", key, value) }
+
+ # skip if no window_data
+ window_data = bsync_reader.get_window_data
+ if window_data.nil?
+ set_measure_argument.call("__SKIP__", true)
+ return
+ end
+ window_pane_type, fenestration_u_factor, solar_heat_gain_coefficient, visible_transmittance = window_data
+
+ # Add args
+ # - __SKIP__
+ set_measure_argument.call("__SKIP__", false)
+ set_measure_argument.call("window_pane_type", window_pane_type)
+ set_measure_argument.call("u_value_ip", fenestration_u_factor)
+ set_measure_argument.call("shgc", solar_heat_gain_coefficient)
+ set_measure_argument.call("vlt", visible_transmittance)
+
+ end
+
def self.populate_openstudio_results_args(osw, bsync_reader)
osw[:steps].append({"measure_dir_name": "openstudio_results", "arguments": {}})
set_measure_argument = lambda {| key, value | OpenStudio::Extension.set_measure_argument(osw, "openstudio_results", key, value) }
diff --git a/spec/files/v2.7.0/all_measures_applied_example.xml b/spec/files/v2.7.0/all_measures_applied_example.xml
new file mode 100644
index 0000000..e57205b
--- /dev/null
+++ b/spec/files/v2.7.0/all_measures_applied_example.xml
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+ All Measures Example Building
+
+
+
+ Denver
+ CO
+
+
+
+
+
+ 5B
+
+
+
+
+
+ Office
+
+
+ 2
+ 0
+
+
+
+
+ Gross
+ 15000.0
+
+
+
+
+ 1.5
+
+
+ 2000
+
+
+
+
+ 10.0
+
+
+
+
+
+
+
+
+
+
+
+
+ Packaged Rooftop Air Conditioner
+
+
+
+
+
+
+ 15
+
+
+
+
+
+
+
+
+
+ Aluminum no thermal break
+ Clear uncoated
+ Double pane
+ 0.50
+ 0.39
+ 0.39
+
+
+
+
+
+
+ 15000
+
+
+
+
+
+
+
diff --git a/spec/tests/integration/write_and_run_osws_spec.rb b/spec/tests/integration/write_and_run_osws_spec.rb
index 180c111..d45b4c3 100644
--- a/spec/tests/integration/write_and_run_osws_spec.rb
+++ b/spec/tests/integration/write_and_run_osws_spec.rb
@@ -11,6 +11,7 @@
test_configs = [
# file_name, standard, epw_path, schema_version
+ ['all_measures_applied_example.xml', ASHRAE90_1, nil, 'v2.7.0'], # exercises every measure path; uncomment to run full simulation
['179D_Example_Building.xml', ASHRAE90_1, nil, 'v2.7.0'],
['BETTER-1.0.0_SampleOffice_gemtest.xml', ASHRAE90_1, nil, 'v2.7.0'],
['building_151.xml', ASHRAE90_1, nil, 'v2.7.0'],
diff --git a/spec/tests/unit/buildingsync_reader_spec.rb b/spec/tests/unit/buildingsync_reader_spec.rb
index d7317d0..91d9d2a 100644
--- a/spec/tests/unit/buildingsync_reader_spec.rb
+++ b/spec/tests/unit/buildingsync_reader_spec.rb
@@ -23,6 +23,24 @@ def wrap_in_site(xml)
XML
end
+def wrap_in_systems(xml)
+ <<~XML
+
+
+
+
+
+
+
+
+ #{xml}
+
+
+
+
+ XML
+end
+
RSpec.describe 'BuildingSyncReader' do
describe 'get_climate_zone should' do
it "get from site" do
@@ -334,24 +352,6 @@ def wrap_in_systems(xml)
end
describe 'get_total_weighted_average_load should' do
- def wrap_in_systems(xml)
- <<~XML
-
-
-
-
-
-
-
-
- #{xml}
-
-
-
-
- XML
- end
-
it "sum weighted average load" do
# Set Up
doc = REXML::Document.new wrap_in_systems(<<~XML)
@@ -423,4 +423,134 @@ def wrap_in_systems(xml)
expect(buidingsync_reader.get_total_weighted_average_load).to eq nil
end
end
+
+ describe 'get_window_data should' do
+ good_window = <<~XML
+
+
+
+
+ Aluminum no thermal break
+ false
+ Average
+ Clear uncoated
+ Single pane
+ 0.391000
+ 0.391000
+ 3.241000
+
+ XML
+ invalid_window = <<~XML
+
+
+
+
+ Fiberglass
+ false
+ Average
+ Clear uncoated
+ Single pane
+ 0.391000
+ 0.391000
+ 3.241000
+
+ XML
+ door = <<~XML
+
+
+
+ Uninsulated metal
+
+
+
+ XML
+
+ it "work in happy case" do
+ # Set Up
+ doc = REXML::Document.new wrap_in_systems(<<~XML)
+
+ #{good_window}
+
+ XML
+
+ # Action
+ buidingsync_reader = BOSS::BuildingSyncReader.new(doc, nil, ASHRAE90_1)
+ window_pane_type, fenestration_u_factor, solar_heat_gain_coefficient, visible_transmittance = buidingsync_reader.get_window_data
+
+ # Assertion
+ expect(window_pane_type).to eq "Single - No LowE - Clear - Aluminum"
+ expect(fenestration_u_factor).to eq "3.241000"
+ expect(solar_heat_gain_coefficient).to eq "0.391000"
+ expect(visible_transmittance).to eq "0.391000"
+ end
+
+ it "ignore doors and skylights" do
+ # Set Up
+ doc = REXML::Document.new wrap_in_systems(<<~XML)
+
+ #{door}
+ #{good_window}
+
+ XML
+
+ # Action
+ buidingsync_reader = BOSS::BuildingSyncReader.new(doc, nil, ASHRAE90_1)
+ window_pane_type, fenestration_u_factor, solar_heat_gain_coefficient, visible_transmittance = buidingsync_reader.get_window_data
+
+ # Assertion
+ expect(window_pane_type).to eq "Single - No LowE - Clear - Aluminum"
+ expect(fenestration_u_factor).to eq "3.241000"
+ expect(solar_heat_gain_coefficient).to eq "0.391000"
+ expect(visible_transmittance).to eq "0.391000"
+ end
+
+ it "work if first window is invalid but second isnt" do
+ # Set Up
+ doc = REXML::Document.new wrap_in_systems(<<~XML)
+
+ #{invalid_window}
+ #{good_window}
+
+ XML
+
+ # Action
+ buidingsync_reader = BOSS::BuildingSyncReader.new(doc, nil, ASHRAE90_1)
+ window_pane_type, fenestration_u_factor, solar_heat_gain_coefficient, visible_transmittance = buidingsync_reader.get_window_data
+
+ # Assertion
+ expect(window_pane_type).to eq "Single - No LowE - Clear - Aluminum"
+ expect(fenestration_u_factor).to eq "3.241000"
+ expect(solar_heat_gain_coefficient).to eq "0.391000"
+ expect(visible_transmittance).to eq "0.391000"
+ end
+
+ it "return nil if there are no windows" do
+ # Set Up
+ doc = REXML::Document.new wrap_in_systems("")
+
+ # Action
+ buidingsync_reader = BOSS::BuildingSyncReader.new(doc, nil, ASHRAE90_1)
+ window_pane_type, fenestration_u_factor, solar_heat_gain_coefficient, visible_transmittance = buidingsync_reader.get_window_data
+
+ # Assertion
+ expect(window_pane_type).to eq nil
+ end
+
+ # warn not error
+ it "return nil if all windows are invalid" do
+ # Set Up
+ doc = REXML::Document.new wrap_in_systems(<<~XML)
+
+ #{invalid_window}
+
+ XML
+
+ # Action
+ buidingsync_reader = BOSS::BuildingSyncReader.new(doc, nil, ASHRAE90_1)
+ window_pane_type, fenestration_u_factor, solar_heat_gain_coefficient, visible_transmittance = buidingsync_reader.get_window_data
+
+ # Assertion
+ expect(window_pane_type).to eq nil
+ end
+ end
end