# ******************************************************************************* # OpenStudio(R), Copyright (c) Alliance for Sustainable Energy, LLC. # See also https://openstudio.net/license # ******************************************************************************* require 'erb' # require 'pry' # start the measure class ZoneReport < OpenStudio::Measure::ReportingMeasure # define the name that a user will see, this method may be deprecated as # the display name in PAT comes from the name field in measure.xml def name return 'Zone Report' end # define the arguments that the user will input def arguments(model = nil) args = OpenStudio::Measure::OSArgumentVector.new return args end # return a vector of IdfObject's to request EnergyPlus objects needed by the run method def energyPlusOutputRequests(runner, user_arguments) super(runner, user_arguments) result = OpenStudio::IdfObjectVector.new # use the built-in error checking if !runner.validateUserArguments(arguments, user_arguments) return result end request = OpenStudio::IdfObject.load('Output:Table:SummaryReports,AllSummaryAndSizingPeriod;').get result << request return result end # Method to make class names nicer (remove OS and _) def niceClassName(class_name) class_name = class_name.gsub('OS_', '') class_name = class_name.tr('_', ' ') return class_name end # Convert a value from the view TabularDataWithStrings from the unit represented there to the given unit. # We make a bunch of special cases for unit aliases used by EnergyPlus which are not in the default # list of OpenStudio conversions # We also special case a few conversions for unitless 'units' so that our final display makes more sense to the user. # When the property is not present (for instance, cooled beams don't have properties in the sql file under 1.5) # this method will return "-" as a placeholder. def eplus_to_openstudio(unitstr) unitstr.gsub('m3', 'm^3').gsub('pa', 'Pa').gsub('m2', 'm^2').gsub(' per ', '/') end def convert_prop(property, final_units) return ['-', '-'] unless property return [property[0].to_f.round(2), ''] if final_units.empty? return [(property[0].to_f * 100).round(2), '%'] if final_units == '%' return [property[0].to_f.round(2), 'COP'] if final_units == 'COP' initial_units = eplus_to_openstudio(property[1]) converted = OpenStudio.convert(property[0].to_f, initial_units, final_units) if converted.empty? "Could not convert from #{initial_units} to #{final_units}" else final_units = final_units.gsub('inH_{2}O', 'in. w.c.') [converted.get.round(2), final_units] end end def properties_for_cooling_coil(e, props, parent) autosized = case e.iddObjectType when OpenStudio::Model::CoilCoolingDXSingleSpeed.iddObjectType coil = e.to_CoilCoolingDXSingleSpeed.get coil.isRatedTotalCoolingCapacityAutosized && coil.isRatedSensibleHeatRatioAutosized && coil.isRatedAirFlowRateAutosized ? 'Yes' : 'No' when OpenStudio::Model::CoilCoolingWater.iddObjectType coil = e.to_CoilCoolingWater.get coil.isDesignWaterFlowRateAutosized && coil.isDesignAirFlowRateAutosized && coil.isDesignInletWaterTemperatureAutosized ? 'Yes' : 'No' when OpenStudio::Model::CoilCoolingWaterToAirHeatPumpEquationFit.iddObjectType coil = e.to_CoilCoolingWaterToAirHeatPumpEquationFit.get coil.isRatedTotalCoolingCapacityAutosized && coil.isRatedSensibleHeatRatioAutosized && coil.isRatedAirFlowRateAutosized ? 'Yes' : 'No' else '?' end coolingcap = convert_prop(props['Nominal Total Capacity'], 'kBtu/hr') coolingeff = convert_prop(props['Nominal Efficiency'], 'COP') sensibleheatratio = convert_prop(props['Nominal Sensible Heat Ratio'], '') parent_name = parent.name.get name = e.name.get coiltype = niceClassName(e.iddObjectType.valueName) { 'Terminal/Zone Equip Name' => parent_name, 'Coil Type' => coiltype, 'Name' => name, 'Autosized' => autosized, 'Nominal Capacity' => coolingcap, 'Nominal Efficiency' => coolingeff, 'Nominal SHR' => sensibleheatratio } end def properties_for_heating_coil(e, props, parent) autosized = '-' eff_units = 'COP' heatingcap = ['-', '-'] heatingeff = ['-', '-'] case e.iddObjectType when OpenStudio::Model::CoilHeatingElectric.iddObjectType coil = e.to_CoilHeatingElectric.get autosized = coil.isNominalCapacityAutosized ? 'Yes' : 'No' heatingcap = convert_prop(props['Nominal Total Capacity'], 'kW') heatingeff = convert_prop(props['Nominal Efficiency'], '%') when OpenStudio::Model::CoilHeatingGas.iddObjectType coil = e.to_CoilHeatingGas.get autosized = coil.isNominalCapacityAutosized ? 'Yes' : 'No' heatingcap = convert_prop(props['Nominal Total Capacity'], 'kBtu/hr') heatingeff = convert_prop(props['Nominal Efficiency'], 'COP') when OpenStudio::Model::CoilHeatingDXSingleSpeed.iddObjectType coil = e.to_CoilHeatingDXSingleSpeed.get autosized = coil.isRatedTotalHeatingCapacityAutosized && coil.isRatedAirFlowRateAutosized ? 'Yes' : 'No' heatingcap = convert_prop(props['Nominal Total Capacity'], 'kBtu/hr') heatingeff = convert_prop(props['Nominal Efficiency'], 'COP') when OpenStudio::Model::CoilHeatingWater.iddObjectType coil = e.to_CoilHeatingWater.get autosized = coil.isMaximumWaterFlowRateAutosized && coil.isRatedCapacityAutosized && coil.isUFactorTimesAreaValueAutosized ? 'Yes' : 'No' heatingcap = convert_prop(props['Nominal Total Capacity'], 'kBtu/hr') heatingeff = ['-', '-'] when OpenStudio::Model::CoilHeatingWaterBaseboard.iddObjectType coil = e.to_CoilHeatingWaterBaseboard.get autosized = coil.isMaximumWaterFlowRateAutosized && coil.isUFactorTimesAreaValueAutosized ? 'Yes' : 'No' heatingcap = convert_prop(props['Nominal Total Capacity'], 'kBtu/hr') heatingeff = ['-', '-'] when OpenStudio::Model::CoilHeatingWaterToAirHeatPumpEquationFit.iddObjectType coil = e.to_CoilHeatingWaterToAirHeatPumpEquationFit.get autosized = coil.isRatedHeatingCapacityAutosized && coil.isRatedAirFlowRateAutosized && coil.isRatedWaterFlowRateAutosized ? 'Yes' : 'No' heatingcap = convert_prop(props['Nominal Total Capacity'], 'kBtu/hr') heatingeff = convert_prop(props['Nominal Efficiency'], 'COP') else autosized = '?' end parent_name = parent.name.get name = e.name.get coiltype = niceClassName(e.iddObjectType.valueName) { 'Terminal/Zone Equip Name' => parent_name, 'Coil Type' => coiltype, 'Name' => name, 'Autosized' => autosized, 'Nominal Capacity' => heatingcap, 'Nominal Efficiency' => heatingeff } end def properties_for_fan(equipment, properties, parent) autosized = case equipment.iddObjectType when OpenStudio::Model::FanConstantVolume.iddObjectType fan = equipment.to_FanConstantVolume.get fan.isMaximumFlowRateAutosized ? 'Yes' : 'No' when OpenStudio::Model::FanVariableVolume.iddObjectType fan = equipment.to_FanVariableVolume.get fan.isMaximumFlowRateAutosized ? 'Yes' : 'No' when OpenStudio::Model::FanOnOff.iddObjectType fan = equipment.to_FanOnOff.get fan.isMaximumFlowRateAutosized ? 'Yes' : 'No' when OpenStudio::Model::FanZoneExhaust.iddObjectType 'N/A' else '?' end name = equipment.name.get parent_name = parent.name.get maxflowrate = convert_prop(properties['Max Air Flow Rate'], 'cfm') fanpressure = convert_prop(properties['Delta Pressure'], 'inH_{2}O') totalefficiency = convert_prop(properties['Total Efficiency'], '%') ratedfanpower = convert_prop(properties['Rated Electric Power'], 'W') { 'Terminal/Zone Equip Name' => parent_name, 'Fan Type' => niceClassName(equipment.iddObjectType.valueName), 'Name' => name, 'Autosized' => autosized, 'Max Flow' => maxflowrate, 'Efficiency' => totalefficiency, 'Pressure' => fanpressure, 'Power' => ratedfanpower } end # Each equipment is grouped into Heating, Cooling or Fans and has properties extracted appropriate for that group. # This method returns a tuple [equipment group, properties] where properties is a hash of name, value or name, [value, units] pairs def properties_for_zone_equipment(equipment, equipment_properties, parent) # Make properties an empty hash if it was nil equipment_properties ||= {} if /^OS_Coil_Cooling/.match?(equipment.iddObjectType.valueName) return 'Cooling', properties_for_cooling_coil(equipment, equipment_properties, parent) elsif /^OS_Coil_Heating/.match?(equipment.iddObjectType.valueName) return 'Heating', properties_for_heating_coil(equipment, equipment_properties, parent) elsif /^OS_Fan/.match?(equipment.iddObjectType.valueName) return 'Fans', properties_for_fan(equipment, equipment_properties, parent) elsif OpenStudio::Model::ZoneHVACBaseboardConvectiveElectric.iddObjectType == equipment.iddObjectType baseboard = equipment.to_ZoneHVACBaseboardConvectiveElectric.get autosized = baseboard.isNominalCapacityAutosized ? 'Yes' : 'No' heatingcap = convert_prop(equipment_properties['Design Size Nominal Capacity'], 'kW') name = baseboard.name.get coiltype = niceClassName(baseboard.iddObjectType.valueName) return 'Heating', { 'Terminal/Zone Equip Name' => name, 'Coil Type' => coiltype, 'Name' => name, 'Autosized' => autosized, 'Nominal Capacity' => heatingcap, 'Nominal Efficiency' => [100, '%'] } else return nil, nil end end # define what happens when the measure is run def run(runner, user_arguments) super(runner, user_arguments) # use the built-in error checking if !runner.validateUserArguments(arguments, user_arguments) return false end @runner = runner # get the last model and sql file model = runner.lastOpenStudioModel if model.empty? runner.registerError('Cannot find last model.') return false end model = model.get @sqlFile = runner.lastEnergyPlusSqlFile if @sqlFile.empty? runner.registerError('Cannot find last sql file.') return false end @sqlFile = @sqlFile.get model.setSqlFile(@sqlFile) @graph_data = [] # Collect and collate information about the hvac equipment # When we are done we will have a hash of equipment keyed by equipment name # who's values are hashes keyed by the property name. The values of the # property hashes are [value, unit] pairs # Some types of equipment (for instance CoilCoolingCooledBeams) are not present # in the database as of OS 1.5 # find table names, etc. in the SupportZoneHVACEquipFields tableNames = getDataByColumn('TableName') columnNames = getDataByColumn('ColumnName') rowNames = getDataByColumn('RowName') units = getDataByColumn('Units') values = getDataByColumn('Value') equipment_rows = tableNames.zip(columnNames, rowNames, units, values) equipment = {} equipment_rows.each do |r| _, field, name, units, value = r if name != 'None' eh = equipment[name] || {} eh[field] = [value, units] equipment[name] = eh end end @zone_collection = [] @testData = {} # Go through each zone in the model and collect all the zone equipment model.getThermalZones.sort.each do |thermalZone| # Skip unconditioned zones if thermalZone.thermostatSetpointDualSetpoint.empty? @runner.registerInfo("Skipping #{thermalZone.name} because it is unconditioned.") next end # Skip plenums if thermalZone.isPlenum == true @runner.registerInfo("Skipping #{thermalZone.name} because it is a plenum.") next end zone_equipment = {} thermalZone.equipment.each do |e| childquipment = e.to_ParentObject.get.children # Look for data on the top level equipment and each of that equipment's children found_primary_heat = false ([e] + childquipment).each do |ce| ename = ce.name.get.upcase reporting_type, equipment_properties = properties_for_zone_equipment(ce, equipment[ename], e) if equipment_properties # The first heating coil in a zone equipment chain will be primary heating; all # following heating coils will be marked as Backup Heating. if reporting_type == 'Heating' reporting_type = 'Backup Heating' if found_primary_heat found_primary_heat = true end # Find the priority for heating or cooling equipment - the priority is based on the 'parent' zone equipment priority if reporting_type == 'Heating' equipment_properties['Priority'] = (thermalZone.equipmentInHeatingOrder.index { |ze| ze.name.get == e.name.get && ze.iddObjectType == e.iddObjectType }) + 1 end if reporting_type == 'Cooling' equipment_properties['Priority'] = (thermalZone.equipmentInCoolingOrder.index { |ze| ze.name.get == e.name.get && ze.iddObjectType == e.iddObjectType }) + 1 end # If we don't yet have equipment of this type, make a new array. Then add # our equipment information to the array and update our zone equipment hash. equip_of_type = zone_equipment[reporting_type] || [] equip_of_type << equipment_properties zone_equipment[reporting_type] = equip_of_type end end end zoneMetrics = {} zoneMetrics[:name] = !thermalZone.name.empty? ? thermalZone.name.get : '' zoneMetrics[:equipment] = zone_equipment zoneMetrics[:area] = OpenStudio.convert(thermalZone.floorArea, 'm^2', 'ft^2').get.round(2) @currentZoneName = zoneMetrics[:name] vals = {} vals[:va] = getDetailsData('LightingSummary', 'Entire Facility', 'Interior Lighting', "#{zoneMetrics[:name]}%", 'Lighting Power Density', 'W/m2', 'W/ft^2').round(2) vals[:vb] = getDetailsData('LightingSummary', 'Entire Facility', 'Interior Lighting', "#{zoneMetrics[:name]}%", 'Full Load Hours/Week', 'hr', 'hr').round(2) vals[:vc] = getDetailsData('EnergyMeters', 'Entire Facility', 'Annual and Peak Values - Electricity', "InteriorLights:Electricity:Zone:#{zoneMetrics[:name]}", 'Electricity Annual Value', 'GJ', 'kWh').round(2) vals[:vd] = getDetailsData('InputVerificationandResultsSummary', 'Entire Facility', 'Zone Summary', zoneMetrics[:name], 'Plug and Process', 'W/m2', 'W/ft^2').round(2) vals[:ve] = getDetailsData('InputVerificationandResultsSummary', 'Entire Facility', 'Zone Summary', zoneMetrics[:name], 'Area', 'm2', 'ft^2').round(2) vals[:vf] = getDetailsData('InputVerificationandResultsSummary', 'Entire Facility', 'Zone Summary', zoneMetrics[:name], 'Conditioned (Y/N)', '', 's') if vals[:vf] == '' then vals[:vf] = 'No' end vals[:vg] = getDetailsData('HVACSizingSummary', 'Entire Facility', 'Zone Heating', zoneMetrics[:name], 'User Design Load', 'W', 'kBtu/hr').round(2) vals[:vh] = getDetailsData('HVACSizingSummary', 'Entire Facility', 'Zone Heating', zoneMetrics[:name], 'User Design Air Flow', 'm3/s', 'ft^3/s').round(2) vals[:vi] = getDetailsData('HVACSizingSummary', 'Entire Facility', 'Zone Cooling', zoneMetrics[:name], 'User Design Load', 'W', 'kBtu/hr').round(2) vals[:vj] = getDetailsData('HVACSizingSummary', 'Entire Facility', 'Zone Cooling', zoneMetrics[:name], 'User Design Air Flow', 'm3/s', 'ft^3/s').round(2) vals[:vk] = getDetailsData('OutdoorAirSummary', 'Entire Facility', 'Average Outdoor Air During Occupied Hours', zoneMetrics[:name], 'Mechanical Ventilation', 'ach', 'ach').round(2) vals[:vl] = getDetailsData('OutdoorAirSummary', 'Entire Facility', 'Average Outdoor Air During Occupied Hours', zoneMetrics[:name], 'Infiltration', 'ach', 'ach').round(2) vals[:vm] = getDetailsData('InputVerificationandResultsSummary', 'Entire Facility', 'Zone Summary', zoneMetrics[:name], 'People', 'm2 per person', 'ft^2/person').round(2) vals[:vn] = getDetailsData('HVACSizingSummary', 'Entire Facility', 'Zone Cooling', zoneMetrics[:name], 'Date/Time Of Peak', '', 's') vals[:vo] = getDetailsData('HVACSizingSummary', 'Entire Facility', 'Zone Heating', zoneMetrics[:name], 'Date/Time Of Peak', '', 's') vals[:vp] = getDetailsData('SystemSummary', 'Entire Facility', 'Time Setpoint Not Met', zoneMetrics[:name], 'During Heating', 'hr', 'hr') vals[:vq] = getDetailsData('SystemSummary', 'Entire Facility', 'Time Setpoint Not Met', zoneMetrics[:name], 'During Cooling', 'hr', 'hr') vals[:vr] = getDetailsData('EnergyMeters', 'Entire Facility', 'Annual and Peak Values - Gas', "InteriorEquipment:Gas:Zone:#{zoneMetrics[:name]}", 'Electricity Annual Value', 'GJ', 'Therm').round(2) vals[:vs] = getDetailsData('EnergyMeters', 'Entire Facility', 'Annual and Peak Values - Electricity', "InteriorEquipment:Electricity:Zone:#{zoneMetrics[:name]}", 'Electricity Annual Value', 'GJ', 'kWh').round(2) vals[:vt] = getDetailsData('EnergyMeters', 'Entire Facility', 'Annual and Peak Values - Gas', "InteriorEquipment:Gas:Zone:#{zoneMetrics[:name]}", 'Gas Maximum Value', 'W', 'kBtu/hr').round(2) vals[:vu] = getDetailsData('EnergyMeters', 'Entire Facility', 'Annual and Peak Values - Gas', "InteriorEquipment:Gas:Zone:#{zoneMetrics[:name]}", 'Timestamp of Maximum', '', 's') vals[:vv] = getDetailsData('EnergyMeters', 'Entire Facility', 'Annual and Peak Values - Electricity', "InteriorLights:Electricity:Zone:#{zoneMetrics[:name]}", 'Electricity Maximum Value', 'W', 'kW').round(2) vals[:vw] = getDetailsData('EnergyMeters', 'Entire Facility', 'Annual and Peak Values - Electricity', "InteriorLights:Electricity:Zone:#{zoneMetrics[:name]}", 'Timestamp of Maximum', '', 's') # X unused vals[:vy] = getDetailsData('EnergyMeters', 'Entire Facility', 'Annual and Peak Values - Electricity', "InteriorEquipment:Electricity:Zone:#{zoneMetrics[:name]}", 'Electricity Maximum Value', 'W', 'kW').round(2) vals[:vz] = getDetailsData('EnergyMeters', 'Entire Facility', 'Annual and Peak Values - Electricity', "InteriorEquipment:Electricity:Zone:#{zoneMetrics[:name]}", 'Timestamp of Maximum', '', 's') vals[:vaa] = getDetailsData('EnergyMeters', 'Entire Facility', 'Annual and Peak Values - Other', "InteriorEquipment:DistrictHeating:Zone:#{zoneMetrics[:name]}", 'Annual Value', 'GJ', 'kBtu').round(2) vals[:vab] = getDetailsData('EnergyMeters', 'Entire Facility', 'Annual and Peak Values - Other', "InteriorEquipment:DistrictHeating:Zone:#{zoneMetrics[:name]}", 'Maximum Value', 'W', 'kBtu/hr').round(2) vals[:vac] = getDetailsData('EnergyMeters', 'Entire Facility', 'Annual and Peak Values - Other', "InteriorEquipment:DistrictHeating:Zone:#{zoneMetrics[:name]}", 'Timestamp of Maximum', '', 's') vals[:vad] = getDetailsData('EnergyMeters', 'Entire Facility', 'Annual and Peak Values - Other', "Heating:EnergyTransfer:Zone:#{zoneMetrics[:name]}", 'Annual Value', 'GJ', 'kBtu').round(2) vals[:vae] = (getDetailsData('EnergyMeters', 'Entire Facility', 'Annual and Peak Values - Other', "Heating:EnergyTransfer:Zone:#{zoneMetrics[:name]}", 'Maximum Value', 'W', 'kBtu/hr') / zoneMetrics[:area]).round(2) vals[:vaf] = getDetailsData('EnergyMeters', 'Entire Facility', 'Annual and Peak Values - Other', "Heating:EnergyTransfer:Zone:#{zoneMetrics[:name]}", 'Timestamp of Maximum', '', 's') vals[:vag] = getDetailsData('EnergyMeters', 'Entire Facility', 'Annual and Peak Values - Other', "Cooling:EnergyTransfer:Zone:#{zoneMetrics[:name]}", 'Annual Value', 'GJ', 'kBtu').round(2) vals[:vah] = (getDetailsData('EnergyMeters', 'Entire Facility', 'Annual and Peak Values - Other', "Cooling:EnergyTransfer:Zone:#{zoneMetrics[:name]}", 'Maximum Value', 'W', 'kBtu/hr') / zoneMetrics[:area]).round(2) vals[:vai] = getDetailsData('EnergyMeters', 'Entire Facility', 'Annual and Peak Values - Other', "Cooling:EnergyTransfer:Zone:#{zoneMetrics[:name]}", 'Timestamp of Maximum', '', 's') vals[:vaj] = zoneHeatComponentCalc('People', zoneMetrics) vals[:vak] = zoneHeatComponentCalc('Lights', zoneMetrics) vals[:val] = zoneHeatComponentCalc('Equipment', zoneMetrics) vals[:vam] = zoneHeatComponentCalc('Refrigeration', zoneMetrics) vals[:van] = zoneHeatComponentCalc('Water Use Equipment', zoneMetrics) vals[:vao] = zoneHeatComponentCalc('HVAC Equipment Losses', zoneMetrics) vals[:vap] = zoneHeatComponentCalc('Power Generation Equipment', zoneMetrics) vals[:vaq] = zoneHeatComponentCalc('Infiltration', zoneMetrics) vals[:var] = zoneHeatComponentCalc('Zone Ventilation', zoneMetrics) vals[:vas] = zoneHeatComponentCalc('Interzone Mixing', zoneMetrics) vals[:vat] = zoneHeatComponentCalc('Exterior Wall', zoneMetrics) vals[:vau] = zoneHeatComponentCalc('Interzone Wall', zoneMetrics) vals[:vav] = zoneHeatComponentCalc('Ground Contact Wall', zoneMetrics) vals[:vaw] = zoneHeatComponentCalc('Other Wall', zoneMetrics) vals[:vax] = zoneHeatComponentCalc('Opaque Door', zoneMetrics) vals[:vay] = zoneHeatComponentCalc('Roof', zoneMetrics) vals[:vaz] = zoneHeatComponentCalc('Interzone Ceiling', zoneMetrics) vals[:vba] = zoneHeatComponentCalc('Other Roof', zoneMetrics) vals[:vbb] = zoneHeatComponentCalc('Exterior Floor', zoneMetrics) vals[:vbc] = zoneHeatComponentCalc('Interzone Floor', zoneMetrics) vals[:vbd] = zoneHeatComponentCalc('Ground Contact Floor', zoneMetrics) vals[:vbe] = zoneHeatComponentCalc('Other Floor', zoneMetrics) vals[:vbf] = zoneHeatComponentCalc('Fenestration Conduction', zoneMetrics) vals[:vbg] = zoneHeatComponentCalc('Fenestration Solar', zoneMetrics) vals[:vbh] = getDetailsData('ZoneComponentLoadSummary', (zoneMetrics[:name]).to_s, 'Heating Peak Conditions', 'Time of Peak Load', 'Value', '', 's') vals[:vbi] = zoneCoolComponentCalc('People', zoneMetrics) vals[:vbj] = zoneCoolComponentCalc('Lights', zoneMetrics) vals[:vbk] = zoneCoolComponentCalc('Equipment', zoneMetrics) vals[:vbl] = zoneCoolComponentCalc('Refrigeration', zoneMetrics) vals[:vbm] = zoneCoolComponentCalc('Water Use Equipment', zoneMetrics) vals[:vbn] = zoneCoolComponentCalc('HVAC Equipment Losses', zoneMetrics) vals[:vbo] = zoneCoolComponentCalc('Power Generation Equipment', zoneMetrics) vals[:vbp] = zoneCoolComponentCalc('Infiltration', zoneMetrics) vals[:vbq] = zoneCoolComponentCalc('Zone Ventilation', zoneMetrics) vals[:vbr] = zoneCoolComponentCalc('Interzone Mixing', zoneMetrics) vals[:vbs] = zoneCoolComponentCalc('Exterior Wall', zoneMetrics) vals[:vbt] = zoneCoolComponentCalc('Interzone Wall', zoneMetrics) vals[:vbu] = zoneCoolComponentCalc('Ground Contact Wall', zoneMetrics) vals[:vbv] = zoneCoolComponentCalc('Other Wall', zoneMetrics) vals[:vbw] = zoneCoolComponentCalc('Opaque Door', zoneMetrics) vals[:vbx] = zoneCoolComponentCalc('Roof', zoneMetrics) vals[:vby] = zoneCoolComponentCalc('Interzone Ceiling', zoneMetrics) vals[:vbz] = zoneCoolComponentCalc('Other Roof', zoneMetrics) vals[:vca] = zoneCoolComponentCalc('Exterior Floor', zoneMetrics) vals[:vcb] = zoneCoolComponentCalc('Interzone Floor', zoneMetrics) vals[:vcc] = zoneCoolComponentCalc('Ground Contact Floor', zoneMetrics) vals[:vcd] = zoneCoolComponentCalc('Other Floor', zoneMetrics) vals[:vce] = zoneCoolComponentCalc('Fenestration Conduction', zoneMetrics) vals[:vcf] = zoneCoolComponentCalc('Fenestration Solar', zoneMetrics) vals[:vcg] = getDetailsData('ZoneComponentLoadSummary', (zoneMetrics[:name]).to_s, 'Cooling Peak Conditions', 'Time of Peak Load', 'Value', '', 's') # vals = loadTestVals( vals ) vals[:sumBasicHeating] = (vals[:vaj] + vals[:vak] + vals[:val] + vals[:vam] + vals[:vaq] + vals[:var] + vals[:vas]).round(2) vals[:sumBasicCooling] = (vals[:vbi] + vals[:vbj] + vals[:vbk] + vals[:vbl] + vals[:vbp] + vals[:vbq] + vals[:vbr]).round(2) vals[:sumOtherHeating] = (vals[:van] + vals[:vao] + vals[:vap]).round(2) vals[:sumOtherCooling] = (vals[:vbm] + vals[:vbn] + vals[:vbo]).round(2) vals[:sumWallDoorHeating] = (vals[:vat] + vals[:vau] + vals[:vav] + vals[:vaw] + vals[:vax]).round(2) vals[:sumWallDoorCooling] = (vals[:vbs] + vals[:vbt] + vals[:vbu] + vals[:vbv] + vals[:vbw]).round(2) vals[:sumRoofCeilingHeating] = (vals[:vay] + vals[:vaz] + vals[:vba]).round(2) vals[:sumRoofCeilingCooling] = (vals[:vbx] + vals[:vby] + vals[:vbz]).round(2) vals[:sumFloorHeating] = (vals[:vbb] + vals[:vbc] + vals[:vbd] + vals[:vbe]).round(2) vals[:sumFloorCooling] = (vals[:vca] + vals[:vcb] + vals[:vcc] + vals[:vcd]).round(2) vals[:sumWindowsHeating] = (vals[:vbf] + vals[:vbg]).round(2) vals[:sumWindowsCooling] = (vals[:vce] + vals[:vcf]).round(2) vals[:sumHeatingTotal] = (vals[:sumBasicHeating] + vals[:sumOtherHeating] + vals[:sumWallDoorHeating] + vals[:sumRoofCeilingHeating] + vals[:sumFloorHeating] + vals[:sumWindowsHeating]).round(2) vals[:sumCoolingTotal] = (vals[:sumBasicCooling] + vals[:sumOtherCooling] + vals[:sumWallDoorCooling] + vals[:sumRoofCeilingCooling] + vals[:sumFloorCooling] + vals[:sumWindowsCooling]).round(2) zoneMetrics[:vals] = vals @zone_collection.push(zoneMetrics) stacked_bars(zoneMetrics) end @zone_collection = @zone_collection.sort_by { |z| z[:name] } equipment_lengths = @zone_collection.map { |z| z[:equipment].values.map(&:length) } max_zone_equipments = equipment_lengths.flatten.max # Convert the graph data to JSON # This measure requires ruby 2.0.0 to create the JSON for the report graph if RUBY_VERSION >= '2.0.0' require 'json' @graph_data = @graph_data.to_json else runner.registerInfo("This Measure needs Ruby 2.0.0 to generate timeseries graphs on the report. You have Ruby #{RUBY_VERSION}. OpenStudio 1.4.2 and higher user Ruby 2.0.0.") end web_asset_path = OpenStudio.getSharedResourcesPath / OpenStudio::Path.new('web_assets') html_in = getResourceFileData('report.html.in') # configure template with variable values renderer = ERB.new(html_in) html_out = renderer.result(binding) writeResourceFileData('report.html', html_out) # copyResourceFile( "graph_resource.js" ) # copyResourceFile( "style_resource.css" ) # closing the sql file @sqlFile.close # reporting final condition runner.registerFinalCondition("Successfully finished writing 'Zone Report'.") return true end def zoneHeatComponentCalc(component, zoneMetrics) (getDetailsData('ZoneComponentLoadSummary', (zoneMetrics[:name]).to_s, 'Estimated Heating Peak Load Components', component, 'Total', 'W', 'Btu/hr') / zoneMetrics[:area]).round(2) end def zoneCoolComponentCalc(component, zoneMetrics) (getDetailsData('ZoneComponentLoadSummary', (zoneMetrics[:name]).to_s, 'Estimated Cooling Peak Load Components', component, 'Total', 'W', 'Btu/hr') / zoneMetrics[:area]).round(2) end def stacked_bars(zoneMetrics) # people, lights, equipment, refrigeration, other, infiltration, zone ventilation, # interzone mixing, walls/doors, roof/ceiling, floor, windows vals = zoneMetrics[:vals] heatingVals = [vals[:vaj], vals[:vak], vals[:val], vals[:vam], vals[:sumOtherHeating], vals[:vaq], vals[:var], vals[:vas], vals[:sumWallDoorHeating], vals[:sumRoofCeilingHeating], vals[:sumFloorHeating], vals[:sumWindowsHeating]] coolingVals = [vals[:vbi], vals[:vbj], vals[:vbk], vals[:vbl], vals[:sumOtherCooling], vals[:vbp], vals[:vbq], vals[:vbr], vals[:sumWallDoorCooling], vals[:sumRoofCeilingCooling], vals[:sumFloorCooling], vals[:sumWindowsCooling]] positiveHeating = heatingVals.select { |v| v >= 0 }.inject { |sum, x| sum + x } || 0 negativeHeating = heatingVals.select { |v| v < 0 }.inject { |sum, x| sum + x } || 0 positiveCooling = coolingVals.select { |v| v >= 0 }.inject { |sum, x| sum + x } || 0 negativeCooling = coolingVals.select { |v| v < 0 }.inject { |sum, x| sum + x } || 0 maxPositive = positiveHeating > positiveCooling ? positiveHeating : positiveCooling maxNegative = negativeHeating < negativeCooling ? negativeHeating : negativeCooling maxPositive = maxPositive == 0 ? maxNegative * -0.20 : maxPositive maxNegative = maxNegative == 0 ? maxPositive * -0.10 : maxNegative maxPositive += (maxPositive * 0.20).round(2) maxNegative += (maxNegative * 0.10).round(2) stacked_vals = [[0, maxPositive, maxNegative, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, vals[:sumHeatingTotal], 0], [2, vals[:vaj], vals[:vak], vals[:val], vals[:vam], vals[:sumOtherHeating], vals[:vaq], vals[:var], vals[:vas], vals[:sumWallDoorHeating], vals[:sumRoofCeilingHeating], vals[:sumFloorHeating], vals[:sumWindowsHeating], 0, 0], [3, vals[:vbi], vals[:vbj], vals[:vbk], vals[:vbl], vals[:sumOtherCooling], vals[:vbp], vals[:vbq], vals[:vbr], vals[:sumWallDoorCooling], vals[:sumRoofCeilingCooling], vals[:sumFloorCooling], vals[:sumWindowsCooling], 0, 0], [4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, vals[:sumCoolingTotal]], [5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] # Add the hourly load data to JSON for the report.html graph = {} graph['title'] = '' graph['xaxislabel'] = '' graph['yaxislabel'] = 'Contribution Btu/hr/ft2' graph['labels'] = ['index', 'people', 'lights', 'equipment', 'refrigeration', 'other', 'infiltration', 'zone_ventilation', 'interzone_mixing', 'walls/doors', 'roof/ceiling', 'floor', 'windows', 'net/heating', 'net/cooling'] graph['colors'] = ['#888855', '#AAAA55', '#3333AA', '#8888FF', '#888888', '#9999FF', '#AAAAFF', '#AA6666', '#777733', '#888833', '#999933', '#9999FF', '#FF9999', '#9999FF'] graph['data'] = stacked_vals @graph_data << graph end def getPctLoad(val, total) if val != '' && total != 0 return (val * 100 / total).round(2) else return 0 end end def getResourceFileData(fileName) data_in_path = "#{File.dirname(__FILE__)}/resources/#{fileName}" if !File.exist?(data_in_path) data_in_path = "#{File.dirname(__FILE__)}/#{fileName}" end html_in = '' File.open(data_in_path, 'r') do |file| html_in = file.read end html_in end def writeResourceFileData(fileName, data) File.open("./#{fileName}", 'w') do |file| file << data # make sure data is written to the disk one way or the other begin file.fsync rescue StandardError file.flush end end end # Fetch a value from the tabulardatawithstrings database view # If final_units is "s" the value is returned unchanged as a string # Otherwise the value is converted from units to final_units - units is specified in energy plus style (m2, m3, etc) # and final_units should be open studio style (m^2, m^3, ...) # If the data is not found or cannot be converted a warning is registered and "" or 0.0 is returned. def getDetailsData(report, forstring, table, row, column, units, final_units) if report == 'ZoneComponentLoadSummary' forstring.upcase! end str_HVACEquipment_query = 'SELECT Value FROM tabulardatawithstrings WHERE ' str_HVACEquipment_query << "ReportName='#{report}' AND " str_HVACEquipment_query << "ReportForString='#{forstring}' AND " str_HVACEquipment_query << "TableName='#{table}' AND " str_HVACEquipment_query << "RowName LIKE '#{row}' AND " str_HVACEquipment_query << "ColumnName='#{column}' AND " str_HVACEquipment_query << "Units='#{units}'" query_results = @sqlFile.execAndReturnFirstString(str_HVACEquipment_query) if query_results.empty? @runner.registerWarning("Could not get data for #{report} #{forstring} #{table} #{row} #{column}.") return final_units == 's' ? '' : 0.0 else r = query_results.get if report == 'ZoneComponentLoadSummary' @testData["#{@currentZoneName}_#{table}_#{row}"] = r end if final_units == 's' return r else converted = OpenStudio.convert(r.to_f, eplus_to_openstudio(units), final_units) if converted.empty? @runner.registerError("Could not convert #{r} from #{units} to #{final_units}") return 0.0 else return converted.get.round(2) end end end end def getDataByColumn(colName) strvec_HVACEquipment_query = "SELECT #{colName} FROM tabulardatawithstrings WHERE " strvec_HVACEquipment_query << "ReportName='EquipmentSummary' and " strvec_HVACEquipment_query << "ReportForString='Entire Facility'" strvec_HVACEquipment_query << 'ORDER BY TableName, ColumnName, RowName, Units, Value' query_results = @sqlFile.execAndReturnVectorOfString(strvec_HVACEquipment_query).get if query_results.empty? @runner.registerError("Could not get data for requested Column #{colName}.") return [] else return query_results end end # Accessor to support unit tests attr_reader :zone_collection end # this allows the measure to be use by the application ZoneReport.new.registerWithApplication