Module: OpenstudioStandards::QAQC
- Defined in:
- lib/openstudio-standards/qaqc/eui.rb,
lib/openstudio-standards/qaqc/hvac.rb,
lib/openstudio-standards/qaqc/envelope.rb,
lib/openstudio-standards/qaqc/reporting.rb,
lib/openstudio-standards/qaqc/schedules.rb,
lib/openstudio-standards/qaqc/calibration.rb,
lib/openstudio-standards/qaqc/weather_files.rb,
lib/openstudio-standards/qaqc/create_results.rb,
lib/openstudio-standards/qaqc/internal_loads.rb,
lib/openstudio-standards/qaqc/zone_conditions.rb,
lib/openstudio-standards/qaqc/service_water_heating.rb
Energy Use Intensity (EUI) collapse
-
.check_eui(category, target_standard, min_pass_pct: 0.1, max_pass_pct: 0.1, name_only: false) ⇒ OpenStudio::Attribute
Checks EUI for reasonableness.
-
.check_eui_by_end_use(category, target_standard, min_pass_pct: 0.2, max_pass_pct: 0.2, name_only: false) ⇒ OpenStudio::Attribute
Checks end use EUIs for reasonableness.
HVAC collapse
-
.check_air_loop_fan_power(category, target_standard, max_pct_delta: 0.3, name_only: false) ⇒ OpenStudio::Attribute
Check the fan power (W/cfm) for each air loop fan in the model to identify unrealistically sized fans.
-
.check_air_loop_temperatures(category, max_sizing_temp_delta: 2.0, max_operating_temp_delta: 5.0, name_only: false) ⇒ OpenStudio::Attribute
Check the air loop and zone operational vs.
-
.check_hvac_capacity(category, target_standard, name_only: false) ⇒ OpenStudio::Attribute
Check mechanical equipment capacity against typical sizing.
-
.check_hvac_efficiency(category, target_standard, min_pass_pct: 0.3, max_pass_pct: 0.3, name_only: false) ⇒ OpenStudio::Attribute
Check the mechanical system efficiencies against a standard.
-
.check_hvac_equipment_part_load_ratios(category, name_only: false) ⇒ OpenStudio::Attribute
Check primary heating and cooling equipment part load ratios to find equipment that is significantly oversized or undersized.
-
.check_hvac_part_load_efficiency(category, target_standard, min_pass_pct: 0.3, max_pass_pct: 0.3, name_only: false) ⇒ OpenStudio::Attribute
Check the mechanical system part load efficiencies against a standard.
-
.check_hvac_system_type(category, target_standard, name_only: false) ⇒ OpenStudio::Attribute
checks the HVAC system type against 90.1 baseline system type.
-
.check_plant_loop_capacity(category, target_standard, max_pct_delta: 0.3, name_only: false) ⇒ OpenStudio::Attribute
Check primary plant loop heating and cooling equipment capacity against coil loads to find equipment that is significantly oversized or undersized.
-
.check_plant_loop_temperatures(category, max_sizing_temp_delta: 2.0, max_operating_temp_delta: 5.0, name_only: false) ⇒ OpenStudio::Attribute
Check the plant loop operational vs.
-
.check_pump_power(category, target_standard, max_pct_delta: 0.3, name_only: false) ⇒ OpenStudio::Attribute
Check the pumping power (W/gpm) for each pump in the model to identify unrealistically sized pumps.
-
.check_simultaneous_heating_and_cooling(category, max_pass_pct: 0.1, name_only: false) ⇒ OpenStudio::Attribute
Check for excess simulataneous heating and cooling.
-
.hourly_part_load_ratio_bins(hourly_part_load_ratios) ⇒ Array<Integer>
Bin the hourly part load ratios into 10% bins.
-
.hvac_equipment_part_load_ratio_message(sql, ann_env_pd, time_step, variable_name, equipment, design_power, units: '', expect_low_plr: false) ⇒ String
Checks part loads ratios for a piece of equipment using the part load timeseries.
Envelope collapse
-
.check_envelope_conductance(category, target_standard, min_pass_pct: 0.2, max_pass_pct: 0.2, name_only: false) ⇒ OpenStudio::Attribute
Check the envelope conductance against a standard.
QAQC HTML reporting collapse
-
.create_qaqc_html(html_in_path, sections, name) ⇒ String
Cleanup and prepare HTML measures calling this must add the following require calls: require ‘json’ require ‘erb’.
-
.create_sections_from_check_attributes(check_elems) ⇒ Array
Make HTML sections from a collection of QAQC checks.
Schedules collapse
-
.check_schedule_coordination(category, target_standard, max_hrs: 2.0, name_only: false) ⇒ OpenStudio::Attribute
Check that the lighting, equipment, and HVAC setpoint schedules coordinate with the occupancy schedules.
Calibration collapse
-
.check_calibration(category, target_standard, max_nmbe: 5.0, max_cvrmse: 15.0, name_only: false) ⇒ OpenStudio::Attribute
Check the calibration against utility bills.
Weather File collapse
-
.check_la_weather_files(category, zip_code, name_only: false) ⇒ OpenStudio::Attribute
checks the weather files matches the appropriate weather file for the Los Angeles zip code.
-
.check_weather_files(category, options, name_only: false) ⇒ OpenStudio::Attribute
Check the weather file design days and climate zone.
Make Results collapse
-
.make_qaqc_results_vector(skip_weekends = true, skip_holidays = true, start_mo = 'June', start_day = 1, start_hr = 14, end_mo = 'September', end_day = 30, end_hr = 18, electricity_consumption_tou_periods = []) ⇒ OpenStudio::AttributeVector
Reports out the detailed simulation results needed by EDAPT and other QAQC programs Results are output as OpenStudio::Attributes.
Internal Loads collapse
-
.check_internal_loads(category, target_standard, min_pass_pct: 0.2, max_pass_pct: 0.2, name_only: false) ⇒ OpenStudio::Attribute
Check the internal loads against a standard.
-
.check_internal_loads_schedules(category, target_standard, min_pass_pct: 0.2, max_pass_pct: 0.2, name_only: false) ⇒ OpenStudio::Attribute
Check the internal load schedules against template prototypes.
-
.space_load_instance_schedule_check(space_load_instance, expected_hours, std: nil, min_pass_pct: 0.2, max_pass_pct: 0.2) ⇒ OpenStudio::Attribute, false
Check the schedule for a space load instance will return false or a single attribute.
Zone Conditions collapse
-
.check_occupied_zones_conditioned(category, target_standard, name_only: false) ⇒ OpenStudio::Attribute
Check that all zones with people are conditioned (have a thermostat with setpoints).
-
.check_plenum_loads(category, target_standard, name_only: false) ⇒ OpenStudio::Attribute
Check that there are no people or lights in plenums.
-
.check_supply_air_and_thermostat_temperature_difference(category, target_standard, max_delta: 2.0, name_only: false) ⇒ OpenStudio::Attribute
Check for excess simulataneous heating and cooling.
-
.check_unmet_hours(category, target_standard, max_unmet_hrs: 550.0, expect_clg_unmet_hrs: false, expect_htg_unmet_hrs: false, name_only: false) ⇒ OpenStudio::Attribute
Check unmet hours.
Service Water Heating collapse
-
.check_service_hot_water(category, target_standard, min_pass_pct: 0.25, max_pass_pct: 0.25, name_only: false) ⇒ OpenStudio::Attribute
Checks the hot water use in a model for typical values.
Class Method Details
.check_air_loop_fan_power(category, target_standard, max_pct_delta: 0.3, name_only: false) ⇒ OpenStudio::Attribute
Check the fan power (W/cfm) for each air loop fan in the model to identify unrealistically sized fans.
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/openstudio-standards/qaqc/hvac.rb', line 244 def self.check_air_loop_fan_power(category, target_standard, max_pct_delta: 0.3, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Fan Power') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that fan power vs flow makes sense.') # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # Check each air loop @model.getAirLoopHVACs.sort.each do |air_loop| # Set the expected W/cfm if air_loop.thermalZones.size.to_i == 1 # expect single zone systems to be lower expected_w_per_cfm = 0.5 else expected_w_per_cfm = 1.1 end # Check the W/cfm for each fan on each air loop air_loop.supplyComponents.each do |component| # Get the W/cfm for the fan obj_type = component.iddObjectType.valueName.to_s case obj_type when 'OS_Fan_ConstantVolume' actual_w_per_cfm = std.fan_rated_w_per_cfm(component.to_FanConstantVolume.get) when 'OS_Fan_OnOff' actual_w_per_cfm = std.fan_rated_w_per_cfm(component.to_FanOnOff.get) when 'OS_Fan_VariableVolume' actual_w_per_cfm = std.fan_rated_w_per_cfm(component.to_FanVariableVolume.get) else next # Skip non-fan objects end # Compare W/cfm to expected/typical values if ((expected_w_per_cfm - actual_w_per_cfm) / actual_w_per_cfm).abs > max_pct_delta check_elems << OpenStudio::Attribute.new('flag', "For #{component.name} on #{air_loop.name}, the actual fan power of #{actual_w_per_cfm.round(1)} W/cfm is more than #{(max_pct_delta * 100.0).round(2)}% different from the expected #{expected_w_per_cfm} W/cfm.") end 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 end |
.check_air_loop_temperatures(category, max_sizing_temp_delta: 2.0, max_operating_temp_delta: 5.0, name_only: false) ⇒ OpenStudio::Attribute
Check the air loop and zone operational vs. sizing temperatures and make sure everything is coordinated. This identifies problems caused by sizing to one set of conditions and operating at a different set.
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/openstudio-standards/qaqc/hvac.rb', line 14 def self.check_air_loop_temperatures(category, max_sizing_temp_delta: 2.0, max_operating_temp_delta: 5.0, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Air System Temperatures') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that air system sizing and operation temperatures are coordinated.') # 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| results << elem.valueAsString end return results end begin # get the weather file run period (as opposed to design day run period) ann_env_pd = nil @sql = @model.sqlFile.get @sql.availableEnvPeriods.each do |env_pd| env_type = @sql.environmentType(env_pd) if env_type.is_initialized && (env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod')) ann_env_pd = env_pd break end end # only try to get the annual timeseries if an annual simulation was run if ann_env_pd.nil? check_elems << OpenStudio::Attribute.new('flag', 'Cannot find the annual simulation run period, cannot check equipment part load ratios.') return check_elems end @model.getAirLoopHVACs.sort.each do |air_loop| supply_outlet_node_name = air_loop.supplyOutletNode.name.to_s design_cooling_sat = air_loop.sizingSystem.centralCoolingDesignSupplyAirTemperature design_cooling_sat = OpenStudio.convert(design_cooling_sat, 'C', 'F').get design_heating_sat = air_loop.sizingSystem.centralHeatingDesignSupplyAirTemperature design_heating_sat = OpenStudio.convert(design_heating_sat, 'C', 'F').get # check if the system is a unitary system is_unitary_system = OpenstudioStandards::HVAC.air_loop_hvac_unitary_system?(air_loop) is_direct_evap = OpenstudioStandards::HVAC.air_loop_hvac_direct_evap?(air_loop) if is_unitary_system && !is_direct_evap unitary_system_name = nil unitary_system_type = '<unspecified>' unitary_min_temp_f = nil unitary_max_temp_f = nil air_loop.supplyComponents.each do |component| obj_type = component.iddObjectType.valueName.to_s case obj_type when 'OS_AirLoopHVAC_UnitarySystem', 'OS_AirLoopHVAC_UnitaryHeatPump_AirToAir', 'OS_AirLoopHVAC_UnitaryHeatPump_AirToAir_MultiSpeed', 'OS_AirLoopHVAC_UnitaryHeatCool_VAVChangeoverBypass' unitary_system_name = component.name.to_s unitary_system_type = obj_type unitary_system_temps = OpenstudioStandards::HVAC.unitary_system_min_max_temperature_value(component) unitary_min_temp_f = unitary_system_temps['min_temp'] unitary_max_temp_f = unitary_system_temps['max_temp'] end end # set expected minimums for operating temperatures expected_min = unitary_min_temp_f.nil? ? design_cooling_sat : [design_cooling_sat, unitary_min_temp_f].min expected_max = unitary_max_temp_f.nil? ? design_heating_sat : [design_heating_sat, unitary_max_temp_f].max else # get setpoint manager spm_name = nil spm_type = '<unspecified>' spm_min_temp_f = nil spm_max_temp_f = nil @model.getSetpointManagers.each do |spm| if spm.setpointNode.is_initialized spm_node = spm.setpointNode.get if spm_node.name.to_s == supply_outlet_node_name spm_name = spm.name spm_type = spm.iddObjectType.valueName.to_s spm_temps_f = OpenstudioStandards::HVAC.setpoint_manager_min_max_temperature(spm) spm_min_temp_f = spm_temps_f['min_temp'] spm_max_temp_f = spm_temps_f['max_temp'] break end end end # check setpoint manager temperatures against design temperatures if spm_min_temp_f && ((spm_min_temp_f - design_cooling_sat).abs > max_sizing_temp_delta) check_elems << OpenStudio::Attribute.new('flag', "Minor Error: Air loop '#{air_loop.name}' sizing uses a #{design_cooling_sat.round(1)}F design cooling supply air temperature, but the setpoint manager operates down to #{spm_min_temp_f.round(1)}F.") end if spm_max_temp_f && ((spm_max_temp_f - design_heating_sat).abs > max_sizing_temp_delta) check_elems << OpenStudio::Attribute.new('flag', "Minor Error: Air loop '#{air_loop.name}' sizing uses a #{design_heating_sat.round(1)}F design heating supply air temperature, but the setpoint manager operates up to #{spm_max_temp_f.round(1)}F.") end # set expected minimums for operating temperatures expected_min = spm_min_temp_f.nil? ? design_cooling_sat : [design_cooling_sat, spm_min_temp_f].min expected_max = spm_max_temp_f.nil? ? design_heating_sat : [design_heating_sat, spm_max_temp_f].max # check zone sizing temperature against air loop design temperatures air_loop.thermalZones.each do |zone| # if this zone has a reheat terminal, get the reheat temp for comparison reheat_op_f = nil reheat_zone = false zone.equipment.each do |equipment| obj_type = equipment.iddObjectType.valueName.to_s case obj_type when 'OS_AirTerminal_SingleDuct_ConstantVolume_Reheat' term = equipment.to_AirTerminalSingleDuctConstantVolumeReheat.get reheat_op_f = OpenStudio.convert(term.maximumReheatAirTemperature, 'C', 'F').get reheat_zone = true when 'OS_AirTerminal_SingleDuct_VAV_HeatAndCool_Reheat' term = equipment.to_AirTerminalSingleDuctVAVHeatAndCoolReheat.get reheat_op_f = OpenStudio.convert(term.maximumReheatAirTemperature, 'C', 'F').get reheat_zone = true when 'OS_AirTerminal_SingleDuct_VAV_Reheat' term = equipment.to_AirTerminalSingleDuctVAVReheat.get reheat_op_f = OpenStudio.convert(term.maximumReheatAirTemperature, 'C', 'F').get reheat_zone = true when 'OS_AirTerminal_SingleDuct_ParallelPIU_Reheat', 'OS_AirTerminal_SingleDuct_SeriesPIU_Reheat' # reheat_op_f = # Not an OpenStudio input reheat_zone = true end end # get the zone heating and cooling SAT for sizing sizing_zone = zone.sizingZone zone_siz_htg_f = OpenStudio.convert(sizing_zone.zoneHeatingDesignSupplyAirTemperature, 'C', 'F').get zone_siz_clg_f = OpenStudio.convert(sizing_zone.zoneCoolingDesignSupplyAirTemperature, 'C', 'F').get # check cooling temperatures if (design_cooling_sat - zone_siz_clg_f).abs > max_sizing_temp_delta check_elems << OpenStudio::Attribute.new('flag', "Minor Error: Air loop '#{air_loop.name}' sizing uses a #{design_cooling_sat.round(1)}F design cooling supply air temperature but the sizing for zone #{zone.name} uses a cooling supply air temperature of #{zone_siz_clg_f.round(1)}F.") end # check heating temperatures if reheat_zone && reheat_op_f if (reheat_op_f - zone_siz_htg_f).abs > max_sizing_temp_delta check_elems << OpenStudio::Attribute.new('flag', "Minor Error: For zone '#{zone.name}', the reheat air temperature is set to #{reheat_op_f.round(1)}F, but the sizing for the zone is done with a heating supply air temperature of #{zone_siz_htg_f.round(1)}F.") end elsif reheat_zone && !reheat_op_f # reheat zone but no reheat temperature available from terminal object elsif (design_heating_sat - zone_siz_htg_f).abs > max_sizing_temp_delta check_elems << OpenStudio::Attribute.new('flag', "Minor Error: Air loop '#{air_loop.name}' sizing uses a #{design_heating_sat.round(1)}F design heating supply air temperature but the sizing for zone #{zone.name} uses a heating supply air temperature of #{zone_siz_htg_f.round(1)}F.") end end end # get supply air temperatures for supply outlet node supply_temp_timeseries = @sql.timeSeries(ann_env_pd, 'Timestep', 'System Node Temperature', supply_outlet_node_name) if supply_temp_timeseries.empty? check_elems << OpenStudio::Attribute.new('flag', "Warning: No supply node temperature timeseries found for '#{air_loop.name}'") next else # convert to ruby array temperatures = [] supply_temp_vector = supply_temp_timeseries.get.values for i in (0..supply_temp_vector.size - 1) temperatures << supply_temp_vector[i] end end # get supply air flow rates for supply outlet node supply_flow_timeseries = @sql.timeSeries(ann_env_pd, 'Timestep', 'System Node Standard Density Volume Flow Rate', supply_outlet_node_name) if supply_flow_timeseries.empty? check_elems << OpenStudio::Attribute.new('flag', "Warning: No supply node temperature timeseries found for '#{air_loop.name}'") next else # convert to ruby array flowrates = [] supply_flow_vector = supply_flow_timeseries.get.values for i in (0..supply_flow_vector.size - 1) flowrates << supply_flow_vector[i] end end # check reasonableness of supply air temperatures when supply air flow rate is operating flow_tolerance = OpenStudio.convert(10.0, 'cfm', 'm^3/s').get = temperatures.select.with_index { |_t, k| flowrates[k] > flow_tolerance } = .map { |t| ((t * 1.8) + 32.0) } next if .empty? runtime_fraction = .size.to_f / temperatures.size temps_out_of_bounds = .select { |t| ((t < 40.0) || (t > 110.0) || ((t + ) < expected_min) || ((t - ) > expected_max)) } next if temps_out_of_bounds.empty? min_op_temp_f = temps_out_of_bounds.min max_op_temp_f = temps_out_of_bounds.max # avg_F = temps_out_of_bounds.inject(:+).to_f / temps_out_of_bounds.size err = [] err << 'Major Error:' err << "Expected supply air temperatures out of bounds for air loop '#{air_loop.name}'" err << "with #{design_cooling_sat.round(1)}F design cooling SAT" err << "and #{design_heating_sat.round(1)}F design heating SAT." unless is_unitary_system && !is_direct_evap err << "Air loop setpoint manager '#{spm_name}' of type '#{spm_type}' with a" err << "#{spm_min_temp_f.round(1)}F minimum setpoint temperature and" err << "#{spm_max_temp_f.round(1)}F maximum setpoint temperature." end if is_unitary_system && !is_direct_evap err << "Unitary system '#{unitary_system_name}' of type '#{unitary_system_type}' with" temp_str = unitary_min_temp_f.nil? ? 'no' : "#{unitary_min_temp_f.round(1)}F" err << "#{temp_str} minimum setpoint temperature and" temp_str = unitary_max_temp_f.nil? ? 'no' : "#{unitary_max_temp_f.round(1)}F" err << "#{temp_str} maximum setpoint temperature." end err << "Out of #{.size}/#{temperatures.size} (#{(runtime_fraction * 100.0).round(1)}%) operating supply air temperatures" err << "#{temps_out_of_bounds.size}/#{.size} (#{((temps_out_of_bounds.size.to_f / .size) * 100.0).round(1)}%)" err << "are out of bounds with #{min_op_temp_f.round(1)}F min and #{max_op_temp_f.round(1)}F max." check_elems << OpenStudio::Attribute.new('flag', err.join(' ').gsub(/\n/, '')) end rescue StandardError => e # brief description of ruby error check_elems << OpenStudio::Attribute.new('flag', "Major Error: 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 end |
.check_calibration(category, target_standard, max_nmbe: 5.0, max_cvrmse: 15.0, name_only: false) ⇒ OpenStudio::Attribute
Check the calibration against utility bills
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/openstudio-standards/qaqc/calibration.rb', line 14 def self.check_calibration(category, target_standard, max_nmbe: 5.0, max_cvrmse: 15.0, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Calibration') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that the model is calibrated to the utility bills.') # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # Check that there are utility bills in the model if @model.getUtilityBills.empty? check_elems << OpenStudio::Attribute.new('flag', 'Model contains no utility bills, cannot check calibration.') end # Check the calibration for each utility bill @model.getUtilityBills.each do |bill| bill_name = bill.name.get fuel = bill.fuelType.valueDescription # Consumption # NMBE if bill.NMBE.is_initialized nmbe = bill.NMBE.get if nmbe > max_nmbe || nmbe < -1.0 * max_nmbe check_elems << OpenStudio::Attribute.new('flag', "For the #{fuel} bill called #{bill_name}, the consumption NMBE of #{nmbe.round(1)}% is outside the limit of +/- #{max_nmbe}%, so the model is not calibrated.") end end # CVRMSE if bill.CVRMSE.is_initialized cvrmse = bill.CVRMSE.get if cvrmse > max_cvrmse check_elems << OpenStudio::Attribute.new('flag', "For the #{fuel} bill called #{bill_name}, the consumption CVRMSE of #{cvrmse.round(1)}% is above the limit of #{max_cvrmse}%, so the model is not calibrated.") end end # Peak Demand (for some fuels) if bill.peakDemandUnitConversionFactor.is_initialized peak_conversion = bill.peakDemandUnitConversionFactor.get # Get modeled and actual values actual_vals = [] modeled_vals = [] bill.billingPeriods.each do |billing_period| actual_peak = billing_period.peakDemand if actual_peak.is_initialized actual_vals << actual_peak.get end modeled_peak = billing_period.modelPeakDemand if modeled_peak.is_initialized modeled_vals << modeled_peak.get end end # Check that both arrays are the same size unless actual_vals.size == modeled_vals.size check_elems << OpenStudio::Attribute.new('flag', "For the #{fuel} bill called #{bill_name}, cannot get the same number of modeled and actual peak demand values, cannot check peak demand calibration.") end # NMBE and CMRMSE ysum = 0 sum_err = 0 squared_err = 0 n = actual_vals.size actual_vals.each_with_index do |actual, i| modeled = modeled_vals[i] actual *= peak_conversion # Convert actual demand to model units ysum += actual sum_err += (actual - modeled) squared_err += (actual - modeled)**2 end if n > 1 = ysum / n # NMBE demand_nmbe = 100.0 * (sum_err / (n - 1)) / if demand_nmbe > max_nmbe || demand_nmbe < -1.0 * max_nmbe check_elems << OpenStudio::Attribute.new('flag', "For the #{fuel} bill called #{bill_name}, the peak demand NMBE of #{demand_nmbe.round(1)}% is outside the limit of +/- #{max_nmbe}%, so the model is not calibrated.") end # CVRMSE demand_cvrmse = 100.0 * ((squared_err / (n - 1))**0.5) / if demand_cvrmse > max_cvrmse check_elems << OpenStudio::Attribute.new('flag', "For the #{fuel} bill called #{bill_name}, the peak demand CVRMSE of #{demand_cvrmse.round(1)}% is above the limit of #{max_cvrmse}%, so the model is not calibrated.") end end 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 end |
.check_envelope_conductance(category, target_standard, min_pass_pct: 0.2, max_pass_pct: 0.2, name_only: false) ⇒ OpenStudio::Attribute
unique tolerance ranges for conductance, reflectance, and shgc
Check the envelope conductance against a standard
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 |
# File 'lib/openstudio-standards/qaqc/envelope.rb', line 15 def self.check_envelope_conductance(category, target_standard, min_pass_pct: 0.2, max_pass_pct: 0.2, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Envelope R-Value') check_elems << OpenStudio::Attribute.new('category', category) if target_standard == 'ICC IECC 2015' dislay_standard = target_standard check_elems << OpenStudio::Attribute.new('description', "Check envelope against Table R402.1.2 and R402.1.4 in #{dislay_standard} Residential Provisions.") elsif target_standard.include?('90.1-2013') display_standard = "ASHRAE #{target_standard}" check_elems << OpenStudio::Attribute.new('description', "Check envelope against #{display_standard} Table 5.5.2, Table G2.1.5 b,c,d,e, Section 5.5.3.1.1a. Roof reflectance of 55%, wall reflectance of 30%.") else # @todo could add more elsifs if want to dsiplay tables and sections for additional 90.1 standards if target_standard.include?('90.1') display_standard = "ASHRAE #{target_standard}" else display_standard = target_standard end check_elems << OpenStudio::Attribute.new('description', "Check envelope against #{display_standard}. Roof reflectance of 55%, wall reflectance of 30%.") end # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) # list of surface types to identify for each space type for surfaces and sub-surfaces construction_type_array = [] construction_type_array << ['ExteriorWall', 'SteelFramed'] construction_type_array << ['ExteriorRoof', 'IEAD'] construction_type_array << ['ExteriorFloor', 'Mass'] construction_type_array << ['ExteriorDoor', 'Swinging'] construction_type_array << ['ExteriorWindow', 'Metal framing (all other)'] construction_type_array << ['Skylight', 'Glass with Curb'] # overhead door doesn't show in list, or glass door begin # loop through all space types used in the model @model.getSpaceTypes.sort.each do |space_type| next if space_type.floorArea <= 0 space_type_const_properties = {} construction_type_array.each do |const_type| # gather data for exterior wall intended_surface_type = const_type[0] standards_construction_type = const_type[1] space_type_const_properties[intended_surface_type] = {} data = std.space_type_get_construction_properties(space_type, intended_surface_type, standards_construction_type) if data.nil? puts "lookup for #{target_standard},#{intended_surface_type},#{standards_construction_type}" check_elems << OpenStudio::Attribute.new('flag', "Didn't find construction for #{standards_construction_type} #{intended_surface_type} for #{space_type.name}.") elsif ['ExteriorWall', 'ExteriorFloor', 'ExteriorDoor'].include? intended_surface_type space_type_const_properties[intended_surface_type]['u_value'] = data['assembly_maximum_u_value'] space_type_const_properties[intended_surface_type]['reflectance'] = 0.30 # hard coded value elsif intended_surface_type == 'ExteriorRoof' space_type_const_properties[intended_surface_type]['u_value'] = data['assembly_maximum_u_value'] space_type_const_properties[intended_surface_type]['reflectance'] = 0.55 # hard coded value else space_type_const_properties[intended_surface_type]['u_value'] = data['assembly_maximum_u_value'] space_type_const_properties[intended_surface_type]['shgc'] = data['assembly_maximum_solar_heat_gain_coefficient'] end end # make array of construction details for surfaces surface_details = [] missing_surface_constructions = [] sub_surface_details = [] missing_sub_surface_constructions = [] # loop through spaces space_type.spaces.each do |space| space.surfaces.each do |surface| next if surface.outsideBoundaryCondition != 'Outdoors' if surface.construction.is_initialized surface_details << { boundary_condition: surface.outsideBoundaryCondition, surface_type: surface.surfaceType, construction: surface.construction.get } else missing_surface_constructions << surface.name.get end # make array of construction details for sub_surfaces surface.subSurfaces.each do |sub_surface| if sub_surface.construction.is_initialized sub_surface_details << { boundary_condition: sub_surface.outsideBoundaryCondition, surface_type: sub_surface.subSurfaceType, construction: sub_surface.construction.get } else missing_sub_surface_constructions << sub_surface.name.get end end end end if !missing_surface_constructions.empty? check_elems << OpenStudio::Attribute.new('flag', "#{missing_surface_constructions.size} surfaces are missing constructions in #{space_type.name}. Spaces and can't be checked.") end if !missing_sub_surface_constructions.empty? check_elems << OpenStudio::Attribute.new('flag', "#{missing_sub_surface_constructions.size} sub surfaces are missing constructions in #{space_type.name}. Spaces and can't be checked.") end # gather target values for this space type # @todo address support for other surface types e.g. overhead door glass door target_r_value_ip = {} target_reflectance = {} target_u_value_ip = {} target_shgc = {} target_r_value_ip['Wall'] = 1.0 / space_type_const_properties['ExteriorWall']['u_value'].to_f target_reflectance['Wall'] = space_type_const_properties['ExteriorWall']['reflectance'].to_f target_r_value_ip['RoofCeiling'] = 1.0 / space_type_const_properties['ExteriorRoof']['u_value'].to_f target_reflectance['RoofCeiling'] = space_type_const_properties['ExteriorRoof']['reflectance'].to_f target_r_value_ip['Floor'] = 1.0 / space_type_const_properties['ExteriorFloor']['u_value'].to_f target_reflectance['Floor'] = space_type_const_properties['ExteriorFloor']['reflectance'].to_f target_r_value_ip['Door'] = 1.0 / space_type_const_properties['ExteriorDoor']['u_value'].to_f target_reflectance['Door'] = space_type_const_properties['ExteriorDoor']['reflectance'].to_f target_u_value_ip['FixedWindow'] = space_type_const_properties['ExteriorWindow']['u_value'].to_f target_shgc['FixedWindow'] = space_type_const_properties['ExteriorWindow']['shgc'].to_f target_u_value_ip['OperableWindow'] = space_type_const_properties['ExteriorWindow']['u_value'].to_f target_shgc['OperableWindow'] = space_type_const_properties['ExteriorWindow']['shgc'].to_f target_u_value_ip['Skylight'] = space_type_const_properties['Skylight']['u_value'].to_f target_shgc['Skylight'] = space_type_const_properties['Skylight']['shgc'].to_f # loop through unique construction array combinations surface_details.uniq.each do |surface_detail| if surface_detail[:construction].thermalConductance.is_initialized # don't use intended surface type of construction, look map based on surface type and boundary condition boundary_condition = surface_detail[:boundary_condition] surface_type = surface_detail[:surface_type] intended_surface_type = '' if boundary_condition.to_s == 'Outdoors' case surface_type.to_s when 'Wall' intended_surface_type = 'ExteriorWall' when 'RoofCeiling' intended_surface_type = 'ExteriorRoof' when 'Floor' intended_surface_type = 'ExteriorFloor' end end film_coefficients_r_value = OpenstudioStandards::Constructions.film_coefficients_r_value(intended_surface_type, includes_int_film = true, includes_ext_film = true) thermal_conductance = surface_detail[:construction].thermalConductance.get r_value_with_film = (1 / thermal_conductance) + film_coefficients_r_value source_units = 'm^2*K/W' target_units = 'ft^2*h*R/Btu' r_value_ip = OpenStudio.convert(r_value_with_film, source_units, target_units).get solar_reflectance = surface_detail[:construction].to_LayeredConstruction.get.layers[0].to_OpaqueMaterial.get.solarReflectance.get # @todo check with exterior air wall # stop if didn't find values (0 or infinity) next if target_r_value_ip[surface_detail[:surface_type]] < 0.01 next if target_r_value_ip[surface_detail[:surface_type]] == Float::INFINITY # check r avlues if r_value_ip < target_r_value_ip[surface_detail[:surface_type]] * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "R value of #{r_value_ip.round(2)} (#{target_units}) for #{surface_detail[:construction].name} in #{space_type.name} is more than #{min_pass_pct * 100} % below the expected value of #{target_r_value_ip[surface_detail[:surface_type]].round(2)} (#{target_units}) for #{display_standard}.") elsif r_value_ip > target_r_value_ip[surface_detail[:surface_type]] * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "R value of #{r_value_ip.round(2)} (#{target_units}) for #{surface_detail[:construction].name} in #{space_type.name} is more than #{max_pass_pct * 100} % above the expected value of #{target_r_value_ip[surface_detail[:surface_type]].round(2)} (#{target_units}) for #{display_standard}.") end # check solar reflectance if (solar_reflectance < target_reflectance[surface_detail[:surface_type]] * (1.0 - min_pass_pct)) && (target_standard != 'ICC IECC 2015') check_elems << OpenStudio::Attribute.new('flag', "Solar Reflectance of #{(solar_reflectance * 100).round} % for #{surface_detail[:construction].name} in #{space_type.name} is more than #{min_pass_pct * 100} % below the expected value of #{(target_reflectance[surface_detail[:surface_type]] * 100).round} %.") elsif (solar_reflectance > target_reflectance[surface_detail[:surface_type]] * (1.0 + max_pass_pct)) && (target_standard != 'ICC IECC 2015') check_elems << OpenStudio::Attribute.new('flag', "Solar Reflectance of #{(solar_reflectance * 100).round} % for #{surface_detail[:construction].name} in #{space_type.name} is more than #{max_pass_pct * 100} % above the expected value of #{(target_reflectance[surface_detail[:surface_type]] * 100).round} %.") end else check_elems << OpenStudio::Attribute.new('flag', "Can't calculate R value for #{surface_detail[:construction].name}.") end end # loop through unique construction array combinations sub_surface_details.uniq.each do |sub_surface_detail| if sub_surface_detail[:surface_type] == 'FixedWindow' || sub_surface_detail[:surface_type] == 'OperableWindow' || sub_surface_detail[:surface_type] == 'Skylight' # check for non opaque sub surfaces source_units = 'W/m^2*K' target_units = 'Btu/ft^2*h*R' surface_construction = sub_surface_detail[:construction].to_LayeredConstruction.get u_factor_si = OpenstudioStandards::Constructions.construction_get_conductance(surface_construction) u_factor_ip = OpenStudio.convert(u_factor_si, source_units, target_units).get shgc = OpenstudioStandards::Constructions.construction_get_solar_transmittance(surface_construction) # stop if didn't find values (0 or infinity) next if target_u_value_ip[sub_surface_detail[:surface_type]] < 0.01 next if target_u_value_ip[sub_surface_detail[:surface_type]] == Float::INFINITY # check u avlues if u_factor_ip < target_u_value_ip[sub_surface_detail[:surface_type]] * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "U value of #{u_factor_ip.round(2)} (#{target_units}) for #{sub_surface_detail[:construction].name} in #{space_type.name} is more than #{min_pass_pct * 100} % below the expected value of #{target_u_value_ip[sub_surface_detail[:surface_type]].round(2)} (#{target_units}) for #{display_standard}.") elsif u_factor_ip > target_u_value_ip[sub_surface_detail[:surface_type]] * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "U value of #{u_factor_ip.round(2)} (#{target_units}) for #{sub_surface_detail[:construction].name} in #{space_type.name} is more than #{max_pass_pct * 100} % above the expected value of #{target_u_value_ip[sub_surface_detail[:surface_type]].round(2)} (#{target_units}) for #{display_standard}.") end # check shgc if shgc < target_shgc[sub_surface_detail[:surface_type]] * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "SHGC of #{shgc.round(2)} % for #{sub_surface_detail[:construction].name} in #{space_type.name} is more than #{min_pass_pct * 100} % below the expected value of #{target_shgc[sub_surface_detail[:surface_type]].round(2)} %.") elsif shgc > target_shgc[sub_surface_detail[:surface_type]] * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "SHGC of #{shgc.round(2)} % for #{sub_surface_detail[:construction].name} in #{space_type.name} is more than #{max_pass_pct * 100} % above the expected value of #{target_shgc[sub_surface_detail[:surface_type]].round(2)} %.") end else # check for opaque sub surfaces if sub_surface_detail[:construction].thermalConductance.is_initialized # don't use intended surface type of construction, look map based on surface type and boundary condition boundary_condition = sub_surface_detail[:boundary_condition] surface_type = sub_surface_detail[:surface_type] intended_surface_type = '' if boundary_condition.to_s == 'Outdoors' # @todo add additional intended surface types if surface_type.to_s == 'Door' then intended_surface_type = 'ExteriorDoor' end end film_coefficients_r_value = OpenstudioStandards::Constructions.film_coefficients_r_value(intended_surface_type, includes_int_film = true, includes_ext_film = true) thermal_conductance = sub_surface_detail[:construction].thermalConductance.get r_value_with_film = (1 / thermal_conductance) + film_coefficients_r_value source_units = 'm^2*K/W' target_units = 'ft^2*h*R/Btu' r_value_ip = OpenStudio.convert(r_value_with_film, source_units, target_units).get solar_reflectance = sub_surface_detail[:construction].to_LayeredConstruction.get.layers[0].to_OpaqueMaterial.get.solarReflectance.get # @todo check what happens with exterior air wall # stop if didn't find values (0 or infinity) next if target_r_value_ip[sub_surface_detail[:surface_type]] < 0.01 next if target_r_value_ip[sub_surface_detail[:surface_type]] == Float::INFINITY # check r avlues if r_value_ip < target_r_value_ip[sub_surface_detail[:surface_type]] * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "R value of #{r_value_ip.round(2)} (#{target_units}) for #{sub_surface_detail[:construction].name} in #{space_type.name} is more than #{min_pass_pct * 100} % below the expected value of #{target_r_value_ip[sub_surface_detail[:surface_type]].round(2)} (#{target_units}) for #{display_standard}.") elsif r_value_ip > target_r_value_ip[sub_surface_detail[:surface_type]] * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "R value of #{r_value_ip.round(2)} (#{target_units}) for #{sub_surface_detail[:construction].name} in #{space_type.name} is more than #{max_pass_pct * 100} % above the expected value of #{target_r_value_ip[sub_surface_detail[:surface_type]].round(2)} (#{target_units}) for #{display_standard}.") end # check solar reflectance if (solar_reflectance < target_reflectance[sub_surface_detail[:surface_type]] * (1.0 - min_pass_pct)) && (target_standard != 'ICC IECC 2015') check_elems << OpenStudio::Attribute.new('flag', "Solar Reflectance of #{(solar_reflectance * 100).round} % for #{sub_surface_detail[:construction].name} in #{space_type.name} is more than #{min_pass_pct * 100} % below the expected value of #{(target_reflectance[sub_surface_detail[:surface_type]] * 100).round} %.") elsif (solar_reflectance > target_reflectance[sub_surface_detail[:surface_type]] * (1.0 + max_pass_pct)) && (target_standard != 'ICC IECC 2015') check_elems << OpenStudio::Attribute.new('flag', "Solar Reflectance of #{(solar_reflectance * 100).round} % for #{sub_surface_detail[:construction].name} in #{space_type.name} is more than #{max_pass_pct * 100} % above the expected value of #{(target_reflectance[sub_surface_detail[:surface_type]] * 100).round} %.") end else check_elems << OpenStudio::Attribute.new('flag', "Can't calculate R value for #{sub_surface_detail[:construction].name}.") end end end end # check spaces without space types against Nonresidential for this climate zone @model.getSpaces.sort.each do |space| unless space.spaceType.is_initialized # make array of construction details for surfaces surface_details = [] missing_surface_constructions = [] sub_surface_details = [] missing_sub_surface_constructions = [] space.surfaces.each do |surface| next if surface.outsideBoundaryCondition != 'Outdoors' if surface.construction.is_initialized surface_details << { boundary_condition: surface.outsideBoundaryCondition, surface_type: surface.surfaceType, construction: surface.construction.get } else missing_surface_constructions << surface.name.get end # make array of construction details for sub_surfaces surface.subSurfaces.each do |sub_surface| if sub_surface.construction.is_initialized sub_surface_details << { boundary_condition: sub_surface.outsideBoundaryCondition, surface_type: sub_surface.subSurfaceType, construction: sub_surface.construction.get } else missing_sub_surface_constructions << sub_surface.name.get end end end unless missing_surface_constructions.empty? check_elems << OpenStudio::Attribute.new('flag', "#{missing_surface_constructions.size} surfaces are missing constructions in #{space_type.name}. Spaces and can't be checked.") end unless missing_sub_surface_constructions.empty? check_elems << OpenStudio::Attribute.new('flag', "#{missing_sub_surface_constructions.size} sub surfaces are missing constructions in #{space_type.name}. Spaces and can't be checked.") end surface_details.uniq.each do |surface_detail| if surface_detail[:construction].thermalConductance.is_initialized # don't use intended surface type of construction, look map based on surface type and boundary condition boundary_condition = surface_detail[:boundary_condition] surface_type = surface_detail[:surface_type] intended_surface_type = '' if boundary_condition.to_s == 'Outdoors' case surface_type.to_s when 'Wall' intended_surface_type = 'ExteriorWall' standards_construction_type = 'SteelFramed' when 'RoofCeiling' intended_surface_type = 'ExteriorRoof' standards_construction_type = 'IEAD' when 'Floor' intended_surface_type = 'ExteriorFloor' standards_construction_type = 'Mass' end end film_coefficients_r_value = OpenstudioStandards::Constructions.film_coefficients_r_value(intended_surface_type, includes_int_film = true, includes_ext_film = true) thermal_conductance = surface_detail[:construction].thermalConductance.get r_value_with_film = (1 / thermal_conductance) + film_coefficients_r_value source_units = 'm^2*K/W' target_units = 'ft^2*h*R/Btu' r_value_ip = OpenStudio.convert(r_value_with_film, source_units, target_units).get solar_reflectance = surface_detail[:construction].to_LayeredConstruction.get.layers[0].to_OpaqueMaterial.get.solarReflectance.get # @todo check what happens with exterior air wall # calculate target_r_value_ip target_reflectance = nil data = std.model_get_construction_properties(@model, intended_surface_type, standards_construction_type) if data.nil? check_elems << OpenStudio::Attribute.new('flag', "Didn't find construction for #{standards_construction_type} #{intended_surface_type} for #{space.name}.") next elsif ['ExteriorWall', 'ExteriorFloor', 'ExteriorDoor'].include? intended_surface_type assembly_maximum_u_value = data['assembly_maximum_u_value'] target_reflectance = 0.30 elsif intended_surface_type == 'ExteriorRoof' assembly_maximum_u_value = data['assembly_maximum_u_value'] target_reflectance = 0.55 else assembly_maximum_u_value = data['assembly_maximum_u_value'] assembly_maximum_solar_heat_gain_coefficient = data['assembly_maximum_solar_heat_gain_coefficient'] end assembly_maximum_r_value_ip = 1 / assembly_maximum_u_value # stop if didn't find values (0 or infinity) next if assembly_maximum_r_value_ip < 0.01 next if assembly_maximum_r_value_ip == Float::INFINITY # check r avlues if r_value_ip < assembly_maximum_r_value_ip * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "R value of #{r_value_ip.round(2)} (#{target_units}) for #{surface_detail[:construction].name} in #{space.name} is more than #{min_pass_pct * 100} % below the expected value of #{assembly_maximum_r_value_ip.round(2)} (#{target_units}) for #{display_standard}.") elsif r_value_ip > assembly_maximum_r_value_ip * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "R value of #{r_value_ip.round(2)} (#{target_units}) for #{surface_detail[:construction].name} in #{space.name} is more than #{max_pass_pct * 100} % above the expected value of #{assembly_maximum_r_value_ip.round(2)} (#{target_units}) for #{display_standard}.") end # check solar reflectance if (solar_reflectance < target_reflectance * (1.0 - min_pass_pct)) && (target_standard != 'ICC IECC 2015') check_elems << OpenStudio::Attribute.new('flag', "Solar Reflectance of #{(solar_reflectance * 100).round} % for #{surface_detail[:construction].name} in #{space.name} is more than #{min_pass_pct * 100} % below the expected value of #{(target_reflectance * 100).round} %.") elsif (solar_reflectance > target_reflectance * (1.0 + max_pass_pct)) && (target_standard != 'ICC IECC 2015') check_elems << OpenStudio::Attribute.new('flag', "Solar Reflectance of #{(solar_reflectance * 100).round} % for #{surface_detail[:construction].name} in #{space.name} is more than #{max_pass_pct * 100} % above the expected value of #{(target_reflectance * 100).round} %.") end else check_elems << OpenStudio::Attribute.new('flag', "Can't calculate R value for #{surface_detail[:construction].name}.") end end sub_surface_details.uniq.each do |sub_surface_detail| # @todo update this so it works for doors and windows check_elems << OpenStudio::Attribute.new('flag', "Not setup to check sub-surfaces of spaces without space types. Can't check properties for #{sub_surface_detail[:construction].name}.") end 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 end |
.check_eui(category, target_standard, min_pass_pct: 0.1, max_pass_pct: 0.1, name_only: false) ⇒ OpenStudio::Attribute
Checks EUI for reasonableness
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/openstudio-standards/qaqc/eui.rb', line 14 def self.check_eui(category, target_standard, min_pass_pct: 0.1, max_pass_pct: 0.1, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'EUI Reasonableness') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', "Check EUI for model against #{target_standard} DOE prototype buildings.") # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # total building area query = 'SELECT Value FROM tabulardatawithstrings WHERE ' query << "ReportName='AnnualBuildingUtilityPerformanceSummary' and " query << "ReportForString='Entire Facility' and " query << "TableName='Building Area' and " query << "RowName='Total Building Area' and " query << "ColumnName='Area' and " query << "Units='m2';" query_results = @sql.execAndReturnFirstDouble(query) if query_results.empty? check_elems << OpenStudio::Attribute.new('flag', "Can't calculate EUI, SQL query for building area failed.") return OpenStudio::Attribute.new('check', check_elems) else energy_plus_area = query_results.get end # temp code to check OS vs. E+ area open_studio_area = @model.getBuilding.floorArea if (energy_plus_area - open_studio_area).abs >= 0.1 check_elems << OpenStudio::Attribute.new('flag', "EnergyPlus reported area is #{energy_plus_area} (m^2). OpenStudio reported area is #{@model.getBuilding.floorArea} (m^2).") end # EUI source_units = 'GJ/m^2' target_units = 'kBtu/ft^2' if energy_plus_area > 0.0 # don't calculate EUI if building doesn't have any area # todo - netSiteEnergy deducts for renewable. May want to update this to show gross consumption vs. net consumption eui = @sql.netSiteEnergy.get / energy_plus_area else check_elems << OpenStudio::Attribute.new('flag', "Can't calculate model EUI, building doesn't have any floor area.") return OpenStudio::Attribute.new('check', check_elems) end # test using new method std = Standard.build(target_standard) target_eui = std.model_find_target_eui(@model) # check model vs. target for user specified tolerance. if target_eui.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't calculate target EUI. Make sure model has expected climate zone and building type.") else eui_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(eui, source_units, target_units).get, 1, true) target_eui_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(target_eui, source_units, target_units).get, 1, true) if eui < target_eui * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "Model EUI of #{eui_ip_neat} (#{target_units}) is less than #{min_pass_pct * 100} % below the expected EUI of #{target_eui_ip_neat} (#{target_units}) for #{target_standard}.") elsif eui > target_eui * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "Model EUI of #{eui_ip_neat} (#{target_units}) is more than #{max_pass_pct * 100} % above the expected EUI of #{target_eui_ip_neat} (#{target_units}) for #{target_standard}.") 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 end |
.check_eui_by_end_use(category, target_standard, min_pass_pct: 0.2, max_pass_pct: 0.2, name_only: false) ⇒ OpenStudio::Attribute
Checks end use EUIs for reasonableness
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/openstudio-standards/qaqc/eui.rb', line 104 def self.check_eui_by_end_use(category, target_standard, min_pass_pct: 0.2, max_pass_pct: 0.2, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'End Use by Category') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', "Check end use by category against #{target_standard} DOE prototype buildings.") # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # total building area query = 'SELECT Value FROM tabulardatawithstrings WHERE ' query << "ReportName='AnnualBuildingUtilityPerformanceSummary' and " query << "ReportForString='Entire Facility' and " query << "TableName='Building Area' and " query << "RowName='Total Building Area' and " query << "ColumnName='Area' and " query << "Units='m2';" query_results = @sql.execAndReturnFirstDouble(query) if query_results.empty? check_elems << OpenStudio::Attribute.new('flag', "Can't calculate EUI, SQL query for building area failed.") return OpenStudio::Attribute.new('check', check_elems) else energy_plus_area = query_results.get end # temp code to check OS vs. E+ area open_studio_area = @model.getBuilding.floorArea if (energy_plus_area - open_studio_area).abs >= 0.1 check_elems << OpenStudio::Attribute.new('flag', "EnergyPlus reported area is #{energy_plus_area} (m^2). OpenStudio reported area is #{@model.getBuilding.floorArea} (m^2).") end # loop through end uses and gather consumption, normalized by floor area actual_eui_by_end_use = {} OpenStudio::EndUseCategoryType.getValues.each do |end_use| # get end uses end_use = OpenStudio::EndUseCategoryType.new(end_use).valueDescription query_elec = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= 'Electricity'" query_gas = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= 'Natural Gas'" query_add = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= 'Additional Fuel'" query_dc = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= 'District Cooling'" query_dh = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= 'District Heating'" results_elec = @sql.execAndReturnFirstDouble(query_elec).get results_gas = @sql.execAndReturnFirstDouble(query_gas).get results_add = @sql.execAndReturnFirstDouble(query_add).get results_dc = @sql.execAndReturnFirstDouble(query_dc).get results_dh = @sql.execAndReturnFirstDouble(query_dh).get total_end_use = results_elec + results_gas + results_add + results_dc + results_dh # populate hash for actual end use normalized by area actual_eui_by_end_use[end_use] = total_end_use / energy_plus_area end # gather target end uses for given standard as hash std = Standard.build(target_standard) target_eui_by_end_use = std.model_find_target_eui_by_end_use(@model) # units for flag display text and unit conversion source_units = 'GJ/m^2' target_units = 'kBtu/ft^2' # check acutal vs. target against tolerance if target_eui_by_end_use.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't calculate target end use EUIs. Make sure model has expected climate zone and building type.") else actual_eui_by_end_use.each do |end_use, value| # this should have value of 0 in model. This page change in the future. It doesn't exist in target lookup next if end_use == 'Exterior Equipment' # perform check and issue flags as needed target_value = target_eui_by_end_use[end_use] eui_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(value, source_units, target_units).get, 5, true) target_eui_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(target_value, source_units, target_units).get, 1, true) # add in use case specific logic to skip checks when near 0 actual and target skip = false if (end_use == 'Heat Recovery') && (value < 0.05) && (target_value < 0.05) then skip = true end if (end_use == 'Pumps') && (value < 0.05) && (target_value < 0.05) then skip = true end if (value < target_value * (1.0 - min_pass_pct)) && !skip check_elems << OpenStudio::Attribute.new('flag', "#{end_use} EUI of #{eui_ip_neat} (#{target_units}) is less than #{min_pass_pct * 100} % below the expected #{end_use} EUI of #{target_eui_ip_neat} (#{target_units}) for #{target_standard}.") elsif (value > target_value * (1.0 + max_pass_pct)) && !skip check_elems << OpenStudio::Attribute.new('flag', "#{end_use} EUI of #{eui_ip_neat} (#{target_units}) is more than #{max_pass_pct * 100} % above the expected #{end_use} EUI of #{target_eui_ip_neat} (#{target_units}) for #{target_standard}.") end 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 end |
.check_hvac_capacity(category, target_standard, name_only: false) ⇒ OpenStudio::Attribute
Check mechanical equipment capacity against typical sizing
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 |
# File 'lib/openstudio-standards/qaqc/hvac.rb', line 387 def self.check_hvac_capacity(category, target_standard, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Mechanical System Capacity') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check HVAC capacity against ASHRAE rules of thumb for chiller max flow rate, air loop max flow rate, air loop cooling capciaty, and zone heating capcaity. Zone heating check will skip thermal zones without any exterior exposure, and thermal zones that are not conditioned.') # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) # Sizing benchmarks. Each option has a target value, min and max fractional tolerance, and units. # In the future climate zone specific targets may be in standards sizing_benchmarks = {} sizing_benchmarks['chiller_max_flow_rate'] = { 'min_error' => 1.5, 'min_warning' => 2.0, 'max_warning' => 3.0, 'max_error' => 3.5, 'units' => 'gal/ton*min' } sizing_benchmarks['air_loop_max_flow_rate'] = { 'min_error' => 0.2, 'min_warning' => 0.5, 'max_warning' => 2.0, 'max_error' => 4.0, 'units' => 'cfm/ft^2' } sizing_benchmarks['air_loop_cooling_capacity'] = { 'min_error' => 200.0, 'min_warning' => 300.0, 'max_warning' => 1500.0, 'max_error' => 2000.0, 'units' => 'ft^2/ton' } sizing_benchmarks['zone_heating_capacity'] = { 'min_error' => 4.0, 'min_warning' => 8.0, 'max_warning' => 30.0, 'max_error' => 60.0, 'units' => 'Btu/ft^2*h' } begin # check max flow rate of chillers in model @model.getPlantLoops.sort.each do |plant_loop| # next if no chiller on plant loop chillers = [] plant_loop.supplyComponents.each do |sc| if sc.to_ChillerElectricEIR.is_initialized chillers << sc.to_ChillerElectricEIR.get end end next if chillers.empty? # gather targets for chiller capacity chiller_max_flow_rate_min_error = sizing_benchmarks['chiller_max_flow_rate']['min_error'] chiller_max_flow_rate_min_warning = sizing_benchmarks['chiller_max_flow_rate']['min_warning'] chiller_max_flow_rate_max_warning = sizing_benchmarks['chiller_max_flow_rate']['max_warning'] chiller_max_flow_rate_max_error = sizing_benchmarks['chiller_max_flow_rate']['max_error'] chiller_max_flow_rate_units_ip = ['chiller_max_flow_rate']['units'] # get capacity of loop (not individual chiller but entire loop) total_cooling_capacity_w = std.plant_loop_total_cooling_capacity(plant_loop) total_cooling_capacity_ton = OpenStudio.convert(total_cooling_capacity_w, 'W', 'Btu/h').get / 12_000.0 # get the max flow rate (not individual chiller) maximum_loop_flow_rate = std.plant_loop_find_maximum_loop_flow_rate(plant_loop) maximum_loop_flow_rate_ip = OpenStudio.convert(maximum_loop_flow_rate, 'm^3/s', 'gal/min').get if total_cooling_capacity_ton < 0.01 check_elems << OpenStudio::Attribute.new('flag', "Cooling capacity for #{plant_loop.name.get} is too small for flow rate #{maximum_loop_flow_rate_ip.round(2)} gal/min.") end # calculate the flow per tons of cooling model_flow_rate_per_ton_cooling_ip = maximum_loop_flow_rate_ip / total_cooling_capacity_ton # check flow rate per capacity if model_flow_rate_per_ton_cooling_ip < chiller_max_flow_rate_min_error check_elems << OpenStudio::Attribute.new('flag', "Error: Flow Rate of #{model_flow_rate_per_ton_cooling_ip.round(2)} #{chiller_max_flow_rate_units_ip} for #{plant_loop.name.get} is below #{chiller_max_flow_rate_min_error.round(2)} #{chiller_max_flow_rate_units_ip}.") elsif model_flow_rate_per_ton_cooling_ip < chiller_max_flow_rate_min_warning check_elems << OpenStudio::Attribute.new('flag', "Warning: Flow Rate of #{model_flow_rate_per_ton_cooling_ip.round(2)} #{chiller_max_flow_rate_units_ip} for #{plant_loop.name.get} is below #{chiller_max_flow_rate_min_warning.round(2)} #{chiller_max_flow_rate_units_ip}.") elsif model_flow_rate_per_ton_cooling_ip > chiller_max_flow_rate_max_warning check_elems << OpenStudio::Attribute.new('flag', "Warning: Flow Rate of #{model_flow_rate_per_ton_cooling_ip.round(2)} #{chiller_max_flow_rate_units_ip} for #{plant_loop.name.get} is above #{chiller_max_flow_rate_max_warning.round(2)} #{chiller_max_flow_rate_units_ip}.") elsif model_flow_rate_per_ton_cooling_ip > chiller_max_flow_rate_max_error check_elems << OpenStudio::Attribute.new('flag', "Error: Flow Rate of #{model_flow_rate_per_ton_cooling_ip.round(2)} #{chiller_max_flow_rate_units_ip} for #{plant_loop.name.get} is above #{chiller_max_flow_rate_max_error.round(2)} #{chiller_max_flow_rate_units_ip}.") end end # loop through air loops to get max flow rate and cooling capacity. @model.getAirLoopHVACs.sort.each do |air_loop| # skip DOAS systems for now sizing_system = air_loop.sizingSystem next if sizing_system.typeofLoadtoSizeOn.to_s == 'VentilationRequirement' # gather argument sizing_benchmarks for air_loop_max_flow_rate checks air_loop_max_flow_rate_min_error = sizing_benchmarks['air_loop_max_flow_rate']['min_error'] air_loop_max_flow_rate_min_warning = sizing_benchmarks['air_loop_max_flow_rate']['min_warning'] air_loop_max_flow_rate_max_warning = sizing_benchmarks['air_loop_max_flow_rate']['max_warning'] air_loop_max_flow_rate_max_error = sizing_benchmarks['air_loop_max_flow_rate']['max_error'] air_loop_max_flow_rate_units_ip = sizing_benchmarks['air_loop_max_flow_rate']['units'] # get values from model for air loop checks floor_area_served = std.air_loop_hvac_floor_area_served(air_loop) design_supply_air_flow_rate = std.air_loop_hvac_find_design_supply_air_flow_rate(air_loop) # check max flow rate of air loops in the model model_normalized_flow_rate_si = design_supply_air_flow_rate / floor_area_served model_normalized_flow_rate_ip = OpenStudio.convert(model_normalized_flow_rate_si, 'm^3/m^2*s', air_loop_max_flow_rate_units_ip).get if model_normalized_flow_rate_ip < air_loop_max_flow_rate_min_error check_elems << OpenStudio::Attribute.new('flag', "Error: Flow Rate of #{model_normalized_flow_rate_ip.round(2)} #{air_loop_max_flow_rate_units_ip} for #{air_loop.name.get} is below #{air_loop_max_flow_rate_min_error.round(2)} #{air_loop_max_flow_rate_units_ip}.") elsif model_normalized_flow_rate_ip < air_loop_max_flow_rate_min_warning check_elems << OpenStudio::Attribute.new('flag', "Warning: Flow Rate of #{model_normalized_flow_rate_ip.round(2)} #{air_loop_max_flow_rate_units_ip} for #{air_loop.name.get} is below #{air_loop_max_flow_rate_min_warning.round(2)} #{air_loop_max_flow_rate_units_ip}.") elsif model_normalized_flow_rate_ip > air_loop_max_flow_rate_max_warning check_elems << OpenStudio::Attribute.new('flag', "Warning: Flow Rate of #{model_normalized_flow_rate_ip.round(2)} #{air_loop_max_flow_rate_units_ip} for #{air_loop.name.get} is above #{air_loop_max_flow_rate_max_warning.round(2)} #{air_loop_max_flow_rate_units_ip}.") elsif model_normalized_flow_rate_ip > air_loop_max_flow_rate_max_error check_elems << OpenStudio::Attribute.new('flag', "Error: Flow Rate of #{model_normalized_flow_rate_ip.round(2)} #{air_loop_max_flow_rate_units_ip} for #{air_loop.name.get} is above #{air_loop_max_flow_rate_max_error.round(2)} #{air_loop_max_flow_rate_units_ip}.") end # loop through air loops to get max flow rate and cooling capacity. # check if DOAS, don't check airflow or cooling capacity if it is sizing_system = air_loop.sizingSystem next if sizing_system.typeofLoadtoSizeOn.to_s == 'VentilationRequirement' # gather argument options for air_loop_cooling_capacity checks air_loop_cooling_capacity_min_error = sizing_benchmarks['air_loop_cooling_capacity']['min_error'] air_loop_cooling_capacity_min_warning = sizing_benchmarks['air_loop_cooling_capacity']['min_warning'] air_loop_cooling_capacity_max_warning = sizing_benchmarks['air_loop_cooling_capacity']['max_warning'] air_loop_cooling_capacity_max_error = sizing_benchmarks['air_loop_cooling_capacity']['max_error'] air_loop_cooling_capacity_units_ip = sizing_benchmarks['air_loop_cooling_capacity']['units'] # check cooling capacity of air loops in the model floor_area_served = std.air_loop_hvac_floor_area_served(air_loop) capacity = std.air_loop_hvac_total_cooling_capacity(air_loop) model_normalized_capacity_si = capacity / floor_area_served model_normalized_capacity_ip = OpenStudio.convert(model_normalized_capacity_si, 'W/m^2', 'Btu/ft^2*h').get / 12_000.0 # want to display in tons/ft^2 so invert number and display for checks model_tons_per_area_ip = 1.0 / model_normalized_capacity_ip if model_tons_per_area_ip < air_loop_cooling_capacity_min_error check_elems << OpenStudio::Attribute.new('flag', "Cooling Capacity of #{model_tons_per_area_ip.round} #{air_loop_cooling_capacity_units_ip} for #{air_loop.name.get} is below #{air_loop_cooling_capacity_min_error.round} #{air_loop_cooling_capacity_units_ip}.") elsif model_tons_per_area_ip < air_loop_cooling_capacity_min_warning check_elems << OpenStudio::Attribute.new('flag', "Cooling Capacity of #{model_tons_per_area_ip.round} #{air_loop_cooling_capacity_units_ip} for #{air_loop.name.get} is below #{air_loop_cooling_capacity_min_warning.round} #{air_loop_cooling_capacity_units_ip}.") elsif model_tons_per_area_ip > air_loop_cooling_capacity_max_warning check_elems << OpenStudio::Attribute.new('flag', "Cooling Capacity of #{model_tons_per_area_ip.round} #{air_loop_cooling_capacity_units_ip} for #{air_loop.name.get} is above #{air_loop_cooling_capacity_max_warning.round} #{air_loop_cooling_capacity_units_ip}.") elsif model_tons_per_area_ip > air_loop_cooling_capacity_max_error check_elems << OpenStudio::Attribute.new('flag', "Cooling Capacity of #{model_tons_per_area_ip.round} #{air_loop_cooling_capacity_units_ip} for #{air_loop.name.get} is above #{air_loop_cooling_capacity_max_error.round} #{air_loop_cooling_capacity_units_ip}.") end end # check heating capacity of thermal zones in the model with exterior exposure report_name = 'HVACSizingSummary' table_name = 'Zone Sensible Heating' column_name = 'User Design Load per Area' min_error = sizing_benchmarks['zone_heating_capacity']['min_error'] min_warning = sizing_benchmarks['zone_heating_capacity']['min_warning'] max_warning = sizing_benchmarks['zone_heating_capacity']['max_warning'] max_error = sizing_benchmarks['zone_heating_capacity']['max_error'] units_ip = sizing_benchmarks['zone_heating_capacity']['units'] @model.getThermalZones.sort.each do |thermal_zone| next if thermal_zone.canBePlenum next if thermal_zone.exteriorSurfaceArea < 0.01 # check actual against target query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{thermal_zone.name.get.upcase}' and ColumnName= '#{column_name}'" results = @sql.execAndReturnFirstDouble(query) model_zone_heating_capacity_ip = OpenStudio.convert(results.to_f, 'W/m^2', units_ip).get if model_zone_heating_capacity_ip < min_error check_elems << OpenStudio::Attribute.new('flag', "Heating Capacity of #{model_zone_heating_capacity_ip.round(2)} Btu/ft^2*h for #{thermal_zone.name.get} is below #{min_error.round(1)} Btu/ft^2*h.") elsif model_zone_heating_capacity_ip < min_warning check_elems << OpenStudio::Attribute.new('flag', "Heating Capacity of #{model_zone_heating_capacity_ip.round(2)} Btu/ft^2*h for #{thermal_zone.name.get} is below #{min_warning.round(1)} Btu/ft^2*h.") elsif model_zone_heating_capacity_ip > max_warning check_elems << OpenStudio::Attribute.new('flag', "Heating Capacity of #{model_zone_heating_capacity_ip.round(2)} Btu/ft^2*h for #{thermal_zone.name.get} is above #{max_warning.round(1)} Btu/ft^2*h.") elsif model_zone_heating_capacity_ip > max_error check_elems << OpenStudio::Attribute.new('flag', "Heating Capacity of #{model_zone_heating_capacity_ip.round(2)} Btu/ft^2*h for #{thermal_zone.name.get} is above #{max_error.round(1)} Btu/ft^2*h.") 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 end |
.check_hvac_efficiency(category, target_standard, min_pass_pct: 0.3, max_pass_pct: 0.3, name_only: false) ⇒ OpenStudio::Attribute
Check the mechanical system efficiencies against a standard
571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 |
# File 'lib/openstudio-standards/qaqc/hvac.rb', line 571 def self.check_hvac_efficiency(category, target_standard, min_pass_pct: 0.3, max_pass_pct: 0.3, name_only: false) component_type_array = ['ChillerElectricEIR', 'CoilCoolingDXSingleSpeed', 'CoilCoolingDXTwoSpeed', 'CoilHeatingDXSingleSpeed', 'BoilerHotWater', 'FanConstantVolume', 'FanVariableVolume', 'PumpConstantSpeed', 'PumpVariableSpeed'] # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Mechanical System Efficiency') check_elems << OpenStudio::Attribute.new('category', category) if target_standard.include?('90.1-2013') check_elems << OpenStudio::Attribute.new('description', "Check against #{target_standard} Tables 6.8.1 A-K for the following component types: #{component_type_array.join(', ')}.") else check_elems << OpenStudio::Attribute.new('description', "Check against #{target_standard} for the following component types: #{component_type_array.join(', ')}.") end # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # check ChillerElectricEIR objects (will also have curve check in different script) @model.getChillerElectricEIRs.sort.each do |component| # eff values from model reference_cop = component.referenceCOP # get eff values from standards (if name doesn't have expected strings find object returns first object of multiple) standard_minimum_full_load_efficiency = std.chiller_electric_eir_standard_minimum_full_load_efficiency(component) # check actual against target if standard_minimum_full_load_efficiency.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target full load efficiency for #{component.name}.") elsif reference_cop < standard_minimum_full_load_efficiency * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "COP of #{reference_cop.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_full_load_efficiency.round(2)}.") elsif reference_cop > standard_minimum_full_load_efficiency * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "COP of #{reference_cop.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_full_load_efficiency.round(2)}.") end end # check CoilCoolingDXSingleSpeed objects (will also have curve check in different script) @model.getCoilCoolingDXSingleSpeeds.each do |component| # eff values from model rated_cop = component.ratedCOP.get # get eff values from standards standard_minimum_cop = std.coil_cooling_dx_single_speed_standard_minimum_cop(component) # check actual against target if standard_minimum_cop.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target COP for #{component.name}.") elsif rated_cop < standard_minimum_cop * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The COP of #{rated_cop.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_cop.round(2)} for #{target_standard}.") elsif rated_cop > standard_minimum_cop * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The COP of #{rated_cop.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_cop.round(2)} for #{target_standard}.") end end # check CoilCoolingDXTwoSpeed objects (will also have curve check in different script) @model.getCoilCoolingDXTwoSpeeds.sort.each do |component| # eff values from model rated_high_speed_cop = component.ratedHighSpeedCOP.get rated_low_speed_cop = component.ratedLowSpeedCOP.get # get eff values from standards standard_minimum_cop = std.coil_cooling_dx_two_speed_standard_minimum_cop(component) # check actual against target if standard_minimum_cop.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target COP for #{component.name}.") elsif rated_high_speed_cop < standard_minimum_cop * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The high speed COP of #{rated_high_speed_cop.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_cop.round(2)} for #{target_standard}.") elsif rated_high_speed_cop > standard_minimum_cop * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The high speed COP of #{rated_high_speed_cop.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_cop.round(2)} for #{target_standard}.") end if standard_minimum_cop.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target COP for #{component.name}.") elsif rated_low_speed_cop < standard_minimum_cop * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The low speed COP of #{rated_low_speed_cop.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_cop.round(2)} for #{target_standard}.") elsif rated_low_speed_cop > standard_minimum_cop * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The low speed COP of #{rated_low_speed_cop.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_cop.round(2)} for #{target_standard}.") end end # check CoilHeatingDXSingleSpeed objects # @todo need to test this once json file populated for this data @model.getCoilHeatingDXSingleSpeeds.sort.each do |component| # eff values from model rated_cop = component.ratedCOP # get eff values from standards standard_minimum_cop = std.coil_heating_dx_single_speed_standard_minimum_cop(component) # check actual against target if standard_minimum_cop.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target COP for #{component.name}.") elsif rated_cop < standard_minimum_cop * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The COP of #{rated_cop.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_cop.round(2)} for #{target_standard}.") elsif rated_cop > standard_minimum_cop * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The COP of #{rated_cop.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_cop.round(2)}. for #{target_standard}") end end # check BoilerHotWater @model.getBoilerHotWaters.sort.each do |component| # eff values from model nominal_thermal_efficiency = component.nominalThermalEfficiency # get eff values from standards standard_minimum_thermal_efficiency = std.boiler_hot_water_standard_minimum_thermal_efficiency(component) # check actual against target if standard_minimum_thermal_efficiency.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target thermal efficiency for #{component.name}.") elsif nominal_thermal_efficiency < standard_minimum_thermal_efficiency * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "Nominal thermal efficiency of #{nominal_thermal_efficiency.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_thermal_efficiency.round(2)} for #{target_standard}.") elsif nominal_thermal_efficiency > standard_minimum_thermal_efficiency * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "Nominal thermal efficiency of #{nominal_thermal_efficiency.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_thermal_efficiency.round(2)} for #{target_standard}.") end end # check FanConstantVolume @model.getFanConstantVolumes.sort.each do |component| # eff values from model motor_eff = component.motorEfficiency # get eff values from standards motor_bhp = std.fan_brake_horsepower(component) standard_minimum_motor_efficiency_and_size = std.fan_standard_minimum_motor_efficiency_and_size(component, motor_bhp)[0] # check actual against target if standard_minimum_motor_efficiency_and_size.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target motor efficiency for #{component.name}.") elsif motor_eff < standard_minimum_motor_efficiency_and_size * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{target_standard}.") elsif motor_eff > standard_minimum_motor_efficiency_and_size * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{target_standard}.") end end # check FanVariableVolume @model.getFanVariableVolumes.sort.each do |component| # eff values from model motor_eff = component.motorEfficiency # get eff values from standards motor_bhp = std.fan_brake_horsepower(component) standard_minimum_motor_efficiency_and_size = std.fan_standard_minimum_motor_efficiency_and_size(component, motor_bhp)[0] # check actual against target if standard_minimum_motor_efficiency_and_size.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target motor efficiency for #{component.name}.") elsif motor_eff < standard_minimum_motor_efficiency_and_size * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{target_standard}.") elsif motor_eff > standard_minimum_motor_efficiency_and_size * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{target_standard}.") end end # check PumpConstantSpeed @model.getPumpConstantSpeeds.sort.each do |component| # eff values from model motor_eff = component.motorEfficiency # get eff values from standards motor_bhp = std.pump_brake_horsepower(component) next if motor_bhp < 0.0001 # less than 1 watt standard_minimum_motor_efficiency_and_size = std.pump_standard_minimum_motor_efficiency_and_size(component, motor_bhp)[0] # check actual against target if standard_minimum_motor_efficiency_and_size.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target motor efficiency for #{component.name}.") elsif motor_eff < standard_minimum_motor_efficiency_and_size * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{target_standard}.") elsif motor_eff > standard_minimum_motor_efficiency_and_size * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{target_standard}.") end end # check PumpVariableSpeed @model.getPumpVariableSpeeds.sort.each do |component| # eff values from model motor_eff = component.motorEfficiency # get eff values from standards motor_bhp = std.pump_brake_horsepower(component) next if motor_bhp < 0.0001 # less than 1 watt standard_minimum_motor_efficiency_and_size = std.pump_standard_minimum_motor_efficiency_and_size(component, motor_bhp)[0] # check actual against target if standard_minimum_motor_efficiency_and_size.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target motor efficiency for #{component.name}.") elsif motor_eff < standard_minimum_motor_efficiency_and_size * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{target_standard}.") elsif motor_eff > standard_minimum_motor_efficiency_and_size * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "Motor efficiency of #{motor_eff.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the expected value of #{standard_minimum_motor_efficiency_and_size.round(2)} for #{target_standard}.") 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 end |
.check_hvac_equipment_part_load_ratios(category, name_only: false) ⇒ OpenStudio::Attribute
Check primary heating and cooling equipment part load ratios to find equipment that is significantly oversized or undersized.
1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 |
# File 'lib/openstudio-standards/qaqc/hvac.rb', line 1686 def self.check_hvac_equipment_part_load_ratios(category, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Part Load') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that equipment operates at reasonable part load ranges.') # 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| results << elem.valueAsString end return results end begin # Establish limits for % of operating hrs expected above 90% part load expected_pct_hrs_above_90 = 0.1 # get the weather file run period (as opposed to design day run period) ann_env_pd = nil @sql.availableEnvPeriods.each do |env_pd| env_type = @sql.environmentType(env_pd) if env_type.is_initialized && (env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod')) ann_env_pd = env_pd break end end # only try to get the annual timeseries if an annual simulation was run if ann_env_pd.nil? check_elems << OpenStudio::Attribute.new('flag', 'Cannot find the annual simulation run period, cannot check equipment part load ratios.') return check_elem end # Boilers @model.getBoilerHotWaters.sort.each do |boiler| msg = OpenstudioStandards::HVAC.(@sql, ann_env_pd, 'Hourly', 'Boiler Part Load Ratio', boiler, 1.0) unless msg.nil? check_elems << OpenStudio::Attribute.new('flag', msg) end end # Chillers @model.getChillerElectricEIRs.sort.each do |chiller| msg = OpenstudioStandards::HVAC.(@sql, ann_env_pd, 'Hourly', 'Chiller Part Load Ratio', chiller, 1.0) unless msg.nil? check_elems << OpenStudio::Attribute.new('flag', msg) end end # Cooling Towers (Single Speed) @model.getCoolingTowerSingleSpeeds.sort.each do |cooling_tower| # Get the design fan power if cooling_tower.fanPoweratDesignAirFlowRate.is_initialized design_power = cooling_tower.fanPoweratDesignAirFlowRate.get elsif cooling_tower.autosizedFanPoweratDesignAirFlowRate.is_initialized design_power = cooling_tower.autosizedFanPoweratDesignAirFlowRate.get else check_elems << OpenStudio::Attribute.new('flag', "Could not determine peak power for #{cooling_tower.name}, cannot check part load ratios.") next end msg = OpenstudioStandards::HVAC.(@sql, ann_env_pd, 'Hourly', 'Cooling Tower Fan Electric Power', cooling_tower, design_power, units: 'W') unless msg.nil? check_elems << OpenStudio::Attribute.new('flag', msg) end end # Cooling Towers (Two Speed) @model.getCoolingTowerTwoSpeeds.sort.each do |cooling_tower| # Get the design fan power if cooling_tower.highFanSpeedFanPower.is_initialized design_power = cooling_tower.highFanSpeedFanPower.get elsif cooling_tower.autosizedHighFanSpeedFanPower.is_initialized design_power = cooling_tower.autosizedHighFanSpeedFanPower.get else check_elems << OpenStudio::Attribute.new('flag', "Could not determine peak power for #{cooling_tower.name}, cannot check part load ratios.") next end msg = OpenstudioStandards::HVAC.(@sql, ann_env_pd, 'Hourly', 'Cooling Tower Fan Electric Power', cooling_tower, design_power, units: 'W') unless msg.nil? check_elems << OpenStudio::Attribute.new('flag', msg) end end # Cooling Towers (Variable Speed) @model.getCoolingTowerVariableSpeeds.sort.each do |cooling_tower| # Get the design fan power if cooling_tower.designFanPower.is_initialized design_power = cooling_tower.designFanPower.get elsif cooling_tower.autosizedDesignFanPower.is_initialized design_power = cooling_tower.autosizedDesignFanPower.get else check_elems << OpenStudio::Attribute.new('flag', "Could not determine peak power for #{cooling_tower.name}, cannot check part load ratios.") next end msg = OpenstudioStandards::HVAC.(@sql, ann_env_pd, 'Hourly', 'Cooling Tower Fan Electric Power', cooling_tower, design_power, units: 'W') unless msg.nil? check_elems << OpenStudio::Attribute.new('flag', msg) end end # DX Cooling Coils (Single Speed) @model.getCoilCoolingDXSingleSpeeds.sort.each do |dx_coil| # Get the design coil capacity if dx_coil.ratedTotalCoolingCapacity.is_initialized design_power = dx_coil.ratedTotalCoolingCapacity.get elsif dx_coil.autosizedRatedTotalCoolingCapacity.is_initialized design_power = dx_coil.autosizedRatedTotalCoolingCapacity.get else check_elems << OpenStudio::Attribute.new('flag', "Could not determine capacity for #{dx_coil.name}, cannot check part load ratios.") next end msg = OpenstudioStandards::HVAC.(@sql, ann_env_pd, 'Hourly', 'Cooling Coil Total Cooling Rate', dx_coil, design_power, units: 'W') unless msg.nil? check_elems << OpenStudio::Attribute.new('flag', msg) end end # DX Cooling Coils (Two Speed) @model.getCoilCoolingDXTwoSpeeds.sort.each do |dx_coil| # Get the design coil capacity if dx_coil.ratedHighSpeedTotalCoolingCapacity.is_initialized design_power = dx_coil.ratedHighSpeedTotalCoolingCapacity.get elsif dx_coil.autosizedRatedHighSpeedTotalCoolingCapacity.is_initialized design_power = dx_coil.autosizedRatedHighSpeedTotalCoolingCapacity.get else check_elems << OpenStudio::Attribute.new('flag', "Could not determine capacity for #{dx_coil.name}, cannot check part load ratios.") next end msg = OpenstudioStandards::HVAC.(@sql, ann_env_pd, 'Hourly', 'Cooling Coil Total Cooling Rate', dx_coil, design_power, units: 'W') unless msg.nil? check_elems << OpenStudio::Attribute.new('flag', msg) end end # DX Cooling Coils (Variable Speed) @model.getCoilCoolingDXVariableSpeeds.sort.each do |dx_coil| # Get the design coil capacity if dx_coil.grossRatedTotalCoolingCapacityAtSelectedNominalSpeedLevel.is_initialized design_power = dx_coil.grossRatedTotalCoolingCapacityAtSelectedNominalSpeedLevel.get elsif dx_coil.autosizedGrossRatedTotalCoolingCapacityAtSelectedNominalSpeedLevel.is_initialized design_power = dx_coil.autosizedGrossRatedTotalCoolingCapacityAtSelectedNominalSpeedLevel.get else check_elems << OpenStudio::Attribute.new('flag', "Could not determine capacity for #{dx_coil.name}, cannot check part load ratios.") next end msg = OpenstudioStandards::HVAC.(@sql, ann_env_pd, 'Hourly', 'Cooling Coil Total Cooling Rate', dx_coil, design_power, units: 'W') unless msg.nil? check_elems << OpenStudio::Attribute.new('flag', msg) end end # Gas Heating Coils @model.getCoilHeatingGass.sort.each do |gas_coil| # Get the design coil capacity if gas_coil.nominalCapacity.is_initialized design_power = gas_coil.nominalCapacity.get elsif gas_coil.autosizedNominalCapacity.is_initialized design_power = gas_coil.autosizedNominalCapacity.get else check_elems << OpenStudio::Attribute.new('flag', "Could not determine capacity for #{gas_coil.name}, cannot check part load ratios.") next end if (gas_coil.name.to_s.include? 'Backup') || (gas_coil.name.to_s.include? 'Supplemental') msg = OpenstudioStandards::HVAC.(@sql, ann_env_pd, 'Hourly', 'Heating Coil Heating Rate', gas_coil, design_power, units: 'W', expect_low_plr: true) else msg = OpenstudioStandards::HVAC.(@sql, ann_env_pd, 'Hourly', 'Heating Coil Heating Rate', gas_coil, design_power, units: 'W') end unless msg.nil? check_elems << OpenStudio::Attribute.new('flag', msg) end end # Electric Heating Coils @model.getCoilHeatingElectrics.sort.each do |electric_coil| # Get the design coil capacity if electric_coil.nominalCapacity.is_initialized design_power = electric_coil.nominalCapacity.get elsif electric_coil.autosizedNominalCapacity.is_initialized design_power = electric_coil.autosizedNominalCapacity.get else check_elems << OpenStudio::Attribute.new('flag', "Could not determine capacity for #{electric_coil.name}, cannot check part load ratios.") next end if (electric_coil.name.to_s.include? 'Backup') || (electric_coil.name.to_s.include? 'Supplemental') msg = OpenstudioStandards::HVAC.(@sql, ann_env_pd, 'Hourly', 'Heating Coil Heating Rate', electric_coil, design_power, units: 'W', expect_low_plr: true) else msg = OpenstudioStandards::HVAC.(@sql, ann_env_pd, 'Hourly', 'Heating Coil Heating Rate', electric_coil, design_power, units: 'W') end unless msg.nil? check_elems << OpenStudio::Attribute.new('flag', msg) end end # DX Heating Coils (Single Speed) @model.getCoilHeatingDXSingleSpeeds.sort.each do |dx_coil| # Get the design coil capacity if dx_coil.ratedTotalHeatingCapacity.is_initialized design_power = dx_coil.ratedTotalHeatingCapacity.get elsif dx_coil.autosizedRatedTotalHeatingCapacity.is_initialized design_power = dx_coil.autosizedRatedTotalHeatingCapacity.get else check_elems << OpenStudio::Attribute.new('flag', "Could not determine capacity for #{dx_coil.name}, cannot check part load ratios.") next end msg = OpenstudioStandards::HVAC.(@sql, ann_env_pd, 'Hourly', 'Heating Coil Heating Rate', dx_coil, design_power, units: 'W') unless msg.nil? check_elems << OpenStudio::Attribute.new('flag', msg) 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 end |
.check_hvac_part_load_efficiency(category, target_standard, min_pass_pct: 0.3, max_pass_pct: 0.3, name_only: false) ⇒ OpenStudio::Attribute
Check the mechanical system part load efficiencies against a standard
797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 |
# File 'lib/openstudio-standards/qaqc/hvac.rb', line 797 def self.check_hvac_part_load_efficiency(category, target_standard, min_pass_pct: 0.3, max_pass_pct: 0.3, name_only: false) component_type_array = ['ChillerElectricEIR', 'CoilCoolingDXSingleSpeed', 'CoilCoolingDXTwoSpeed', 'CoilHeatingDXSingleSpeed'] # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Mechanical System Part Load Efficiency') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', "Check 40% and 80% part load efficency against #{target_standard} for the following compenent types: #{component_type_array.join(', ')}. Checking EIR Function of Part Load Ratio curve for chiller and EIR Function of Flow Fraction for DX coils.") # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) # @todo add in check for VAV fan begin # @todo dynamically generate a list of possible options from the standards json chiller_air_cooled_condenser_types = ['WithCondenser', 'WithoutCondenser'] chiller_water_cooled_compressor_types = ['Reciprocating', 'Scroll', 'Rotary Screw', 'Centrifugal'] absorption_types = ['Single Effect', 'Double Effect Indirect Fired', 'Double Effect Direct Fired'] # check getChillerElectricEIRs objects (will also have curve check in different script) @model.getChillerElectricEIRs.sort.each do |component| # get curve and evaluate electric_input_to_cooling_output_ratio_function_of_plr = component.electricInputToCoolingOutputRatioFunctionOfPLR curve_40_pct = electric_input_to_cooling_output_ratio_function_of_plr.evaluate(0.4) curve_80_pct = electric_input_to_cooling_output_ratio_function_of_plr.evaluate(0.8) # find ac properties search_criteria = std.chiller_electric_eir_find_search_criteria(component) # extend search_criteria for absorption_type absorption_types.each do |absorption_type| if component.name.to_s.include?(absorption_type) search_criteria['absorption_type'] = absorption_type next end end # extend search_criteria for condenser type or compressor type if search_criteria['cooling_type'] == 'AirCooled' chiller_air_cooled_condenser_types.each do |condenser_type| if component.name.to_s.include?(condenser_type) search_criteria['condenser_type'] = condenser_type next end end # if no match and also no absorption_type then issue warning if (!search_criteria.key?('condenser_type') || search_criteria['condenser_type'].nil?) && (!search_criteria.key?('absorption_type') || search_criteria['absorption_type'].nil?) check_elems << OpenStudio::Attribute.new('flag', "Can't find unique search criteria for #{component.name}. #{search_criteria}") next # don't go past here end elsif search_criteria['cooling_type'] == 'WaterCooled' chiller_air_cooled_condenser_types.each do |compressor_type| if component.name.to_s.include?(compressor_type) search_criteria['compressor_type'] = compressor_type next end end # if no match and also no absorption_type then issue warning if (!search_criteria.key?('compressor_type') || search_criteria['compressor_type'].nil?) && (!search_criteria.key?('absorption_type') || search_criteria['absorption_type'].nil?) check_elems << OpenStudio::Attribute.new('flag', "Can't find unique search criteria for #{component.name}. #{search_criteria}") next # don't go past here end end # lookup chiller capacity_w = std.chiller_electric_eir_find_capacity(component) capacity_tons = OpenStudio.convert(capacity_w, 'W', 'ton').get chlr_props = std.model_find_object(std.standards_data['chillers'], search_criteria, capacity_tons, Date.today) if chlr_props.nil? check_elems << OpenStudio::Attribute.new('flag', "Didn't find chiller for #{component.name}. #{search_criteria}") next # don't go past here in loop if can't find curve end # temp model to hold temp curve model_temp = OpenStudio::Model::Model.new # create temp curve target_curve_name = chlr_props['eirfplr'] if target_curve_name.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target eirfplr curve for #{component.name}") next # don't go past here in loop if can't find curve end temp_curve = std.model_add_curve(model_temp, target_curve_name) target_curve_40_pct = temp_curve.evaluate(0.4) target_curve_80_pct = temp_curve.evaluate(0.8) # check curve at two points if curve_40_pct < target_curve_40_pct * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.") elsif curve_40_pct > target_curve_40_pct * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.") end if curve_80_pct < target_curve_80_pct * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.") elsif curve_80_pct > target_curve_80_pct * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.") end end # check getCoilCoolingDXSingleSpeeds objects (will also have curve check in different script) @model.getCoilCoolingDXSingleSpeeds.sort.each do |component| # get curve and evaluate eir_function_of_flow_fraction_curve = component.energyInputRatioFunctionOfFlowFractionCurve curve_40_pct = eir_function_of_flow_fraction_curve.evaluate(0.4) curve_80_pct = eir_function_of_flow_fraction_curve.evaluate(0.8) # find ac properties search_criteria = std.coil_dx_find_search_criteria(component) capacity_w = std.coil_cooling_dx_single_speed_find_capacity(component) capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get if std.coil_dx_heat_pump?(component) ac_props = std.model_find_object(std.standards_data['heat_pumps'], search_criteria, capacity_btu_per_hr, Date.today) else ac_props = std.model_find_object(std.standards_data['unitary_acs'], search_criteria, capacity_btu_per_hr, Date.today) end # temp model to hold temp curve model_temp = OpenStudio::Model::Model.new # create temp curve target_curve_name = ac_props['cool_eir_fflow'] if target_curve_name.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target cool_eir_fflow curve for #{component.name}") next # don't go past here in loop if can't find curve end temp_curve = std.model_add_curve(model_temp, target_curve_name) target_curve_40_pct = temp_curve.evaluate(0.4) target_curve_80_pct = temp_curve.evaluate(0.8) # check curve at two points if curve_40_pct < target_curve_40_pct * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.") elsif curve_40_pct > target_curve_40_pct * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.") end if curve_80_pct < target_curve_80_pct * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.") elsif curve_80_pct > target_curve_80_pct * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.") end end # check CoilCoolingDXTwoSpeed objects (will also have curve check in different script) @model.getCoilCoolingDXTwoSpeeds.sort.each do |component| # get curve and evaluate eir_function_of_flow_fraction_curve = component.energyInputRatioFunctionOfFlowFractionCurve curve_40_pct = eir_function_of_flow_fraction_curve.evaluate(0.4) curve_80_pct = eir_function_of_flow_fraction_curve.evaluate(0.8) # find ac properties search_criteria = std.coil_dx_find_search_criteria(component) capacity_w = std.coil_cooling_dx_two_speed_find_capacity(component) capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get ac_props = std.model_find_object(std.standards_data['unitary_acs'], search_criteria, capacity_btu_per_hr, Date.today) # temp model to hold temp curve model_temp = OpenStudio::Model::Model.new # create temp curve target_curve_name = ac_props['cool_eir_fflow'] if target_curve_name.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target cool_eir_flow curve for #{component.name}") next # don't go past here in loop if can't find curve end temp_curve = std.model_add_curve(model_temp, target_curve_name) target_curve_40_pct = temp_curve.evaluate(0.4) target_curve_80_pct = temp_curve.evaluate(0.8) # check curve at two points if curve_40_pct < target_curve_40_pct * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.") elsif curve_40_pct > target_curve_40_pct * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.") end if curve_80_pct < target_curve_80_pct * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.") elsif curve_80_pct > target_curve_80_pct * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.") end end # check CoilCoolingDXTwoSpeed objects (will also have curve check in different script) @model.getCoilHeatingDXSingleSpeeds.sort.each do |component| # get curve and evaluate eir_function_of_flow_fraction_curve = component.energyInputRatioFunctionofFlowFractionCurve # why lowercase of here but not in CoilCoolingDX objects curve_40_pct = eir_function_of_flow_fraction_curve.evaluate(0.4) curve_80_pct = eir_function_of_flow_fraction_curve.evaluate(0.8) # find ac properties search_criteria = std.coil_dx_find_search_criteria(component) capacity_w = std.coil_heating_dx_single_speed_find_capacity(component) capacity_btu_per_hr = OpenStudio.convert(capacity_w, 'W', 'Btu/hr').get ac_props = std.model_find_object(std.standards_data['heat_pumps_heating'], search_criteria, capacity_btu_per_hr, Date.today) if ac_props.nil? target_curve_name = nil else target_curve_name = ac_props['heat_eir_fflow'] end # temp model to hold temp curve model_temp = OpenStudio::Model::Model.new # create temp curve if target_curve_name.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find target curve for #{component.name}") next # don't go past here in loop if can't find curve end temp_curve = std.model_add_curve(model_temp, target_curve_name) # Ensure that the curve was found in standards before attempting to evaluate if temp_curve.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't find coefficients of curve called #{target_curve_name} for #{component.name}, cannot check part-load performance.") next end target_curve_40_pct = temp_curve.evaluate(0.4) target_curve_80_pct = temp_curve.evaluate(0.8) # check curve at two points if curve_40_pct < target_curve_40_pct * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.") elsif curve_40_pct > target_curve_40_pct * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.") end if curve_80_pct < target_curve_80_pct * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.") elsif curve_80_pct > target_curve_80_pct * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.") end end # check @model.getFanVariableVolumes.sort.each do |component| # skip if not on multi-zone system. if component.airLoopHVAC.is_initialized airloop = component.airLoopHVAC.get next unless airloop.thermalZones.size > 1.0 end # skip of brake horsepower is 0 next if std.fan_brake_horsepower(component) < 0.0001 # less than 1 wat # temp model for use by temp model and target curve model_temp = OpenStudio::Model::Model.new # get coeficents for fan model_fan_coefs = [] model_fan_coefs << component.fanPowerCoefficient1.get model_fan_coefs << component.fanPowerCoefficient2.get model_fan_coefs << component.fanPowerCoefficient3.get model_fan_coefs << component.fanPowerCoefficient4.get model_fan_coefs << component.fanPowerCoefficient5.get # make model curve model_curve = OpenStudio::Model::CurveQuartic.new(model_temp) model_curve.setCoefficient1Constant(model_fan_coefs[0]) model_curve.setCoefficient2x(model_fan_coefs[1]) model_curve.setCoefficient3xPOW2(model_fan_coefs[2]) model_curve.setCoefficient4xPOW3(model_fan_coefs[3]) model_curve.setCoefficient5xPOW4(model_fan_coefs[4]) curve_40_pct = model_curve.evaluate(0.4) curve_80_pct = model_curve.evaluate(0.8) # get target coefs target_fan = OpenStudio::Model::FanVariableVolume.new(model_temp) std.fan_variable_volume_set_control_type(target_fan, 'Multi Zone VAV with VSD and Static Pressure Reset') # get coeficents for fan target_fan_coefs = [] target_fan_coefs << target_fan.fanPowerCoefficient1.get target_fan_coefs << target_fan.fanPowerCoefficient2.get target_fan_coefs << target_fan.fanPowerCoefficient3.get target_fan_coefs << target_fan.fanPowerCoefficient4.get target_fan_coefs << target_fan.fanPowerCoefficient5.get # make model curve target_curve = OpenStudio::Model::CurveQuartic.new(model_temp) target_curve.setCoefficient1Constant(target_fan_coefs[0]) target_curve.setCoefficient2x(target_fan_coefs[1]) target_curve.setCoefficient3xPOW2(target_fan_coefs[2]) target_curve.setCoefficient4xPOW3(target_fan_coefs[3]) target_curve.setCoefficient5xPOW4(target_fan_coefs[4]) target_curve_40_pct = target_curve.evaluate(0.4) target_curve_80_pct = target_curve.evaluate(0.8) # check curve at two points if curve_40_pct < target_curve_40_pct * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.") elsif curve_40_pct > target_curve_40_pct * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 40% of #{curve_40_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_40_pct.round(2)} for #{target_standard}.") end if curve_80_pct < target_curve_80_pct * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{min_pass_pct * 100} % below the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.") elsif curve_80_pct > target_curve_80_pct * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The curve value at 80% of #{curve_80_pct.round(2)} for #{component.name} is more than #{max_pass_pct * 100} % above the typical value of #{target_curve_80_pct.round(2)} for #{target_standard}.") 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 end |
.check_hvac_system_type(category, target_standard, name_only: false) ⇒ OpenStudio::Attribute
checks the HVAC system type against 90.1 baseline system type
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 |
# File 'lib/openstudio-standards/qaqc/hvac.rb', line 314 def self.check_hvac_system_type(category, target_standard, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Mechanical System Type') check_elems << OpenStudio::Attribute.new('category', category) # add ASHRAE to display of target standard if includes with 90.1 if target_standard.include?('90.1 2013') check_elems << OpenStudio::Attribute.new('description', 'Check against ASHRAE 90.1 2013 Tables G3.1.1 A-B. Infers the baseline system type based on the equipment serving the zone and their heating/cooling fuels. Only does a high-level inference; does not look for the presence/absence of required controls, etc.') else check_elems << OpenStudio::Attribute.new('description', 'Check against ASHRAE 90.1. Infers the baseline system type based on the equipment serving the zone and their heating/cooling fuels. Only does a high-level inference; does not look for the presence/absence of required controls, etc.') end # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # Get the actual system type for all zones in the model act_zone_to_sys_type = {} @model.getThermalZones.each do |zone| act_zone_to_sys_type[zone] = std.thermal_zone_infer_system_type(zone) end # Get the baseline system type for all zones in the model climate_zone = std.model_get_building_properties(@model)['climate_zone'] req_zone_to_sys_type = std.model_get_baseline_system_type_by_zone(@model, climate_zone) # Compare the actual to the correct @model.getThermalZones.each do |zone| is_plenum = false zone.spaces.each do |space| if OpenstudioStandards::Space.space_plenum?(space) is_plenum = true end end next if is_plenum req_sys_type = req_zone_to_sys_type[zone] act_sys_type = act_zone_to_sys_type[zone] unless act_sys_type == req_sys_type if req_sys_type == '' then req_sys_type = 'Unknown' end check_elems << OpenStudio::Attribute.new('flag', "#{zone.name} baseline system type is incorrect. Supposed to be #{req_sys_type}, but was #{act_sys_type} instead.") 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 end |
.check_internal_loads(category, target_standard, min_pass_pct: 0.2, max_pass_pct: 0.2, name_only: false) ⇒ OpenStudio::Attribute
Check the internal loads against a standard
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 |
# File 'lib/openstudio-standards/qaqc/internal_loads.rb', line 14 def self.check_internal_loads(category, target_standard, min_pass_pct: 0.2, max_pass_pct: 0.2, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Internal Loads') check_elems << OpenStudio::Attribute.new('category', category) if target_standard == 'ICC IECC 2015' check_elems << OpenStudio::Attribute.new('description', 'Check internal loads against Table R405.5.2(1) in ICC IECC 2015 Residential Provisions.') else if target_standard.include?('90.1') display_standard = "ASHRAE #{target_standard}" else display_standard = target_standard end check_elems << OpenStudio::Attribute.new('description', "Check LPD, ventilation rates, occupant density, plug loads, and equipment loads against #{display_standard} and DOE Prototype buildings.") end # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin 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 iecc internal loads for the building bedrooms_per_unit = 2.0 # assumption num_units = num_people / 2.5 # Avg 2.5 units per person. target_loads_hash = std.model_find_icc_iecc_2015_internal_loads(@model, num_units, bedrooms_per_unit) # get model internal gains for lights, elec equipment, and gas equipment model_internal_gains_si = 0.0 query_eleint_lights = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= 'Interior Lighting' and ColumnName= 'Electricity'" query_elec_equip = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= 'Interior Equipment' and ColumnName= 'Electricity'" query_gas_equip = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= 'Interior Equipment' and ColumnName= 'Natural Gas'" model_internal_gains_si += results_elec = @sql.execAndReturnFirstDouble(query_eleint_lights).get model_internal_gains_si += results_elec = @sql.execAndReturnFirstDouble(query_elec_equip).get model_internal_gains_si += results_elec = @sql.execAndReturnFirstDouble(query_gas_equip).get model_internal_gains_si_kbtu_per_day = OpenStudio.convert(model_internal_gains_si, 'GJ', 'kBtu').get / 365.0 # assumes annual run # get target internal loads target_igain_btu_per_day = target_loads_hash['igain_btu_per_day'] target_igain_kbtu_per_day = OpenStudio.convert(target_igain_btu_per_day, 'Btu', 'kBtu').get # check internal loads if model_internal_gains_si_kbtu_per_day < target_igain_kbtu_per_day * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The model average of #{OpenStudio.toNeatString(model_internal_gains_si_kbtu_per_day, 2, true)} (kBtu/day) is more than #{min_pass_pct * 100} % below the expected value of #{OpenStudio.toNeatString(target_igain_kbtu_per_day, 2, true)} (kBtu/day) for #{target_standard}.") elsif model_internal_gains_si_kbtu_per_day > target_igain_kbtu_per_day * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The model average of #{OpenStudio.toNeatString(model_internal_gains_si_kbtu_per_day, 2, true)} (kBtu/day) is more than #{max_pass_pct * 100} % above the expected value of #{OpenStudio.toNeatString(target_igain_kbtu_per_day, 2, true)} k(Btu/day) for #{target_standard}.") end # get target mech vent target_mech_vent_cfm = target_loads_hash['mech_vent_cfm'] # get model mech vent model_mech_vent_si = 0 @model.getSpaceTypes.each do |space_type| next if space_type.floorArea <= 0 # get necessary space type information floor_area = space_type.floorArea num_people = space_type.getNumberOfPeople(floor_area) # get volume for space type for use with ventilation and infiltration space_type_volume = 0.0 space_type_exterior_area = 0.0 space_type_exterior_wall_area = 0.0 space_type.spaces.each do |space| space_type_volume += space.volume * space.multiplier space_type_exterior_area = space.exteriorArea * space.multiplier space_type_exterior_wall_area = space.exteriorWallArea * space.multiplier end # get design spec OA object if space_type.designSpecificationOutdoorAir.is_initialized oa = space_type.designSpecificationOutdoorAir.get oa_method = oa.outdoorAirMethod oa_per_person = oa.outdoorAirFlowperPerson * num_people oa_ach = oa.outdoorAirFlowAirChangesperHour * space_type_volume oa_per_area = oa.outdoorAirFlowperFloorArea * floor_area oa_flow_rate = oa.outdoorAirFlowRate oa_space_type_total = oa_per_person + oa_ach + oa_per_area + oa_flow_rate value_count = 0 if oa_per_person > 0 then value_count += 1 end if oa_ach > 0 then value_count += 1 end if oa_per_area > 0 then value_count += 1 end if oa_flow_rate > 0 then value_count += 1 end if (oa_method != 'Sum') && (value_count > 1) check_elems << OpenStudio::Attribute.new('flag', "Outdoor Air Method for #{space_type.name} was #{oa_method}. Expected value was Sum.") end else oa_space_type_total = 0.0 end # add to building total oa model_mech_vent_si += oa_space_type_total end # check oa model_mech_vent_cfm = OpenStudio.convert(model_mech_vent_si, 'm^3/s', 'cfm').get if model_mech_vent_cfm < target_mech_vent_cfm * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The model mechanical ventilation of #{OpenStudio.toNeatString(model_mech_vent_cfm, 2, true)} cfm is more than #{min_pass_pct * 100} % below the expected value of #{OpenStudio.toNeatString(target_mech_vent_cfm, 2, true)} cfm for #{target_standard}.") elsif model_mech_vent_cfm > target_mech_vent_cfm * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "The model mechanical ventilation of #{OpenStudio.toNeatString(model_mech_vent_cfm, 2, true)} cfm is more than #{max_pass_pct * 100} % above the expected value of #{OpenStudio.toNeatString(target_mech_vent_cfm, 2, true)} cfm for #{target_standard}.") end else # loop through all space types used in the model @model.getSpaceTypes.sort.each do |space_type| next if space_type.floorArea <= 0 next if space_type.name.to_s == 'Plenum' # get necessary space type information floor_area = space_type.floorArea num_people = space_type.getNumberOfPeople(floor_area) # load in standard info for this space type data = std.space_type_get_standards_data(space_type) if data.nil? || data.empty? # skip if all spaces using this space type are plenums all_spaces_plenums = true space_type.spaces.each do |space| unless OpenstudioStandards::Space.space_plenum?(space) all_spaces_plenums = false next end end unless all_spaces_plenums check_elems << OpenStudio::Attribute.new('flag', "Unexpected standards type for #{space_type.name}, can't validate internal loads.") end next end # check lpd for space type model_lights_si = space_type.getLightingPowerPerFloorArea(floor_area, num_people) data['lighting_per_area'].nil? ? (target_lights_ip = 0.0) : (target_lights_ip = data['lighting_per_area']) source_units = 'W/m^2' target_units = 'W/ft^2' load_type = 'Lighting Power Density' model_ip = OpenStudio.convert(model_lights_si, source_units, target_units).get target_ip = target_lights_ip.to_f model_ip_neat = OpenStudio.toNeatString(model_ip, 2, true) target_ip_neat = OpenStudio.toNeatString(target_ip, 2, true) if model_ip < target_ip * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{min_pass_pct * 100} % below the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.") elsif model_ip > target_ip * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{max_pass_pct * 100} % above the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.") end # check electric equipment model_elec_si = space_type.getElectricEquipmentPowerPerFloorArea(floor_area, num_people) data['electric_equipment_per_area'].nil? ? (target_elec_ip = 0.0) : (target_elec_ip = data['electric_equipment_per_area']) source_units = 'W/m^2' target_units = 'W/ft^2' load_type = 'Electric Power Density' model_ip = OpenStudio.convert(model_elec_si, source_units, target_units).get target_ip = target_elec_ip.to_f model_ip_neat = OpenStudio.toNeatString(model_ip, 2, true) target_ip_neat = OpenStudio.toNeatString(target_ip, 2, true) if model_ip < target_ip * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{min_pass_pct * 100} % below the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.") elsif model_ip > target_ip * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{max_pass_pct * 100} % above the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.") end # check gas equipment model_gas_si = space_type.getGasEquipmentPowerPerFloorArea(floor_area, num_people) data['gas_equipment_per_area'].nil? ? (target_gas_ip = 0.0) : (target_gas_ip = data['gas_equipment_per_area']) source_units = 'W/m^2' target_units = 'Btu/hr*ft^2' load_type = 'Gas Power Density' model_ip = OpenStudio.convert(model_gas_si, source_units, target_units).get target_ip = target_gas_ip.to_f model_ip_neat = OpenStudio.toNeatString(model_ip, 2, true) target_ip_neat = OpenStudio.toNeatString(target_ip, 2, true) if model_ip < target_ip * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{min_pass_pct * 100} % below the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.") elsif model_ip > target_ip * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{max_pass_pct * 100} % above the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.") end # check people model_occ_si = space_type.getPeoplePerFloorArea(floor_area) data['occupancy_per_area'].nil? ? (target_occ_ip = 0.0) : (target_occ_ip = data['occupancy_per_area']) source_units = '1/m^2' # people/m^2 target_units = '1/ft^2' # people per ft^2 (can't add *1000) to the bottom, need to do later load_type = 'Occupancy per Area' model_ip = OpenStudio.convert(model_occ_si, source_units, target_units).get * 1000.0 target_ip = target_occ_ip.to_f model_ip_neat = OpenStudio.toNeatString(model_ip, 2, true) target_ip_neat = OpenStudio.toNeatString(target_ip, 2, true) # for people need to update target units just for display. Can't be used for converstion. target_units = 'People/1000 ft^2' if model_ip < target_ip * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{min_pass_pct * 100} % below the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.") elsif model_ip > target_ip * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{max_pass_pct * 100} % above the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.") end # get volume for space type for use with ventilation and infiltration space_type_volume = 0.0 space_type_exterior_area = 0.0 space_type_exterior_wall_area = 0.0 space_type.spaces.each do |space| space_type_volume += space.volume * space.multiplier space_type_exterior_area = space.exteriorArea * space.multiplier space_type_exterior_wall_area = space.exteriorWallArea * space.multiplier end # get design spec OA object if space_type.designSpecificationOutdoorAir.is_initialized oa = space_type.designSpecificationOutdoorAir.get oa_method = oa.outdoorAirMethod oa_per_person = oa.outdoorAirFlowperPerson oa_ach = oa.outdoorAirFlowAirChangesperHour * space_type_volume oa_per_area = oa.outdoorAirFlowperFloorArea * floor_area oa_flow_rate = oa.outdoorAirFlowRate oa_total = oa_ach + oa_per_area + oa_flow_rate value_count = 0 if oa_per_person > 0 then value_count += 1 end if oa_ach > 0 then value_count += 1 end if oa_per_area > 0 then value_count += 1 end if oa_flow_rate > 0 then value_count += 1 end if (oa_method != 'Sum') && (value_count > 1) check_elems << OpenStudio::Attribute.new('flag', "Outdoor Air Method for #{space_type.name} was #{oa_method}. Expected value was Sum.") end else oa_per_person = 0.0 end # get target values for OA target_oa_per_person_ip = data['ventilation_per_person'].to_f # ft^3/min*person target_oa_ach_ip = data['ventilation_air_changes'].to_f # ach target_oa_per_area_ip = data['ventilation_per_area'].to_f # ft^3/min*ft^2 if target_oa_per_person_ip.nil? target_oa_per_person_si = 0.0 else target_oa_per_person_si = OpenStudio.convert(target_oa_per_person_ip, 'cfm', 'm^3/s').get end if target_oa_ach_ip.nil? target_oa_ach_si = 0.0 else target_oa_ach_si = target_oa_ach_ip * space_type_volume end if target_oa_per_area_ip.nil? target_oa_per_area_si = 0.0 else target_oa_per_area_si = OpenStudio.convert(target_oa_per_area_ip, 'cfm/ft^2', 'm^3/s*m^2').get * floor_area end target_oa_total = target_oa_ach_si + target_oa_per_area_si # check oa per person source_units = 'm^3/s' target_units = 'cfm' load_type = 'Outdoor Air Per Person' model_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(oa_per_person, source_units, target_units).get, 2, true) target_ip_neat = OpenStudio.toNeatString(target_oa_per_person_ip, 2, true) if oa_per_person < target_oa_per_person_si * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{min_pass_pct * 100} % below the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.") elsif oa_per_person > target_oa_per_person_si * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{max_pass_pct * 100} % above the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.") end # check other oa source_units = 'm^3/s' target_units = 'cfm' load_type = 'Outdoor Air (Excluding per Person Value)' model_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(oa_total, source_units, target_units).get, 2, true) target_ip_neat = OpenStudio.toNeatString(OpenStudio.convert(target_oa_total, source_units, target_units).get, 2, true) if oa_total < target_oa_total * (1.0 - min_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{min_pass_pct * 100} % below the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.") elsif oa_total > target_oa_total * (1.0 + max_pass_pct) check_elems << OpenStudio::Attribute.new('flag', "#{load_type} of #{model_ip_neat} (#{target_units}) for #{space_type.name} is more than #{max_pass_pct * 100} % above the expected value of #{target_ip_neat} (#{target_units}) for #{display_standard}.") end end # warn if there are spaces in model that don't use space type unless they appear to be plenums @model.getSpaces.sort.each do |space| next if OpenstudioStandards::Space.space_plenum?(space) if !space.spaceType.is_initialized check_elems << OpenStudio::Attribute.new('flag', "#{space.name} doesn't have a space type assigned, can't validate internal loads.") end end # @todo need to address internal loads where fuel is variable like cooking and laundry # @todo For now we are not going to loop through spaces looking for loads beyond what comes from space type # @todo space infiltration 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 end |
.check_internal_loads_schedules(category, target_standard, min_pass_pct: 0.2, max_pass_pct: 0.2, name_only: false) ⇒ OpenStudio::Attribute
Check the internal load schedules against template prototypes
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 |
# File 'lib/openstudio-standards/qaqc/internal_loads.rb', line 347 def self.check_internal_loads_schedules(category, target_standard, min_pass_pct: 0.2, max_pass_pct: 0.2, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Schedules') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check schedules for lighting, ventilation, occupant density, plug loads, and equipment based on DOE reference building schedules in terms of full load hours per year.') # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # loop through all space types used in the model @model.getSpaceTypes.each do |space_type| next if space_type.floorArea <= 0 # load in standard info for this space type data = std.space_type_get_standards_data(space_type) if data.nil? || data.empty? # skip if all spaces using this space type are plenums all_spaces_plenums = true space_type.spaces.each do |space| unless OpenstudioStandards::Space.space_plenum?(space) all_spaces_plenums = false break end end unless all_spaces_plenums check_elems << OpenStudio::Attribute.new('flag', "Unexpected standards type for #{space_type.name}, can't validate schedules.") end next end # temp model to hold schedules to check model_temp = OpenStudio::Model::Model.new # check lighting schedules data['lighting_per_area'].nil? ? (target_ip = 0.0) : (target_ip = data['lighting_per_area']) if target_ip.to_f > 0 schedule_target = std.model_add_schedule(model_temp, data['lighting_schedule']) if !schedule_target check_elems << OpenStudio::Attribute.new('flag', "Didn't find schedule named #{data['lighting_schedule']} in standards json.") elsif !schedule_target.to_ScheduleRuleset.is_initialized check_elems << OpenStudio::Attribute.new('flag', "Schedule named #{schedule_target.name} is a #{schedule_target.class}, not a ScheduleRuleset schedule.") else schedule_target = schedule_target.to_ScheduleRuleset.get # loop through and test individual load instances expected_hours = OpenstudioStandards::Schedules.schedule_ruleset_get_equivalent_full_load_hours(schedule_target) space_type.lights.each do |space_load_instance| inst_sch_check = OpenstudioStandards::QAQC.space_load_instance_schedule_check(space_load_instance, expected_hours, std: std, min_pass_pct: min_pass_pct, max_pass_pct: max_pass_pct) if inst_sch_check then check_elems << inst_sch_check end end end end # check electric equipment schedules data['electric_equipment_per_area'].nil? ? (target_ip = 0.0) : (target_ip = data['electric_equipment_per_area']) if target_ip.to_f > 0 schedule_target = std.model_add_schedule(model_temp, data['electric_equipment_schedule']) if !schedule_target check_elems << OpenStudio::Attribute.new('flag', "Didn't find schedule named #{data['electric_equipment_schedule']} in standards json.") elsif !schedule_target.to_ScheduleRuleset.is_initialized check_elems << OpenStudio::Attribute.new('flag', "Schedule named #{schedule_target.name} is a #{schedule_target.class}, not a ScheduleRuleset schedule.") else schedule_target = schedule_target.to_ScheduleRuleset.get # loop through and test individual load instances expected_hours = OpenstudioStandards::Schedules.schedule_ruleset_get_equivalent_full_load_hours(schedule_target) space_type.electricEquipment.each do |space_load_instance| inst_sch_check = OpenstudioStandards::QAQC.space_load_instance_schedule_check(space_load_instance, expected_hours, std: std, min_pass_pct: min_pass_pct, max_pass_pct: max_pass_pct) if inst_sch_check then check_elems << inst_sch_check end end end end # check gas equipment schedules # @todo - update measure test to with space type to check this data['gas_equipment_per_area'].nil? ? (target_ip = 0.0) : (target_ip = data['gas_equipment_per_area']) if target_ip.to_f > 0 schedule_target = std.model_add_schedule(model_temp, data['gas_equipment_schedule']) if !schedule_target check_elems << OpenStudio::Attribute.new('flag', "Didn't find schedule named #{data['gas_equipment_schedule']} in standards json.") elsif !schedule_target.to_ScheduleRuleset.is_initialized check_elems << OpenStudio::Attribute.new('flag', "Schedule named #{schedule_target.name} is a #{schedule_target.class}, not a ScheduleRuleset schedule.") else schedule_target = schedule_target.to_ScheduleRuleset.get # loop through and test individual load instances expected_hours = OpenstudioStandards::Schedules.schedule_ruleset_get_equivalent_full_load_hours(schedule_target) space_type.gasEquipment.each do |space_load_instance| inst_sch_check = OpenstudioStandards::QAQC.space_load_instance_schedule_check(space_load_instance, expected_hours, std: std, min_pass_pct: min_pass_pct, max_pass_pct: max_pass_pct) if inst_sch_check then check_elems << inst_sch_check end end end end # check occupancy schedules data['occupancy_per_area'].nil? ? (target_ip = 0.0) : (target_ip = data['occupancy_per_area']) if target_ip.to_f > 0 schedule_target = std.model_add_schedule(model_temp, data['occupancy_schedule']) if !schedule_target check_elems << OpenStudio::Attribute.new('flag', "Didn't find schedule named #{data['occupancy_schedule']} in standards json.") elsif !schedule_target.to_ScheduleRuleset.is_initialized check_elems << OpenStudio::Attribute.new('flag', "Schedule named #{schedule_target.name} is a #{schedule_target.class}, not a ScheduleRuleset schedule.") else schedule_target = schedule_target.to_ScheduleRuleset.get # loop through and test individual load instances expected_hours = OpenstudioStandards::Schedules.schedule_ruleset_get_equivalent_full_load_hours(schedule_target) space_type.people.each do |space_load_instance| inst_sch_check = OpenstudioStandards::QAQC.space_load_instance_schedule_check(space_load_instance, expected_hours, std: std, min_pass_pct: min_pass_pct, max_pass_pct: max_pass_pct) if inst_sch_check then check_elems << inst_sch_check end end end end # @todo: check ventilation schedules # if objects are in the model should they just be always on schedule, or have a 8760 annual equivalent value # oa_schedule should not exist, or if it does shoudl be always on or have 8760 annual equivalent value if space_type.designSpecificationOutdoorAir.is_initialized oa = space_type.designSpecificationOutdoorAir.get if oa.outdoorAirFlowRateFractionSchedule.is_initialized # @todo: update measure test to check this expected_hours = 8760 inst_sch_check = OpenstudioStandards::QAQC.space_load_instance_schedule_check(oa, expected_hours, std: std, min_pass_pct: min_pass_pct, max_pass_pct: max_pass_pct) if inst_sch_check then check_elems << inst_sch_check end end end # notes # current logic only looks at 8760 values and not design days # when multiple instances of a type currently check every schedule by itself. In future could do weighted avgerage merge # not looking at infiltration schedules # not looking at luminaires # not looking at space loads, only loads at space type # only checking schedules where standard shows non zero load value # model load for space type where standards doesn't have one wont throw flag about mis-matched schedules end # warn if there are spaces in model that don't use space type unless they appear to be plenums @model.getSpaces.sort.each do |space| next if OpenstudioStandards::Space.space_plenum?(space) if !space.spaceType.is_initialized check_elems << OpenStudio::Attribute.new('flag', "#{space.name} doesn't have a space type assigned, can't validate schedules.") 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 end |
.check_la_weather_files(category, zip_code, name_only: false) ⇒ OpenStudio::Attribute
checks the weather files matches the appropriate weather file for the Los Angeles zip code
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 |
# File 'lib/openstudio-standards/qaqc/weather_files.rb', line 111 def self.check_la_weather_files(category, zip_code, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'LA Weather Files') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that correct weather file was used for the selected zip code.') # 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| results << elem.valueAsString end return results end begin # get weather file model_epw = nil if @model.getWeatherFile.url.is_initialized model_epw = @model.getWeatherFile.url.get model_epw = model_epw.gsub('file:', '') model_epw = model_epw.gsub('files/', '') end # Get the correct weather file based on the zip code zip_to_epw = { '90001' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90002' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90003' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90004' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90005' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90006' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90007' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90008' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90010' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90011' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90012' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90013' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90014' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90015' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90016' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90017' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90018' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90019' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90020' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90021' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90022' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90023' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90024' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90025' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90026' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90027' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '90028' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '90029' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90031' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90032' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90033' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90034' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90035' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90036' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90037' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90038' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90039' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90040' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90041' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90042' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90043' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90044' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90045' => 'USA_CA_Los.Angeles.Intl.AP.722950_TMY3.epw', '90046' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '90047' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90048' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90049' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90056' => 'USA_CA_Los.Angeles.Intl.AP.722950_TMY3.epw', '90057' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90058' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90059' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90061' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90062' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90063' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90064' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90065' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90066' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90067' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90068' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '90069' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '90071' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90073' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90077' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90089' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90094' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90095' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90201' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90210' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '90211' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90212' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '90222' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90230' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90232' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90240' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90245' => 'USA_CA_Los.Angeles.Intl.AP.722950_TMY3.epw', '90247' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90248' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90249' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90250' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90254' => 'USA_CA_Los.Angeles.Intl.AP.722950_TMY3.epw', '90255' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90260' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90262' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90263' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90265' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '90266' => 'USA_CA_Los.Angeles.Intl.AP.722950_TMY3.epw', '90270' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90272' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90274' => 'TORRANCE_722955_CZ2010.epw', '90275' => 'TORRANCE_722955_CZ2010.epw', '90277' => 'TORRANCE_722955_CZ2010.epw', '90278' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90280' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90290' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90291' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90292' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90293' => 'USA_CA_Los.Angeles.Intl.AP.722950_TMY3.epw', '90301' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90302' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90303' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90304' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90305' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90401' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90402' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90403' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90404' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90405' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '90501' => 'TORRANCE_722955_CZ2010.epw', '90502' => 'TORRANCE_722955_CZ2010.epw', '90503' => 'TORRANCE_722955_CZ2010.epw', '90504' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90505' => 'TORRANCE_722955_CZ2010.epw', '90506' => 'USA_CA_Hawthorne-Jack.Northrop.Field.722956_TMY3.epw', '90601' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90602' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90603' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90604' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90605' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90606' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90621' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90631' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90638' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90639' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90640' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90650' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90660' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90670' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '90680' => 'TORRANCE_722955_CZ2010.epw', '90710' => 'TORRANCE_722955_CZ2010.epw', '90717' => 'TORRANCE_722955_CZ2010.epw', '90720' => 'TORRANCE_722955_CZ2010.epw', '90731' => 'TORRANCE_722955_CZ2010.epw', '90732' => 'TORRANCE_722955_CZ2010.epw', '90740' => 'TORRANCE_722955_CZ2010.epw', '90742' => 'TORRANCE_722955_CZ2010.epw', '90743' => 'TORRANCE_722955_CZ2010.epw', '90744' => 'TORRANCE_722955_CZ2010.epw', '90745' => 'TORRANCE_722955_CZ2010.epw', '90746' => 'TORRANCE_722955_CZ2010.epw', '90755' => 'TORRANCE_722955_CZ2010.epw', '90802' => 'TORRANCE_722955_CZ2010.epw', '90803' => 'TORRANCE_722955_CZ2010.epw', '90804' => 'TORRANCE_722955_CZ2010.epw', '90806' => 'TORRANCE_722955_CZ2010.epw', '90807' => 'TORRANCE_722955_CZ2010.epw', '90810' => 'TORRANCE_722955_CZ2010.epw', '90813' => 'TORRANCE_722955_CZ2010.epw', '90814' => 'TORRANCE_722955_CZ2010.epw', '90815' => 'TORRANCE_722955_CZ2010.epw', '90840' => 'TORRANCE_722955_CZ2010.epw', '91001' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91006' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91007' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91008' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91010' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91011' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91016' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91020' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91024' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91030' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91040' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91042' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91101' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91103' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91104' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91105' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91106' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91107' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91108' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91123' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91201' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91202' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91203' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91204' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91205' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91206' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91207' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91208' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91214' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91301' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91302' => 'USA_CA_Santa.Monica.Muni.AP.722885_TMY3.epw', '91303' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91304' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91306' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91307' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91311' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91316' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91320' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91321' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91324' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91325' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91326' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91330' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91331' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91335' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91340' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91342' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91343' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91344' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91345' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91350' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91351' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91352' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91354' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91355' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91356' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91360' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91361' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91362' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91364' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91367' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91371' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91377' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91381' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91384' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91387' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91390' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91401' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91402' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91403' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91405' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91406' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91411' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91423' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91436' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '91501' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91502' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91504' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91505' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91506' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91521' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91522' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91523' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91601' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91602' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91604' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91605' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91606' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91607' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91608' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw', '91702' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91706' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91709' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91710' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91711' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91722' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91723' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91724' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91731' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91732' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91733' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91740' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91741' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91744' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91745' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91746' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91748' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91750' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91754' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91755' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91763' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91765' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91766' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91767' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91768' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91770' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91773' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91775' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91776' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91780' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91784' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91789' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91790' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91791' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91792' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91801' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '91803' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '92603' => 'TORRANCE_722955_CZ2010.epw', '92612' => 'TORRANCE_722955_CZ2010.epw', '92614' => 'TORRANCE_722955_CZ2010.epw', '92617' => 'TORRANCE_722955_CZ2010.epw', '92624' => 'TORRANCE_722955_CZ2010.epw', '92625' => 'TORRANCE_722955_CZ2010.epw', '92626' => 'TORRANCE_722955_CZ2010.epw', '92627' => 'TORRANCE_722955_CZ2010.epw', '92629' => 'TORRANCE_722955_CZ2010.epw', '92637' => 'TORRANCE_722955_CZ2010.epw', '92646' => 'TORRANCE_722955_CZ2010.epw', '92647' => 'TORRANCE_722955_CZ2010.epw', '92648' => 'TORRANCE_722955_CZ2010.epw', '92649' => 'TORRANCE_722955_CZ2010.epw', '92651' => 'TORRANCE_722955_CZ2010.epw', '92653' => 'TORRANCE_722955_CZ2010.epw', '92655' => 'TORRANCE_722955_CZ2010.epw', '92656' => 'TORRANCE_722955_CZ2010.epw', '92657' => 'TORRANCE_722955_CZ2010.epw', '92660' => 'TORRANCE_722955_CZ2010.epw', '92661' => 'TORRANCE_722955_CZ2010.epw', '92662' => 'TORRANCE_722955_CZ2010.epw', '92663' => 'TORRANCE_722955_CZ2010.epw', '92672' => 'TORRANCE_722955_CZ2010.epw', '92673' => 'TORRANCE_722955_CZ2010.epw', '92675' => 'TORRANCE_722955_CZ2010.epw', '92677' => 'TORRANCE_722955_CZ2010.epw', '92683' => 'TORRANCE_722955_CZ2010.epw', '92691' => 'TORRANCE_722955_CZ2010.epw', '92692' => 'TORRANCE_722955_CZ2010.epw', '92697' => 'TORRANCE_722955_CZ2010.epw', '92703' => 'TORRANCE_722955_CZ2010.epw', '92704' => 'TORRANCE_722955_CZ2010.epw', '92707' => 'TORRANCE_722955_CZ2010.epw', '92708' => 'TORRANCE_722955_CZ2010.epw', '92821' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '92823' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '92833' => 'LOS-ANGELES-DOWNTOWN_722874_CZ2010.epw', '92841' => 'TORRANCE_722955_CZ2010.epw', '92843' => 'TORRANCE_722955_CZ2010.epw', '92844' => 'TORRANCE_722955_CZ2010.epw', '92845' => 'TORRANCE_722955_CZ2010.epw', '93001' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '93003' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '93004' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '93012' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '93013' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '93015' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '93021' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '93022' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '93023' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '93060' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '93063' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '93065' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '93066' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '93108' => 'USA_CA_Van.Nuys.AP.722886_TMY3.epw', '93510' => 'USA_CA_Burbank-Glendale-Pasadena.Bob.Hope.AP.722880_ TMY3.epw' } correct_epw = zip_to_epw[zip_code] if correct_epw.nil? check_elems << OpenStudio::Attribute.new('flag', "There is no correct weather file specified for the zip code #{zip_code}") end unless model_epw == correct_epw check_elems << OpenStudio::Attribute.new('flag', "The selected weather file #{model_epw} is incorrect for zip code #{zip_code}. The correct weather file is #{correct_epw}.") 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 end |
.check_occupied_zones_conditioned(category, target_standard, name_only: false) ⇒ OpenStudio::Attribute
Check that all zones with people are conditioned (have a thermostat with setpoints)
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/openstudio-standards/qaqc/zone_conditions.rb', line 159 def self.check_occupied_zones_conditioned(category, target_standard, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Conditioned Zones') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that all zones with people have thermostats.') # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin @model.getThermalZones.sort.each do |zone| # only check zones with people num_ppl = zone.numberOfPeople next unless zone.numberOfPeople > 0 # Check that the zone is heated (at a minimum) by checking that the heating setpoint is at least 41F. # Sometimes people include thermostats but use setpoints such that the system never comes on. unless OpenstudioStandards::ThermalZone.thermal_zone_heated?(zone) check_elems << OpenStudio::Attribute.new('flag', "#{zone.name} has #{num_ppl} people but is not heated. Zones containing people are expected to be conditioned, heated-only at a minimum. Heating setpoint must be at least 41F to be considered heated.") 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 end |
.check_plant_loop_capacity(category, target_standard, max_pct_delta: 0.3, name_only: false) ⇒ OpenStudio::Attribute
Check primary plant loop heating and cooling equipment capacity against coil loads to find equipment that is significantly oversized or undersized.
1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 |
# File 'lib/openstudio-standards/qaqc/hvac.rb', line 1126 def self.check_plant_loop_capacity(category, target_standard, max_pct_delta: 0.3, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Plant Capacity') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that plant equipment capacity matches loads.') # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # Check the heating and cooling capacity of the plant loops against their coil loads @model.getPlantLoops.sort.each do |plant_loop| # Heating capacity htg_cap_w = std.plant_loop_total_heating_capacity(plant_loop) # Cooling capacity clg_cap_w = std.plant_loop_total_cooling_capacity(plant_loop) # Sum the load for each coil on the loop htg_load_w = 0.0 clg_load_w = 0.0 plant_loop.demandComponents.each do |dc| obj_type = dc.iddObjectType.valueName.to_s case obj_type when 'OS_Coil_Heating_Water' coil = dc.to_CoilHeatingWater.get if coil.ratedCapacity.is_initialized htg_load_w += coil.ratedCapacity.get elsif coil.autosizedRatedCapacity.is_initialized htg_load_w += coil.autosizedRatedCapacity.get end when 'OS_Coil_Cooling_Water' coil = dc.to_CoilCoolingWater.get if coil.autosizedDesignCoilLoad.is_initialized clg_load_w += coil.autosizedDesignCoilLoad.get end end end # Don't check loops with no loads. These are probably SWH or non-typical loops that can't be checked by simple methods. # Heating if htg_load_w > 0 htg_cap_kbtu_per_hr = OpenStudio.convert(htg_cap_w, 'W', 'kBtu/hr').get.round(1) htg_load_kbtu_per_hr = OpenStudio.convert(htg_load_w, 'W', 'kBtu/hr').get.round(1) if ((htg_cap_w - htg_load_w) / htg_cap_w).abs > max_pct_delta check_elems << OpenStudio::Attribute.new('flag', "For #{plant_loop.name}, the total heating capacity of #{htg_cap_kbtu_per_hr} kBtu/hr is more than #{(max_pct_delta * 100.0).round(2)}% different from the combined coil load of #{htg_load_kbtu_per_hr} kBtu/hr. This could indicate significantly oversized or undersized equipment.") end end # Cooling if clg_load_w > 0 clg_cap_tons = OpenStudio.convert(clg_cap_w, 'W', 'ton').get.round(1) clg_load_tons = OpenStudio.convert(clg_load_w, 'W', 'ton').get.round(1) if ((clg_cap_w - clg_load_w) / clg_cap_w).abs > max_pct_delta check_elems << OpenStudio::Attribute.new('flag', "For #{plant_loop.name}, the total cooling capacity of #{clg_load_tons} tons is more than #{(max_pct_delta * 100.0).round(2)}% different from the combined coil load of #{clg_load_tons} tons. This could indicate significantly oversized or undersized equipment.") end 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 end |
.check_plant_loop_temperatures(category, max_sizing_temp_delta: 2.0, max_operating_temp_delta: 5.0, name_only: false) ⇒ OpenStudio::Attribute
Check the plant loop operational vs. sizing temperatures and make sure everything is coordinated. This identifies problems caused by sizing to one set of conditions and operating at a different set.
1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 |
# File 'lib/openstudio-standards/qaqc/hvac.rb', line 1215 def self.check_plant_loop_temperatures(category, max_sizing_temp_delta: 2.0, max_operating_temp_delta: 5.0, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Plant Loop Temperatures') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that plant loop sizing and operation temperatures are coordinated.') # 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| results << elem.valueAsString end return results end begin # get the weather file run period (as opposed to design day run period) ann_env_pd = nil @sql.availableEnvPeriods.each do |env_pd| env_type = @sql.environmentType(env_pd) if env_type.is_initialized if env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod') ann_env_pd = env_pd break end end end # only try to get the annual timeseries if an annual simulation was run if ann_env_pd.nil? check_elems << OpenStudio::Attribute.new('flag', 'Cannot find the annual simulation run period, cannot check equipment part load ratios.') return check_elems end # Check each plant loop in the model @model.getPlantLoops.sort.each do |plant_loop| supply_outlet_node_name = plant_loop.supplyOutletNode.name.to_s design_supply_temperature = plant_loop.sizingPlant.designLoopExitTemperature design_supply_temperature = OpenStudio.convert(design_supply_temperature, 'C', 'F').get design_temperature_difference = plant_loop.sizingPlant.loopDesignTemperatureDifference design_temperature_difference = OpenStudio.convert(design_temperature_difference, 'K', 'R').get # get min and max temperatures from setpoint manager spm_name = '' spm_type = '<unspecified>' spm_min_temp_f = nil spm_max_temp_f = nil spms = plant_loop.supplyOutletNode.setpointManagers unless spms.empty? spm = spms[0] # assume first setpoint manager is only setpoint manager spm_name = spm.name spm_type = spm.iddObjectType.valueName.to_s spm_temps_f = OpenstudioStandards::HVAC.setpoint_manager_min_max_temperature(spm) spm_min_temp_f = spm_temps_f['min_temp'] spm_max_temp_f = spm_temps_f['max_temp'] end # check setpoint manager temperatures against design temperatures case plant_loop.sizingPlant.loopType when 'Heating' if spm_max_temp_f && ((spm_max_temp_f - design_supply_temperature).abs > max_sizing_temp_delta) check_elems << OpenStudio::Attribute.new('flag', "Minor Error: #{plant_loop.name} sizing uses a #{design_supply_temperature.round(1)}F supply water temperature, but the setpoint manager operates up to #{spm_max_temp_f.round(1)}F.") end when 'Cooling' if spm_min_temp_f && ((spm_min_temp_f - design_supply_temperature).abs > max_sizing_temp_delta) check_elems << OpenStudio::Attribute.new('flag', "Minor Error: #{plant_loop.name} sizing uses a #{design_supply_temperature.round(1)}F supply water temperature, but the setpoint manager operates down to #{spm_min_temp_f.round(1)}F.") end end # get supply water temperatures for supply outlet node supply_temp_timeseries = @sql.timeSeries(ann_env_pd, 'Timestep', 'System Node Temperature', supply_outlet_node_name) if supply_temp_timeseries.empty? check[:items] << { type: 'warning', msg: "No supply node temperature timeseries found for '#{plant_loop.name}'" } next else # convert to ruby array temperatures = [] supply_temp_vector = supply_temp_timeseries.get.values for i in (0..supply_temp_vector.size - 1) temperatures << supply_temp_vector[i] end end # get supply water flow rates for supply outlet node supply_flow_timeseries = @sql.timeSeries(ann_env_pd, 'Timestep', 'System Node Standard Density Volume Flow Rate', supply_outlet_node_name) if supply_flow_timeseries.empty? check_elems << OpenStudio::Attribute.new('flag', "Warning: No supply node temperature timeseries found for '#{plant_loop.name}'") next else # convert to ruby array flowrates = [] supply_flow_vector = supply_flow_timeseries.get.values for i in (0..supply_flow_vector.size - 1) flowrates << supply_flow_vector[i].to_f end end # check reasonableness of supply water temperatures when supply water flow rate is operating = temperatures.select.with_index { |_t, k| flowrates[k] > 1e-8 } = .map { |t| ((t * 1.8) + 32.0) } if .empty? check_elems << OpenStudio::Attribute.new('flag', "Warning: Flowrates are all zero in supply node timeseries for '#{plant_loop.name}'") next end runtime_fraction = .size / temperatures.size.to_f temps_out_of_bounds = [] case plant_loop.sizingPlant.loopType when 'Heating' design_return_temperature = design_supply_temperature - design_temperature_difference expected_max = spm_max_temp_f.nil? ? design_supply_temperature : [design_supply_temperature, spm_max_temp_f].max expected_min = spm_min_temp_f.nil? ? design_return_temperature : [design_return_temperature, spm_min_temp_f].min temps_out_of_bounds = (.select { |t| (((t + ) < expected_min) || ((t - ) > expected_max)) }) when 'Cooling' design_return_temperature = design_supply_temperature + design_temperature_difference expected_max = spm_max_temp_f.nil? ? design_return_temperature : [design_return_temperature, spm_max_temp_f].max expected_min = spm_min_temp_f.nil? ? design_supply_temperature : [design_supply_temperature, spm_min_temp_f].min temps_out_of_bounds = (.select { |t| (((t + ) < expected_min) || ((t - ) > expected_max)) }) when 'Condenser' design_return_temperature = design_supply_temperature + design_temperature_difference expected_max = spm_max_temp_f.nil? ? design_return_temperature : [design_return_temperature, spm_max_temp_f].max temps_out_of_bounds = (.select { |t| ((t < 35.0) || (t > 100.0) || ((t - ) > expected_max)) }) end next if temps_out_of_bounds.empty? min_op_temp_f = temps_out_of_bounds.min max_op_temp_f = temps_out_of_bounds.max # avg_F = temps_out_of_bounds.inject(:+).to_f / temps_out_of_bounds.size spm_min_temp_f = spm_min_temp_f.round(1) unless spm_min_temp_f.nil? spm_max_temp_f = spm_max_temp_f.round(1) unless spm_max_temp_f.nil? err = [] err << 'Major Error:' err << 'Expected supply water temperatures out of bounds for' err << "#{plant_loop.sizingPlant.loopType} plant loop '#{plant_loop.name}'" err << "with a #{design_supply_temperature.round(1)}F design supply temperature and" err << "#{design_return_temperature.round(1)}F design return temperature and" err << "a setpoint manager '#{spm_name}' of type '#{spm_type}' with a" err << "#{spm_min_temp_f}F minimum setpoint temperature and" err << "#{spm_max_temp_f}F maximum setpoint temperature." err << "Out of #{.size}/#{temperatures.size} (#{(runtime_fraction * 100.0).round(1)}%) operating supply water temperatures" err << "#{temps_out_of_bounds.size}/#{.size} (#{((temps_out_of_bounds.size.to_f / .size) * 100.0).round(1)}%)" err << "are out of bounds with #{min_op_temp_f.round(1)}F min and #{max_op_temp_f.round(1)}F max." check_elems << OpenStudio::Attribute.new('flag', err.join(' ').gsub(/\n/, '')) 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 end |
.check_plenum_loads(category, target_standard, name_only: false) ⇒ OpenStudio::Attribute
Check that there are no people or lights in plenums.
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/openstudio-standards/qaqc/zone_conditions.rb', line 12 def self.check_plenum_loads(category, target_standard, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Plenum Loads') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that the plenums do not have people or lights.') # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin @model.getThermalZones.sort.each do |zone| next unless OpenstudioStandards::ThermalZone.thermal_zone_plenum?(zone) # people num_people = zone.numberOfPeople if num_people > 0 check_elems << OpenStudio::Attribute.new('flag', "#{zone.name} is a plenum, but has #{num_people.round(1)} people. Plenums should not contain people.") end # lights lights_w = zone.lightingPower if lights_w > 0 check_elems << OpenStudio::Attribute.new('flag', "#{zone.name} is a plenum, but has #{lights_w.round(1)} W of lights. Plenums should not contain lights.") 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 end |
.check_pump_power(category, target_standard, max_pct_delta: 0.3, name_only: false) ⇒ OpenStudio::Attribute
Check the pumping power (W/gpm) for each pump in the model to identify unrealistically sized pumps.
1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 |
# File 'lib/openstudio-standards/qaqc/hvac.rb', line 1383 def self.check_pump_power(category, target_standard, max_pct_delta: 0.3, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Pump Power') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that pump power vs flow makes sense.') # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # Check each plant loop @model.getPlantLoops.sort.each do |plant_loop| # Set the expected/typical W/gpm loop_type = plant_loop.sizingPlant.loopType case loop_type when 'Heating', 'Condenser' expected_w_per_gpm = 19.0 when 'Cooling' expected_w_per_gpm = 22.0 end # Check the W/gpm for each pump on each plant loop plant_loop.supplyComponents.each do |component| # Get the W/gpm for the pump obj_type = component.iddObjectType.valueName.to_s case obj_type when 'OS_Pump_ConstantSpeed' actual_w_per_gpm = std.pump_rated_w_per_gpm(component.to_PumpConstantSpeed.get) when 'OS_Pump_VariableSpeed' actual_w_per_gpm = std.pump_rated_w_per_gpm(component.to_PumpVariableSpeed.get) when 'OS_HeaderedPumps_ConstantSpeed' actual_w_per_gpm = std.pump_rated_w_per_gpm(component.to_HeaderedPumpsConstantSpeed.get) when 'OS_HeaderedPumps_VariableSpeed' actual_w_per_gpm = std.pump_rated_w_per_gpm(component.to_HeaderedPumpsVariableSpeed.get) else next # Skip non-pump objects end # Compare W/gpm to expected/typical values if ((expected_w_per_gpm - actual_w_per_gpm) / actual_w_per_gpm).abs > max_pct_delta if plant_loop.name.get.to_s.downcase.include? 'service water loop' # some service water loops use just water main pressure and have a dummy pump check_elems << OpenStudio::Attribute.new('flag', "Warning: For #{component.name} on #{plant_loop.name}, the pumping power is #{actual_w_per_gpm.round(1)} W/gpm.") else check_elems << OpenStudio::Attribute.new('flag', "For #{component.name} on #{plant_loop.name}, the actual pumping power of #{actual_w_per_gpm.round(1)} W/gpm is more than #{(max_pct_delta * 100.0).round(2)}% different from the expected #{expected_w_per_gpm} W/gpm for a #{loop_type} plant loop.") end end 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 end |
.check_schedule_coordination(category, target_standard, max_hrs: 2.0, name_only: false) ⇒ OpenStudio::Attribute
Check that the lighting, equipment, and HVAC setpoint schedules coordinate with the occupancy schedules. This is defined as having start and end times within the specified number of hours away from the occupancy schedule.
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/openstudio-standards/qaqc/schedules.rb', line 14 def self.check_schedule_coordination(category, target_standard, max_hrs: 2.0, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Schedule Coordination') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check that lighting, equipment, and HVAC schedules coordinate with occupancy.') # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # Convert max hr limit to OpenStudio Time max_hrs = OpenStudio::Time.new(0, max_hrs.to_i, 0, 0) # Check schedules in each space @model.getSpaces.sort.each do |space| # Occupancy, Lighting, and Equipment Schedules coord_schs = [] occ_schs = [] # Get the space type (optional) space_type = space.spaceType # Occupancy occs = [] occs += space.people # From space directly occs += space_type.get.people if space_type.is_initialized # Inherited from space type occs.each do |occ| occ_schs << occ.numberofPeopleSchedule.get if occ.numberofPeopleSchedule.is_initialized end # Lights lts = [] lts += space.lights # From space directly lts += space_type.get.lights if space_type.is_initialized # Inherited from space type lts.each do |lt| coord_schs << lt.schedule.get if lt.schedule.is_initialized end # Equip plugs = [] plugs += space.electricEquipment # From space directly plugs += space_type.get.electricEquipment if space_type.is_initialized # Inherited from space type plugs.each do |plug| coord_schs << plug.schedule.get if plug.schedule.is_initialized end # HVAC Schedule (airloop-served zones only) if space.thermalZone.is_initialized zone = space.thermalZone.get if zone.airLoopHVAC.is_initialized coord_schs << zone.airLoopHVAC.get.availabilitySchedule end end # Cannot check spaces with no occupancy schedule to compare against next if occ_schs.empty? # Get start and end occupancy times from the first occupancy schedule times = OpenstudioStandards::Schedules.schedule_ruleset_get_start_and_end_times(occ_schs[0]) occ_start_time = times['start_time'] occ_end_time = times['end_time'] # Cannot check a space where the occupancy start time or end time cannot be determined next if occ_start_time.nil? || occ_end_time.nil? # Check all schedules against occupancy # Lights should have a start and end within X hrs of the occupancy start and end coord_schs.each do |coord_sch| # Get start and end time of load/HVAC schedule times = OpenstudioStandards::Schedules.schedule_ruleset_get_start_and_end_times(coord_sch) start_time = times['start_time'] end_time = times['end_time]'] if start_time.nil? check_elems << OpenStudio::Attribute.new('flag', "Could not determine start time of a schedule called #{coord_sch.name}, cannot determine if schedule coordinates with occupancy schedule.") next elsif end_time.nil? check_elems << OpenStudio::Attribute.new('flag', "Could not determine end time of a schedule called #{coord_sch.name}, cannot determine if schedule coordinates with occupancy schedule.") next end # Check start time if (occ_start_time - start_time) > max_hrs || (start_time - occ_start_time) > max_hrs check_elems << OpenStudio::Attribute.new('flag', "The start time of #{coord_sch.name} is #{start_time}, which is more than #{max_hrs} away from the occupancy schedule start time of #{occ_start_time} for #{occ_schs[0].name} in #{space.name}. Schedules do not coordinate.") end # Check end time if (occ_end_time - end_time) > max_hrs || (end_time - occ_end_time) > max_hrs check_elems << OpenStudio::Attribute.new('flag', "The end time of #{coord_sch.name} is #{end_time}, which is more than #{max_hrs} away from the occupancy schedule end time of #{occ_end_time} for #{occ_schs[0].name} in #{space.name}. Schedules do not coordinate.") end 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 end |
.check_service_hot_water(category, target_standard, min_pass_pct: 0.25, max_pass_pct: 0.25, name_only: false) ⇒ OpenStudio::Attribute
Checks the hot water use in a model for typical values
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'lib/openstudio-standards/qaqc/service_water_heating.rb', line 14 def self.check_service_hot_water(category, target_standard, min_pass_pct: 0.25, max_pass_pct: 0.25, 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 # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) 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 annual_equiv_flow_rate = OpenstudioStandards::Schedules.schedule_get_equivalent_full_load_hours(schedule_inst) if annual_equiv_flow_rate.nil? 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. target_consumption = std.model_find_icc_iecc_2015_hot_water_demand(@model, num_units, bedrooms_per_unit) 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 ashrae_hot_water_demand = std.model_find_ashrae_hot_water_demand(@model) # 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.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 annual_equivalent_people = OpenstudioStandards::Schedules.schedule_get_equivalent_full_load_hours(inst_schedule) if annual_equivalent_people.nil? 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_equivalent_people = 0.0 end inst_num_people_hours = annual_equivalent_people * inst_num_people space_type_num_people_hours += inst_num_people_hours 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 annual_equivalent_people = OpenstudioStandards::Schedules.schedule_get_equivalent_full_load_hours(inst_schedule) if annual_equivalent_people.nil? 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_equivalent_people = 0.0 end inst_num_people_hours = annual_equivalent_people * inst_num_people space_type_num_people_hours += inst_num_people_hours 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 service_water_consumption_daily_avg_gal < target_consumption * (1.0 - min_pass_pct) 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_pct * 100} % below the expected value of #{target_consumption.round} gallons per day.") elsif service_water_consumption_daily_avg_gal > target_consumption * (1.0 + max_pass_pct) 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_pct * 100} % above the expected value of #{target_consumption.round} gallons per day.") 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 end |
.check_simultaneous_heating_and_cooling(category, max_pass_pct: 0.1, name_only: false) ⇒ OpenStudio::Attribute
Check for excess simulataneous heating and cooling
1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 |
# File 'lib/openstudio-standards/qaqc/hvac.rb', line 1461 def self.check_simultaneous_heating_and_cooling(category, max_pass_pct: 0.1, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Simultaneous Heating and Cooling') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check for simultaneous heating and cooling by looping through all Single Duct VAV Reheat Air Terminals and analyzing hourly data when there is a cooling load. ') # 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| results << elem.valueAsString end return results end begin # get the weather file run period (as opposed to design day run period) ann_env_pd = nil @sql.availableEnvPeriods.each do |env_pd| env_type = @sql.environmentType(env_pd) if env_type.is_initialized && (env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod')) ann_env_pd = env_pd break end end # only try to get the annual timeseries if an annual simulation was run if ann_env_pd.nil? check_elems << OpenStudio::Attribute.new('flag', 'Cannot find the annual simulation run period, cannot determine simultaneous heating and cooling.') return check_elem end # For each VAV reheat terminal, calculate # the annual total % reheat hours. @model.getAirTerminalSingleDuctVAVReheats.sort.each do |term| # Reheat coil heating rate rht_coil = term.reheatCoil key_value = rht_coil.name.get.to_s.upcase # must be in all caps. time_step = 'Hourly' # "Zone Timestep", "Hourly", "HVAC System Timestep" variable_name = 'Heating Coil Heating Rate' variable_name_alt = 'Heating Coil Air Heating Rate' rht_rate_ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) # key value would go at the end if we used it. # try and alternate variable name if rht_rate_ts.empty? rht_rate_ts = @sql.timeSeries(ann_env_pd, time_step, variable_name_alt, key_value) # key value would go at the end if we used it. end if rht_rate_ts.empty? check_elems << OpenStudio::Attribute.new('flag', "Heating Coil (Air) Heating Rate Timeseries not found for #{key_value}.") else rht_rate_ts = rht_rate_ts.get.values # Put timeseries into array rht_rate_vals = [] for i in 0..(rht_rate_ts.size - 1) rht_rate_vals << rht_rate_ts[i] end # Zone Air Terminal Sensible Heating Rate key_value = "ADU #{term.name.get.to_s.upcase}" # must be in all caps. time_step = 'Hourly' # "Zone Timestep", "Hourly", "HVAC System Timestep" variable_name = 'Zone Air Terminal Sensible Cooling Rate' clg_rate_ts = @sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) # key value would go at the end if we used it. if clg_rate_ts.empty? check_elems << OpenStudio::Attribute.new('flag', "Zone Air Terminal Sensible Cooling Rate Timeseries not found for #{key_value}.") else clg_rate_ts = clg_rate_ts.get.values # Put timeseries into array clg_rate_vals = [] for i in 0..(clg_rate_ts.size - 1) clg_rate_vals << clg_rate_ts[i] end # Loop through each timestep and calculate the hourly # % reheat value. ann_rht_hrs = 0 ann_clg_hrs = 0 ann_pcts = [] rht_rate_vals.zip(clg_rate_vals).each do |rht_w, clg_w| # Skip hours with no cooling (in heating mode) next if clg_w == 0 pct_overcool_rht = rht_w / (rht_w + clg_w) ann_rht_hrs += pct_overcool_rht # implied * 1hr b/c hrly results ann_clg_hrs += 1 ann_pcts << pct_overcool_rht.round(3) end # Calculate annual % reheat hours ann_pct_reheat = ((ann_rht_hrs / ann_clg_hrs) * 100).round(1) # Compare to limit if ann_pct_reheat > max_pass_pct * 100.0 check_elems << OpenStudio::Attribute.new('flag', "#{term.name} has #{ann_pct_reheat}% overcool-reheat, which is greater than the limit of #{max_pass_pct * 100.0}%. This terminal is in cooling mode for #{ann_clg_hrs} hours of the year.") end end 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 end |
.check_supply_air_and_thermostat_temperature_difference(category, target_standard, max_delta: 2.0, name_only: false) ⇒ OpenStudio::Attribute
Check for excess simulataneous heating and cooling
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/openstudio-standards/qaqc/zone_conditions.rb', line 66 def self.check_supply_air_and_thermostat_temperature_difference(category, target_standard, max_delta: 2.0, name_only: false) # G3.1.2.9 requires a 20 degree F delta between supply air temperature and zone temperature. target_clg_delta = 20.0 # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Supply and Zone Air Temperature') check_elems << OpenStudio::Attribute.new('category', category) if @utility_name.nil? check_elems << OpenStudio::Attribute.new('description', "Check if fans modeled to ASHRAE 90.1 2013 Section G3.1.2.9 requirements. Compare the supply air temperature for each thermal zone against the thermostat setpoints. Throw flag if temperature difference excedes threshold of #{target_clg_delta}F plus the selected tolerance.") else check_elems << OpenStudio::Attribute.new('description', "Check if fans modeled to ASHRAE 90.1 2013 Section G3.1.2.9 requirements. Compare the supply air temperature for each thermal zone against the thermostat setpoints. Throw flag if temperature difference excedes threshold set by #{@utility_name}.") end # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin # loop through thermal zones @model.getThermalZones.sort.each do |thermal_zone| # skip plenums next if OpenstudioStandards::ThermalZone.thermal_zone_plenum?(thermal_zone) # skip zones without thermostats next unless thermal_zone.thermostatSetpointDualSetpoint.is_initialized # populate thermostat ranges model_clg_min = nil thermostat = thermal_zone.thermostatSetpointDualSetpoint.get if thermostat.coolingSetpointTemperatureSchedule.is_initialized clg_sch = thermostat.coolingSetpointTemperatureSchedule.get schedule_values = nil if clg_sch.to_ScheduleRuleset.is_initialized schedule_values = OpenstudioStandards::Schedules.schedule_ruleset_get_min_max(clg_sch.to_ScheduleRuleset.get) elsif clg_sch.to_ScheduleConstant.is_initialized schedule_values = OpenstudioStandards::Schedules.schedule_constant_get_min_max(clg_sch.to_ScheduleConstant.get) end unless schedule_values.nil? model_clg_min = schedule_values['min'] end end # flag if there is setpoint schedule can't be inspected (isn't ruleset) if model_clg_min.nil? check_elems << OpenStudio::Attribute.new('flag', "Can't inspect thermostat schedules for #{thermal_zone.name}") else # get supply air temps from thermal zone sizing sizing_zone = thermal_zone.sizingZone clg_supply_air_temp = sizing_zone.zoneCoolingDesignSupplyAirTemperature # convert model values to IP model_clg_min_ip = OpenStudio.convert(model_clg_min, 'C', 'F').get clg_supply_air_temp_ip = OpenStudio.convert(clg_supply_air_temp, 'C', 'F').get # check supply air against zone temperature (only check against min setpoint, assume max is night setback) if model_clg_min_ip - clg_supply_air_temp_ip > target_clg_delta + max_delta check_elems << OpenStudio::Attribute.new('flag', "For #{thermal_zone.name} the delta temp between the cooling supply air temp of #{clg_supply_air_temp_ip.round(2)} (F) and the minimum thermostat cooling temp of #{model_clg_min_ip.round(2)} (F) is more than #{max_delta} (F) larger than the expected delta of #{target_clg_delta} (F)") elsif model_clg_min_ip - clg_supply_air_temp_ip < target_clg_delta - max_delta check_elems << OpenStudio::Attribute.new('flag', "For #{thermal_zone.name} the delta temp between the cooling supply air temp of #{clg_supply_air_temp_ip.round(2)} (F) and the minimum thermostat cooling temp of #{model_clg_min_ip.round(2)} (F) is more than #{max_delta} (F) smaller than the expected delta of #{target_clg_delta} (F)") end 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 end |
.check_unmet_hours(category, target_standard, max_unmet_hrs: 550.0, expect_clg_unmet_hrs: false, expect_htg_unmet_hrs: false, name_only: false) ⇒ OpenStudio::Attribute
Check unmet hours
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/openstudio-standards/qaqc/zone_conditions.rb', line 212 def self.check_unmet_hours(category, target_standard, max_unmet_hrs: 550.0, expect_clg_unmet_hrs: false, expect_htg_unmet_hrs: false, name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Unmet Hours') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', 'Check model unmet hours.') # 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| results << elem.valueAsString end return results end std = Standard.build(target_standard) begin unmet_heating_hrs = OpenstudioStandards::SqlFile.model_get_annual_occupied_unmet_heating_hours(@model) unmet_cooling_hrs = OpenstudioStandards::SqlFile.model_get_annual_occupied_unmet_cooling_hours(@model) unmet_hrs = OpenstudioStandards::SqlFile.model_get_annual_occupied_unmet_hours(@model) if unmet_hrs if unmet_hrs > max_unmet_hrs if expect_clg_unmet_hrs && expect_htg_unmet_hrs check_elems << OpenStudio::Attribute.new('flag', "Warning: Unmet heating and cooling hours expected. There were #{unmet_heating_hrs.round(1)} unmet occupied heating hours and #{unmet_cooling_hrs.round(1)} unmet occupied cooling hours (total: #{unmet_hrs.round(1)}).") elsif expect_clg_unmet_hrs && !expect_htg_unmet_hrs && unmet_heating_hrs >= max_unmet_hrs check_elems << OpenStudio::Attribute.new('flag', "Major Error: Unmet cooling hours expected, but unmet heating hours exceeds limit of #{max_unmet_hrs}. There were #{unmet_heating_hrs.round(1)} unmet occupied heating hours and #{unmet_cooling_hrs.round(1)} unmet occupied cooling hours (total: #{unmet_hrs.round(1)}).") elsif expect_clg_unmet_hrs && !expect_htg_unmet_hrs && unmet_heating_hrs < max_unmet_hrs check_elems << OpenStudio::Attribute.new('flag', "Warning: Unmet cooling hours expected. There were #{unmet_heating_hrs.round(1)} unmet occupied heating hours and #{unmet_cooling_hrs.round(1)} unmet occupied cooling hours (total: #{unmet_hrs.round(1)}).") elsif expect_htg_unmet_hrs && !expect_clg_unmet_hrs && unmet_cooling_hrs >= max_unmet_hrs check_elems << OpenStudio::Attribute.new('flag', "Major Error: Unmet heating hours expected, but unmet cooling hours exceeds limit of #{max_unmet_hrs}. There were #{unmet_heating_hrs.round(1)} unmet occupied heating hours and #{unmet_cooling_hrs.round(1)} unmet occupied cooling hours (total: #{unmet_hrs.round(1)}).") elsif expect_htg_unmet_hrs && !expect_clg_unmet_hrs && unmet_cooling_hrs < max_unmet_hrs check_elems << OpenStudio::Attribute.new('flag', "Warning: Unmet heating hours expected. There were #{unmet_heating_hrs.round(1)} unmet occupied heating hours and #{unmet_cooling_hrs.round(1)} unmet occupied cooling hours (total: #{unmet_hrs.round(1)}).") else check_elems << OpenStudio::Attribute.new('flag', "Major Error: There were #{unmet_heating_hrs.round(1)} unmet occupied heating hours and #{unmet_cooling_hrs.round(1)} unmet occupied cooling hours (total: #{unmet_hrs.round(1)}), more than the limit of #{max_unmet_hrs}.") end end else check_elems << OpenStudio::Attribute.new('flag', 'Warning: Could not determine unmet hours; simulation may have failed.') 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 end |
.check_weather_files(category, options, name_only: false) ⇒ OpenStudio::Attribute
Check the weather file design days and climate zone
with child objects, ‘summer’ and ‘winter’ for each design day (strings), and ‘climate_zone’ for the climate zone number
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/openstudio-standards/qaqc/weather_files.rb', line 14 def self.check_weather_files(category, , name_only: false) # summary of the check check_elems = OpenStudio::AttributeVector.new check_elems << OpenStudio::Attribute.new('name', 'Weather Files') check_elems << OpenStudio::Attribute.new('category', category) check_elems << OpenStudio::Attribute.new('description', "Check weather file, design days, and climate zone against #{@utility_name} list of allowable options.") # 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| results << elem.valueAsString end return results end begin # get weather file model_epw = nil if @model.getWeatherFile.url.is_initialized raw_epw = @model.getWeatherFile.url.get end_path_index = raw_epw.rindex('/') model_epw = raw_epw.slice!(end_path_index + 1, raw_epw.length) # everything right of last forward slash end # check design days (model must have one or more of the required summer and winter design days) # get design days names from model model_summer_dd_names = [] model_winter_dd_names = [] @model.getDesignDays.each do |design_day| if design_day.dayType == 'SummerDesignDay' model_summer_dd_names << design_day.name.to_s elsif design_day.dayType == 'WinterDesignDay' model_winter_dd_names << design_day.name.to_s else puts "unexpected day type of #{design_day.dayType} wont' be included in check" end end # find matching weather file from options, as well as design days and climate zone if .key?(model_epw) required_summer_dd = [model_epw]['summer'] required_winter_dd = [model_epw]['winter'] valid_climate_zones = [[model_epw]['climate_zone']] # check for intersection betwen model valid design days summer_intersection = (required_summer_dd & model_summer_dd_names) winter_intersection = (required_winter_dd & model_winter_dd_names) if summer_intersection.empty? && !required_summer_dd.empty? check_elems << OpenStudio::Attribute.new('flag', "Didn't find any of the expected summer design days for #{model_epw}") end if winter_intersection.empty? && !required_winter_dd.empty? check_elems << OpenStudio::Attribute.new('flag', "Didn't find any of the expected winter design days for #{model_epw}") end else check_elems << OpenStudio::Attribute.new('flag', "#{model_epw} is not a an expected weather file.") check_elems << OpenStudio::Attribute.new('flag', "Model doesn't have expected epw file, as a result can't validate design days.") valid_climate_zones = [] .each do |lookup_epw, value| valid_climate_zones << value['climate_zone'] end end # get ashrae climate zone from model model_climate_zone = nil @model.getClimateZones.climateZones.each do |climate_zone| if climate_zone.institution == 'ASHRAE' model_climate_zone = climate_zone.value next end end if model_climate_zone == '' check_elems << OpenStudio::Attribute.new('flag', "The model's ASHRAE climate zone has not been defined. Expected climate zone was #{valid_climate_zones.uniq.join(',')}.") elsif !valid_climate_zones.include?(model_climate_zone) check_elems << OpenStudio::Attribute.new('flag', "The model's ASHRAE climate zone was #{model_climate_zone}. Expected climate zone was #{valid_climate_zones.uniq.join(',')}.") 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 end |
.create_qaqc_html(html_in_path, sections, name) ⇒ String
Cleanup and prepare HTML measures calling this must add the following require calls: require ‘json’ require ‘erb’
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/openstudio-standards/qaqc/reporting.rb', line 15 def self.create_qaqc_html(html_in_path, sections, name) # read in template unless File.exist?(html_in_path) html_in_path = "#{File.dirname(__FILE__)}/report.html.erb" end html_in = '' File.open(html_in_path, 'r') do |file| html_in = file.read end # configure template with variable values # instance variables for erb @sections = sections @name = name renderer = ERB.new(html_in) html_out = renderer.result(binding) # write html file html_out_path = './report.html' File.open(html_out_path, 'w') do |file| file << html_out # make sure data is written to the disk one way or the other begin file.fsync rescue StandardError file.flush end end return html_out_path end |
.create_sections_from_check_attributes(check_elems) ⇒ Array
Make HTML sections from a collection of QAQC checks
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/openstudio-standards/qaqc/reporting.rb', line 51 def self.create_sections_from_check_attributes(check_elems) # developer notes # method below is custom version of standard OpenStudio results methods. It passes an array of sections vs. a single section. # It doesn't use the model or SQL file. It just gets data form OpenStudio attributes passed in # It doesn't have a name_only section since it doesn't populate user arguments # inspecting check attributes # make single table with checks. # make second table with flag description (with column for where it came from) # array to hold sections sections = [] # gather data for section qaqc_check_summary = {} qaqc_check_summary[:title] = 'List of Checks in Measure' qaqc_check_summary[:header] = ['Name', 'Category', 'Flags', 'Description'] qaqc_check_summary[:data] = [] qaqc_check_summary[:data_color] = [] @qaqc_check_section = {} @qaqc_check_section[:title] = 'QAQC Check Summary' @qaqc_check_section[:tables] = [qaqc_check_summary] # add sections to array sections << @qaqc_check_section # counter for flags thrown num_flags = 0 check_elems.each do |check| # gather data for section qaqc_flag_details = {} qaqc_flag_details[:title] = "List of Flags Triggered for #{check.valueAsAttributeVector.first.valueAsString}." qaqc_flag_details[:header] = ['Flag Detail'] qaqc_flag_details[:data] = [] @qaqc_flag_section = {} @qaqc_flag_section[:title] = check.valueAsAttributeVector.first.valueAsString.to_s @qaqc_flag_section[:tables] = [qaqc_flag_details] check_name = nil check_cat = nil check_desc = nil flags = [] # loop through attributes (name,category,description,then optionally one or more flag attributes) check.valueAsAttributeVector.each_with_index do |value, index| case index when 0 check_name = value.valueAsString when 1 check_cat = value.valueAsString when 2 check_desc = value.valueAsString else # should be flag flags << value.valueAsString qaqc_flag_details[:data] << [value.valueAsString] OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.QAQC', "#{check_name} - #{value.valueAsString}") num_flags += 1 end end # add row to table for this check qaqc_check_summary[:data] << [check_name, check_cat, flags.size, check_desc] # add info message for check if no flags found (this way user still knows what ran) if check.valueAsAttributeVector.size < 4 OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.QAQC', "#{check_name} - no flags.") end # color cells based and add logging messages based on flag status if flags.empty? qaqc_check_summary[:data_color] << ['', '', 'lightgreen', ''] OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.QAQC', "#{check_name.downcase.tr(' ', '_')} #{flags.size} flags") else qaqc_check_summary[:data_color] << ['', '', 'indianred', ''] OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.QAQC', "#{check_name.downcase.tr(' ', '_')} #{flags.size} flags") end # add table for this check if there are flags if !qaqc_flag_details[:data].empty? sections << @qaqc_flag_section end end # add total flags registerValue OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.QAQC', "total flags: #{num_flags}") return sections end |
.hourly_part_load_ratio_bins(hourly_part_load_ratios) ⇒ Array<Integer>
Bin the hourly part load ratios into 10% bins
1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 |
# File 'lib/openstudio-standards/qaqc/hvac.rb', line 1582 def self.hourly_part_load_ratio_bins(hourly_part_load_ratios) bins = Array.new(11, 0) hourly_part_load_ratios.each do |plr| if plr <= 0 bins[0] += 1 elsif plr > 0 && plr <= 0.1 bins[1] += 1 elsif plr > 0.1 && plr <= 0.2 bins[2] += 1 elsif plr > 0.2 && plr <= 0.3 bins[3] += 1 elsif plr > 0.3 && plr <= 0.4 bins[4] += 1 elsif plr > 0.4 && plr <= 0.5 bins[5] += 1 elsif plr > 0.5 && plr <= 0.6 bins[6] += 1 elsif plr > 0.6 && plr <= 0.7 bins[7] += 1 elsif plr > 0.7 && plr <= 0.8 bins[8] += 1 elsif plr > 0.8 && plr <= 0.9 bins[9] += 1 elsif plr > 0.9 # add over-100% PLRs to final bin bins[10] += 1 end end # Convert bins from hour counts to % of operating hours. bins.each_with_index do |bin, i| bins[i] = bins[i] * 1.0 / hourly_part_load_ratios.size end return bins end |
.hvac_equipment_part_load_ratio_message(sql, ann_env_pd, time_step, variable_name, equipment, design_power, units: '', expect_low_plr: false) ⇒ String
Checks part loads ratios for a piece of equipment using the part load timeseries
1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 |
# File 'lib/openstudio-standards/qaqc/hvac.rb', line 1629 def self.(sql, ann_env_pd, time_step, variable_name, equipment, design_power, units: '', expect_low_plr: false) msg = nil key_value = equipment.name.get.to_s.upcase # must be in all caps ts = sql.timeSeries(ann_env_pd, time_step, variable_name, key_value) if ts.empty? msg = "Warning: #{variable_name} Timeseries not found for #{key_value}." return msg end if design_power.zero? return msg end # Convert to array ts = ts.get.values plrs = [] for i in 0..(ts.size - 1) plrs << (ts[i] / design_power.to_f) end # Bin part load ratios bins = OpenstudioStandards::HVAC.hourly_part_load_ratio_bins(plrs) frac_hrs_above_90 = bins[10] frac_hrs_above_80 = frac_hrs_above_90 + bins[9] frac_hrs_above_70 = frac_hrs_above_80 + bins[8] frac_hrs_above_60 = frac_hrs_above_70 + bins[7] frac_hrs_above_50 = frac_hrs_above_60 + bins[6] frac_hrs_zero = bins[0] pretty_bins = bins.map { |x| (x * 100).round(2) } # Check top-end part load ratio bins if expect_low_plr msg = "Warning: For #{equipment.name} with design size #{design_power.round(2)} #{units} is expected to have a low part load ratio. Bins of PLR [0%,0%-10%,...]: #{pretty_bins}." elsif frac_hrs_zero == 1.0 msg = "Warning: For #{equipment.name}, all hrs are zero; equipment never runs." elsif frac_hrs_above_50 < 0.01 msg = "Major Error: For #{equipment.name} with design size #{design_power.round(2)} #{units}, #{(frac_hrs_above_50 * 100).round(2)}% of hrs are above 50% part load. This indicates significantly oversized equipment. Bins of PLR [0%,0%-10%,...]: #{pretty_bins}." elsif frac_hrs_above_60 < 0.01 msg = "Minor Error: For #{equipment.name} with design size #{design_power.round(2)} #{units}, #{(frac_hrs_above_60 * 100).round(2)}% of hrs are above 60% part load. This indicates significantly oversized equipment. Bins of PLR [0%,0%-10%,...]: #{pretty_bins}." elsif frac_hrs_above_80 < 0.01 msg = "Warning: For #{equipment.name} with design size #{design_power.round(2)} #{units}, #{(frac_hrs_above_80 * 100).round(2)}% of hrs are above 80% part load. This indicates oversized equipment. Bins of PLR [0%,0%-10%,...]: #{pretty_bins}." elsif frac_hrs_above_90 > 0.05 msg = "Warning: For #{equipment.name} with design size #{design_power.round(2)} #{units}, #{(frac_hrs_above_90 * 100).round(2)}% of hrs are above 90% part load. This indicates undersized equipment. Bins of PLR [0%,0%-10%,...]: #{pretty_bins}." elsif frac_hrs_above_90 > 0.1 msg = "Minor Error: For #{equipment.name} with design size #{design_power.round(2)} #{units}, #{(frac_hrs_above_90 * 100).round(2)}% of hrs are above 90% part load. This indicates significantly undersized equipment. Bins of PLR [0%,0%-10%,...]: #{pretty_bins}." elsif frac_hrs_above_90 > 0.2 msg = "Major Error: For #{equipment.name} with design size #{design_power.round(2)} #{units}, #{(frac_hrs_above_90 * 100).round(2)}% of hrs are above 90% part load. This indicates significantly undersized equipment. Bins of PLR [0%,0%-10%,...]: #{pretty_bins}." end return msg end |
.make_qaqc_results_vector(skip_weekends = true, skip_holidays = true, start_mo = 'June', start_day = 1, start_hr = 14, end_mo = 'September', end_day = 30, end_hr = 18, electricity_consumption_tou_periods = []) ⇒ OpenStudio::AttributeVector
Reports out the detailed simulation results needed by EDAPT and other QAQC programs Results are output as OpenStudio::Attributes
time-of-use electricity consumption values to the annual consumption information. Periods may overlap, but should be listed in the order in which they must be checked, where the value will be assigned to the first encountered period it falls into. An example hash looks like this:
{
'tou_name' => 'system_peak',
'tou_id' => 1,
'skip_weekends' => true,
'skip_holidays' => true,
'start_mo' => 'July',
'start_day' => 1,
'start_hr' => 14,
'end_mo' => 'August',
'end_day' => 31,
'end_hr' => 18
}
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 |
# File 'lib/openstudio-standards/qaqc/create_results.rb', line 35 def self.make_qaqc_results_vector(skip_weekends = true, skip_holidays = true, start_mo = 'June', start_day = 1, start_hr = 14, end_mo = 'September', end_day = 30, end_hr = 18, electricity_consumption_tou_periods = []) # get the current version of OS being used to determine if sql query # changes are needed (for when E+ changes). os_version = OpenStudio::VersionString.new(OpenStudio.openStudioVersion) # make an attribute vector to hold results result_elems = OpenStudio::AttributeVector.new # floor_area floor_area_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='Building Area' AND RowName='Net Conditioned Building Area' AND ColumnName='Area' AND Units='m2'" floor_area = @sql.execAndReturnFirstDouble(floor_area_query) if floor_area.is_initialized result_elems << OpenStudio::Attribute.new('floor_area', floor_area.get, 'm^2') else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.QAQC', 'Building floor area not found') return false end # inflation approach inf_appr_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Life-Cycle Cost Parameters' AND RowName='Inflation Approach' AND ColumnName='Value'" inf_appr = @sql.execAndReturnFirstString(inf_appr_query) if inf_appr.is_initialized if inf_appr.get == 'ConstantDollar' inf_appr = 'Constant Dollar' elsif inf_appr.get == 'CurrentDollar' inf_appr = 'Current Dollar' else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.QAQC', "Inflation approach: #{inf_appr.get} not recognized") return OpenStudio::Attribute.new('report', result_elems) end OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.QAQC', "Inflation approach = #{inf_appr}") else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.QAQC', 'Could not determine inflation approach used') return OpenStudio::Attribute.new('report', result_elems) end # base year base_yr_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Life-Cycle Cost Parameters' AND RowName='Base Date' AND ColumnName='Value'" base_yr = @sql.execAndReturnFirstString(base_yr_query) if base_yr.is_initialized if base_yr.get =~ /\d\d\d\d/ base_yr = base_yr.get.match(/\d\d\d\d/)[0].to_f else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.QAQC', "Could not determine the analysis start year from #{base_yr.get}") return OpenStudio::Attribute.new('report', result_elems) end else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.QAQC', 'Could not determine analysis start year') return OpenStudio::Attribute.new('report', result_elems) end # analysis length length_yrs_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Life-Cycle Cost Parameters' AND RowName='Length of Study Period in Years' AND ColumnName='Value'" length_yrs = @sql.execAndReturnFirstInt(length_yrs_query) if length_yrs.is_initialized OpenStudio.logFree(OpenStudio::Error, "Analysis length = #{length_yrs.get} yrs") length_yrs = length_yrs.get else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.QAQC', 'Could not determine analysis length') return OpenStudio::Attribute.new('report', result_elems) end # cash flows cash_flow_elems = OpenStudio::AttributeVector.new # setup a vector for each type of cash flow cap_cash_flow_elems = OpenStudio::AttributeVector.new om_cash_flow_elems = OpenStudio::AttributeVector.new energy_cash_flow_elems = OpenStudio::AttributeVector.new water_cash_flow_elems = OpenStudio::AttributeVector.new tot_cash_flow_elems = OpenStudio::AttributeVector.new # add the type to the element cap_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Capital Costs") om_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Operating Costs") energy_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Energy Costs") water_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Water Costs") tot_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Total Costs") # record the cash flow in these hashes cap_cash_flow = {} om_cash_flow = {} energy_cash_flow = {} water_cash_flow = {} tot_cash_flow = {} # loop through each year and record the cash flow for i in 0..(length_yrs - 1) do new_yr = base_yr + i yr = nil if os_version > OpenStudio::VersionString.new('1.5.3') yr = "January #{new_yr.round}" else yr = "January #{new_yr.round}" end ann_cap_cash = 0.0 ann_om_cash = 0.0 ann_energy_cash = 0.0 ann_water_cash = 0.0 ann_tot_cash = 0.0 # capital cash flow cap_cash_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Capital Cash Flow by Category (Without Escalation)' AND RowName='#{yr}' AND ColumnName='Total'" cap_cash = @sql.execAndReturnFirstDouble(cap_cash_query) if cap_cash.is_initialized ann_cap_cash += cap_cash.get ann_tot_cash += cap_cash.get end # o&m cash flow (excluding utility costs) om_types = ['Maintenance', 'Repair', 'Operation', 'Replacement', 'MinorOverhaul', 'MajorOverhaul', 'OtherOperational'] om_types.each do |om_type| om_cash_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Operating Cash Flow by Category (Without Escalation)' AND RowName='#{yr}' AND ColumnName='#{om_type}'" om_cash = @sql.execAndReturnFirstDouble(om_cash_query) if om_cash.is_initialized ann_om_cash += om_cash.get ann_tot_cash += om_cash.get end end # energy cash flow energy_cash_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Operating Cash Flow by Category (Without Escalation)' AND RowName='#{yr}' AND ColumnName='Energy'" energy_cash = @sql.execAndReturnFirstDouble(energy_cash_query) if energy_cash.is_initialized ann_energy_cash += energy_cash.get ann_tot_cash += energy_cash.get end # water cash flow water_cash_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Operating Cash Flow by Category (Without Escalation)' AND RowName='#{yr}' AND ColumnName='Water'" water_cash = @sql.execAndReturnFirstDouble(water_cash_query) if water_cash.is_initialized ann_water_cash += water_cash.get ann_tot_cash += water_cash.get end # log the values for this year cap_cash_flow[yr] = ann_cap_cash om_cash_flow[yr] = ann_om_cash energy_cash_flow[yr] = ann_energy_cash water_cash_flow[yr] = ann_water_cash tot_cash_flow[yr] = ann_tot_cash cap_cash_flow_elems << OpenStudio::Attribute.new('year', ann_cap_cash, 'dollars') om_cash_flow_elems << OpenStudio::Attribute.new('year', ann_om_cash, 'dollars') energy_cash_flow_elems << OpenStudio::Attribute.new('year', ann_energy_cash, 'dollars') water_cash_flow_elems << OpenStudio::Attribute.new('year', ann_water_cash, 'dollars') tot_cash_flow_elems << OpenStudio::Attribute.new('year', ann_tot_cash, 'dollars') end # end cash flows cash_flow_elems << OpenStudio::Attribute.new('cash_flow', cap_cash_flow_elems) cash_flow_elems << OpenStudio::Attribute.new('cash_flow', om_cash_flow_elems) cash_flow_elems << OpenStudio::Attribute.new('cash_flow', energy_cash_flow_elems) cash_flow_elems << OpenStudio::Attribute.new('cash_flow', water_cash_flow_elems) cash_flow_elems << OpenStudio::Attribute.new('cash_flow', tot_cash_flow_elems) result_elems << OpenStudio::Attribute.new('cash_flows', cash_flow_elems) # list of all end uses in OpenStudio end_use_cat_types = [] OpenStudio::EndUseCategoryType.getValues.each do |end_use_val| end_use_cat_types << OpenStudio::EndUseCategoryType.new(end_use_val) end # list of all end use fule types in OpenStudio end_use_fuel_types = [] OpenStudio::EndUseFuelType.getValues.each do |end_use_fuel_type_val| end_use_fuel_types << OpenStudio::EndUseFuelType.new(end_use_fuel_type_val) end # list of the 12 months of the year in OpenStudio months = [] OpenStudio::MonthOfYear.getValues.each do |month_of_year_val| if (month_of_year_val >= 1) && (month_of_year_val <= 12) months << OpenStudio::MonthOfYear.new(month_of_year_val) end end # map each end use category type to the name that will be used in the xml end_use_map = { OpenStudio::EndUseCategoryType.new('Heating').value => 'heating', OpenStudio::EndUseCategoryType.new('Cooling').value => 'cooling', OpenStudio::EndUseCategoryType.new('InteriorLights').value => 'lighting_interior', OpenStudio::EndUseCategoryType.new('ExteriorLights').value => 'lighting_exterior', OpenStudio::EndUseCategoryType.new('InteriorEquipment').value => 'equipment_interior', OpenStudio::EndUseCategoryType.new('ExteriorEquipment').value => 'equipment_exterior', OpenStudio::EndUseCategoryType.new('Fans').value => 'fans', OpenStudio::EndUseCategoryType.new('Pumps').value => 'pumps', OpenStudio::EndUseCategoryType.new('HeatRejection').value => 'heat_rejection', OpenStudio::EndUseCategoryType.new('Humidifier').value => 'humidification', OpenStudio::EndUseCategoryType.new('HeatRecovery').value => 'heat_recovery', OpenStudio::EndUseCategoryType.new('WaterSystems').value => 'water_systems', OpenStudio::EndUseCategoryType.new('Refrigeration').value => 'refrigeration', OpenStudio::EndUseCategoryType.new('Generators').value => 'generators' } # map each fuel type in EndUseFuelTypes to a specific FuelTypes fuel_type_map = { OpenStudio::EndUseFuelType.new('Electricity').value => OpenStudio::FuelType.new('Electricity'), OpenStudio::EndUseFuelType.new('Gas').value => OpenStudio::FuelType.new('Gas'), OpenStudio::EndUseFuelType.new('AdditionalFuel').value => OpenStudio::FuelType.new('Diesel'), # TODO: add other fuel types OpenStudio::EndUseFuelType.new('DistrictCooling').value => OpenStudio::FuelType.new('DistrictCooling'), OpenStudio::EndUseFuelType.new('DistrictHeating').value => OpenStudio::FuelType.new('DistrictHeating'), OpenStudio::EndUseFuelType.new('Water').value => OpenStudio::FuelType.new('Water') } # map each fuel type in EndUseFuelTypes to a specific FuelTypes fuel_type_alias_map = { OpenStudio::EndUseFuelType.new('Electricity').value => 'electricity', OpenStudio::EndUseFuelType.new('Gas').value => 'gas', OpenStudio::EndUseFuelType.new('AdditionalFuel').value => 'other_energy', OpenStudio::EndUseFuelType.new('DistrictCooling').value => 'district_cooling', OpenStudio::EndUseFuelType.new('DistrictHeating').value => 'district_heating', OpenStudio::EndUseFuelType.new('Water').value => 'water' } # annual "annual" annual_elems = OpenStudio::AttributeVector.new # consumption "consumption" cons_elems = OpenStudio::AttributeVector.new # electricity electricity = @sql.electricityTotalEndUses if electricity.is_initialized cons_elems << OpenStudio::Attribute.new('electricity', electricity.get, 'GJ') else cons_elems << OpenStudio::Attribute.new('electricity', 0.0, 'GJ') end # gas gas = @sql.naturalGasTotalEndUses if gas.is_initialized cons_elems << OpenStudio::Attribute.new('gas', gas.get, 'GJ') else cons_elems << OpenStudio::Attribute.new('gas', 0.0, 'GJ') end # other_energy other_energy = @sql.otherFuelTotalEndUses if other_energy.is_initialized cons_elems << OpenStudio::Attribute.new('other_energy', other_energy.get, 'GJ') else cons_elems << OpenStudio::Attribute.new('other_energy', 0.0, 'GJ') end # district_cooling district_cooling = @sql.districtCoolingTotalEndUses if district_cooling.is_initialized cons_elems << OpenStudio::Attribute.new('district_cooling', district_cooling.get, 'GJ') else cons_elems << OpenStudio::Attribute.new('district_cooling', 0.0, 'GJ') end # district_heating district_heating = @sql.districtHeatingTotalEndUses if district_heating.is_initialized cons_elems << OpenStudio::Attribute.new('district_heating', district_heating.get, 'GJ') else cons_elems << OpenStudio::Attribute.new('district_heating', 0.0, 'GJ') end # water water = @sql.waterTotalEndUses if water.is_initialized cons_elems << OpenStudio::Attribute.new('water', water.get, 'm^3') else cons_elems << OpenStudio::Attribute.new('water', 0.0, 'm^3') end # end consumption annual_elems << OpenStudio::Attribute.new('consumption', cons_elems) # demand "demand" demand_elems = OpenStudio::AttributeVector.new # get the weather file run period (as opposed to design day run period) ann_env_pd = nil @sql.availableEnvPeriods.each do |env_pd| env_type = @sql.environmentType(env_pd) if env_type.is_initialized && (env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod')) ann_env_pd = env_pd end end # only try to get the annual peak demand if an annual simulation was run if ann_env_pd # make some units to use joule_unit = OpenStudio.createUnit('J').get gigajoule_unit = OpenStudio.createUnit('GJ').get hrs_unit = OpenStudio.createUnit('h').get kilowatt_unit = OpenStudio.createUnit('kW').get # get the annual hours simulated hrs_sim = '(0 - no partial annual simulation)' if @sql.hoursSimulated.is_initialized hrs_sim = @sql.hoursSimulated.get if hrs_sim != 8760 OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.QAQC', "Simulation was only #{hrs_sim} hrs; EDA requires an annual simulation (8760 hrs)") return OpenStudio::Attribute.new('report', result_elems) end end # Get the electricity timeseries to determine the year used elec = @sql.timeSeries(ann_env_pd, 'Zone Timestep', 'Electricity:Facility', '') timeseries_yr = nil if elec.is_initialized timeseries_yr = elec.get.dateTimes[0].date.year else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.QAQC', 'Peak Demand timeseries (Electricity:Facility at zone timestep) could not be found, cannot determine the informatino needed to calculate savings or incentives.') end # Setup the peak demand time window based on input arguments. # Note that holidays and weekends are not excluded because # of a bug in EnergyPlus dates. # This will only impact corner-case buildings that have # peak demand on weekends or holidays, which is unusual. OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.QAQC', "Peak Demand window is #{start_mo} #{start_day} to #{end_mo} #{end_day} from #{start_hr}:00 to #{end_hr}:00.") start_date = OpenStudio::DateTime.new(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(start_mo), start_day, timeseries_yr), OpenStudio::Time.new(0, 0, 0, 0)) end_date = OpenStudio::DateTime.new(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(end_mo), end_day, timeseries_yr), OpenStudio::Time.new(0, 24, 0, 0)) start_time = OpenStudio::Time.new(0, start_hr, 0, 0) end_time = OpenStudio::Time.new(0, end_hr, 0, 0) # Get the day type timeseries. day_types = nil day_type_indices = @sql.timeSeries(ann_env_pd, 'Zone Timestep', 'Site Day Type Index', 'Environment') if day_type_indices.is_initialized # Put values into array day_types = [] day_type_vals = day_type_indices.get.values for i in 0..(day_type_vals.size - 1) day_types << day_type_vals[i] end else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.QAQC', 'Day Type timeseries (Site Day Type Index at zone timestep) could not be found, cannot accurately determine the peak demand.') end # electricity_peak_demand electricity_peak_demand = -1.0 electricity_peak_demand_time = nil # deduce the timestep based on the hours simulated and the number of datapoints in the timeseries if elec.is_initialized && day_types elec = elec.get num_int = elec.values.size int_len_hrs = OpenStudio::Quantity.new(hrs_sim / num_int, hrs_unit) # Put timeseries into array elec_vals = [] ann_elec_vals = elec.values for i in 0..(ann_elec_vals.size - 1) elec_vals << ann_elec_vals[i] end # Put values into array elec_times = [] ann_elec_times = elec.dateTimes for i in 0..(ann_elec_times.size - 1) elec_times << ann_elec_times[i] end # Loop through the time/value pairs and find the peak # excluding the times outside of the Xcel peak demand window elec_times.zip(elec_vals).each_with_index do |vs, ind| date_time = vs[0] val = vs[1] day_type = day_types[ind] time = date_time.time date = date_time.date day_of_week = date.dayOfWeek # Convert the peak demand to kW val_j_per_hr = val / int_len_hrs.value val_kw = OpenStudio.convert(val_j_per_hr, 'J/h', 'kW').get # puts("#{val_kw}kW; #{date}; #{time}; #{day_of_week.valueName}") # Skip times outside of the correct months next if date_time < start_date || date_time > end_date # Skip times before 2pm and after 6pm next if time < start_time || time > end_time # Skip weekends if asked, Sunday = 1, Saturday = 7 if skip_weekends && ((day_type == 1) || (day_type == 7)) next end # Skip holidays if asked, Holiday = 8 if skip_holidays && (day_type == 8) next end # puts("VALID #{val_kw}kW; #{date}; #{time}; #{day_of_week.valueName}") # Check peak demand against this timestep # and update if this timestep is higher. if val > electricity_peak_demand electricity_peak_demand = val electricity_peak_demand_time = date_time end end elec_peak_demand_timestep_j = OpenStudio::Quantity.new(electricity_peak_demand, joule_unit) num_int = elec.values.size int_len_hrs = OpenStudio::Quantity.new(hrs_sim / num_int, hrs_unit) elec_peak_demand_hourly_j_per_hr = elec_peak_demand_timestep_j / int_len_hrs electricity_peak_demand = OpenStudio.convert(elec_peak_demand_hourly_j_per_hr, kilowatt_unit).get.value demand_elems << OpenStudio::Attribute.new('electricity_peak_demand', electricity_peak_demand, 'kW') OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.QAQC', "Peak Demand = #{electricity_peak_demand.round(2)}kW on #{electricity_peak_demand_time}") else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.QAQC', 'Peak Demand timeseries (Electricity:Facility at zone timestep) could not be found, cannot determine the informatino needed to calculate savings or incentives.') demand_elems << OpenStudio::Attribute.new('electricity_peak_demand', 0.0, 'kW') end # Describe the TOU periods electricity_consumption_tou_periods.each do |tou_pd| OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.QAQC', "TOU period #{tou_pd['tou_id']} represents #{tou_pd['tou_name']} and covers #{tou_pd['start_mo']}-#{tou_pd['start_day']} to #{tou_pd['end_mo']}-#{tou_pd['end_day']} from #{tou_pd['start_hr']} to #{tou_pd['end_hr']}, skip weekends = #{tou_pd['skip_weekends']}, skip holidays = #{tou_pd['skip_holidays']}") end # electricity time-of-use periods elec = @sql.timeSeries(ann_env_pd, 'Zone Timestep', 'Electricity:Facility', '') if elec.is_initialized && day_types elec = elec.get # Put timeseries into array elec_vals = [] ann_elec_vals = elec.values for i in 0..(ann_elec_vals.size - 1) elec_vals << ann_elec_vals[i] end # Put values into array elec_times = [] ann_elec_times = elec.dateTimes for i in 0..(ann_elec_times.size - 1) elec_times << ann_elec_times[i] end # Loop through the time/value pairs and find the peak # excluding the times outside of the Xcel peak demand window electricity_tou_vals = Hash.new(0) elec_times.zip(elec_vals).each_with_index do |vs, ind| date_time = vs[0] joules = vs[1] day_type = day_types[ind] time = date_time.time date = date_time.date # puts("#{val_kw}kW; #{date}; #{time}; #{day_of_week.valueName}") # Determine which TOU period this hour falls into tou_period_assigned = false electricity_consumption_tou_periods.each do |tou_pd| pd_start_date = OpenStudio::DateTime.new(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(tou_pd['start_mo']), tou_pd['start_day'], timeseries_yr), OpenStudio::Time.new(0, 0, 0, 0)) pd_end_date = OpenStudio::DateTime.new(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(tou_pd['end_mo']), tou_pd['end_day'], timeseries_yr), OpenStudio::Time.new(0, 24, 0, 0)) pd_start_time = OpenStudio::Time.new(0, tou_pd['start_hr'], 0, 0) pd_end_time = OpenStudio::Time.new(0, tou_pd['end_hr'], 0, 0) # Skip times outside of the correct months next if date_time < pd_start_date || date_time > pd_end_date # Skip times before some time and after another time next if time < pd_start_time || time > pd_end_time # Skip weekends if asked, Sunday = 1, Saturday = 7 if tou_pd['skip_weekends'] && ((day_type == 1) || (day_type == 7)) next end # Skip holidays if asked, Holiday = 8 if tou_pd['skip_holidays'] && (day_type == 8) next end # If here, this hour falls into the specified period tou_period_assigned = true electricity_tou_vals[tou_pd['tou_id']] += joules break end # Ensure that the value fell into a period unless tou_period_assigned OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.QAQC', "Did not find a TOU period covering #{time} on #{date}, kWh will not be included in any TOU period.") end end # Register values for any time-of-use period with kWh electricity_tou_vals.each do |tou_pd_id, joules_in_pd| gj_in_pd = OpenStudio.convert(joules_in_pd, 'J', 'GJ').get kwh_in_pd = OpenStudio.convert(joules_in_pd, 'J', 'kWh').get OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.QAQC', "TOU period #{tou_pd_id} annual electricity consumption = #{kwh_in_pd} kWh.") end else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.QAQC', 'Electricity timeseries (Electricity:Facility at zone timestep) could not be found, cannot determine the information needed to calculate savings or incentives.') end # electricity_annual_avg_peak_demand val = @sql.electricityTotalEndUses if val.is_initialized ann_elec_gj = OpenStudio::Quantity.new(val.get, gigajoule_unit) ann_hrs = OpenStudio::Quantity.new(hrs_sim, hrs_unit) elec_ann_avg_peak_demand_hourly_gj_per_hr = ann_elec_gj / ann_hrs electricity_annual_avg_peak_demand = OpenStudio.convert(elec_ann_avg_peak_demand_hourly_gj_per_hr, kilowatt_unit).get.value demand_elems << OpenStudio::Attribute.new('electricity_annual_avg_peak_demand', electricity_annual_avg_peak_demand, 'kW') else demand_elems << OpenStudio::Attribute.new('electricity_annual_avg_peak_demand', 0.0, 'kW') end # district_cooling_peak_demand district_cooling_peak_demand = -1.0 ann_dist_clg_peak_demand_time = nil dist_clg = @sql.timeSeries(ann_env_pd, 'Zone Timestep', 'DistrictCooling:Facility', '') # deduce the timestep based on the hours simulated and the number of datapoints in the timeseries if dist_clg.is_initialized && day_types dist_clg = dist_clg.get num_int = dist_clg.values.size int_len_hrs = OpenStudio::Quantity.new(hrs_sim / num_int, hrs_unit) # Put timeseries into array dist_clg_vals = [] ann_dist_clg_vals = dist_clg.values for i in 0..(ann_dist_clg_vals.size - 1) dist_clg_vals << ann_dist_clg_vals[i] end # Put values into array dist_clg_times = [] ann_dist_clg_times = dist_clg.dateTimes for i in 0..(ann_dist_clg_times.size - 1) dist_clg_times << ann_dist_clg_times[i] end # Loop through the time/value pairs and find the peak # excluding the times outside of the Xcel peak demand window dist_clg_times.zip(dist_clg_vals).each_with_index do |vs, ind| date_time = vs[0] val = vs[1] day_type = day_types[ind] time = date_time.time date = date_time.date day_of_week = date.dayOfWeek # Convert the peak demand to kW val_j_per_hr = val / int_len_hrs.value val_kw = OpenStudio.convert(val_j_per_hr, 'J/h', 'kW').get # puts("#{val_kw}kW; #{date}; #{time}; #{day_of_week.valueName}") # Skip times outside of the correct months next if date_time < start_date || date_time > end_date # Skip times before 2pm and after 6pm next if time < start_time || time > end_time # Skip weekends if asked, Sunday = 1, Saturday = 7 if skip_weekends && ((day_type == 1) || (day_type == 7)) next end # Skip holidays if asked, Holiday = 8 if skip_holidays && (day_type == 8) next end # puts("VALID #{val_kw}kW; #{date}; #{time}; #{day_of_week.valueName}") # Check peak demand against this timestep # and update if this timestep is higher. if val > district_cooling_peak_demand district_cooling_peak_demand = val ann_dist_clg_peak_demand_time = date_time end end dist_clg_peak_demand_timestep_j = OpenStudio::Quantity.new(district_cooling_peak_demand, joule_unit) num_int = dist_clg.values.size int_len_hrs = OpenStudio::Quantity.new(hrs_sim / num_int, hrs_unit) dist_clg_peak_demand_hourly_j_per_hr = dist_clg_peak_demand_timestep_j / int_len_hrs district_cooling_peak_demand = OpenStudio.convert(dist_clg_peak_demand_hourly_j_per_hr, kilowatt_unit).get.value demand_elems << OpenStudio::Attribute.new('district_cooling_peak_demand', district_cooling_peak_demand, 'kW') OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.QAQC', "District Cooling Peak Demand = #{district_cooling_peak_demand.round(2)}kW on #{ann_dist_clg_peak_demand_time}") else demand_elems << OpenStudio::Attribute.new('district_cooling_peak_demand', 0.0, 'kW') end # district cooling time-of-use periods dist_clg = @sql.timeSeries(ann_env_pd, 'Zone Timestep', 'DistrictCooling:Facility', '') if dist_clg.is_initialized && day_types dist_clg = dist_clg.get # Put timeseries into array dist_clg_vals = [] ann_dist_clg_vals = dist_clg.values for i in 0..(ann_dist_clg_vals.size - 1) dist_clg_vals << ann_dist_clg_vals[i] end # Put values into array dist_clg_times = [] ann_dist_clg_times = dist_clg.dateTimes for i in 0..(ann_dist_clg_times.size - 1) dist_clg_times << ann_dist_clg_times[i] end # Loop through the time/value pairs and find the peak # excluding the times outside of the Xcel peak demand window dist_clg_tou_vals = Hash.new(0) dist_clg_times.zip(dist_clg_vals).each_with_index do |vs, ind| date_time = vs[0] joules = vs[1] day_type = day_types[ind] time = date_time.time date = date_time.date # puts("#{val_kw}kW; #{date}; #{time}; #{day_of_week.valueName}") # Determine which TOU period this hour falls into tou_period_assigned = false electricity_consumption_tou_periods.each do |tou_pd| pd_start_date = OpenStudio::DateTime.new(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(tou_pd['start_mo']), tou_pd['start_day'], timeseries_yr), OpenStudio::Time.new(0, 0, 0, 0)) pd_end_date = OpenStudio::DateTime.new(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(tou_pd['end_mo']), tou_pd['end_day'], timeseries_yr), OpenStudio::Time.new(0, 24, 0, 0)) pd_start_time = OpenStudio::Time.new(0, tou_pd['start_hr'], 0, 0) pd_end_time = OpenStudio::Time.new(0, tou_pd['end_hr'], 0, 0) # Skip times outside of the correct months next if date_time < pd_start_date || date_time > pd_end_date # Skip times before some time and after another time next if time < pd_start_time || time > pd_end_time # Skip weekends if asked, Sunday = 1, Saturday = 7 if tou_pd['skip_weekends'] && ((day_type == 1) || (day_type == 7)) next end # Skip holidays if asked, Holiday = 8 if tou_pd['skip_holidays'] && (day_type == 8) next end # If here, this hour falls into the specified period tou_period_assigned = true dist_clg_tou_vals[tou_pd['tou_id']] += joules break end # Ensure that the value fell into a period unless tou_period_assigned OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.QAQC', "Did not find a TOU period covering #{time} on #{date}, kWh will not be included in any TOU period.") end end # Register values for any time-of-use period with kWh dist_clg_tou_vals.each do |tou_pd_id, joules_in_pd| gj_in_pd = OpenStudio.convert(joules_in_pd, 'J', 'GJ').get kwh_in_pd = OpenStudio.convert(joules_in_pd, 'J', 'kWh').get OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.QAQC', "TOU period #{tou_pd_id} annual district cooling consumption = #{kwh_in_pd} kWh.") end else # If TOU periods were specified but this model has no district cooling, report zeroes if !electricity_consumption_tou_periods.empty? # Get the TOU ids tou_ids = [] electricity_consumption_tou_periods.each do |tou_pd| tou_ids << tou_pd['tou_id'] end end end else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.QAQC', 'Could not find an annual run period') return OpenStudio::Attribute.new('report', result_elems) end # end demand annual_elems << OpenStudio::Attribute.new('demand', demand_elems) # utility_cost utility_cost_elems = OpenStudio::AttributeVector.new annual_utility_cost_map = {} # electricity electricity = @sql.annualTotalCost(OpenStudio::FuelType.new('Electricity')) if electricity.is_initialized utility_cost_elems << OpenStudio::Attribute.new('electricity', electricity.get, 'dollars') annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Electricity').valueName] = electricity.get else utility_cost_elems << OpenStudio::Attribute.new('electricity', 0.0, 'dollars') annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Electricity').valueName] = 0.0 end # electricity_consumption_charge and electricity_demand_charge electric_consumption_charge = 0.0 electric_demand_charge = 0.0 electric_rate_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-3. Energy Type Summary' AND RowName='Electricity' AND ColumnName='Utility Rate'" electric_rate_name = @sql.execAndReturnFirstString(electric_rate_query) if electric_rate_name.is_initialized electric_rate_name = electric_rate_name.get.strip # electricity_consumption_charge electric_consumption_charge_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='Tariff Report' AND ReportForString='#{electric_rate_name}' AND TableName='Categories' AND RowName='EnergyCharges (~~$~~)' AND ColumnName='Sum'" val = @sql.execAndReturnFirstDouble(electric_consumption_charge_query) if val.is_initialized electric_consumption_charge = val.get end # electricity_demand_charge electric_demand_charge_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='Tariff Report' AND ReportForString='#{electric_rate_name}' AND TableName='Categories' AND RowName='DemandCharges (~~$~~)' AND ColumnName='Sum'" val = @sql.execAndReturnFirstDouble(electric_demand_charge_query) if val.is_initialized electric_demand_charge = val.get end end utility_cost_elems << OpenStudio::Attribute.new('electricity_consumption_charge', electric_consumption_charge, 'dollars') utility_cost_elems << OpenStudio::Attribute.new('electricity_demand_charge', electric_demand_charge, 'dollars') # gas gas = @sql.annualTotalCost(OpenStudio::FuelType.new('Gas')) if gas.is_initialized annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Gas').valueName] = gas.get else annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Gas').valueName] = 0.0 end # district_cooling district_cooling_charge = 0.0 district_cooling_rate_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-3. Energy Type Summary' AND RowName='District Cooling' AND ColumnName='Utility Rate'" district_cooling_rate_name = @sql.execAndReturnFirstString(district_cooling_rate_query) if district_cooling_rate_name.is_initialized district_cooling_rate_name = district_cooling_rate_name.get.strip # district_cooling_charge district_cooling_charge_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='Tariff Report' AND ReportForString='#{district_cooling_rate_name}' AND TableName='Categories' AND RowName='Basis (~~$~~)' AND ColumnName='Sum'" val = @sql.execAndReturnFirstDouble(district_cooling_charge_query) if val.is_initialized district_cooling_charge = val.get end end annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictCooling').valueName] = district_cooling_charge # district_heating district_heating_charge = 0.0 district_heating_rate_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-3. Energy Type Summary' AND RowName='District Heating' AND ColumnName='Utility Rate'" district_heating_rate_name = @sql.execAndReturnFirstString(district_heating_rate_query) if district_heating_rate_name.is_initialized district_heating_rate_name = district_heating_rate_name.get.strip # district_heating_charge district_heating_charge_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='Tariff Report' AND ReportForString='#{district_heating_rate_name}' AND TableName='Categories' AND RowName='Basis (~~$~~)' AND ColumnName='Sum'" val = @sql.execAndReturnFirstDouble(district_heating_charge_query) if val.is_initialized district_heating_charge = val.get end end annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictHeating').valueName] = district_heating_charge # water water = @sql.annualTotalCost(OpenStudio::FuelType.new('Water')) if water.is_initialized annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Water').valueName] = water.get else annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Water').valueName] = 0.0 end # total total_query = "SELECT Value from tabulardatawithstrings where (reportname = 'Economics Results Summary Report') and (ReportForString = 'Entire Facility') and (TableName = 'Annual Cost') and (ColumnName ='Total') and (((RowName = 'Cost') and (Units = '~~$~~')) or (RowName = 'Cost (~~$~~)'))" total = @sql.execAndReturnFirstDouble(total_query) # other_energy # Subtract off the already accounted for fuel types from the total # to account for fuels on custom meters where the fuel type is not known. prev_tot = 0.0 annual_utility_cost_map.each do |fuel, value| prev_tot += value end if total.is_initialized other_val = total.get - prev_tot annual_utility_cost_map[OpenStudio::EndUseFuelType.new('AdditionalFuel').valueName] = other_val else annual_utility_cost_map[OpenStudio::EndUseFuelType.new('AdditionalFuel').valueName] = 0.0 end # export remaining costs in the correct order # gas utility_cost_elems << OpenStudio::Attribute.new('gas', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Gas').valueName], 'dollars') # other_energy utility_cost_elems << OpenStudio::Attribute.new('other_energy', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('AdditionalFuel').valueName], 'dollars') # district_cooling utility_cost_elems << OpenStudio::Attribute.new('district_cooling', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictCooling').valueName], 'dollars') # district_heating utility_cost_elems << OpenStudio::Attribute.new('district_heating', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictHeating').valueName], 'dollars') # water utility_cost_elems << OpenStudio::Attribute.new('water', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Water').valueName], 'dollars') # total if total.is_initialized utility_cost_elems << OpenStudio::Attribute.new('total', total.get, 'dollars') else utility_cost_elems << OpenStudio::Attribute.new('total', 0.0, 'dollars') end # end_uses - utility costs by end use using average blended cost end_uses_elems = OpenStudio::AttributeVector.new # map to store the costs by end use cost_by_end_use = {} # fill the map with 0.0's to start end_use_cat_types.each do |end_use_cat_type| cost_by_end_use[end_use_cat_type] = 0.0 end # only attempt to get monthly data if enduses table is available if @sql.endUses.is_initialized end_uses_table = @sql.endUses.get # loop through all the fuel types end_use_fuel_types.each do |end_use_fuel_type| # get the annual total cost for this fuel type ann_cost = annual_utility_cost_map[end_use_fuel_type.valueName] # get the total annual usage for this fuel type in all end use categories # loop through all end uses, adding the annual usage value to the aggregator ann_usg = 0.0 end_use_cat_types.each do |end_use_cat_type| ann_usg += end_uses_table.getEndUse(end_use_fuel_type, end_use_cat_type) end # figure out the annual blended rate for this fuel type avg_ann_rate = 0.0 if ann_cost > 0 && ann_usg > 0 avg_ann_rate = ann_cost / ann_usg end # for each end use category, figure out the cost if using # the avg ann rate; add this cost to the map end_use_cat_types.each do |end_use_cat_type| cost_by_end_use[end_use_cat_type] += end_uses_table.getEndUse(end_use_fuel_type, end_use_cat_type) * avg_ann_rate end end # loop through the end uses and record the annual total cost based on the avg annual rate end_use_cat_types.each do |end_use_cat_type| # record the value end_uses_elems << OpenStudio::Attribute.new(end_use_map[end_use_cat_type.value], cost_by_end_use[end_use_cat_type], 'dollars') end else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.QAQC', 'End-Use table not available in results; could not retrieve monthly costs by end use') return OpenStudio::Attribute.new('report', result_elems) end # end end_uses utility_cost_elems << OpenStudio::Attribute.new('end_uses', end_uses_elems) # end utility_costs annual_elems << OpenStudio::Attribute.new('utility_cost', utility_cost_elems) # end annual result_elems << OpenStudio::Attribute.new('annual', annual_elems) # monthly monthly_elems = OpenStudio::AttributeVector.new # consumption cons_elems = OpenStudio::AttributeVector.new # loop through all end uses end_use_cat_types.each do |end_use_cat| end_use_elems = OpenStudio::AttributeVector.new end_use_name = end_use_map[end_use_cat.value] # in each end use, loop through all fuel types end_use_fuel_types.each do |end_use_fuel_type| fuel_type_elems = OpenStudio::AttributeVector.new fuel_type_name = fuel_type_alias_map[end_use_fuel_type.value] ann_energy_cons = 0.0 # in each end use, loop through months and get monthly enedy consumption months.each_with_index do |month, i| mon_energy_cons = 0.0 val = @sql.energyConsumptionByMonth(end_use_fuel_type, end_use_cat, month) if val.is_initialized monthly_consumption_j = OpenStudio::Quantity.new(val.get, joule_unit) monthly_consumption_gj = OpenStudio.convert(monthly_consumption_j, gigajoule_unit).get.value mon_energy_cons = monthly_consumption_gj ann_energy_cons += monthly_consumption_gj end # record the monthly value if end_use_fuel_type == OpenStudio::EndUseFuelType.new('Water') fuel_type_elems << OpenStudio::Attribute.new('month', mon_energy_cons, 'm^3') else fuel_type_elems << OpenStudio::Attribute.new('month', mon_energy_cons, 'GJ') end end # record the annual total fuel_type_elems << OpenStudio::Attribute.new('year', ann_energy_cons, 'GJ') # add this fuel type end_use_elems << OpenStudio::Attribute.new(fuel_type_alias_map[end_use_fuel_type.value], fuel_type_elems) end # add this end use cons_elems << OpenStudio::Attribute.new(end_use_map[end_use_cat.value], end_use_elems) end # end consumption monthly_elems << OpenStudio::Attribute.new('consumption', cons_elems) # create a unit to use watt_unit = OpenStudio.createUnit('W').get kilowatt_unit = OpenStudio.createUnit('kW').get # demand demand_elems = OpenStudio::AttributeVector.new # loop through all end uses end_use_cat_types.each do |end_use_cat| end_use_elems = OpenStudio::AttributeVector.new end_use_name = end_use_map[end_use_cat.value] # in each end use, loop through all fuel types end_use_fuel_types.each do |end_use_fuel_type| fuel_type_elems = OpenStudio::AttributeVector.new fuel_type_name = fuel_type_alias_map[end_use_fuel_type.value] ann_peak_demand = 0.0 # in each end use, loop through months and get monthly enedy consumption months.each_with_index do |month, month_index| mon_peak_demand = 0.0 val = @sql.peakEnergyDemandByMonth(end_use_fuel_type, end_use_cat, month) if val.is_initialized mon_peak_demand_w = OpenStudio::Quantity.new(val.get, watt_unit) mon_peak_demand = OpenStudio.convert(mon_peak_demand_w, kilowatt_unit).get.value end # record the monthly value fuel_type_elems << OpenStudio::Attribute.new('month', mon_peak_demand, 'kW') # if month peak demand > ann peak demand make this new ann peak demand if mon_peak_demand > ann_peak_demand ann_peak_demand = mon_peak_demand end end # record the annual peak demand fuel_type_elems << OpenStudio::Attribute.new('year', ann_peak_demand, 'kW') # add this fuel type end_use_elems << OpenStudio::Attribute.new(fuel_type_alias_map[end_use_fuel_type.value], fuel_type_elems) end # add this end use demand_elems << OpenStudio::Attribute.new(end_use_map[end_use_cat.value], end_use_elems) end # end demand monthly_elems << OpenStudio::Attribute.new('demand', demand_elems) # end monthly result_elems << OpenStudio::Attribute.new('monthly', monthly_elems) result_elem = OpenStudio::Attribute.new('results', result_elems) return result_elem end |
.space_load_instance_schedule_check(space_load_instance, expected_hours, std: nil, min_pass_pct: 0.2, max_pass_pct: 0.2) ⇒ OpenStudio::Attribute, false
Check the schedule for a space load instance will return false or a single attribute
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 |
# File 'lib/openstudio-standards/qaqc/internal_loads.rb', line 527 def self.space_load_instance_schedule_check(space_load_instance, expected_hours, std: nil, min_pass_pct: 0.2, max_pass_pct: 0.2) if std.nil? std = Standard.build('90.1-2013') end if space_load_instance.spaceType.is_initialized space_type = space_load_instance end # get schedule if (space_load_instance.class.to_s == 'OpenStudio::Model::People') && space_load_instance.numberofPeopleSchedule.is_initialized schedule_inst = space_load_instance.numberofPeopleSchedule.get elsif (space_load_instance.class.to_s == 'OpenStudio::Model::DesignSpecificationOutdoorAir') && space_load_instance.outdoorAirFlowRateFractionSchedule.is_initialized schedule_inst = space_load_instance.outdoorAirFlowRateFractionSchedule.get elsif space_load_instance.schedule.is_initialized schedule_inst = space_load_instance.schedule.get else return OpenStudio::Attribute.new('flag', "#{space_load_instance.name} in #{space_type.name} doesn't have a schedule assigned.") end # get annual equiv for model schedule inst_hrs = OpenstudioStandards::Schedules.schedule_get_equivalent_full_load_hours(schedule_inst) if inst_hrs.nil? return OpenStudio::Attribute.new('flag', "#{schedule_inst.name} isn't a Ruleset or Constant schedule. Can't calculate annual equivalent full load hours.") end # check instance against target if inst_hrs < expected_hours * (1.0 - min_pass_pct) return OpenStudio::Attribute.new('flag', "#{inst_hrs.round} annual equivalent full load hours for #{schedule_inst.name} in #{space_type.name} is more than #{min_pass_pct * 100} (%) below the typical value of #{expected_hours.round} hours from the DOE Prototype building.") elsif inst_hrs > expected_hours * (1.0 + max_pass_pct) return OpenStudio::Attribute.new('flag', "#{inst_hrs.round} annual equivalent full load hours for #{schedule_inst.name} in #{space_type.name} is more than #{max_pass_pct * 100} (%) above the typical value of #{expected_hours.round} hours DOE Prototype building.") end # will get to this if no flag was thrown return false end |