From cc7ba98f51a6e568dc570544c8863ffc5b94561d Mon Sep 17 00:00:00 2001 From: heslinge Date: Fri, 14 Aug 2026 16:32:51 -0600 Subject: [PATCH 1/2] Add logger --- lib/BOSS/boss_logger.rb | 21 ++ .../buildingsync_reader.rb | 187 +++++++++++++----- lib/BOSS/osw_arg_populator.rb | 135 ++++++++++--- 3 files changed, 275 insertions(+), 68 deletions(-) create mode 100644 lib/BOSS/boss_logger.rb diff --git a/lib/BOSS/boss_logger.rb b/lib/BOSS/boss_logger.rb new file mode 100644 index 0000000..249939c --- /dev/null +++ b/lib/BOSS/boss_logger.rb @@ -0,0 +1,21 @@ +# ******************************************************************************* +# OpenStudio(R), Copyright (c) Alliance for Energy Innovation, LLC. +# BuildingSync(R), Copyright (c) Alliance for Energy Innovation, LLC. +# See also https://github.com/BuildingSync/BOSS/blob/develop/LICENSE.md +# ******************************************************************************* + +require 'logger' + +module BOSS + # module BOSSLogger + @@BOSS_logger = Logger.new($stdout) + + # Set Logger::DEBUG for development + @@BOSS_logger.level = Logger::DEBUG + ## + # Defining class variable "@@logger" to log errors, info and warning messages. + def self.BOSS_logger + @@BOSS_logger + end + # end +end diff --git a/lib/BOSS/buildingsync_reader/buildingsync_reader.rb b/lib/BOSS/buildingsync_reader/buildingsync_reader.rb index 028a0b6..3c83a2a 100644 --- a/lib/BOSS/buildingsync_reader/buildingsync_reader.rb +++ b/lib/BOSS/buildingsync_reader/buildingsync_reader.rb @@ -37,18 +37,31 @@ def get_epw_file_path return @epw_file_path end + BOSS.BOSS_logger.info("|\t|\t- checking for city state...") city, state = get_city_state if !city.nil? && !state.nil? - return BOSS::BCLWeatherFileDownloader.download_weather_file_from_city_name(city, state) + BOSS.BOSS_logger.info("|\t|\t city state: #{city}, #{state}") + BOSS.BOSS_logger.info("|\t|\t- downloading weather file for #{city}, #{state}...") + weather_file = BOSS::BCLWeatherFileDownloader.download_weather_file_from_city_name(city, state) + BOSS.BOSS_logger.info("|\t|\t downloaded") + return weather_file + else + BOSS.BOSS_logger.info("|\t|\t city state not found") end + BOSS.BOSS_logger.info("|\t|\t- checking for climate zone...") climate_zone = get_climate_zone if !climate_zone.nil? - return OpenstudioStandards::Weather.climate_zone_representative_weather_file_path(climate_zone) + BOSS.BOSS_logger.info("|\t|\t climate zone: #{climate_zone}") + BOSS.BOSS_logger.info("|\t|\t- downloading weather file for #{climate_zone}...") + weather_file = OpenstudioStandards::Weather.climate_zone_representative_weather_file_path(climate_zone) + BOSS.BOSS_logger.info("|\t|\t downloaded") + return weather_file + else + BOSS.BOSS_logger.info("|\t|\t climate_zone not found") end message = "Could not set a weather file as neither climate zone not city/state could be found" - OpenStudio.logFree(OpenStudio::Error, 'BOSS.BuildingSyncReader.get_epw_file_path', message) raise StandardError, 'BOSS.BuildingSyncReader.get_epw_file_path ' + message end @@ -67,10 +80,14 @@ def _format_climate_zone(climate_zone, type) preferred_type = @standard_to_be_used == ASHRAE90_1 ? "ASHRAE" : "CaliforniaTitle24" + BOSS.BOSS_logger.info("|\t|\t|\t- checking building climate zone...") climate_zone = _get_climate_zone(@building_xml, preferred_type) + BOSS.BOSS_logger.info("|\t|\t|\t climate zone: #{climate_zone}") return _format_climate_zone(climate_zone, preferred_type) if !climate_zone.nil? + BOSS.BOSS_logger.info("|\t|\t|\t- checking site climate zone...") climate_zone = _get_climate_zone(@site_xml, preferred_type) + BOSS.BOSS_logger.info("|\t|\t|\t climate zone: #{climate_zone}") return _format_climate_zone(climate_zone, preferred_type) if !climate_zone.nil? end @@ -85,18 +102,29 @@ def _get_city_state(xml) ] end + BOSS.BOSS_logger.info("|\t|\t|\t- checking building city state...") city, state = _get_city_state(@building_xml) + BOSS.BOSS_logger.info("|\t|\t|\t city state: #{city}, #{state}") return city, state if !city.nil? && !state.nil? + BOSS.BOSS_logger.info("|\t|\t|\t- checking site city state...") city, state = _get_city_state(@site_xml) + BOSS.BOSS_logger.info("|\t|\t|\t city state: #{city}, #{state}") return city, state if !city.nil? && !state.nil? end # Use buildings OccupancyClassification, total_floor_area, and total_number_floors to find a openstudio mapping in building_types_by_occupancy_classification.json def get_building_type_and_bar_division_method + BOSS.BOSS_logger.info("|\t|\t- gathering requirements of building type from file...") + BOSS.BOSS_logger.info("|\t|\t|\t- getting occupancy classification...") occupancy_classification = @building_xml.elements["#{@ns}OccupancyClassification"].text - total_floor_area = get_total_floor_area + BOSS.BOSS_logger.info("|\t|\t|\t occupancy classification: #{occupancy_classification}") + BOSS.BOSS_logger.info("|\t|\t|\t- getting total floor area...") + total_floor_area = get_total_floor_area(num_indent=4) + BOSS.BOSS_logger.info("|\t|\t|\t total_floor_area: #{total_floor_area}") + BOSS.BOSS_logger.info("|\t|\t|\t- getting number of floors...") total_number_floors = _get_total_number_floors + BOSS.BOSS_logger.info("|\t|\t|\t total_number_floors: #{total_number_floors}") # get possible building_types based on occupancy_classification building_types_by_occupancy_classification = JSON.parse( @@ -105,30 +133,37 @@ def get_building_type_and_bar_division_method ) building_types = building_types_by_occupancy_classification[occupancy_classification.to_sym] || [] # find the one thats the right size + BOSS.BOSS_logger.info("|\t|\t- finding building type and bar division method for occupancy_classification '#{occupancy_classification}', area of #{total_floor_area} sqft, #{total_number_floors} floors, standard '#{@standard_to_be_used}'...") building_types.each do |building_type| next if total_floor_area < (building_type[:min_floor_area]&.to_f || 0) # too small! next if total_floor_area > (building_type[:max_floor_area]&.to_f || Float::INFINITY) # too big! next if total_number_floors < (building_type[:min_number_floors]&.to_f || 0) # too small! next if total_number_floors > (building_type[:max_number_floors]&.to_f || Float::INFINITY) # too big! - return building_type[:standards_building_type][:"#{@standard_to_be_used}"], building_type[:bar_division_method] # just right! + # just right! + standards_building_type = building_type[:standards_building_type][:"#{@standard_to_be_used}"] + bar_division_method = building_type[:bar_division_method] + BOSS.BOSS_logger.info("|\t|\t found building type '#{standards_building_type}', bar division method '#{bar_division_method}'") + return standards_building_type, bar_division_method end end # tries to get total floor area from: # 1. /FloorAreas of building # 2. /FloorAreas of site - def get_total_floor_area + def get_total_floor_area(num_indent=2) # check site floor area - puts "++ site floor areas ++" + BOSS.BOSS_logger.info("#{"|\t"*num_indent}- checking site for total floor area...") site_floor_areas_xml = @site_xml.elements["#{@ns}FloorAreas"] - site_area = !site_floor_areas_xml.nil? ? _get_total_floor_area_from_floor_areas_xml(site_floor_areas_xml) : 0 + site_area = !site_floor_areas_xml.nil? ? _get_total_floor_area_from_floor_areas_xml(site_floor_areas_xml, num_indent+1) : 0 + BOSS.BOSS_logger.info("#{"|\t"*num_indent} site floor area is #{site_area}") return site_area if site_area > 0 # check building floor area + BOSS.BOSS_logger.info("#{"|\t"*num_indent}- checking building for total floor area...") building_floor_areas_xml = @building_xml.elements["#{@ns}FloorAreas"] - puts "++ building floor areas ++" - building_area = !building_floor_areas_xml.nil? ? _get_total_floor_area_from_floor_areas_xml(building_floor_areas_xml) : 0 + building_area = !building_floor_areas_xml.nil? ? _get_total_floor_area_from_floor_areas_xml(building_floor_areas_xml, num_indent+1) : 0 + BOSS.BOSS_logger.info("#{"|\t"*num_indent} building floor area is #{building_area}") return building_area if building_area > 0 # TODO: read sections @@ -139,7 +174,7 @@ def get_total_floor_area # 1. Gross floor area # 2. Sum of "Conditioned" + "Common" + "Heated and Cooled" + "Heated Only" + "Cooled Only" floor area # 3. Sum of "Conditioned above grade" + "Conditioned below grade" floor area - def _get_total_floor_area_from_floor_areas_xml(floor_areas_xml) + def _get_total_floor_area_from_floor_areas_xml(floor_areas_xml, num_indent=5) # build a hash of the floor area in meters, by type floor_area_by_type = {} floor_area_by_type.default = 0 @@ -150,18 +185,23 @@ def _get_total_floor_area_from_floor_areas_xml(floor_areas_xml) # puts " #{floor_area_type} #{floor_area} #{OpenStudio.convert(floor_area, 'ft^2', 'm^2').get}" floor_area_by_type[floor_area_type] += floor_area end - puts floor_area_by_type # first, try to get it from simply "Gross" + BOSS.BOSS_logger.info("#{"|\t"*num_indent} - checking gross floor area") gross_floor_area = floor_area_by_type["Gross"] + BOSS.BOSS_logger.info("#{"|\t"*num_indent} - gross floor area: #{gross_floor_area}") return gross_floor_area if gross_floor_area > 0 # next, try to get it from "Conditioned" + "Common" + "Heated and Cooled" + "Heated Only" + "Cooled Only" + BOSS.BOSS_logger.info("#{"|\t"*num_indent} - checking conditioned floor area") conditioned_area = floor_area_by_type["Conditioned"] + floor_area_by_type["Common"] + floor_area_by_type["Heated and Cooled"] + floor_area_by_type["Heated Only"] + floor_area_by_type["Cooled Only"] + BOSS.BOSS_logger.info("#{"|\t"*num_indent} - conditioned floor area: #{conditioned_area}") return conditioned_area if conditioned_area > 0 # next, try to get it from "Conditioned above grade" + "Conditioned below grade" + BOSS.BOSS_logger.info("#{"|\t"*num_indent} - checking conditioned floor area (above + below)") conditioned_area = floor_area_by_type["Conditioned above grade"] + floor_area_by_type["Conditioned below grade"] + BOSS.BOSS_logger.info("#{"|\t"*num_indent} - conditioned floor area (above + below): #{conditioned_area}") return conditioned_area if conditioned_area > 0 # thats all we got! @@ -171,38 +211,54 @@ def _get_total_floor_area_from_floor_areas_xml(floor_areas_xml) # tries to get total number floor from: # 1. building's FloorsAboveGrade # 2. building's ConditionedFloorsAboveGrade + UnconditionedFloorsAboveGrade - def get_floor_above_grade + def get_floor_above_grade(num_indent=2) + BOSS.BOSS_logger.info("#{"|\t"*num_indent}- checking building floors above grade...") floors_above_grade = @building_xml.elements["#{@ns}FloorsAboveGrade"]&.text + BOSS.BOSS_logger.info("#{"|\t"*num_indent} floors above grade: #{floors_above_grade}") return floors_above_grade.to_f if !floors_above_grade.nil? + BOSS.BOSS_logger.info("#{"|\t"*num_indent}- checking building conditioned floors above grade and unconditioned floors above grade...") conditioned_floors_above_grade = @building_xml.elements["#{@ns}ConditionedFloorsAboveGrade"]&.text unconditioned_floors_above_grade = @building_xml.elements["#{@ns}UnconditionedFloorsAboveGrade"]&.text + BOSS.BOSS_logger.info("#{"|\t"*num_indent} conditioned floors above grade: #{conditioned_floors_above_grade}") + BOSS.BOSS_logger.info("#{"|\t"*num_indent} unconditioned floors above grade: #{unconditioned_floors_above_grade}") if !conditioned_floors_above_grade.nil? || !unconditioned_floors_above_grade.nil? return (conditioned_floors_above_grade.to_f || 0) + (unconditioned_floors_above_grade.to_f || 0) end + BOSS.BOSS_logger.info("#{"|\t"*num_indent}- cannot find floors above grade") return nil end # tries to get total number floor from: # 1. building's FloorsBelowGrade # 2. building's ConditionedFloorsBelowGrade + UnconditionedFloorsBelowGrade - def get_floor_below_grade + def get_floor_below_grade(num_indent=2) + BOSS.BOSS_logger.info("#{"|\t"*num_indent}- checking building floors below grade...") floors_below_grade = @building_xml.elements["#{@ns}FloorsBelowGrade"]&.text + BOSS.BOSS_logger.info("#{"|\t"*num_indent} floors below grade: #{floors_below_grade}") return floors_below_grade.to_f if !floors_below_grade.nil? + BOSS.BOSS_logger.info("#{"|\t"*num_indent}- checking building conditioned floors below grade and unconditioned floors below grade...") conditioned_floors_below_grade = @building_xml.elements["#{@ns}ConditionedFloorsBelowGrade"]&.text unconditioned_floors_below_grade = @building_xml.elements["#{@ns}UnconditionedFloorsBelowGrade"]&.text + BOSS.BOSS_logger.info("#{"|\t"*num_indent} conditioned floors below grade: #{conditioned_floors_below_grade}") + BOSS.BOSS_logger.info("#{"|\t"*num_indent} unconditioned floors below grade: #{unconditioned_floors_below_grade}") if !conditioned_floors_below_grade.nil? || !unconditioned_floors_below_grade.nil? return (conditioned_floors_below_grade.to_f || 0) + (unconditioned_floors_below_grade.to_f || 0) end + BOSS.BOSS_logger.info("#{"|\t"*num_indent}- cannot find floors below grade") return nil end def _get_total_number_floors - floors_above_grade = get_floor_above_grade - floors_below_grade = get_floor_below_grade + BOSS.BOSS_logger.info("|\t|\t|\t|\t- getting floors above grade...") + floors_above_grade = get_floor_above_grade(num_indent=5) + BOSS.BOSS_logger.info("|\t|\t|\t|\t floors above grade: #{floors_above_grade}") + BOSS.BOSS_logger.info("|\t|\t|\t|\t- getting floors below grade...") + floors_below_grade = get_floor_below_grade(num_indent=5) + BOSS.BOSS_logger.info("|\t|\t|\t|\t floors below grade: #{floors_below_grade}") return (floors_above_grade || 1) + (floors_below_grade || 0) end @@ -212,43 +268,60 @@ def _get_total_number_floors # 1. /YearOfLastMajorRemodel of building # 2. /YearOfConstruction of building def get_built_year + BOSS.BOSS_logger.info("|\t|\t|\t|\t- checking year of major remodel...") year_of_major_remodel = @building_xml.elements["#{@ns}YearOfLastMajorRemodel"]&.text.to_f - return year_of_major_remodel if !year_of_major_remodel.nil? + BOSS.BOSS_logger.info("|\t|\t|\t|\t year of major remodel: #{year_of_major_remodel}...") + return year_of_major_remodel if !year_of_major_remodel.nil? && year_of_major_remodel != 0 + BOSS.BOSS_logger.info("|\t|\t|\t|\t- checking year of construction...") year_of_construction = @building_xml.elements["#{@ns}YearOfConstruction"]&.text.to_f - return year_of_construction if !year_of_construction.nil? + BOSS.BOSS_logger.info("|\t|\t|\t|\t year of construction: #{year_of_construction}...") + return year_of_construction if !year_of_construction.nil? && year_of_construction != 0 end # map year built and standard_to_be_used to a standard_template def get_standard_template - built_year = get_built_year - if @standard_to_be_used == CA_TITLE24 - return "DEER Pre-1975" if built_year < 1975 - return "DEER 1985" if built_year >= 1975 && built_year < 1985 - return "DEER 1996" if built_year >= 1985 && built_year < 1996 - return "DEER 2003" if built_year >= 1996 && built_year < 2003 - return "DEER 2007" if built_year >= 2003 && built_year < 2007 - return "DEER 2011" if built_year >= 2007 && built_year < 2011 - return "DEER 2014" if built_year >= 2011 && built_year < 2014 - return "DEER 2015" if built_year >= 2014 && built_year < 2015 - return "DEER 2017" if built_year >= 2015 && built_year < 2017 - return "DEER 2020" - - elsif (@standard_to_be_used == ASHRAE90_1) - return "DOE Ref Pre-1980" if built_year < 1980 - return "DOE Ref 1980-2004" if built_year >= 1980 && built_year < 2004 - return "90.1-2007" if built_year >= 2004 && built_year < 2007 - return "90.1-2010" if built_year >= 2007 && built_year < 2010 - return "90.1-2013" if built_year >= 2010 && built_year < 2013 - return "90.1-2016" if built_year >= 2013 && built_year < 2016 - return "90.1-2019" + def _get_standard_template(built_year) + if @standard_to_be_used == CA_TITLE24 + return "DEER Pre-1975" if built_year < 1975 + return "DEER 1985" if built_year >= 1975 && built_year < 1985 + return "DEER 1996" if built_year >= 1985 && built_year < 1996 + return "DEER 2003" if built_year >= 1996 && built_year < 2003 + return "DEER 2007" if built_year >= 2003 && built_year < 2007 + return "DEER 2011" if built_year >= 2007 && built_year < 2011 + return "DEER 2014" if built_year >= 2011 && built_year < 2014 + return "DEER 2015" if built_year >= 2014 && built_year < 2015 + return "DEER 2017" if built_year >= 2015 && built_year < 2017 + return "DEER 2020" + + elsif (@standard_to_be_used == ASHRAE90_1) + return "DOE Ref Pre-1980" if built_year < 1980 + return "DOE Ref 1980-2004" if built_year >= 1980 && built_year < 2004 + return "90.1-2007" if built_year >= 2004 && built_year < 2007 + return "90.1-2010" if built_year >= 2007 && built_year < 2010 + return "90.1-2013" if built_year >= 2010 && built_year < 2013 + return "90.1-2016" if built_year >= 2013 && built_year < 2016 + return "90.1-2019" + end end + + BOSS.BOSS_logger.info("|\t|\t- gathering requirements of template from file...") + BOSS.BOSS_logger.info("|\t|\t|\t- getting year built...") + built_year = get_built_year + BOSS.BOSS_logger.info("|\t|\t|\t year built: #{built_year}") + BOSS.BOSS_logger.info("|\t|\t- finding template for building built in #{built_year}, standard #{@standard_to_be_used}...") + template = _get_standard_template(built_year) + BOSS.BOSS_logger.info("|\t|\t template: #{template}") + + return template end def get_floor_to_floor_height + BOSS.BOSS_logger.info("|\t|\t- checking the floor height of every section in building from file...") section_elements = @building_xml.elements.each("#{@ns}Sections/#{@ns}Section"){|s| s} return nil if section_elements.nil? floor_to_floor_heights = section_elements.map {|section_element| section_element.elements["#{@ns}FloorToFloorHeight"]&.text.to_f} + BOSS.BOSS_logger.info("|\t|\t section floor heights: #{floor_to_floor_heights}") return floor_to_floor_heights.first end @@ -262,9 +335,13 @@ def get_aspect_ratio # get principal hvac system type # @return [String] def get_principal_HVAC_system_type + BOSS.BOSS_logger.info("|\t|\t- finding first hvac system with principal hvac system type...") # if no hvac systems, return nil hvac_systems = @facility_xml.elements.each("#{@ns}Systems/#{@ns}HVACSystems/#{@ns}HVACSystem") {|s| s} - return nil if hvac_systems.nil? + if hvac_systems.nil? + BOSS.BOSS_logger.info("|\t|\t no hvac systems found") + return nil + end # find first none nil principal_HVAC_system_type hvac_systems = hvac_systems.reject {|s| s.class == REXML::Comment} @@ -272,20 +349,35 @@ def get_principal_HVAC_system_type first_principal_HVAC_system_type = principal_HVAC_system_type.find {|x| !x.nil?} # if none, return nil, else return mapping - return nil if first_principal_HVAC_system_type.nil? - return BuildingSyncToOSSystemMaps.get_hvac_map[first_principal_HVAC_system_type.to_s] + if first_principal_HVAC_system_type.nil? + BOSS.BOSS_logger.info("|\t|\t no hvac systems principal hvac system type ") + return nil + end + + # map + BOSS.BOSS_logger.info("|\t|\t principal hvac system type: #{first_principal_HVAC_system_type}") + BOSS.BOSS_logger.info("|\t|\t- mapping #{first_principal_HVAC_system_type} to measure arg options...") + os_principal_HVAC_system_type = BuildingSyncToOSSystemMaps.get_hvac_map[first_principal_HVAC_system_type.to_s] + BOSS.BOSS_logger.info("|\t|\t os principal hvac system type: #{os_principal_HVAC_system_type}") + + return os_principal_HVAC_system_type end # get sum of /Systems/LightingSystems/LightingSystem/InstalledPower # @return [String] def get_total_installed_power # if no lighting systems, return nil + BOSS.BOSS_logger.info("|\t|\t|\t- finding all lighting system installed power...") lighting_systems = @facility_xml.elements.each("#{@ns}Systems/#{@ns}LightingSystems/#{@ns}LightingSystem") {|s| s} - return nil if lighting_systems.nil? + if lighting_systems.nil? + BOSS.BOSS_logger.info("|\t|\t|\t no lighting systems found") + return nil + end # get all all_installed_powers lighting_systems = lighting_systems.reject {|s| s.class == REXML::Comment} all_installed_powers = lighting_systems.map {|s| s.elements["#{@ns}InstalledPower/"]&.first&.to_s&.to_f} + BOSS.BOSS_logger.info("|\t|\t|\t all lighting system installed power: #{all_installed_powers}") # if any nil, return nil, else return sum return nil if all_installed_powers.length == 0 @@ -297,13 +389,16 @@ def get_total_installed_power # @return [String] def get_total_weighted_average_load # if no plug_loads, return nil + BOSS.BOSS_logger.info("|\t|\t|\t- finding all plug load systems weighted average load...") plug_loads = @facility_xml.elements.each("#{@ns}Systems/#{@ns}PlugLoads/#{@ns}PlugLoad") {|s| s} - return nil if plug_loads.nil? - + if plug_loads.nil? + BOSS.BOSS_logger.info("|\t|\t|\t no plug load systems found") + return nil + end # get all weighted_average_loads plug_loads = plug_loads.reject {|s| s.class == REXML::Comment} all_weighted_average_loads = plug_loads.map {|s| s.elements["#{@ns}WeightedAverageLoad/"]&.text} - + BOSS.BOSS_logger.info("|\t|\t|\t all plug load system weighted average load: #{all_weighted_average_loads}") # if any nil, return nil, else return sum return nil if all_weighted_average_loads.length == 0 diff --git a/lib/BOSS/osw_arg_populator.rb b/lib/BOSS/osw_arg_populator.rb index 2d899d0..a166aaf 100644 --- a/lib/BOSS/osw_arg_populator.rb +++ b/lib/BOSS/osw_arg_populator.rb @@ -5,9 +5,11 @@ # ******************************************************************************* require 'openstudio/extension' +require 'BOSS/boss_logger' class OSWArgPopulator def self.populate_set_run_period_args(osw, bsync_reader) + BOSS.BOSS_logger.info("+++++++++++++++++++++++++++ populating measure set_run_period +++++++++++++++++++++++") osw[:steps].append({"measure_dir_name": "set_run_period", "arguments": {}}) set_measure_argument = lambda {| key, value | OpenStudio::Extension.set_measure_argument(osw, "set_run_period", key, value) } @@ -15,35 +17,58 @@ def self.populate_set_run_period_args(osw, bsync_reader) # - __SKIP__ set_measure_argument.call("__SKIP__", false) # - timesteps_per_hour + BOSS.BOSS_logger.info("|\t- setting arg 'timesteps_per_hour'...") + BOSS.BOSS_logger.info("|\t defaults to '4'") set_measure_argument.call("timesteps_per_hour", "4") # - begin_date + BOSS.BOSS_logger.info("|\t- setting arg 'begin_date'...") + BOSS.BOSS_logger.info("|\t defaults to '2019-01-01'") set_measure_argument.call("begin_date", "2019-01-01") # - end_date + BOSS.BOSS_logger.info("|\t- setting arg 'end_date'...") + BOSS.BOSS_logger.info("|\t defaults to '2019-12-31'") set_measure_argument.call("end_date", "2019-12-31") + + BOSS.BOSS_logger.info("++++++++++++++++++++++++++++++++++ populated. +++++++++++++++++++++++++++++++++++++++") + BOSS.BOSS_logger.info("") end def self.populate_change_building_location_args(osw, bsync_reader) + BOSS.BOSS_logger.info("+++++++++++++++++++++ populating measure ChangeBuildingLocation +++++++++++++++++++++") osw[:steps].append({"measure_dir_name": "ChangeBuildingLocation", "arguments": {}}) set_measure_argument = lambda {| key, value | OpenStudio::Extension.set_measure_argument(osw, "ChangeBuildingLocation", key, value) } # - __SKIP__ set_measure_argument.call("__SKIP__", false) # - weather_file_name - set_measure_argument.call("weather_file_name", bsync_reader.get_epw_file_path) + BOSS.BOSS_logger.info("|\t- setting arg 'weather_file_name'...") + weather_file_name = bsync_reader.get_epw_file_path + BOSS.BOSS_logger.info("|\t set to #{weather_file_name}") + set_measure_argument.call("weather_file_name", weather_file_name) # - climate_zone - set_measure_argument.call("climate_zone", bsync_reader.get_climate_zone || "Lookup From Stat File") + BOSS.BOSS_logger.info("|\t- setting arg 'climate_zone'...") + climate_zone = bsync_reader.get_climate_zone || "Lookup From Stat File" + BOSS.BOSS_logger.info("|\t set to #{climate_zone}") + set_measure_argument.call("climate_zone", climate_zone) + + BOSS.BOSS_logger.info("++++++++++++++++++++++++++++++++++ populated. +++++++++++++++++++++++++++++++++++++++") + BOSS.BOSS_logger.info("") end def self.populate_create_bar_from_building_type_ratios_args(osw, bsync_reader) + BOSS.BOSS_logger.info("++++ populating measure create_bar_from_building_type_ratios ++++++++++++++") osw[:steps].append({"measure_dir_name": "create_bar_from_building_type_ratios", "arguments": {}}) set_measure_argument = lambda {| key, value | OpenStudio::Extension.set_measure_argument(osw, "create_bar_from_building_type_ratios", key, value) } - building_type, bar_division_method = bsync_reader.get_building_type_and_bar_division_method + # Add args # - __SKIP__ set_measure_argument.call("__SKIP__", false) # - bldg_type_a + BOSS.BOSS_logger.info("|\t- setting arg 'bldg_type_a'...") + building_type, bar_division_method = bsync_reader.get_building_type_and_bar_division_method + BOSS.BOSS_logger.info("|\t set to #{building_type}") set_measure_argument.call("bldg_type_a", building_type) # - bldg_type_a_num_units # - bldg_type_b @@ -56,44 +81,68 @@ def self.populate_create_bar_from_building_type_ratios_args(osw, bsync_reader) # - bldg_type_d_fract_bldg_area # - bldg_type_d_num_units # - total_bldg_floor_area - set_measure_argument.call("total_bldg_floor_area", bsync_reader.get_total_floor_area) + BOSS.BOSS_logger.info("|\t- setting arg 'total_bldg_floor_area'...") + total_bldg_floor_area = bsync_reader.get_total_floor_area(num_indent=2) + BOSS.BOSS_logger.info("|\t set to #{total_bldg_floor_area}") + set_measure_argument.call("total_bldg_floor_area", total_bldg_floor_area) # - floor_height -> FloorToFloorHeight, in total building section, else set to 0, smart default. - set_measure_argument.call("floor_height", bsync_reader.get_floor_to_floor_height || 0) + BOSS.BOSS_logger.info("|\t- setting arg 'floor_height'...") + floor_height = bsync_reader.get_floor_to_floor_height || 0 + BOSS.BOSS_logger.info("|\t set to #{floor_height}") + set_measure_argument.call("floor_height", floor_height) # - num_stories_above_grade - set_measure_argument.call("num_stories_above_grade", bsync_reader.get_floor_above_grade || 1) + BOSS.BOSS_logger.info("|\t- setting arg 'num_stories_above_grade'...") + num_stories_above_grade = bsync_reader.get_floor_above_grade || 1 + BOSS.BOSS_logger.info("|\t set to #{num_stories_above_grade}") + set_measure_argument.call("num_stories_above_grade", num_stories_above_grade) # - num_stories_below_grade - set_measure_argument.call("num_stories_below_grade", bsync_reader.get_floor_below_grade || 0) + BOSS.BOSS_logger.info("|\t- setting arg 'num_stories_below_grade'...") + num_stories_below_grade = bsync_reader.get_floor_below_grade || 0 + BOSS.BOSS_logger.info("|\t set to #{num_stories_below_grade}") + set_measure_argument.call("num_stories_below_grade", num_stories_below_grade) # - building_rotation # - template - set_measure_argument.call("template", bsync_reader.get_standard_template) + BOSS.BOSS_logger.info("|\t- setting arg 'template'...") + template = bsync_reader.get_standard_template + BOSS.BOSS_logger.info("|\t set to #{template}") + set_measure_argument.call("template", template) # - ns_to_ew_ratio - set_measure_argument.call("ns_to_ew_ratio", bsync_reader.get_aspect_ratio || 0) + BOSS.BOSS_logger.info("|\t- setting arg 'ns_to_ew_ratio'...") + ns_to_ew_ratio = bsync_reader.get_aspect_ratio || 0 + BOSS.BOSS_logger.info("|\t set to #{ns_to_ew_ratio}") + set_measure_argument.call("ns_to_ew_ratio", ns_to_ew_ratio) # - wwr # - party_wall_fraction # set_measure_argument.call("party_wall_fraction", building.party_wall_fraction.to_f) # - story_multiplier_method + BOSS.BOSS_logger.info("|\t- setting arg 'story_multiplier_method'...") + BOSS.BOSS_logger.info("|\t defaults to 'None'") set_measure_argument.call("story_multiplier_method", "None") # needed until os allows mutli basement # - bar_division_method, as of right now, always use the default # set_measure_argument.call("bar_division_method", bar_division_method) + + BOSS.BOSS_logger.info("++++++++++++++++++++++++++++++++++ populated. +++++++++++++++++++++++++++++++++++++++") + BOSS.BOSS_logger.info("") end def self.populate_create_typical_building_from_model_args(osw, bsync_reader) + BOSS.BOSS_logger.info("+++++ populating measure create_typical_building_from_model +++++++++++++++") osw[:steps].append({"measure_dir_name": "create_typical_building_from_model", "arguments": {}}) set_measure_argument = lambda {| key, value | OpenStudio::Extension.set_measure_argument(osw, "create_typical_building_from_model", key, value) } - principal_HVAC_system_type = bsync_reader.get_principal_HVAC_system_type - if principal_HVAC_system_type.nil? - OpenStudio.logFree(OpenStudio::Warn, 'BuildingSync.OSWARGPopulator.populate_create_typical_building_from_model_args', - 'No PrincipalHVACSystemType found in the BuildingSync XML. HVAC and SWH systems will be defaulted using the Inferred system type.') - end - # Add args # - __SKIP__ set_measure_argument.call("__SKIP__", false) # template - set_measure_argument.call("template", bsync_reader.get_standard_template) + BOSS.BOSS_logger.info("|\t- setting arg 'template'...") + template = bsync_reader.get_standard_template + BOSS.BOSS_logger.info("|\t set to #{template}") + set_measure_argument.call("template", template) # system_type - set_measure_argument.call("system_type", principal_HVAC_system_type || "Inferred") + BOSS.BOSS_logger.info("|\t- setting arg 'system_type'...") + system_type = bsync_reader.get_principal_HVAC_system_type || "Inferred" + BOSS.BOSS_logger.info("|\t set to #{system_type}") + set_measure_argument.call("system_type", system_type) # hvac_delivery_type # htg_src # clg_src @@ -109,9 +158,13 @@ def self.populate_create_typical_building_from_model_args(osw, bsync_reader) # onsite_parking_fraction # add_exhaust # add_swh + BOSS.BOSS_logger.info("|\t- setting arg 'add_swh'...") + BOSS.BOSS_logger.info("|\t defaults to 'true'") set_measure_argument.call("add_swh", true) # add_thermostat # add_hvac + BOSS.BOSS_logger.info("|\t- setting arg 'add_hvac'...") + BOSS.BOSS_logger.info("|\t defaults to 'true'") set_measure_argument.call("add_hvac", true) # add_refrigeration # modify_wkdy_op_hrs @@ -123,18 +176,32 @@ def self.populate_create_typical_building_from_model_args(osw, bsync_reader) # unmet_hours_tolerance # remove_objects # use_upstream_args - set to false to prevent nested workflow creation + BOSS.BOSS_logger.info("|\t- setting arg 'use_upstream_args'...") + BOSS.BOSS_logger.info("|\t defaults to 'false'") set_measure_argument.call("use_upstream_args", false) # enable_dst + + BOSS.BOSS_logger.info("++++++++++++++++++++++++++++++++++ populated. +++++++++++++++++++++++++++++++++++++++") + BOSS.BOSS_logger.info("") end def self.populate_set_lighting_loads_by_LPD_args(osw, bsync_reader) + BOSS.BOSS_logger.info("++++++++++++++++++++++ populating measure SetLightingLoadsByLPD ++++++++++++++++++++") osw[:steps].append({"measure_dir_name": "SetLightingLoadsByLPD", "arguments": {}}) set_measure_argument = lambda {| key, value | OpenStudio::Extension.set_measure_argument(osw, "SetLightingLoadsByLPD", key, value) } # skip if no total_installed_power or no usable floor area + BOSS.BOSS_logger.info("|\t- gathering measure requirements...") + BOSS.BOSS_logger.info("|\t|\t- getting total install power...") total_installed_power = bsync_reader.get_total_installed_power - floor_area = bsync_reader.get_total_floor_area + BOSS.BOSS_logger.info("|\t|\t total install power: #{total_installed_power}") + BOSS.BOSS_logger.info("|\t|\t- getting total floor area...") + floor_area = bsync_reader.get_total_floor_area(num_indent=3) + BOSS.BOSS_logger.info("|\t|\t total floor area: #{floor_area}") if total_installed_power.nil? || total_installed_power == 0 || floor_area.nil? || floor_area <= 0 + BOSS.BOSS_logger.info("|\t measure requirements unmet. Skippping.") + BOSS.BOSS_logger.info("+++++++++++++++++++++++++++++++++++ skipped. ++++++++++++++++++++++++++++++++++++++++") + BOSS.BOSS_logger.info("") set_measure_argument.call("__SKIP__", true) return end @@ -144,7 +211,10 @@ def self.populate_set_lighting_loads_by_LPD_args(osw, bsync_reader) set_measure_argument.call("__SKIP__", false) # space_type "entire building" # lpd - set_measure_argument.call("lpd", total_installed_power * 1000.0 / floor_area) + BOSS.BOSS_logger.info("|\t- setting arg 'lpd'...") + lpd = total_installed_power * 1000.0 / floor_area + BOSS.BOSS_logger.info("|\t set to #{lpd}") + set_measure_argument.call("lpd", lpd) # add_instance_all_spaces # material_cost # demolition_cost @@ -153,17 +223,27 @@ def self.populate_set_lighting_loads_by_LPD_args(osw, bsync_reader) # expected_life # om_cost # om_frequency - + BOSS.BOSS_logger.info("++++++++++++++++++++++++++++++++++ populated. +++++++++++++++++++++++++++++++++++++++") + BOSS.BOSS_logger.info("") end def self.populate_set_electric_equipment_loads_by_epd_args(osw, bsync_reader) + BOSS.BOSS_logger.info("+++++ populating measure set_electric_equipment_loads_by_epd ++++++++++++++") osw[:steps].append({"measure_dir_name": "set_electric_equipment_loads_by_epd", "arguments": {}}) set_measure_argument = lambda {| key, value | OpenStudio::Extension.set_measure_argument(osw, "set_electric_equipment_loads_by_epd", key, value) } # skip if no total_weighted_average_load or no usable floor area + BOSS.BOSS_logger.info("|\t- gathering measure requirements...") + BOSS.BOSS_logger.info("|\t|\t- getting total weighted average load...") total_weighted_average_load = bsync_reader.get_total_weighted_average_load - floor_area = bsync_reader.get_total_floor_area + BOSS.BOSS_logger.info("|\t|\t total weighted average load: #{total_weighted_average_load}") + BOSS.BOSS_logger.info("|\t|\t- getting total floor area...") + floor_area = bsync_reader.get_total_floor_area(num_indent=3) + BOSS.BOSS_logger.info("|\t|\t total floor area: #{floor_area}") if total_weighted_average_load.nil? || total_weighted_average_load == 0 || floor_area.nil? || floor_area <= 0 + BOSS.BOSS_logger.info("|\t measure requirements unmet. Skippping.") + BOSS.BOSS_logger.info("+++++++++++++++++++++++++++++++++++ skipped. ++++++++++++++++++++++++++++++++++++++++") + BOSS.BOSS_logger.info("") set_measure_argument.call("__SKIP__", true) return end @@ -173,7 +253,10 @@ def self.populate_set_electric_equipment_loads_by_epd_args(osw, bsync_reader) set_measure_argument.call("__SKIP__", false) # space_type "entire building" # epd - set_measure_argument.call("epd", total_weighted_average_load / floor_area) + BOSS.BOSS_logger.info("|\t- setting arg 'epd'...") + epd = total_weighted_average_load / floor_area + BOSS.BOSS_logger.info("|\t set to #{epd}") + set_measure_argument.call("epd", epd) # material_cost # demolition_cost # years_until_costs_start @@ -181,9 +264,12 @@ def self.populate_set_electric_equipment_loads_by_epd_args(osw, bsync_reader) # expected_life # om_cost # om_frequency + BOSS.BOSS_logger.info("++++++++++++++++++++++++++++++++++ populated. +++++++++++++++++++++++++++++++++++++++") + BOSS.BOSS_logger.info("") end def self.populate_openstudio_results_args(osw, bsync_reader) + BOSS.BOSS_logger.info("+++++++++++++++++++++++++ populating measure openstudio_results +++++++++++++++++++++") 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) } @@ -191,6 +277,11 @@ def self.populate_openstudio_results_args(osw, bsync_reader) # - __SKIP__ set_measure_argument.call("__SKIP__", false) # - reg_monthly_details - enable monthly fuel breakdown for results processing + BOSS.BOSS_logger.info("|\t- setting arg 'reg_monthly_details'...") + BOSS.BOSS_logger.info("|\t defaults to 'true'") set_measure_argument.call("reg_monthly_details", true) + + BOSS.BOSS_logger.info("++++++++++++++++++++++++++++++++++ populated. +++++++++++++++++++++++++++++++++++++++") + BOSS.BOSS_logger.info("") end end From d15fe7a336868d3d0d91d6675eaf67313d2052bc Mon Sep 17 00:00:00 2001 From: Katherine Fleming <2205659+kflemin@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:42:12 -0600 Subject: [PATCH 2/2] small update for occupancy classification retrieval --- .../buildingsync_reader.rb | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/BOSS/buildingsync_reader/buildingsync_reader.rb b/lib/BOSS/buildingsync_reader/buildingsync_reader.rb index b77118c..299ef12 100644 --- a/lib/BOSS/buildingsync_reader/buildingsync_reader.rb +++ b/lib/BOSS/buildingsync_reader/buildingsync_reader.rb @@ -113,11 +113,36 @@ def _get_city_state(xml) return city, state if !city.nil? && !state.nil? end + # tries to get occupancy classification from: + # 1. OccupancyClassification directly on the building + # 2. OccupancyClassification of the building's sections: if only one section has it, use that one; + # if multiple sections have it, use the one attached to the largest floor area + def get_occupancy_classification + occupancy_classification = @building_xml.elements["#{@ns}OccupancyClassification"]&.text + return occupancy_classification if !occupancy_classification.nil? + + section_elements = @building_xml.elements.each("#{@ns}Sections/#{@ns}Section"){|s| s} + return nil if section_elements.nil? + + candidates = section_elements.filter_map do |section_element| + section_occupancy_classification = section_element.elements["#{@ns}OccupancyClassification"]&.text + next if section_occupancy_classification.nil? + + section_floor_areas_xml = section_element.elements["#{@ns}FloorAreas"] + section_floor_area = !section_floor_areas_xml.nil? ? _get_total_floor_area_from_floor_areas_xml(section_floor_areas_xml) : 0 + {occupancy_classification: section_occupancy_classification, floor_area: section_floor_area} + end + return nil if candidates.empty? + return candidates.first[:occupancy_classification] if candidates.length == 1 + + return candidates.max_by { |candidate| candidate[:floor_area] }[:occupancy_classification] + end + # Use buildings OccupancyClassification, total_floor_area, and total_number_floors to find a openstudio mapping in building_types_by_occupancy_classification.json def get_building_type_and_bar_division_method BOSS.BOSS_logger.info("|\t|\t- gathering requirements of building type from file...") BOSS.BOSS_logger.info("|\t|\t|\t- getting occupancy classification...") - occupancy_classification = @building_xml.elements["#{@ns}OccupancyClassification"].text + occupancy_classification = get_occupancy_classification BOSS.BOSS_logger.info("|\t|\t|\t occupancy classification: #{occupancy_classification}") BOSS.BOSS_logger.info("|\t|\t|\t- getting total floor area...") total_floor_area = get_total_floor_area(num_indent=4) @@ -269,7 +294,7 @@ def _get_total_number_floors # 2. /YearOfConstruction of building def get_built_year BOSS.BOSS_logger.info("|\t|\t|\t|\t- checking year of major remodel...") - ear_of_major_remodel = @building_xml.elements["#{@ns}YearOfLastMajorRemodel"]&.text + year_of_major_remodel = @building_xml.elements["#{@ns}YearOfLastMajorRemodel"]&.text BOSS.BOSS_logger.info("|\t|\t|\t|\t year of major remodel: #{year_of_major_remodel}...") return year_of_major_remodel.to_f if !year_of_major_remodel.nil?