# ******************************************************************************* # OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC. # See also https://openstudio.net/license # ******************************************************************************* module OsLib_QAQC # include any general notes about QAQC method here # checks the number of unmet hours in the model def check_domestic_hot_water(category, target_standard, min_pass, max_pass, name_only = false) # TODO: - could expose meal turnover and people per unit for res and hotel into arguments # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Domestic Hot Water') check_elems << OpenStudio::Attribute.new('category', category) if target_standard == 'ICC IECC 2015' check_elems << OpenStudio::Attribute.new('description', 'Check service water heating consumption against Table R405.5.2(1) in ICC IECC 2015 Residential Provisions.') else check_elems << OpenStudio::Attribute.new('description', 'Check against the 2011 ASHRAE Handbook - HVAC Applications, Table 7 section 50.14.') end check_elems << OpenStudio::Attribute.new('min_pass', min_pass * 100) check_elems << OpenStudio::Attribute.new('max_pass', max_pass * 100) # stop here if only name is requested this is used to populate display name for arguments if name_only == true results = [] check_elems.each do |elem| next if ['Double','Integer'].include? (elem.valueType.valueDescription) results << elem.valueAsString end return results end # Versions of OpenStudio greater than 2.4.0 use a modified version of # openstudio-standards with different method calls. These methods # require a "Standard" object instead of the standard being passed into method calls. # This Standard object is used throughout the QAQC check. if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.3') use_old_gem_code = true else use_old_gem_code = false std = Standard.build(target_standard) end begin # loop through water_use_equipment service_water_consumption_daily_avg_gal = 0.0 @model.getWaterUseEquipments.each do |water_use_equipment| # get peak flow rate from def peak_flow_rate_si = water_use_equipment.waterUseEquipmentDefinition.peakFlowRate source_units = 'm^3/s' target_units = 'gal/min' peak_flow_rate_ip = OpenStudio.convert(peak_flow_rate_si, source_units, target_units).get # get value from flow rate schedule if water_use_equipment.flowRateFractionSchedule.is_initialized # get annual equiv for model schedule schedule_inst = water_use_equipment.flowRateFractionSchedule.get if schedule_inst.to_ScheduleRuleset.is_initialized if use_old_gem_code annual_equiv_flow_rate = schedule_inst.to_ScheduleRuleset.get.annual_equivalent_full_load_hrs else annual_equiv_flow_rate = std.schedule_ruleset_annual_equivalent_full_load_hrs(schedule_inst.to_ScheduleRuleset.get) end elsif schedule_inst.to_ScheduleConstant.is_initialized if use_old_gem_code annual_equiv_flow_rate = schedule_inst.to_ScheduleConstant.get.annual_equivalent_full_load_hrs else annual_equiv_flow_rate = std.schedule_constant_annual_equivalent_full_load_hrs(schedule_inst.to_ScheduleConstant.get) end else check_elems << OpenStudio::Attribute.new('flag', "#{schedule_inst.name} isn't a Ruleset or Constant schedule. Can't calculate annual equivalent full load hours.") next end else # issue flag check_elems << OpenStudio::Attribute.new('flag', "#{water_use_equipment.name} doesn't have a schedule. Can't identify hot water consumption.") next end # add to global service water consumpiton value service_water_consumption_daily_avg_gal += 60.0 * peak_flow_rate_ip * annual_equiv_flow_rate / 365.0 end if target_standard == 'ICC IECC 2015' num_people = 0.0 @model.getSpaceTypes.each do |space_type| next if !space_type.standardsSpaceType.is_initialized next if space_type.standardsSpaceType.get != 'Apartment' # currently only supports midrise apt space type space_type_floor_area = space_type.floorArea space_type_num_people = space_type.getNumberOfPeople(space_type_floor_area) num_people += space_type_num_people end # lookup target gal/day for the building bedrooms_per_unit = 2.0 # assumption num_units = num_people / 2.5 # Avg 2.5 units per person. if use_old_gem_code target_consumption = @model.find_icc_iecc_2015_hot_water_demand(num_units, bedrooms_per_unit) else target_consumption = std.model_find_icc_iecc_2015_hot_water_demand(@model, num_units, bedrooms_per_unit) end else # only other path for now is 90.1-2013 # get building type building_type = '' if @model.getBuilding.standardsBuildingType.is_initialized building_type = @model.getBuilding.standardsBuildingType.get end # lookup data from standards if use_old_gem_code ashrae_hot_water_demand = @model.find_ashrae_hot_water_demand else ashrae_hot_water_demand = std.model_find_ashrae_hot_water_demand(@model) end # building type specific logic for water consumption # todo - update test to exercise various building types if !ashrae_hot_water_demand.empty? if building_type == 'FullServiceRestaurant' num_people_hours = 0.0 @model.getSpaceTypes.each do |space_type| next if !space_type.standardsSpaceType.is_initialized next if space_type.standardsSpaceType.get != 'Dining' space_type_floor_area = space_type.floorArea space_type_num_people_hours = 0.0 # loop through peole instances space_type.people.each do |inst| inst_num_people = inst.getNumberOfPeople(space_type_floor_area) inst_schedule = inst.numberofPeopleSchedule.get # sim will fail prior to this if doesn't have it if inst_schedule.to_ScheduleRuleset.is_initialized if use_old_gem_code annual_equiv_flow_rate = inst_schedule.to_ScheduleRuleset.get.annual_equivalent_full_load_hrs else annual_equiv_flow_rate = std.schedule_ruleset_annual_equivalent_full_load_hrs(inst_schedule.to_ScheduleRuleset.get) end elsif inst_schedule.to_ScheduleConstant.is_initialized if use_old_gem_code annual_equiv_flow_rate = inst_schedule.to_ScheduleConstant.get.annual_equivalent_full_load_hrs else annual_equiv_flow_rate = std.schedule_constant_annual_equivalent_full_load_hrs(inst_schedule.to_ScheduleConstant.get) end else check_elems << OpenStudio::Attribute.new('flag', "#{inst_schedule.name} isn't a Ruleset or Constant schedule. Can't calculate annual equivalent full load hours.") annual_equiv_flow_rate = 0.0 end inst_num_people_horus = annual_equiv_flow_rate * inst_num_people space_type_num_people_hours += inst_num_people_horus end num_people_hours += space_type_num_people_hours end num_meals = num_people_hours / (365.0 * 1.5) # 90 minute meal target_consumption = num_meals * ashrae_hot_water_demand.first[:avg_day_unit] elsif ['LargeHotel', 'SmallHotel'].include? building_type num_people = 0.0 @model.getSpaceTypes.each do |space_type| next if !space_type.standardsSpaceType.is_initialized next if space_type.standardsSpaceType.get != 'GuestRoom' space_type_floor_area = space_type.floorArea space_type_num_people = space_type.getNumberOfPeople(space_type_floor_area) num_people += space_type_num_people end # find best fit from returned results num_units = num_people / 2.0 # 2 people per room design load, not typical occupancy avg_day_unit = nil fit = nil ashrae_hot_water_demand.each do |block| if fit.nil? avg_day_unit = block[:avg_day_unit] fit = (avg_day_unit - block[:block]).abs elsif (avg_day_unit - block[:block]).abs - fit avg_day_unit = block[:avg_day_unit] fit = (avg_day_unit - block[:block]).abs end end target_consumption = num_units * avg_day_unit elsif building_type == 'MidriseApartment' num_people = 0.0 @model.getSpaceTypes.each do |space_type| next if !space_type.standardsSpaceType.is_initialized next if space_type.standardsSpaceType.get != 'Apartment' space_type_floor_area = space_type.floorArea space_type_num_people = space_type.getNumberOfPeople(space_type_floor_area) num_people += space_type_num_people end # find best fit from returned results num_units = num_people / 2.5 # Avg 2.5 units per person. avg_day_unit = nil fit = nil ashrae_hot_water_demand.each do |block| if fit.nil? avg_day_unit = block[:avg_day_unit] fit = (avg_day_unit - block[:block]).abs elsif (avg_day_unit - block[:block]).abs - fit avg_day_unit = block[:avg_day_unit] fit = (avg_day_unit - block[:block]).abs end end target_consumption = num_units * avg_day_unit elsif ['Office', 'LargeOffice', 'MediumOffice', 'SmallOffice'].include? building_type num_people = @model.getBuilding.numberOfPeople target_consumption = num_people * ashrae_hot_water_demand.first[:avg_day_unit] elsif building_type == 'PrimarySchool' num_people = 0.0 @model.getSpaceTypes.each do |space_type| next if !space_type.standardsSpaceType.is_initialized next if space_type.standardsSpaceType.get != 'Classroom' space_type_floor_area = space_type.floorArea space_type_num_people = space_type.getNumberOfPeople(space_type_floor_area) num_people += space_type_num_people end target_consumption = num_people * ashrae_hot_water_demand.first[:avg_day_unit] elsif building_type == 'QuickServiceRestaurant' num_people_hours = 0.0 @model.getSpaceTypes.each do |space_type| next if !space_type.standardsSpaceType.is_initialized next if space_type.standardsSpaceType.get != 'Dining' space_type_floor_area = space_type.floorArea space_type_num_people_hours = 0.0 # loop through peole instances space_type.peoples.each do |inst| inst_num_people = inst.getNumberOfPeople(space_type_floor_area) inst_schedule = inst.numberofPeopleSchedule.get # sim will fail prior to this if doesn't have it if inst_schedule.to_ScheduleRuleset.is_initialized if use_old_gem_code annual_equiv_flow_rate = inst_schedule.to_ScheduleRuleset.get.annual_equivalent_full_load_hrs else annual_equiv_flow_rate = std.schedule_ruleset_annual_equivalent_full_load_hrs(inst_schedule.to_ScheduleRuleset.get) end elsif inst_schedule.to_ScheduleConstant.is_initialized if use_old_gem_code annual_equiv_flow_rate = inst_schedule.to_ScheduleConstant.get.annual_equivalent_full_load_hrs else annual_equiv_flow_rate = std.schedule_constant_annual_equivalent_full_load_hrs(inst_schedule.to_ScheduleConstant.get) end else check_elems << OpenStudio::Attribute.new('flag', "#{inst_schedule.name} isn't a Ruleset or Constant schedule. Can't calculate annual equivalent full load hours.") annual_equiv_flow_rate = 0.0 end inst_num_people_horus = annual_equiv_flow_rate * inst_num_people space_type_num_people_hours += inst_num_people_horus end num_people_hours += space_type_num_people_hours end num_meals = num_people_hours / (365.0 * 0.5) # 30 minute leal # todo - add logic to address drive through traffic target_consumption = num_meals * ashrae_hot_water_demand.first[:avg_day_unit] elsif building_type == 'SecondarySchool' num_people = 0.0 @model.getSpaceTypes.each do |space_type| next if !space_type.standardsSpaceType.is_initialized next if space_type.standardsSpaceType.get != 'Classroom' space_type_floor_area = space_type.floorArea space_type_num_people = space_type.getNumberOfPeople(space_type_floor_area) num_people += space_type_num_people end target_consumption = num_people * ashrae_hot_water_demand.first[:avg_day_unit] else check_elems << OpenStudio::Attribute.new('flag', "No rule of thumb values exist for #{building_type}. Hot water consumption was not checked.") end else check_elems << OpenStudio::Attribute.new('flag', "No rule of thumb values exist for #{building_type}. Hot water consumption was not checked.") end end # check actual against target if !target_consumption.nil? if service_water_consumption_daily_avg_gal < target_consumption * (1.0 - min_pass) check_elems << OpenStudio::Attribute.new('flag', "Annual average of #{service_water_consumption_daily_avg_gal.round} gallons per day of hot water is more than #{min_pass * 100} % below the value of #{target_consumption.round} gallons per day.") elsif service_water_consumption_daily_avg_gal > target_consumption * (1.0 + max_pass) check_elems << OpenStudio::Attribute.new('flag', "Annual average of #{service_water_consumption_daily_avg_gal.round} gallons per day of hot water is more than #{max_pass * 100} % above the value of #{target_consumption.round} gallons per day.") end end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Error prevented QAQC check from running (#{e}).") # backtrace of ruby error for diagnostic use if @error_backtrace then check_elems << OpenStudio::Attribute.new('flag', e.backtrace.join("\n").to_s) end end # add check_elms to new attribute check_elem = OpenStudio::Attribute.new('check', check_elems) return check_elem # note: registerWarning and registerValue will be added for checks downstream using os_lib_reporting_qaqc.rb end end