# *******************************************************************************
# OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC.
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# (1) Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# (2) Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# (3) Neither the name of the copyright holder nor the names of any contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission from the respective party.
#
# (4) Other than as required in clauses (1) and (2), distributions in any form
# of modifications or other derivative works may not use the "OpenStudio"
# trademark, "OS", "os", or any other confusingly similar designation without
# specific prior written permission from Alliance for Sustainable Energy, LLC.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
# UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
# THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# *******************************************************************************
require 'json'
require 'openstudio-standards'
module OsLib_Reporting
# setup - get model, sql, and setup web assets path
def self.setup(runner)
results = {}
# get the last model
model = runner.lastOpenStudioModel
if model.empty?
runner.registerError('Cannot find last model.')
return false
end
model = model.get
# get the last idf
workspace = runner.lastEnergyPlusWorkspace
if workspace.empty?
runner.registerError('Cannot find last idf file.')
return false
end
workspace = workspace.get
# get the last sql file
sqlFile = runner.lastEnergyPlusSqlFile
if sqlFile.empty?
runner.registerError('Cannot find last sql file.')
return false
end
sqlFile = sqlFile.get
model.setSqlFile(sqlFile)
# populate hash to pass to measure
results[:model] = model
# results[:workspace] = workspace
results[:sqlFile] = sqlFile
return results
end
def self.ann_env_pd(sqlFile)
# get the weather file run period (as opposed to design day run period)
ann_env_pd = nil
sqlFile.availableEnvPeriods.each do |env_pd|
env_type = sqlFile.environmentType(env_pd)
if env_type.is_initialized
if env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod')
ann_env_pd = env_pd
end
end
end
return ann_env_pd
end
def self.create_xls
require 'rubyXL'
book = ::RubyXL::Workbook.new
# delete initial worksheet
return book
end
def self.save_xls(book)
file = book.write 'excel-file.xlsx'
return file
end
# write an Excel file from table data
# the Excel Functions are not currently being used, left in as example
# Requires ruby Gem which isn't currently supported in OpenStudio GUIs.
# Current setup is simple, creates new workbook for each table
# Could be updated to have one section per workbook
def self.write_xls(table_data, book)
worksheet = book.add_worksheet table_data[:title]
row_cnt = 0
# write the header row
header = table_data[:header]
header.each_with_index do |h, i|
worksheet.add_cell(row_cnt, i, h)
end
worksheet.change_row_fill(row_cnt, '0ba53d')
# loop over data rows
data = table_data[:data]
data.each do |d|
row_cnt += 1
d.each_with_index do |c, i|
worksheet.add_cell(row_cnt, i, c)
end
end
return book
end
# cleanup - prep html and close sql
def self.cleanup(html_in_path)
# TODO: - would like to move code here, but couldn't get it working. May look at it again later on.
return html_out_path
end
# clean up unkown strings used for runner.registerValue names
def self.reg_val_string_prep(string)
# replace non alpha-numberic characters with an underscore
string = string.gsub(/[^0-9a-z]/i, '_')
# snake case string
string = OpenStudio.toUnderscoreCase(string)
return string
end
# hard code fuel types (for E+ 9.4 shouldn't have it twice, should eventually come form OS)
def self.fuel_type_names(extended = false)
# get short or extended list (not using now)
fuel_types = []
OpenStudio::EndUseFuelType.getValues.each do |fuel_type|
# convert integer to string
fuel_name = OpenStudio::EndUseFuelType.new(fuel_type).valueDescription
next if fuel_name == 'Water'
fuel_types << fuel_name
end
return fuel_types
end
# create template section
def self.template_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
template_tables = []
# gather data for section
@template_section = {}
@template_section[:title] = 'Tasty Treats'
@template_section[:tables] = template_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @template_section
end
# notes:
# The data below would typically come from the model or simulation results
# You can loop through objects to make a table for each item of that type, such as air loops
# If a section will only have one table you can leave the table title blank and just rely on the section title
# these will be updated later to support graphs
# create table
template_table_01 = {}
template_table_01[:title] = 'Fruit'
template_table_01[:header] = ['Definition', 'Value']
template_table_01[:units] = ['', '$/pound']
template_table_01[:data] = []
# add rows to table
template_table_01[:data] << ['Banana', 0.23]
template_table_01[:data] << ['Apple', 0.75]
template_table_01[:data] << ['Orange', 0.50]
# add table to array of tables
template_tables << template_table_01
# using helper method that generates table for second example
template_tables << OsLib_Reporting.template_table(model, sqlFile, runner, is_ip_units = true)
return @template_section
end
# create template section
def self.template_table(model, sqlFile, runner, is_ip_units = true)
# create a second table
template_table = {}
template_table[:title] = 'Ice Cream'
template_table[:header] = ['Definition', 'Base Flavor', 'Toppings', 'Value']
template_table[:units] = ['', '', '', 'scoop']
template_table[:data] = []
# add rows to table
template_table[:data] << ['Vanilla', 'Vanilla', 'NA', 1.5]
template_table[:data] << ['Rocky Road', 'Chocolate', 'Nuts', 1.5]
template_table[:data] << ['Mint Chip', 'Mint', 'Chocolate Chips', 1.5]
return template_table
end
# building_summary section
def self.building_summary_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
general_tables = []
# gather data for section
@building_summary_section = {}
@building_summary_section[:title] = 'Model Summary'
@building_summary_section[:tables] = general_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @building_summary_section
end
# add in general information from method
general_tables << OsLib_Reporting.general_building_information_table(model, sqlFile, runner, is_ip_units)
general_tables << OsLib_Reporting.weather_summary_table(model, sqlFile, runner, is_ip_units)
general_tables << OsLib_Reporting.design_day_table(model, sqlFile, runner, is_ip_units)
general_tables << OsLib_Reporting.setpoint_not_met_summary_table(model, sqlFile, runner, is_ip_units)
general_tables << OsLib_Reporting.setpoint_not_met_criteria_table(model, sqlFile, runner, is_ip_units)
# general_tables << OsLib_Reporting.site_performance_table(model,sqlFile,runner)
site_power_generation_table = OsLib_Reporting.site_power_generation_table(model, sqlFile, runner, is_ip_units)
if site_power_generation_table
general_tables << OsLib_Reporting.site_power_generation_table(model, sqlFile, runner, is_ip_units)
end
return @building_summary_section
end
# annual_overview section
def self.annual_overview_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
annual_tables = []
# gather data for section
@annual_overview_section = {}
@annual_overview_section[:title] = 'Annual Overview'
@annual_overview_section[:tables] = annual_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @annual_overview_section
end
# add in annual overview from method
annual_tables << OsLib_Reporting.output_data_end_use_table(model, sqlFile, runner, is_ip_units)
annual_tables << OsLib_Reporting.output_data_energy_use_table(model, sqlFile, runner, is_ip_units)
annual_tables << OsLib_Reporting.output_data_end_use_electricity_table(model, sqlFile, runner, is_ip_units)
annual_tables << OsLib_Reporting.output_data_end_use_gas_table(model, sqlFile, runner, is_ip_units)
return @annual_overview_section
end
# create table with general building information
# this just makes a table, and not a full section. It feeds into another method that makes a full section
def self.general_building_information_table(model, sqlFile, runner, is_ip_units = true)
# general building information type data output
general_building_information = {}
general_building_information[:title] = 'Building Summary' # name will be with section
general_building_information[:header] = ['Data', 'Value']
general_building_information[:units] = [] # won't populate for this table since each row has different units
general_building_information[:data] = []
# structure ID / building name
display = 'Building Name'
target_units = 'n/a'
value = model.getBuilding.name.to_s
general_building_information[:data] << [display, value]
runner.registerValue(OsLib_Reporting.reg_val_string_prep(display), value, target_units)
# total site energy
display = 'Total Site Energy'
source_units = 'GJ'
if is_ip_units
target_units = 'kBtu'
else
target_units = 'kWh'
end
value = OpenStudio.convert(sqlFile.totalSiteEnergy.get, source_units, target_units).get
value_neat = "#{OpenStudio.toNeatString(value, 0, true)} #{target_units}"
runner.registerValue(OsLib_Reporting.reg_val_string_prep(display), value, target_units)
general_building_information[:data] << [display, value_neat]
# net site energy
display = 'Net Site Energy'
source_units = 'GJ'
if is_ip_units
target_units = 'kBtu'
else
target_units = 'kWh'
end
value = OpenStudio.convert(sqlFile.netSiteEnergy.get, source_units, target_units).get
value_neat = "#{OpenStudio.toNeatString(value, 0, true)} #{target_units}"
runner.registerValue(OsLib_Reporting.reg_val_string_prep(display), value, target_units)
# always register value, but only add to table if net is different than total
if sqlFile.totalSiteEnergy.get != sqlFile.netSiteEnergy.get
general_building_information[:data] << [display, value_neat]
end
# 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 = sqlFile.execAndReturnFirstDouble(query)
if query_results.empty?
runner.registerWarning('Did not find value for total building area.')
return false
else
display = 'Total Building Area'
source_units = 'm^2'
if is_ip_units
target_units = 'ft^2'
else
target_units = source_units
end
value = OpenStudio.convert(query_results.get, source_units, target_units).get
value_neat = "#{OpenStudio.toNeatString(value, 0, true)} #{target_units}"
general_building_information[:data] << [display, value_neat]
runner.registerValue(OsLib_Reporting.reg_val_string_prep(display), value, target_units)
end
# code to check OS vs. E+ area
energy_plus_area = query_results.get
open_studio_area = model.getBuilding.floorArea
if (energy_plus_area - open_studio_area).abs >= 1.0
runner.registerWarning("EnergyPlus reported area is #{query_results.get.round} (m^2). OpenStudio reported area is #{model.getBuilding.floorArea.round} (m^2).")
end
# total EUI
eui = sqlFile.totalSiteEnergy.get / energy_plus_area
display = 'Total Site EUI'
source_units = 'GJ/m^2'
if is_ip_units
target_units = 'kBtu/ft^2'
else
target_units = 'kWh/m^2'
end
if query_results.get > 0.0 # don't calculate EUI if building doesn't have any area
value = OpenStudio.convert(eui, source_units, target_units).get
value_neat = "#{OpenStudio.toNeatString(value, 2, true)} #{target_units}"
runner.registerValue(OsLib_Reporting.reg_val_string_prep(display), value, target_units) # is it ok not to calc EUI if no area in model
else
value_neat = "can't calculate Total EUI."
end
general_building_information[:data] << [display.to_s, value_neat]
# net EUI
eui = sqlFile.netSiteEnergy.get / energy_plus_area
display = 'EUI'
if query_results.get > 0.0 # don't calculate EUI if building doesn't have any area
value = OpenStudio.convert(eui, source_units, target_units).get
value_neat = "#{OpenStudio.toNeatString(value, 2, true)} #{target_units}"
runner.registerValue(OsLib_Reporting.reg_val_string_prep(display), value, target_units) # is it ok not to calc EUI if no area in model
else
value_neat = 'Net EUI could not be calculated.'
end
# always register value, but only add to table if net is different than total
if sqlFile.totalSiteEnergy.get != sqlFile.netSiteEnergy.get
general_building_information[:data] << ['Net Site EUI', value_neat]
end
# TODO: - add total EUI for conditioned floor area if not the same as total. Doesn't seem necessary to also calculate net conditioned EUI if it exists as a unique value.
# conditioned building area
query = 'SELECT Value FROM tabulardatawithstrings WHERE '
query << "ReportName='AnnualBuildingUtilityPerformanceSummary' and "
query << "ReportForString='Entire Facility' and "
query << "TableName='Building Area' and "
query << "RowName='Net Conditioned Building Area' and "
query << "ColumnName='Area' and "
query << "Units='m2';"
query_results = sqlFile.execAndReturnFirstDouble(query)
if query_results.empty?
runner.registerWarning('Did not find value for net conditioned building area.')
return false
elsif query_results.get == 0.0
display_a = 'Conditioned Building Area'
source_units_a = 'm^2'
if is_ip_units
target_units_a = 'ft^2'
else
target_units_a = source_units_a
end
value_a = OpenStudio.convert(query_results.get, source_units_a, target_units_a).get
value_neat_a = "#{OpenStudio.toNeatString(value_a, 0, true)} #{target_units_a}"
runner.registerValue(OsLib_Reporting.reg_val_string_prep(display_a), value_a, target_units_a)
# conditioned EUI
eui = sqlFile.totalSiteEnergy.get / energy_plus_area
display_e = 'Conditioned Site EUI'
source_units_e = 'GJ/m^2'
if is_ip_units
target_units_e = 'kBtu/ft^2'
else
target_units_e = 'kWh/m^2'
end
value_e = OpenStudio.convert(eui, source_units_e, target_units_e).get
value_neat_e = "#{OpenStudio.toNeatString(value_e, 2, true)} #{target_units_e}"
runner.registerValue(OsLib_Reporting.reg_val_string_prep(display_e), value_e, target_units_e)
# always register value, but only add to table if net is different than total
if energy_plus_area - query_results.get >= 1.0
general_building_information[:data] << [display_a, value_neat_a]
general_building_information[:data] << [display_e, value_neat_e]
end
end
# get standards building type
building_type = 'n/a'
if model.getBuilding.standardsBuildingType.is_initialized
building_type = model.getBuilding.standardsBuildingType.get
end
general_building_information[:data] << ['OpenStudio Standards Building Type', building_type]
return general_building_information
end
# create table of space type breakdown
def self.space_type_breakdown_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# space type data output
output_data_space_type_breakdown = {}
output_data_space_type_breakdown[:title] = ''
output_data_space_type_breakdown[:header] = ['Space Type Name', 'Floor Area', 'Standards Building Type', 'Standards Space Type']
if is_ip_units
target_units = 'ft^2'
else
target_units = 'm^2'
end
output_data_space_type_breakdown[:units] = ['', target_units, '', '']
output_data_space_type_breakdown[:data] = []
output_data_space_type_breakdown[:chart_type] = 'simple_pie'
output_data_space_type_breakdown[:chart] = []
# gather data for section
@output_data_space_type_breakdown_section = {}
@output_data_space_type_breakdown_section[:title] = 'Space Type Breakdown'
@output_data_space_type_breakdown_section[:tables] = [output_data_space_type_breakdown] # only one table for this section
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @output_data_space_type_breakdown_section
end
space_types = model.getSpaceTypes
space_types.sort.each do |spaceType|
next if spaceType.floorArea == 0
# get color
color = spaceType.renderingColor
if !color.empty?
color = color.get
red = color.renderingRedValue
green = color.renderingGreenValue
blue = color.renderingBlueValue
color = "rgb(#{red},#{green},#{blue})"
else
# TODO: - this should set red green and blue as separate values
color = 'rgb(20,20,20)' # maybe do random or let d3 pick color instead of this?
end
# data for space type breakdown
display = spaceType.name.get
floor_area_si = spaceType.floorArea
floor_area_si = 0
# loop through spaces so I can skip if not included in floor area
spaceType.spaces.each do |space|
next if !space.partofTotalFloorArea
floor_area_si += space.floorArea * space.multiplier
end
# julien
source_units = 'm^2'
if is_ip_units
target_units = 'ft^2'
else
target_units = source_units
end
value = OpenStudio.convert(floor_area_si, 'm^2', target_units).get
num_people = nil
value_neat = OpenStudio.toNeatString(value, 0, true)
# get standards information
if spaceType.standardsBuildingType.is_initialized
standards_building_type = spaceType.standardsBuildingType.get
else
standards_building_type = ''
end
if spaceType.standardsSpaceType.is_initialized
standards_space_type = spaceType.standardsSpaceType.get
else
standards_space_type = ''
end
output_data_space_type_breakdown[:data] << [display, value_neat, standards_building_type, standards_space_type]
runner.registerValue("space_type_#{OsLib_Reporting.reg_val_string_prep(display)}", value, target_units)
# data for graph
output_data_space_type_breakdown[:chart] << JSON.generate(label: display, value: value, color: color)
end
spaces = model.getSpaces
# count area of spaces that have no space type
no_space_type_area_counter = 0
spaces.each do |space|
if space.spaceType.empty?
next if !space.partofTotalFloorArea
no_space_type_area_counter += space.floorArea * space.multiplier
end
end
if no_space_type_area_counter > 0
display = 'No Space Type'
# julien
source_units = 'm^2'
if is_ip_units
target_units = 'ft^2'
else
target_units = source_units
end
value = OpenStudio.convert(no_space_type_area_counter, 'm^2', target_units).get
value_neat = OpenStudio.toNeatString(value, 0, true)
output_data_space_type_breakdown[:data] << [display, value_neat]
runner.registerValue("space_type_#{OsLib_Reporting.reg_val_string_prep(display)}", value, target_units)
# data for graph
color = 'rgb(20,20,20)' # maybe do random or let d3 pick color instead of this?
output_data_space_type_breakdown[:chart] << JSON.generate(label: 'No SpaceType Assigned',
value: OpenStudio.convert(no_space_type_area_counter, 'm^2', target_units),
color: color)
end
return @output_data_space_type_breakdown_section
end
# create table with general building information
# this just makes a table, and not a full section. It feeds into another method that makes a full section
def self.output_data_end_use_table(model, sqlFile, runner, is_ip_units = true)
# end use data output
output_data_end_use = {}
output_data_end_use[:title] = 'End Use'
output_data_end_use[:header] = ['End Use', 'Consumption']
if is_ip_units
target_units = 'kBtu'
else
target_units = 'kWh'
end
output_data_end_use[:units] = ['', target_units]
output_data_end_use[:data] = []
output_data_end_use[:chart_type] = 'simple_pie'
output_data_end_use[:chart] = []
end_use_colors = ['#EF1C21', '#0071BD', '#F7DF10', '#DEC310', '#4A4D4A', '#B5B2B5', '#FF79AD', '#632C94', '#F75921', '#293094', '#CE5921', '#FFB239', '#29AAE7', '#8CC739']
# loop through fuels for consumption tables
counter = 0
OpenStudio::EndUseCategoryType.getValues.each do |end_use|
# get end uses
end_use = OpenStudio::EndUseCategoryType.new(end_use).valueDescription
# loop through fuels
total_end_use = 0.0
fuel_type_names.each do |fuel_type|
query_fuel = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= '#{fuel_type}'"
results_fuel = sqlFile.execAndReturnFirstDouble(query_fuel).get
total_end_use += results_fuel
end
# convert value and populate table
value = OpenStudio.convert(total_end_use, 'GJ', target_units).get
value_neat = OpenStudio.toNeatString(value, 0, true)
output_data_end_use[:data] << [end_use, value_neat]
runner.registerValue("end_use_#{end_use.downcase.tr(' ', '_')}", value, target_units)
if value > 0
output_data_end_use[:chart] << JSON.generate(label: end_use, value: value, color: end_use_colors[counter])
end
counter += 1
end
return output_data_end_use
end
# create table with general building information
# this just makes a table, and not a full section. It feeds into another method that makes a full section
def self.output_data_end_use_electricity_table(model, sqlFile, runner, is_ip_units = true)
# end use data output
output_data_end_use_electricity = {}
output_data_end_use_electricity[:title] = 'EUI - Electricity'
output_data_end_use_electricity[:header] = ['End Use', 'Consumption']
target_units = 'kWh'
output_data_end_use_electricity[:units] = ['', target_units]
output_data_end_use_electricity[:data] = []
output_data_end_use_electricity[:chart_type] = 'simple_pie'
output_data_end_use_electricity[:chart] = []
end_use_colors = ['#EF1C21', '#0071BD', '#F7DF10', '#DEC310', '#4A4D4A', '#B5B2B5', '#FF79AD', '#632C94', '#F75921', '#293094', '#CE5921', '#FFB239', '#29AAE7', '#8CC739']
# loop through fuels for consumption tables
counter = 0
OpenStudio::EndUseCategoryType.getValues.each do |end_use|
# get end uses
end_use = OpenStudio::EndUseCategoryType.new(end_use).valueDescription
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= 'Electricity'"
results = sqlFile.execAndReturnFirstDouble(query)
value = OpenStudio.convert(results.get, 'GJ', target_units).get
value_neat = OpenStudio.toNeatString(value, 0, true)
output_data_end_use_electricity[:data] << [end_use, value_neat]
runner.registerValue("end_use_electricity_#{end_use.downcase.tr(' ', '_')}", value, target_units)
if value > 0
output_data_end_use_electricity[:chart] << JSON.generate(label: end_use, value: value, color: end_use_colors[counter])
end
counter += 1
end
return output_data_end_use_electricity
end
# create table with general building information
# this just makes a table, and not a full section. It feeds into another method that makes a full section
def self.output_data_end_use_gas_table(model, sqlFile, runner, is_ip_units = true)
# end use data output
output_data_end_use_gas = {}
output_data_end_use_gas[:title] = 'EUI - Gas'
output_data_end_use_gas[:header] = ['End Use', 'Consumption']
if is_ip_units
target_units = 'therm'
target_display_unit = 'therms'
else
target_units = 'kWh'
target_display_unit = 'kWh'
end
output_data_end_use_gas[:units] = ['', target_display_unit]
output_data_end_use_gas[:data] = []
output_data_end_use_gas[:chart_type] = 'simple_pie'
output_data_end_use_gas[:chart] = []
output_data_end_use_gas[:chart_type] = 'simple_pie'
output_data_end_use_gas[:chart] = []
end_use_colors = ['#EF1C21', '#0071BD', '#F7DF10', '#DEC310', '#4A4D4A', '#B5B2B5', '#FF79AD', '#632C94', '#F75921', '#293094', '#CE5921', '#FFB239', '#29AAE7', '#8CC739']
# loop through fuels for consumption tables
counter = 0
OpenStudio::EndUseCategoryType.getValues.each do |end_use|
# get end uses
end_use = OpenStudio::EndUseCategoryType.new(end_use).valueDescription
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= 'Natural Gas'"
results = sqlFile.execAndReturnFirstDouble(query)
value = OpenStudio.convert(results.get, 'GJ', target_units).get
value_neat = OpenStudio.toNeatString(value, 0, true)
output_data_end_use_gas[:data] << [end_use, value_neat]
runner.registerValue("end_use_natural_gas_#{end_use.downcase.tr(' ', '_')}", value, target_units)
if value > 0
output_data_end_use_gas[:chart] << JSON.generate(label: end_use, value: value, color: end_use_colors[counter])
end
counter += 1
end
return output_data_end_use_gas
end
# create table with general building information
# this just makes a table, and not a full section. It feeds into another method that makes a full section
def self.output_data_energy_use_table(model, sqlFile, runner, is_ip_units = true)
# energy use data output
output_data_energy_use = {}
output_data_energy_use[:title] = 'Energy Use'
output_data_energy_use[:header] = ['Fuel', 'Consumption']
if is_ip_units
target_units = 'kBtu'
else
target_units = 'kWh'
end
output_data_energy_use[:units] = ['', target_units]
output_data_energy_use[:data] = []
output_data_energy_use[:chart_type] = 'simple_pie'
output_data_energy_use[:chart] = []
# list of colors for fuel. Also used for cash flow chart
color = []
color << '#DDCC77' # Electricity
color << '#999933' # Natural Gas
# not used for 9.4
# color << '#AA4499' # Additional Fuel
# TODO: - new colors to add for 9.4 (for nwo using color of Additional Fuel)
color << '#AA4499'
color << '#AA4499'
color << '#AA4499'
color << '#AA4499'
color << '#AA4499'
color << '#AA4499'
color << '#AA4499'
color << '#88CCEE' # District Cooling
color << '#CC6677' # District Heating
# color << "#332288" # Water (not used here but is in cash flow chart)
# color << "#117733" # Capital and O&M (not used here but is in cash flow chart)
# loop through fuels for consumption tables
counter = 0
fuel_type_names.each do |fuel_type| # OpenStudio::EndUseFuelType.getValues
# get fuel type and units
# fuel_type = OpenStudio::EndUseFuelType.new(fuel_type).valueDescription
next if fuel_type == 'Water'
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= 'Total End Uses' and ColumnName= '#{fuel_type}'"
results = sqlFile.execAndReturnFirstDouble(query)
# julien
source_units = 'kWh'
if is_ip_units
target_units = 'kBtu'
else
target_units = source_units
end
target_units = 'kBtu'
value = OpenStudio.convert(results.get, 'GJ', target_units).get
value_neat = OpenStudio.toNeatString(value, 0, true)
output_data_energy_use[:data] << [fuel_type, value_neat]
runner.registerValue("fuel_#{fuel_type.downcase.tr(' ', '_')}", value, target_units)
if value > 0
output_data_energy_use[:chart] << JSON.generate(label: fuel_type, value: value, color: color[counter])
end
counter += 1
end
return output_data_energy_use
end
# create table for advisory messages
def self.setpoint_not_met_summary_table(model, sqlFile, runner, is_ip_units = true)
# unmet hours data output
setpoint_not_met_summary = {}
setpoint_not_met_summary[:title] = 'Unmet Hours Summary'
setpoint_not_met_summary[:header] = ['Time Setpoint Not Met', 'Time']
target_units = 'hr'
setpoint_not_met_summary[:units] = ['', target_units]
setpoint_not_met_summary[:data] = []
# create string for rows (transposing from what is in tabular data)
setpoint_not_met_cat = []
setpoint_not_met_cat << 'During Heating'
setpoint_not_met_cat << 'During Cooling'
setpoint_not_met_cat << 'During Occupied Heating'
setpoint_not_met_cat << 'During Occupied Cooling'
# loop through messages
setpoint_not_met_cat.each do |cat|
# Retrieve end use percentages from table
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='SystemSummary' and TableName = 'Time Setpoint Not Met' and RowName= 'Facility' and ColumnName='#{cat}';"
setpoint_not_met_cat_value = sqlFile.execAndReturnFirstDouble(query)
if setpoint_not_met_cat_value.empty?
runner.registerWarning("Did not find value for #{cat}.")
return false
else
# net site energy
display = cat
source_units = 'hr'
value = setpoint_not_met_cat_value.get
value_neat = value # OpenStudio::toNeatString(value,0,true)
setpoint_not_met_summary[:data] << [display, value_neat]
runner.registerValue("unmet_hours_#{OsLib_Reporting.reg_val_string_prep(display)}", value, target_units)
end
end # setpoint_not_met_cat.each do
return setpoint_not_met_summary
end
# create table for setpoint_not_met_criteria
def self.setpoint_not_met_criteria_table(model, sqlFile, runner, is_ip_units = true)
# unmet hours data output
tolerance_summary = {}
tolerance_summary[:title] = 'Unmet Hours Tolerance'
tolerance_summary[:header] = ['Tolerance for Time Setpoint Not Met', 'Temperature']
# julien
source_units = 'C'
if is_ip_units
target_units = 'F'
else
target_units = source_units
end
tolerance_summary[:units] = ['', target_units]
tolerance_summary[:data] = []
# create string for rows (transposing from what is in tabular data)
setpoint_not_met_cat = []
setpoint_not_met_cat << 'Heating'
setpoint_not_met_cat << 'Cooling'
# loop through messages
setpoint_not_met_cat.each do |cat|
# Retrieve end use percentages from table
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName = 'Setpoint Not Met Criteria' and RowName= 'Tolerance for Zone #{cat} Setpoint Not Met Time' and ColumnName='Degrees';"
setpoint_not_met_cat_value = sqlFile.execAndReturnFirstDouble(query)
if setpoint_not_met_cat_value.empty?
runner.registerWarning("Did not find value for #{cat}.")
return false
else
# net site energy
display = cat
# julien
source_units = 'K'
if is_ip_units
target_units = 'R'
else
target_units = source_units
end
value = OpenStudio.convert(setpoint_not_met_cat_value.get.to_f, source_units, target_units).get
value_neat = value.round(2)
tolerance_summary[:data] << [display, value_neat]
runner.registerValue("unmet_hours_tolerance_#{cat.downcase}", value, target_units)
end
end # setpoint_not_met_cat.each do
return tolerance_summary
end
# Generalized method for getting component performance characteristics
def self.general_component_summary_logic(component, is_ip_units)
data_arrays = []
# Convert to HVAC Component
component = component.to_HVACComponent
return data_arrays if component.empty?
component = component.get
# Skip object types that are not informative
types_to_skip = ['SetpointManager:MixedAir', 'Node']
idd_obj_type = component.iddObject.name.gsub('OS:', '')
return data_arrays if types_to_skip.include?(idd_obj_type)
# Only report the component type once
comp_name_used = false
# Airflow, heating and cooling capacity, and water flow
summary_types = []
summary_types << ['Air Flow Rate', 'maxAirFlowRate', 'm^3/s', 4, 'cfm', 0]
summary_types << ['Heating Capacity', 'maxHeatingCapacity', 'W', 1, 'Btu/hr', 1]
summary_types << ['Cooling Capacity', 'maxCoolingCapacity', 'W', 1, 'ton', 1]
summary_types << ['Water Flow Rate', 'maxWaterFlowRate', 'm^3/s', 4, 'gal/min', 2]
summary_types << ['Rated Power', 'ratedPower', 'W', 1, 'W', 1]
summary_types.each do |s|
val_name = s[0]
val_method = s[1]
units_si = s[2]
decimal_places_si = s[3]
units_ip = s[4]
decimal_places_ip = s[5]
# Get the value and skip if not available
val_si = component.public_send(val_method)
next if val_si.empty?
# Determine if the value was autosized or hard sized
siz = 'Hard Sized'
if component.public_send("#{val_method}Autosized").is_initialized
if component.public_send("#{val_method}Autosized").get
siz = 'Autosized'
end
end
# Convert and report the value
source_units = units_si
if is_ip_units
target_units = units_ip
decimal_places = decimal_places_ip
else
target_units = source_units
decimal_places = decimal_places_si
end
val_ip = OpenStudio.convert(val_si.get, source_units, target_units).get
val_ip_neat = OpenStudio.toNeatString(val_ip, decimal_places, true)
if is_ip_units
data_arrays << ['', val_name, "#{val_ip_neat} #{units_ip}", siz, '']
else
data_arrays << ['', val_name, "#{val_ip_neat} #{units_si}", siz, '']
end
end
# Performance characteristics (specific to each component type)
perf_chars = component.performanceCharacteristics.each do |char|
perf_val = char[0].to_s.to_f
perf_name = char[1]
display_units = '' # For Display
# Unit conversion for pressure rise and pump head
if perf_name.downcase.include?('pressure rise')
source_units = 'Pa'
if is_ip_units
target_units = 'inH_{2}O'
display_units = 'in w.g.'
else
target_units = source_units
display_units = source_units
end
perf_val = OpenStudio.convert(perf_val, source_units, target_units).get
perf_val = OpenStudio.toNeatString(perf_val, 2, true)
elsif perf_name.downcase.include?('pump head')
source_units = 'Pa'
if is_ip_units
target_units = 'ftH_{2}O'
display_units = 'ft H2O'
n_decimals = 1
else
target_units = source_units
display_units = source_units
n_decimals = 0
end
perf_val = OpenStudio.convert(perf_val, source_units, target_units).get
perf_val = OpenStudio.toNeatString(perf_val, n_decimals, true)
elsif perf_name.downcase.include?('efficiency') || perf_name.downcase.include?('effectiveness')
display_units = '%'
perf_val *= 100
perf_val = OpenStudio.toNeatString(perf_val, 1, true)
end
# Report the value
data_arrays << ['', perf_name, "#{perf_val} #{display_units}", '', '']
end
return data_arrays
end
# Gives the Plant Loop connection information for an HVAC Component
def self.water_component_logic(component, is_ip_units)
data_arrays = []
component = component.to_HVACComponent
return data_arrays if component.empty?
component = component.get
# Only deal with plant-connected components
return data_arrays unless component.respond_to?('plantLoop')
# Report the plant loop name
if component.plantLoop.is_initialized
data_arrays << ['', 'Plant Loop', component.plantLoop.get.name, '', '']
end
return data_arrays
end
# Shows the calculated brake horsepower for fans and pumps
def self.motor_component_logic(component, is_ip_units)
data_arrays = []
# Skip exhaust fans for now because of bug in openstudio-standards
return data_arrays if component.to_FanZoneExhaust.is_initialized
concrete_comp = component.cast_to_concrete_type
component = concrete_comp unless component.nil?
# Only deal with plant-connected components
return data_arrays unless component.respond_to?('brake_horsepower')
# Report the plant loop name
bhp = component.brake_horsepower
bhp_neat = OpenStudio.toNeatString(bhp, 2, true)
data_arrays << ['', 'Brake Horsepower', "#{bhp_neat} HP", '', '']
return data_arrays
end
# Shows the setpoint manager details depending on type
def self.spm_logic(component, is_ip_units)
data_arrays = []
case component.iddObject.name
when 'OS:SetpointManager:Scheduled'
# Constrol type and temperature range
setpoint = component.to_SetpointManagerScheduled.get
supply_air_temp_schedule = setpoint.schedule
schedule_values = OsLib_Schedules.getMinMaxAnnualProfileValue(component.model, supply_air_temp_schedule)
if schedule_values.nil?
schedule_values_pretty = "can't inspect schedule"
target_units = ''
else
if setpoint.controlVariable.to_s == 'Temperature'
# julien
source_units = 'C'
if is_ip_units
target_units = 'F'
else
target_units = source_units
end
schedule_values_pretty = "#{OpenStudio.convert(schedule_values['min'], source_units, target_units).get.round(1)} to #{OpenStudio.convert(schedule_values['max'], source_units, target_units).get.round(1)}"
else # TODO: - add support for other control variables
schedule_values_pretty = "#{schedule_values['min']} to #{schedule_values['max']}"
target_units = 'raw si values'
end
end
data_arrays << ['', "Control Variable - #{setpoint.controlVariable}", "#{schedule_values_pretty} #{target_units}", '', '']
when 'OS:SetpointManager:SingleZone:Reheat'
# Control Zone
setpoint = component.to_SetpointManagerSingleZoneReheat.get
control_zone = setpoint.controlZone
if control_zone.is_initialized
control_zone_name = control_zone.get.name
else
control_zone_name = ''
end
data_arrays << ['', 'Control Zone', control_zone_name, '', '']
when 'OS:SetpointManager:FollowOutdoorAirTemperature'
setpoint = component.to_SetpointManagerFollowOutdoorAirTemperature.get
ref_temp_type = setpoint.referenceTemperatureType
data_arrays << [setpoint.iddObject.name, 'Reference Temperature Type', ref_temp_type, '', '']
when 'OS:SetpointManager:OutdoorAirReset'
setpoint = component.to_SetpointManagerOutdoorAirReset.get
source_units = 'C'
if is_ip_units
target_units = 'F'
else
target_units = source_units
end
wt_at_hi_oat_f = OpenStudio.convert(setpoint.setpointatOutdoorHighTemperature, source_units, target_units).get.round(1)
wt_at_lo_oat_f = OpenStudio.convert(setpoint.setpointatOutdoorLowTemperature, source_units, target_units).get.round(1)
hi_oat_f = OpenStudio.convert(setpoint.outdoorHighTemperature, source_units, target_units).get.round(1)
lo_oat_f = OpenStudio.convert(setpoint.outdoorLowTemperature, source_units, target_units).get.round(1)
# julien
if is_ip_units
desc = "#{wt_at_lo_oat_f} F to #{wt_at_hi_oat_f.round} F btwn OAT #{lo_oat_f} F to #{hi_oat_f} F."
else
desc = "#{wt_at_lo_oat_f} C to #{wt_at_hi_oat_f.round} C btwn OAT #{lo_oat_f} C to #{hi_oat_f} C."
end
data_arrays << [setpoint.iddObject.name, 'Reset', desc, '', '']
when 'OS:SetpointManager:Warmest'
setpoint = component.to_SetpointManagerWarmest.get
source_units = 'C'
if is_ip_units
target_units = 'F'
else
target_units = source_units
end
min_sat_f = OpenStudio.convert(setpoint.minimumSetpointTemperature, source_units, target_units).get.round(1)
max_sat_f = OpenStudio.convert(setpoint.maximumSetpointTemperature, source_units, target_units).get.round(1)
desc = "#{min_sat_f} #{target_units} to #{max_sat_f.round} #{target_units}"
data_arrays << [setpoint.iddObject.name, 'Reset SAT per Worst Zone', desc, '', '']
when 'OS:SetpointManager:WarmestTemperatureFlow'
setpoint = component.to_SetpointManagerWarmestTemperatureFlow.get
source_units = 'C'
if is_ip_units
target_units = 'F'
else
target_units = source_units
end
min_sat_f = OpenStudio.convert(setpoint.minimumSetpointTemperature, source_units, target_units).get.round(1)
max_sat_f = OpenStudio.convert(setpoint.maximumSetpointTemperature, source_units, target_units).get.round(1)
desc = "#{min_sat_f} #{target_units} to #{max_sat_f.round} #{target_units}, #{setpoint.strategy}"
data_arrays << [setpoint.iddObject.name, 'Reset SAT & Flow per Worst Zone', desc, '', '']
end
return data_arrays
end
# summary of what to show for each type of air loop component
def self.air_loop_component_summary_logic(component, model, is_ip_units)
# Generic component logic first
data_arrays = general_component_summary_logic(component, is_ip_units)
# Water component logic
data_arrays += water_component_logic(component, is_ip_units)
# Motor component logic
data_arrays += motor_component_logic(component, is_ip_units)
# Setpoint manager logic
data_arrays += spm_logic(component, is_ip_units)
# Unique logic for subset of components
case component.iddObject.name
when 'OS:AirLoopHVAC:OutdoorAirSystem'
component = component.to_AirLoopHVACOutdoorAirSystem.get
controller_oa = component.getControllerOutdoorAir
# Min OA
# julien
source_units = 'm^3/s'
if is_ip_units
target_units = 'cfm'
n_decimals = 0
else
target_units = 'm^3/h'
n_decimals = 0
end
if controller_oa.minimumOutdoorAirFlowRate.is_initialized
value = OpenStudio.convert(controller_oa.minimumOutdoorAirFlowRate.get, source_units, target_units).get
value_neat = OpenStudio.toNeatString(value, n_decimals, true)
siz = 'Hard Sized'
elsif controller_oa.autosizedMinimumOutdoorAirFlowRate.is_initialized
value = OpenStudio.convert(controller_oa.autosizedMinimumOutdoorAirFlowRate.get, source_units, target_units).get
value_neat = OpenStudio.toNeatString(value, n_decimals, true)
siz = 'Autosized'
else
value_neat = 'Autosized'
end
data_arrays << ['', 'Minimum Outdoor Air Flow Rate', "#{value_neat} #{target_units}", siz, '']
# Max OA
if controller_oa.maximumOutdoorAirFlowRate.is_initialized
value_ip = OpenStudio.convert(controller_oa.maximumOutdoorAirFlowRate.get, source_units, target_units).get
value_ip_neat = OpenStudio.toNeatString(value_ip, n_decimals, true)
siz = 'Hard Sized'
elsif controller_oa.autosizedMaximumOutdoorAirFlowRate.is_initialized
value_ip = OpenStudio.convert(controller_oa.autosizedMaximumOutdoorAirFlowRate.get, source_units, target_units).get
value_ip_neat = OpenStudio.toNeatString(value_ip, n_decimals, true)
siz = 'Autosized'
else
value_ip_neat = 'Autosized'
end
data_arrays << ['', 'Maximum Outdoor Air Flow Rate', "#{value_ip_neat} #{target_units}", siz, '']
end
# Make the component type the first element of the first row
if !data_arrays.empty?
data_arrays[0][0] = component.iddObject.name.gsub('OS:', '')
end
return data_arrays
end
# create table air loop summary
def self.air_loops_detail_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
output_data_air_loop_tables = []
# gather data for section
@output_data_air_loop_section = {}
@output_data_air_loop_section[:title] = 'Air Loops Detail'
@output_data_air_loop_section[:tables] = output_data_air_loop_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @output_data_air_loop_section
end
# loop through air loops
model.getAirLoopHVACs.sort.each do |air_loop|
# air loop data output
output_data_air_loops = {}
output_data_air_loops[:title] = air_loop.name.get # TODO: - confirm first that it has name
output_data_air_loops[:header] = ['Object', 'Description', 'Value', 'Sizing', 'Count']
output_data_air_loops[:units] = [] # not using units for these tables
output_data_air_loops[:data] = []
output_data_air_loops[:data] << [{ sub_header: 'supply' }, '', '', '', '']
# hold values for later use
dcv_setting = 'na' # should hit this if there isn't an outdoor air object on the loop
economizer_setting = 'na' # should hit this if there isn't an outdoor air object on the loop
# loop through components
air_loop.supplyComponents.each do |component|
# skip some object types, but look for node with setpoint manager
if component.to_Node.is_initialized
setpoint_managers = component.to_Node.get.setpointManagers
if !setpoint_managers.empty?
# setpoint type
setpoint = setpoint_managers[0] # TODO: - could have more than one in some situations
data_arrays = OsLib_Reporting.air_loop_component_summary_logic(setpoint, model, is_ip_units)
data_arrays.each do |data_array|
output_data_air_loops[:data] << data_array
end
end
else
# populate table for everything but setpoint managers, which are added above.
data_arrays = OsLib_Reporting.air_loop_component_summary_logic(component, model, is_ip_units)
data_arrays.each do |data_array|
output_data_air_loops[:data] << data_array
end
end
# gather controls information to use later
if component.to_AirLoopHVACOutdoorAirSystem.is_initialized
hVACComponent = component.to_AirLoopHVACOutdoorAirSystem.get
# get ControllerOutdoorAir
controller_oa = hVACComponent.getControllerOutdoorAir
# get ControllerMechanicalVentilation
controller_mv = controller_oa.controllerMechanicalVentilation
# get dcv value
dcv_setting = controller_mv.demandControlledVentilation
if dcv_setting
dcv_setting = 'On'
else
dcv_setting = 'Off'
end
# get economizer setting
economizer_setting = controller_oa.getEconomizerControlType
end
end
output_data_air_loops[:data] << [{ sub_header: 'demand' }, '', '', '', '']
# demand side summary, list of terminal types used, and number of zones
thermal_zones = []
terminals = []
cooling_temp_ranges = []
heating_temps_ranges = []
air_loop.demandComponents.each do |component|
# gather array of thermal zones and terminals
if component.to_ThermalZone.is_initialized
thermal_zone = component.to_ThermalZone.get
thermal_zones << thermal_zone
thermal_zone.equipment.each do |zone_equip|
next if zone_equip.to_ZoneHVACComponent.is_initialized # should only find terminals
terminals << zone_equip.iddObject.name.gsub('OS:', '')
end
# populate thermostat ranges
if thermal_zone.thermostatSetpointDualSetpoint.is_initialized
thermostat = thermal_zone.thermostatSetpointDualSetpoint.get
if thermostat.coolingSetpointTemperatureSchedule.is_initialized
schedule_values = OsLib_Schedules.getMinMaxAnnualProfileValue(model, thermostat.coolingSetpointTemperatureSchedule.get)
unless schedule_values.nil?
cooling_temp_ranges << schedule_values['min']
cooling_temp_ranges << schedule_values['max']
end
end
if thermostat.heatingSetpointTemperatureSchedule.is_initialized
schedule_values = OsLib_Schedules.getMinMaxAnnualProfileValue(model, thermostat.heatingSetpointTemperatureSchedule.get)
unless schedule_values.nil?
heating_temps_ranges << schedule_values['min']
heating_temps_ranges << schedule_values['max']
end
end
end
end
end
# get floor area of thermal zones
total_loop_floor_area = 0
thermal_zones.each do |zone|
total_loop_floor_area += zone.floorArea
end
source_units = 'm^2'
if is_ip_units
target_units = 'ft^2'
else
target_units = source_units
end
total_loop_floor_area = OpenStudio.convert(total_loop_floor_area, source_units, target_units).get
total_loop_floor_area_neat = OpenStudio.toNeatString(total_loop_floor_area, 0, true)
# output zone and terminal data
if is_ip_units
output_data_air_loops[:data] << ['Thermal Zones', 'Total Floor Area', "#{total_loop_floor_area_neat} ft^2", '', thermal_zones.size]
else
output_data_air_loops[:data] << ['Thermal Zones', 'Total Floor Area', "#{total_loop_floor_area_neat} m^2", '', thermal_zones.size]
end
# heating and cooling temperature range data
source_units = 'C'
if is_ip_units
target_units = 'F'
target_units_display = 'F'
else
target_units = source_units
target_units_display = 'C'
end
if cooling_temp_ranges.empty?
cooling_temp_ranges_pretty = "can't inspect schedules"
else
cooling_temp_ranges_pretty = "#{OpenStudio.convert(cooling_temp_ranges.min, source_units, target_units).get.round(1)} to #{OpenStudio.convert(cooling_temp_ranges.max, source_units, target_units).get.round(1)}"
end
if heating_temps_ranges.empty?
heating_temps_ranges_pretty = "can't inspect schedules"
else
heating_temps_ranges_pretty = "#{OpenStudio.convert(heating_temps_ranges.min, source_units, target_units).get.round(1)} to #{OpenStudio.convert(heating_temps_ranges.max, source_units, target_units).get.round(1)}"
end
output_data_air_loops[:data] << ['Thermal Zones', 'Cooling Setpoint Range', "#{cooling_temp_ranges_pretty} #{target_units_display}", '', '']
output_data_air_loops[:data] << ['Thermal Zones', 'Heating Setpoint Range', "#{heating_temps_ranges_pretty} #{target_units_display}", '', '']
output_data_air_loops[:data] << ['Terminal Types Used', terminals.uniq.sort.join(', '), '', '', terminals.size]
# controls summary
# julien
source_units = 'C'
if is_ip_units
target_units = 'F'
target_units_display = 'F'
else
target_units = source_units
target_units_display = 'C'
end
output_data_air_loops[:data] << [{ sub_header: 'controls' }, '', '', '', '']
output_data_air_loops[:data] << ['HVAC Operation Schedule', '', air_loop.availabilitySchedule.name, '', ''] # I think this is a bool
output_data_air_loops[:data] << ['Night Cycle Setting', '', air_loop.nightCycleControlType, '', '']
output_data_air_loops[:data] << ['Economizer Setting', '', economizer_setting, '', '']
output_data_air_loops[:data] << ['Demand Controlled Ventilation Status', '', dcv_setting, '', '']
htg_sat_si = air_loop.sizingSystem.centralHeatingDesignSupplyAirTemperature
htg_sat_ip = OpenStudio.toNeatString(OpenStudio.convert(htg_sat_si, source_units, target_units).get, 1, true)
output_data_air_loops[:data] << ['Central Heating Design Supply Air Temperature', '', "#{htg_sat_ip} #{target_units_display}", '', '']
clg_sat_si = air_loop.sizingSystem.centralCoolingDesignSupplyAirTemperature
clg_sat_ip = OpenStudio.toNeatString(OpenStudio.convert(clg_sat_si, source_units, target_units).get, 1, true)
output_data_air_loops[:data] << ['Central Cooling Design Supply Air Temperature', '', "#{clg_sat_ip} #{target_units_display}", '', '']
output_data_air_loops[:data] << ['Load to Size On', '', air_loop.sizingSystem.typeofLoadtoSizeOn, '', '']
# populate tables for section
output_data_air_loop_tables << output_data_air_loops
end
return @output_data_air_loop_section
end
# summary of what to show for each type of plant loop component
def self.plant_loop_component_summary_logic(component, model, is_ip_units)
# Generic component logic first
data_arrays = general_component_summary_logic(component, is_ip_units)
# Motor component logic
data_arrays += motor_component_logic(component, is_ip_units)
# Setpoint manager logic
data_arrays += spm_logic(component, is_ip_units)
# Make the component type the first element of the first row
if !data_arrays.empty?
data_arrays[0][0] = component.iddObject.name.gsub('OS:', '')
end
return data_arrays
end
# create table plant loop summary
def self.plant_loops_detail_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
output_data_plant_loop_tables = []
# gather data for section
@output_data_plant_loop_section = {}
@output_data_plant_loop_section[:title] = 'Plant Loops Detail'
@output_data_plant_loop_section[:tables] = output_data_plant_loop_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @output_data_plant_loop_section
end
# loop through plant loops
model.getPlantLoops.sort.each do |plant_loop|
# plant loop data output
output_data_plant_loops = {}
output_data_plant_loops[:title] = plant_loop.name.get # TODO: - confirm first that it has name
output_data_plant_loops[:header] = ['Object', 'Description', 'Value', 'Sizing', 'Count']
output_data_plant_loops[:units] = [] # not using units for these tables
output_data_plant_loops[:data] = []
output_data_plant_loops[:data] << [{ sub_header: 'supply' }, '', '', '', '']
plant_loop.supplyComponents.each do |component|
if component.to_ThermalZone.is_initialized
end
# skip some object types
next if component.to_PipeAdiabatic.is_initialized
next if component.to_Splitter.is_initialized
next if component.to_Mixer.is_initialized
if component.to_Node.is_initialized
setpoint_managers = component.to_Node.get.setpointManagers
if !setpoint_managers.empty?
# setpoint type
setpoint = setpoint_managers[0] # TODO: - could have more than one in some situations
data_arrays = OsLib_Reporting.plant_loop_component_summary_logic(setpoint, model, is_ip_units)
data_arrays.each do |data_array| # typically just one, but in some cases there are a few
output_data_plant_loops[:data] << data_array
end
end
else
# populate table for everything but setpoint managers, which are added above.
data_arrays = OsLib_Reporting.plant_loop_component_summary_logic(component, model, is_ip_units)
data_arrays.each do |data_array| # typically just one, but in some cases there are a few
output_data_plant_loops[:data] << data_array
end
end
end
# loop through demand components
output_data_plant_loops[:data] << [{ sub_header: 'demand' }, '', '', '', '']
# keep track of terminal count to report later
terminal_connections = [] # Not sure how I want to list in display
# loop through plant demand components
plant_loop.demandComponents.each do |component|
# flag for terminal connecxtions
terminal_connection = false
# skip some object types
next if component.to_PipeAdiabatic.is_initialized
next if component.to_Splitter.is_initialized
next if component.to_Mixer.is_initialized
next if component.to_Node.is_initialized
# determine if water to air
if component.to_WaterToAirComponent.is_initialized
component = component.to_WaterToAirComponent.get
if component.airLoopHVAC.is_initialized
description = 'Air Loop'
value = component.airLoopHVAC.get.name
else
# this is a terminal connection
terminal_connection = true
terminal_connections << component
end
elsif component.to_WaterToWaterComponent.is_initialized
description = 'Plant Loop'
component = component.to_WaterToWaterComponent.get
ww_loop = component.plantLoop
if ww_loop.is_initialized
value = ww_loop.get.name
else
value = ''
end
else # water use connections would go here
description = component.name
value = ''
end
# don't report here if this component is connected to a terminal
next if terminal_connection == true
output_data_plant_loops[:data] << [component.iddObject.name.gsub('OS:', ''), description, value, '', '']
end
# report terminal connections
if !terminal_connections.empty?
output_data_plant_loops[:data] << ['Air Terminal Connections', '', '', '', terminal_connections.size]
end
output_data_plant_loops[:data] << [{ sub_header: 'controls' }, '', '', '', '']
# Min loop flow rate
source_units = 'm^3/s'
if is_ip_units
target_units = 'gal/min'
n_decimals = 2
else
target_units = 'm^3/h'
n_decimals = 0
end
if plant_loop.minimumLoopFlowRate.is_initialized
value = OpenStudio.convert(plant_loop.minimumLoopFlowRate.get, source_units, target_units).get
value_neat = OpenStudio.toNeatString(value, n_decimals, true)
siz = 'Hard Sized'
elsif plant_loop.autosizedMinimumLoopFlowRate.is_initialized
value = OpenStudio.convert(plant_loop.autosizedMinimumLoopFlowRate.get, source_units, target_units).get
value_neat = OpenStudio.toNeatString(value, n_decimals, true)
siz = 'Autosized'
else
value_neat = 'Autosized'
end
output_data_plant_loops[:data] << ['Loop Flow Rate Range', 'Minimum Loop Flow Rate', "#{value_neat} #{target_units}", siz, '']
# Max loop flow rate
if plant_loop.maximumLoopFlowRate.is_initialized
value = OpenStudio.convert(plant_loop.maximumLoopFlowRate.get, source_units, target_units).get
value_neat = OpenStudio.toNeatString(value, n_decimals, true)
siz = 'Hard Sized'
elsif
plant_loop.autosizedMaximumLoopFlowRate.is_initialized
value = OpenStudio.convert(plant_loop.autosizedMaximumLoopFlowRate.get, source_units, target_units).get
value_neat = OpenStudio.toNeatString(value, n_decimals, true)
siz = 'Autosized'
else
value_neat = 'Autosized'
end
output_data_plant_loops[:data] << ['Loop Flow Rate Range', 'Maximum Loop Flow Rate', "#{value_neat} #{target_units}", siz, '']
# loop temperatures
source_units = 'C'
if is_ip_units
target_units = 'F'
else
target_units = 'C'
end
min_temp = plant_loop.minimumLoopTemperature
max_temp = plant_loop.maximumLoopTemperature
value_neat = "#{OpenStudio.convert(min_temp, source_units, target_units).get.round(1)} to #{OpenStudio.convert(max_temp, source_units, target_units).get.round(1)}"
output_data_plant_loops[:data] << ['Loop Temperature Range', '', "#{value_neat} #{target_units}", '', '']
# get values out of sizing plant
sizing_plant = plant_loop.sizingPlant
source_units = 'C'
if is_ip_units
target_units = 'F'
else
target_units = 'C'
end
loop_exit_temp = sizing_plant.designLoopExitTemperature
value_neat = OpenStudio.toNeatString(OpenStudio.convert(loop_exit_temp, source_units, target_units).get, 1, true)
output_data_plant_loops[:data] << ['Loop Design Exit Temperature', '', "#{value_neat} #{target_units}", '', '']
source_units = 'K'
if is_ip_units
target_units = 'R'
else
target_units = 'K'
end
loop_design_temp_diff = sizing_plant.loopDesignTemperatureDifference
value_neat = OpenStudio.toNeatString(OpenStudio.convert(loop_design_temp_diff, source_units, target_units).get, 1, true)
output_data_plant_loops[:data] << ['Loop Design Temperature Difference', '', "#{value_neat} #{target_units}", '', '']
# Equipment staging
output_data_plant_loops[:data] << ['Equipment Loading/Staging', '', plant_loop.loadDistributionScheme, '', '']
# push tables
output_data_plant_loop_tables << output_data_plant_loops
end
return @output_data_plant_loop_section
end
# summary of what to show for each type of zone equipment component
def self.zone_equipment_component_summary_logic(component, model, is_ip_units)
# Generic component logic first
data_arrays = general_component_summary_logic(component, is_ip_units)
# Motor component logic
data_arrays += motor_component_logic(component, is_ip_units)
return data_arrays
end
# create table plant loop summary
def self.zone_equipment_detail_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
output_data_zone_equipment = []
# gather data for section
@output_data_zone_equipment_section = {}
@output_data_zone_equipment_section[:title] = 'Zone Equipment Detail'
@output_data_zone_equipment_section[:tables] = output_data_zone_equipment
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @output_data_zone_equipment_section
end
# loop through thermal zones
model.getThermalZones.sort.each do |zone|
# plant loop data output
output_data_zone_equipment = {}
output_data_zone_equipment[:title] = zone.name.get # TODO: - confirm that zone has a name
output_data_zone_equipment[:header] = ['Object', 'Description', 'Value', 'Sizing', 'Count']
output_data_zone_equipment[:units] = [] # not using units for these tables
output_data_zone_equipment[:data] = []
zone.equipment.sort.each do |zone_equip|
next unless zone_equip.to_ZoneHVACComponent.is_initialized # skip any terminals
data_arrays = OsLib_Reporting.zone_equipment_component_summary_logic(zone_equip, model, is_ip_units)
data_arrays.each do |data_array| # typically just one, but in some cases there are a few
output_data_zone_equipment[:data] << data_array
end
# Make the component type the first element of the first row
if !data_arrays.empty?
data_arrays[0][0] = zone_equip.iddObject.name.gsub('OS:', '')
end
end
# push table to array
if !output_data_zone_equipment[:data].empty?
@output_data_zone_equipment_section[:tables] << output_data_zone_equipment
end
end
return @output_data_zone_equipment_section
end
# create table for constructions
def self.envelope_section_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# Versions of OpenStudio greater than 2.4.0 use a modified version of
# openstudio-standards with different method calls. These methods
# require a "Standard" object instead of the standard being passed into method calls.
# This Standard object is used throughout the QAQC check.
if OpenStudio::VersionString.new(OpenStudio.openStudioVersion) < OpenStudio::VersionString.new('2.4.2')
use_old_gem_code = true
else
use_old_gem_code = false
std = Standard.build('90.1-2013') # actual standard doesn't matter in this case
end
# array to hold tables
envelope_tables = []
# gather data for section
@envelope_section = {}
@envelope_section[:title] = 'Envelope Summary'
@envelope_section[:tables] = envelope_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @envelope_section
end
# summary of exterior constructions used in the model for base surfaces
surface_data = {}
surface_data[:title] = 'Base Surface Constructions'
surface_data[:header] = ['Construction', 'Net Area', 'Surface Count', 'R Value']
source_area_units = 'm^2'
source_rvalue_units = 'm^2*K/W'
source_ufactor_units = 'W/m^2*K'
if is_ip_units
target_area_units = 'ft^2'
target_rvalue_units = 'ft^2*h*R/Btu'
target_ufactor_units = 'Btu/ft^2*h*R'
n_decimals_area = 0
n_decimals_rvalue = 2
else
target_area_units = source_area_units
target_rvalue_units = source_rvalue_units
target_ufactor_units = source_ufactor_units
n_decimals_area = 1
n_decimals_rvalue = 2
end
surface_data[:units] = ['', target_area_units, '', target_rvalue_units]
surface_data[:data] = []
# construction details
# TODO
# add table with subheads for each construction, and rows for each material layer
# construction_details = {}
# construction_details[:title] = 'Construction Details'
# construction_details[:header] = ['Material']
# construction_details[:data] = []
# loop through surfaces to get constructions
ext_const_base = {}
model.getSurfaces.each do |surface|
next if surface.outsideBoundaryCondition != 'Outdoors'
if ext_const_base.include? surface.construction.get
ext_const_base[surface.construction.get] += 1
else
ext_const_base[surface.construction.get] = 1
end
end
ext_const_base.sort.each do |construction, count|
net_area = construction.getNetArea
net_area_conv = OpenStudio.convert(net_area, source_area_units, target_area_units).get
net_area_neat = OpenStudio.toNeatString(net_area_conv, n_decimals_area, true)
surface_count = count
if construction.thermalConductance.is_initialized
thermal_conductance = construction.thermalConductance.get
r_value = OpenStudio.convert(1 / thermal_conductance, source_rvalue_units, target_rvalue_units).get
r_value_neat = OpenStudio.toNeatString(r_value, n_decimals_rvalue, true)
else
r_value_neat = ''
end
surface_data[:data] << [construction.name, net_area_neat, surface_count, r_value_neat]
runner.registerValue(OsLib_Reporting.reg_val_string_prep(construction.name.to_s), net_area_conv, target_area_units)
# TODO: Get this working like subsurfaces
# construction_details[:data] << [{ sub_header: "Material Layers in Construction '#{construction.name}':"}]
# construction.layers.each do |l|
# construction_details[:data] << [l.name]
# end
end
envelope_tables << surface_data
# summary of exterior constructions used in the model for sub surfaces
sub_surface_data = {}
sub_surface_data[:title] = 'Sub Surface Constructions'
sub_surface_data[:header] = ['Construction', 'Net Area', 'Surface Count', 'U-factor', 'SHGC', 'VLT']
sub_surface_data[:units] = ['', target_area_units, '', target_ufactor_units]
sub_surface_data[:data] = []
construction_details = {}
construction_details[:title] = 'Sub Surface Construction Details (Material Layers)'
construction_details[:header] = ['Material Name']
construction_details[:data] = []
# loop through sub_surfaces to get constructions
ext_const_sub = {}
model.getSubSurfaces.each do |sub_surface|
next if sub_surface.outsideBoundaryCondition != 'Outdoors'
if ext_const_sub.include? sub_surface.construction.get
ext_const_sub[sub_surface.construction.get] += 1
else
ext_const_sub[sub_surface.construction.get] = 1
end
end
ext_const_sub.sort.each do |construction, count|
net_area = construction.getNetArea
net_area_conv = OpenStudio.convert(net_area, target_area_units, source_area_units).get
net_area_neat = OpenStudio.toNeatString(net_area_conv, n_decimals_area, true)
surface_count = count
vlt_neat = 'n/a'
shgc_neat = 'n/a'
u_factor_neat = ''
if construction.to_Construction.is_initialized
construction_root = construction.to_Construction.get
if construction_root.isFenestration
if use_old_gem_code
shgc = construction_root.calculated_solar_heat_gain_coefficient
else
shgc = std.construction_calculated_solar_heat_gain_coefficient(construction_root)
end
shgc_neat = OpenStudio.toNeatString(shgc, n_decimals_rvalue, false)
if use_old_gem_code
vlt = construction_root.calculated_visible_transmittance
else
vlt = std.construction_calculated_visible_transmittance(construction_root)
end
vlt_neat = OpenStudio.toNeatString(vlt, n_decimals_rvalue, false)
if use_old_gem_code
u_factor = construction_root.calculated_u_factor
else
u_factor = std.construction_calculated_u_factor(construction_root)
end
ufactor_conv = OpenStudio.convert(u_factor, source_ufactor_units, target_ufactor_units).get
ufactor_neat = OpenStudio.toNeatString(ufactor_conv, n_decimals_rvalue, false)
else
u_factor = construction.thermalConductance.get
u_factor_conv = OpenStudio.convert(u_factor, source_ufactor_units, target_ufactor_units).get
u_factor_neat = OpenStudio.toNeatString(u_factor_conv, n_decimals_rvalue, false)
end
# add layer details for each construction
construction_details[:data] << [{ sub_header: "Material Layers in Construction '#{construction.name}':" }]
construction_root.layers.each do |l|
construction_details[:data] << [l.name]
end
end
sub_surface_data[:data] << [construction.name, net_area_neat, surface_count, ufactor_neat, shgc_neat, vlt_neat]
runner.registerValue(OsLib_Reporting.reg_val_string_prep(construction.name.to_s), net_area_conv, target_area_units)
end
envelope_tables << sub_surface_data
envelope_tables << construction_details
# Conditioned Window-Wall Ratio and Skylight-Roof Ratio
fenestration_data = {}
fenestration_data[:title] = 'Window-to-Wall and Skylight-to-Roof area Ratios'
fenestration_data[:header] = ['Description', 'Total', 'North', 'East', 'South', 'West']
target_units = '%' # ratios reported as percentages
fenestration_data[:units] = ['', target_units, target_units, target_units, target_units, target_units]
fenestration_data[:data] = []
# create string for rows
fenestrations = []
fenestrations << 'Gross Window-Wall Ratio' # [%]
# loop rows
fenestrations.each do |fenestration|
query0 = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='InputVerificationandResultsSummary' and TableName='Window-Wall Ratio' and RowName='#{fenestration}' and ColumnName='Total'"
query1 = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='InputVerificationandResultsSummary' and TableName='Window-Wall Ratio' and RowName='#{fenestration}' and ColumnName='North (315 to 45 deg)'"
query2 = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='InputVerificationandResultsSummary' and TableName='Window-Wall Ratio' and RowName='#{fenestration}' and ColumnName='East (45 to 135 deg)'"
query3 = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='InputVerificationandResultsSummary' and TableName='Window-Wall Ratio' and RowName='#{fenestration}' and ColumnName='South (135 to 225 deg)'"
query4 = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='InputVerificationandResultsSummary' and TableName='Window-Wall Ratio' and RowName='#{fenestration}' and ColumnName='West (225 to 315 deg)'"
query5 = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='InputVerificationandResultsSummary' and TableName='Skylight-Roof Ratio' and RowName='Skylight-Roof Ratio'"
query6 = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='InputVerificationandResultsSummary' and TableName='Conditioned Window-Wall Ratio' and RowName='#{fenestration}' and ColumnName='Total'"
query7 = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='InputVerificationandResultsSummary' and TableName='Conditioned Window-Wall Ratio' and RowName='#{fenestration}' and ColumnName='North (315 to 45 deg)'"
query8 = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='InputVerificationandResultsSummary' and TableName='Conditioned Window-Wall Ratio' and RowName='#{fenestration}' and ColumnName='East (45 to 135 deg)'"
query9 = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='InputVerificationandResultsSummary' and TableName='Conditioned Window-Wall Ratio' and RowName='#{fenestration}' and ColumnName='South (135 to 225 deg)'"
query10 = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='InputVerificationandResultsSummary' and TableName='Conditioned Window-Wall Ratio' and RowName='#{fenestration}' and ColumnName='West (225 to 315 deg)'"
total = sqlFile.execAndReturnFirstDouble(query0)
north = sqlFile.execAndReturnFirstDouble(query1)
east = sqlFile.execAndReturnFirstDouble(query2)
south = sqlFile.execAndReturnFirstDouble(query3)
west = sqlFile.execAndReturnFirstDouble(query4)
skylight = sqlFile.execAndReturnFirstDouble(query5)
total_cond = sqlFile.execAndReturnFirstDouble(query6)
north_cond = sqlFile.execAndReturnFirstDouble(query7)
east_cond = sqlFile.execAndReturnFirstDouble(query8)
south_cond = sqlFile.execAndReturnFirstDouble(query9)
west_cond = sqlFile.execAndReturnFirstDouble(query10)
if total.empty? || north.empty? || east.empty? || south.empty? || west.empty? || total_cond.empty? || north_cond.empty? || east.empty? || south_cond.empty? || west_cond.empty? || skylight.empty?
runner.registerWarning('Did not find value for Window or Skylight Ratio')
return false
else
# add data
display = fenestration
fenestration_data[:data] << [display, total.get, north.get, east.get, south.get, west.get]
fenestration_data[:data] << ["#{display} (Conditioned)", total_cond.get, north_cond.get, east_cond.get, south_cond.get, west_cond.get]
runner.registerValue(OsLib_Reporting.reg_val_string_prep(display).to_s, total.get, target_units)
runner.registerValue("#{OsLib_Reporting.reg_val_string_prep(display)}_conditioned", total_cond.get, target_units)
# skylight
# skylight seems to provide back percentage vs. fraction. Changing to fraction to match vertical fenestration.
fenestration_data[:data] << ['Skylight-Roof Ratio', skylight.get, '', '', '', '']
runner.registerValue('skylight_roof_ratio', skylight.get, target_units)
end
end
envelope_tables << fenestration_data
return @envelope_section
end
# create table for service water heating
def self.water_use_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# water use equipment from model
water_use_data = {}
water_use_data[:title] = ''
water_use_data[:header] = ['Instance', 'Plant Loop', 'Definition', 'Space', 'Peak Flow Rate', 'Flow Rate Schedule', 'Target Temp Range']
source_units = 'm^3/s'
source_units_temp = 'C'
if is_ip_units
target_units = 'gal/min'
n_decimals = 2
target_units_temp = 'F'
else
target_units = 'm^3/h'
n_decimals = 2
target_units_temp = 'C'
end
water_use_data[:units] = ['', '', '', '', target_units, '', target_units_temp]
water_use_data[:data] = []
# gather data for section
@water_use_data_section = {}
@water_use_data_section[:title] = 'Water Use Equipment'
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @water_use_data_section
end
# loop through water use equipment
water_use_equipment = model.getWaterUseEquipments
water_use_equipment.sort.each do |instance|
water_use_equipment_def = instance.waterUseEquipmentDefinition
if instance.waterUseConnections.is_initialized && instance.waterUseConnections.get.plantLoop.is_initialized
plant_loop = instance.waterUseConnections.get.plantLoop.get.name
else
plant_loop = ''
end
if instance.flowRateFractionSchedule.is_initialized
water_use_equipment_flow_rate_sch = instance.flowRateFractionSchedule.get.name
else
water_use_equipment_flow_rate_sch = ''
end
if instance.space.is_initialized
space = instance.space.get.name
else
space = ''
end
peak_flow_rate = water_use_equipment_def.peakFlowRate
peak_flow_rate_conv = OpenStudio.convert(peak_flow_rate, source_units, target_units).get
peak_flow_rate_neat = OpenStudio.toNeatString(peak_flow_rate_conv, n_decimals, true)
if water_use_equipment_def.targetTemperatureSchedule.is_initialized
target_temp_sch = water_use_equipment_def.targetTemperatureSchedule.get
schedule_values = OsLib_Schedules.getMinMaxAnnualProfileValue(model, target_temp_sch)
if !schedule_values.nil?
min_conv = OpenStudio.convert(schedule_values['min'], source_units_temp, target_units_temp).get
max_conv = OpenStudio.convert(schedule_values['max'], source_units_temp, target_units_temp).get
target_temp_range = "#{min_conv.round(1)} to #{max_conv.round(1)}"
else
target_temp_range = "can't inspect schedule."
end
else
target_temp_range = ''
end
water_use_data[:data] << [instance.name, plant_loop, water_use_equipment_def.name, space, peak_flow_rate_neat, water_use_equipment_flow_rate_sch, target_temp_range]
runner.registerValue(OsLib_Reporting.reg_val_string_prep(instance.name.to_s), peak_flow_rate_conv, target_units)
end
# don't create empty table
if !water_use_data[:data].empty?
@water_use_data_section[:tables] = [water_use_data] # only one table for this section
else
@water_use_data_section[:tables] = []
end
return @water_use_data_section
end
# create table for exterior lights
def self.exterior_light_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# Exterior Lighting from output
# gather data for section
@ext_light_data_section = {}
@ext_light_data_section[:title] = 'Exterior Lighting'
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @ext_light_data_section
end
# data for query
report_name = 'LightingSummary'
table_name = 'Exterior Lighting'
columns = ['Description', 'Total Power', 'Astronomical', 'Schedule Name', 'Annual Consumption']
columns_query = ['', 'Total Watts', 'Astronomical Clock/Schedule', 'Schedule Name', 'Consumption']
# populate dynamic rows
rows_name_query = "SELECT DISTINCT RowName FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}'"
row_names = sqlFile.execAndReturnVectorOfString(rows_name_query).get
rows = []
row_names.each do |row_name|
rows << row_name
end
# Zone-level Lighting Summary
table = {}
table[:title] = 'Exterior Lighting'
table[:header] = columns
table[:source_units] = ['', 'W', '', '', 'GJ'] # used for conversion, not needed for rendering.
table[:units] = ['', 'W', '', '', 'kWh']
table[:data] = []
# run query and populate table
rows.each do |row|
row_data = [row]
column_counter = -1
columns_query.each do |header|
column_counter += 1
next if header == ''
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{row}' and ColumnName= '#{header}'"
if table[:source_units][column_counter] != ''
results = sqlFile.execAndReturnFirstDouble(query)
row_data_ip = OpenStudio.convert(results.to_f, table[:source_units][column_counter], table[:units][column_counter]).get
row_data << row_data_ip.round(2)
else
results = sqlFile.execAndReturnFirstString(query)
row_data << results
end
end
table[:data] << row_data
end
# don't create empty table
if table[:data].size > 1 # did > 1 instead of > 0 because I thought empty one might still have one row
@ext_light_data_section[:tables] = [table] # only one table for this section
else
@ext_light_data_section[:tables] = []
end
return @ext_light_data_section
end
# create table for elevators
# TODO: - update this to be custom load with user supplied string (or strings)
def self.elevator_data_table(model, sqlFile, runner, name_only = false, is_ip_units = true)
# elevators from model
@elevator_data = {}
@elevator_data[:title] = 'Elevators'
@elevator_data[:header] = ['Instance', 'Definition', 'Thermal Zone', 'Power Per Elevator', 'Units', 'Count']
@elevator_data[:data] = []
elec_equip_instances = model.getElectricEquipments
elec_equip_instances.sort.each do |instance|
elec_equip_def = instance.electricEquipmentDefinition
# see if it is expected and valid object
next if elec_equip_def.name.to_s != 'ElevatorElecEquipDef'
unless instance.space.is_initialized
runner.registerWarning("#{instance.name} doesn't have a space.")
next
end
# get other data
elev_space = instance.space.get
elev_zone = elev_space.thermalZone.get # should check this
elev_power = elec_equip_def.designLevel.get # should check this
elev_power_neat = OpenStudio.toNeatString(elev_power, 0, true)
units = 'W'
count = instance.multiplier
@elevator_data[:data] << [instance.name.to_s, elec_equip_def.name, elev_zone.name.get, elev_power_neat, units, OpenStudio.toNeatString(count, 2, true)]
runner.registerValue(OsLib_Reporting.reg_val_string_prep(instance.name.to_s), elev_power, units)
end
return @elevator_data
end
# create table of space type details
def self.space_type_details_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
output_data_space_type_detail_tables = []
# gather data for section
@output_data_space_type_section = {}
@output_data_space_type_section[:title] = 'Space Type Summary'
@output_data_space_type_section[:tables] = output_data_space_type_detail_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @output_data_space_type_section
end
source_units_surf = 'm^2'
source_units_temp = 'C'
source_units_FlowRate = 'm^3/s'
source_units_flowperSpaceFloorArea = 'm/s'
if is_ip_units
target_units_surf = 'ft^2'
target_units_temp = 'F'
target_units_FlowRate = 'ft^3/min'
def_units_powerPerFloorArea = 'W/ft^2'
inst_units_FlowRate = 'cfm'
target_units_flowperSpaceFloorArea = 'ft/min'
inst_units_outdoorAirFlowperPerson = 'cfm/person'
inst_units_flowperSpaceFloorArea = 'cfm/ floor area ft^2'
inst_units_flowperExteriorSurfaceArea = 'cfm/ext surf area ft^2'
inst_units_flowperExteriorWallArea = 'cfm/ext wall area ft^2'
def_units_surfaceAreaPerFloorArea = 'ft^2/floor area ft^2'
def_units_surfaceAreaPerPerson = 'ft^2/person'
def_units_peoplePerFloorArea = 'people/ft^2'
else
target_units_surf = source_units_surf
target_units_temp = source_units_temp
target_units_FlowRate = 'm^3/h'
def_units_powerPerFloorArea = 'W/m^2'
inst_units_FlowRate = 'm^3/h'
target_units_flowperSpaceFloorArea = 'm/s'
inst_units_outdoorAirFlowperPerson = 'm^3/h/person'
inst_units_flowperSpaceFloorArea = 'm^3/h/ floor area m^2'
inst_units_flowperExteriorSurfaceArea = 'm^3/h/ext surf area m^2'
inst_units_flowperExteriorWallArea = 'm^3/h/ext wall area m^2'
def_units_surfaceAreaPerFloorArea = 'm^2/floor area m^2'
def_units_surfaceAreaPerPerson = 'm^2/person'
def_units_peoplePerFloorArea = 'people/m^2'
end
# loop through space types
model.getSpaceTypes.sort.each do |spaceType|
next if spaceType.floorArea == 0
# get floor area
floor_area_si = spaceType.floorArea
# create variable for number of people
num_people = nil
# gather list of spaces and zones in space type
zone_name_list = []
space_name_list = []
spaceType.spaces.each do |space|
# grabspace and zone names
space_name_list << space.name.to_s
if space.thermalZone.is_initialized
zone_name_list << space.thermalZone.get.name.to_s
end
end
# output_data_space_type_details[:data] << [space_name_list.uniq.join(","),space_name_list.uniq.size,"Spaces",""]
# output_data_space_type_details[:data] << [zone_name_list.uniq.join(","),zone_name_list.uniq.size,"Thermal Zones",""]
# space type details data output
output_data_space_type_details = {}
output_data_space_type_details[:title] = "#{spaceType.name}
(#{space_name_list.uniq.size} spaces and #{zone_name_list.uniq.size} thermal zones)"
output_data_space_type_details[:header] = ['Definition', 'Value', 'Unit', 'Inst. Multiplier']
output_data_space_type_details[:units] = [] # won't use this for these tables since units change
output_data_space_type_details[:data] = []
# data for space type details
instances = spaceType.internalMass
instances.each do |instance|
def_display = instance.definition.name
if instance.surfaceArea.is_initialized && instance.surfaceArea.get > 0
def_value = OpenStudio.convert(instance.surfaceArea.get, source_units_surf, target_units_surf).get
def_value_neat = OpenStudio.toNeatString(def_value, 0, true)
def_units = target_units_surf
elsif instance.surfaceAreaPerFloorArea.is_initialized && instance.surfaceAreaPerFloorArea.get > 0
def_value = instance.surfaceAreaPerFloorArea.get
def_value_neat = OpenStudio.toNeatString(def_value, 0, true)
def_units = def_units_surfaceAreaPerFloorArea
elsif instance.surfaceAreaPerPerson.is_initialized && instance.surfaceAreaPerPerson.get > 0
def_value = OpenStudio.convert(instance.surfaceAreaPerPerson.get, source_units_surf, target_units_surf).get
def_value_neat = OpenStudio.toNeatString(def_value, 0, true)
def_units = def_units_surfaceAreaPerPerson
end
count = instance.multiplier
output_data_space_type_details[:data] << [def_display, def_value_neat, def_units, count]
end
instances = spaceType.people
instances.each do |instance|
def_display = instance.definition.name
if instance.numberOfPeople.is_initialized && instance.numberOfPeople.get > 0
def_value = instance.numberOfPeople.get
def_value_neat = OpenStudio.toNeatString(def_value, 0, true)
def_units = 'people'
elsif instance.peoplePerFloorArea.is_initialized && instance.peoplePerFloorArea.get > 0
def_value = instance.peoplePerFloorArea.get / OpenStudio.convert(1, source_units_surf, target_units_surf).get
def_value_neat = OpenStudio.toNeatString(def_value, 4, true)
def_units = def_units_peoplePerFloorArea
elsif instance.spaceFloorAreaPerPerson.is_initialized && instance.spaceFloorAreaPerPerson.get > 0
def_value = OpenStudio.convert(instance.spaceFloorAreaPerPerson.get, source_units_surf, target_units_surf).get
def_value_neat = OpenStudio.toNeatString(def_value, 0, true)
def_units = def_units_surfaceAreaPerPerson
end
count = instance.multiplier
output_data_space_type_details[:data] << [def_display, def_value_neat, def_units, count]
end
instances = spaceType.electricEquipment
instances.each do |instance|
def_display = instance.definition.name
if instance.designLevel.is_initialized && instance.designLevel.get > 0
def_value = instance.designLevel.get
def_value_neat = OpenStudio.toNeatString(def_value, 0, true)
def_units = 'W'
elsif instance.powerPerFloorArea.is_initialized && instance.powerPerFloorArea.get > 0
def_value = instance.powerPerFloorArea.get / OpenStudio.convert(1, source_units_surf, target_units_surf).get
def_value_neat = OpenStudio.toNeatString(def_value, 4, true)
def_units = def_units_powerPerFloorArea
elsif instance.powerPerPerson .is_initialized && instance.powerPerPerson .get > 0
def_value = OpenStudio.convert(instance.powerPerPerson .get, source_units_surf, target_units_surf).get
def_value_neat = OpenStudio.toNeatString(def_value, 0, true)
def_units = 'W/person'
end
count = instance.multiplier
output_data_space_type_details[:data] << [def_display, def_value_neat, def_units, count]
end
instances = spaceType.gasEquipment
instances.each do |instance|
def_display = instance.definition.name
if instance.designLevel.is_initialized && instance.designLevel.get > 0
def_value = instance.designLevel.get
def_value_neat = OpenStudio.toNeatString(def_value, 0, true)
def_units = 'W'
elsif instance.powerPerFloorArea.is_initialized && instance.powerPerFloorArea.get > 0
def_value = instance.powerPerFloorArea.get / OpenStudio.convert(1, source_units_surf, target_units_surf).get
def_value_neat = OpenStudio.toNeatString(def_value, 4, true)
def_units = def_units_powerPerFloorArea
elsif instance.powerPerPerson .is_initialized && instance.powerPerPerson .get > 0
def_value = OpenStudio.convert(instance.powerPerPerson .get, source_units_surf, target_units_surf).get
def_value_neat = OpenStudio.toNeatString(def_value, 0, true)
def_units = 'W/person'
end
count = instance.multiplier
output_data_space_type_details[:data] << [def_display, def_value_neat, def_units, count]
end
instances = spaceType.lights
instances.each do |instance|
def_display = instance.definition.name
if instance.lightingLevel.is_initialized && instance.lightingLevel.get > 0
def_value = instance.lightingLevel.get
def_value_neat = OpenStudio.toNeatString(def_value, 0, true)
def_units = 'W'
elsif instance.powerPerFloorArea.is_initialized && instance.powerPerFloorArea.get > 0
def_value = instance.powerPerFloorArea.get / OpenStudio.convert(1, source_units_surf, target_units_surf).get
def_value_neat = OpenStudio.toNeatString(def_value, 4, true)
def_units = def_units_powerPerFloorArea
elsif instance.powerPerPerson .is_initialized && instance.powerPerPerson .get > 0
def_value = OpenStudio.convert(instance.powerPerPerson .get, source_units_surf, target_units_surf).get
def_value_neat = OpenStudio.toNeatString(def_value, 0, true)
def_units = 'W/person'
end
count = instance.multiplier
output_data_space_type_details[:data] << [def_display, def_value_neat, def_units, count]
end
instances = spaceType.spaceInfiltrationDesignFlowRates
instances.each do |instance|
instance_display = instance.name
if instance.designFlowRate.is_initialized
inst_value = OpenStudio.convert(instance.designFlowRate.get, source_units_FlowRate, target_units_FlowRate).get
inst_value_neat = OpenStudio.toNeatString(inst_value, 4, true)
inst_units = inst_units_FlowRate
count = ''
output_data_space_type_details[:data] << [instance_display, inst_value_neat, inst_units, count]
end
if instance.flowperSpaceFloorArea.is_initialized
inst_value = OpenStudio.convert(instance.flowperSpaceFloorArea.get, source_units_flowperSpaceFloorArea, target_units_flowperSpaceFloorArea).get
inst_value_neat = OpenStudio.toNeatString(inst_value, 4, true)
inst_units = inst_units_flowperSpaceFloorArea
count = ''
output_data_space_type_details[:data] << [instance_display, inst_value_neat, inst_units, count]
end
if instance.flowperExteriorSurfaceArea.is_initialized
inst_value = OpenStudio.convert(instance.flowperExteriorSurfaceArea.get, source_units_flowperSpaceFloorArea, target_units_flowperSpaceFloorArea).get
inst_value_neat = OpenStudio.toNeatString(inst_value, 4, true)
inst_units = inst_units_flowperExteriorSurfaceArea
count = ''
output_data_space_type_details[:data] << [instance_display, inst_value_neat, inst_units, count]
end
if instance.flowperExteriorWallArea.is_initialized # uses same input as exterior surface area but different calc method
inst_value = OpenStudio.convert(instance.flowperExteriorWallArea.get, source_units_flowperSpaceFloorArea, target_units_flowperSpaceFloorArea).get
inst_value_neat = OpenStudio.toNeatString(inst_value, 4, true)
inst_units = inst_units_flowperExteriorWallArea
count = ''
output_data_space_type_details[:data] << [instance_display, inst_value_neat, inst_units, count]
end
if instance.airChangesperHour.is_initialized
inst_value = instance.airChangesperHour.get
inst_value_neat = OpenStudio.toNeatString(inst_value, 4, true)
inst_units = 'ach'
count = ''
output_data_space_type_details[:data] << [instance_display, inst_value_neat, inst_units, count]
end
end
if spaceType.designSpecificationOutdoorAir.is_initialized
instance = spaceType.designSpecificationOutdoorAir.get
instance_display = instance.name
if instance.to_DesignSpecificationOutdoorAir.is_initialized
instance = instance.to_DesignSpecificationOutdoorAir.get
outdoor_air_method = instance.outdoorAirMethod
count = ''
# calculate and report various methods
if instance.outdoorAirFlowperPerson > 0
inst_value = OpenStudio.convert(instance.outdoorAirFlowperPerson, source_units_FlowRate, target_units_FlowRate).get
inst_value_neat = OpenStudio.toNeatString(inst_value, 4, true)
inst_units = inst_units_outdoorAirFlowperPerson
output_data_space_type_details[:data] << ["#{instance_display} (outdoor air method #{outdoor_air_method})", inst_value_neat, inst_units, count]
end
if instance.outdoorAirFlowperFloorArea > 0
inst_value = OpenStudio.convert(instance.outdoorAirFlowperFloorArea, source_units_flowperSpaceFloorArea, target_units_flowperSpaceFloorArea).get
inst_value_neat = OpenStudio.toNeatString(inst_value, 4, true)
inst_units = inst_units_flowperSpaceFloorArea
output_data_space_type_details[:data] << ["#{instance_display} (outdoor air method #{outdoor_air_method})", inst_value_neat, inst_units, count]
end
if instance.outdoorAirFlowRate > 0
inst_value = OpenStudio.convert(instance.outdoorAirFlowRate, source_units_FlowRate, target_units_FlowRate).get
inst_value_neat = OpenStudio.toNeatString(inst_value, 4, true)
inst_units = inst_units_FlowRate
output_data_space_type_details[:data] << ["#{instance_display} (outdoor air method #{outdoor_air_method})", inst_value_neat, inst_units, count]
end
if instance.outdoorAirFlowAirChangesperHour > 0
inst_value = instance.outdoorAirFlowAirChangesperHour
inst_value_neat = OpenStudio.toNeatString(inst_value, 4, true)
inst_units = 'ach'
output_data_space_type_details[:data] << ["#{instance_display} (outdoor air method #{outdoor_air_method})", inst_value_neat, inst_units, count]
end
end
end
# add table to array of tables
output_data_space_type_detail_tables << output_data_space_type_details
end
return @output_data_space_type_section
end
# create template section
def self.weather_summary_table(model, sqlFile, runner, is_ip_units)
# data for query
report_name = 'InputVerificationandResultsSummary'
table_name = 'General'
columns = ['', 'Value']
rows = ['Weather File', 'Latitude', 'Longitude', 'Elevation', 'Time Zone', 'North Axis Angle']
# create table
table = {}
table[:title] = 'Weather Summary'
table[:header] = columns
table[:units] = []
table[:data] = []
source_units = 'm'
if is_ip_units
target_units = 'ft'
else
target_units = 'm'
end
# run query and populate table
rows.each do |row|
row_data = [row]
column_counter = -1
table[:header].each do |header|
column_counter += 1
next if header == ''
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{row}' and ColumnName= '#{header}'"
results = sqlFile.execAndReturnFirstString(query) # this is first time I needed string vs. double for weather file
# TODO: - would be nice to get units from first column
if row == 'Elevation' then results = "#{OpenStudio.convert(results.get.to_f, source_units, target_units).get.round} #{target_units}" end
# if row == "Elevation" then results = "#{results.class} (f)" end
row_data << results
end
table[:data] << row_data
end
# add in climate zone from OpenStudio model
# get ashrae climate zone from model
climate_zone = ''
climateZones = model.getClimateZones
climateZones.climateZones.each do |climateZone|
if climateZone.institution == 'ASHRAE'
climate_zone = climateZone.value
next
end
end
table[:data] << ['ASHRAE Climate Zone', climate_zone]
return table
end
# create design_day_table
def self.design_day_table(model, sqlFile, runner, is_ip_units)
# data for query
report_name = 'ClimaticDataSummary'
table_name = 'SizingPeriod:DesignDay'
columns = ['', 'Maximum Dry Bulb', 'Daily Temperature Range', 'Humidity Value', 'Humidity Type', 'Wind Speed', 'Wind Direction']
# populate dynamic rows
rows_name_query = "SELECT DISTINCT RowName FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}'"
row_names = sqlFile.execAndReturnVectorOfString(rows_name_query).get
rows = []
row_names.each do |row_name|
rows << row_name
end
# create table
table = {}
table[:title] = 'Sizing Period Design Days'
table[:header] = columns
table[:source_units] = ['', 'C', 'K', '', '', 'm/s', '']
if is_ip_units
table[:units] = ['', 'F', 'R', '', '', 'mph', '']
else
table[:units] = table[:source_units]
end
table[:data] = []
# run query and populate table
rows.each do |row|
row_data = [row]
column_counter = -1
table[:header].each do |header|
column_counter += 1
next if header == ''
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{row.gsub("'", "''")}' and ColumnName= '#{header}'"
if header == 'Humidity Type'
results = sqlFile.execAndReturnFirstString(query).get
if is_ip_units
# TODO: <11-12-18, jmarrec> # That doesn't seem right to me
results = results.gsub('[C]', '[F]')
results = results.gsub('[J/kg]', '[Btu/lb]')
end
# any other types?
elsif header == 'Humidity Value'
results = sqlFile.execAndReturnFirstDouble(query).get
# get humidity units for conversion
query_humidity_type = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{row.gsub("'", "''")}' and ColumnName= 'Humidity Type'"
results_units = sqlFile.execAndReturnFirstString(query_humidity_type).get
if is_ip_units
if results_units.include?('[C]')
results = OpenStudio.convert(results, 'C', 'F').get.round(2)
elsif results_units.include?('[J/kg]')
results = OpenStudio.convert(results, 'J/kg', 'Btu/lb').get.round(2)
# any other types?
end
end
else
results_si = sqlFile.execAndReturnFirstString(query).get.to_s.delete(' ').to_f
if is_ip_units
results = OpenStudio.convert(results_si, table[:source_units][column_counter], table[:units][column_counter]).get.round(2)
else
results = results_si
end
end
row_data << results
end
table[:data] << row_data
end
return table
end
# create template section
def self.building_performance_table(model, sqlFile, runner, is_ip_units = true)
# create a second table
building_performance_table = {}
building_performance_table[:title] = 'Building Performance'
building_performance_table[:header] = ['Description', 'Value']
building_performance_table[:units] = []
building_performance_table[:data] = []
# add rows to table
# building_performance_table[:data] << ["Vanilla",1.5]
return building_performance_table
end
# create template section
def self.site_performance_table(model, sqlFile, runner, is_ip_units = true)
# create a second table
site_performance_table = {}
site_performance_table[:title] = 'Site Performance'
site_performance_table[:header] = ['Description', 'Value']
site_performance_table[:units] = []
site_performance_table[:data] = []
# add rows to table
# site_performance_table[:data] << ["Vanilla",1.5]
return site_performance_table
end
# create template section
def self.site_power_generation_table(model, sqlFile, runner, is_ip_units = true)
# create a second table
site_power_generation_table = {}
site_power_generation_table[:title] = 'Renewable Energy Source Summary'
site_power_generation_table[:header] = ['', 'Rated Capacity', 'Annual Energy Generated']
site_power_generation_table[:source_units] = ['', 'kW', 'GJ']
site_power_generation_table[:units] = ['', 'kW', 'kWh']
site_power_generation_table[:data] = []
# create string for LEED advisories
rows = []
rows << 'Photovoltaic'
rows << 'Wind'
# loop through advisory messages
value_found = false
rows.each do |row|
row_data = [row]
column_counter = -1
site_power_generation_table[:header].each do |header|
column_counter += 1
next if column_counter == 0
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='LEEDsummary' and RowName= '#{row}' and ColumnName='#{header}';"
data = sqlFile.execAndReturnFirstDouble(query).get
data_ip = OpenStudio.convert(data, site_power_generation_table[:source_units][column_counter], site_power_generation_table[:units][column_counter]).get
if data > 0 then value_found = true end
row_data << data_ip.round(2)
end
site_power_generation_table[:data] << row_data
end
if value_found
return site_power_generation_table
else
return false
end
end
# create template section
def self.monthly_overview_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
monthly_tables = []
# gather data for section
@monthly_overview_section = {}
@monthly_overview_section[:title] = 'Monthly Overview'
@monthly_overview_section[:tables] = monthly_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @monthly_overview_section
end
# check value of reg_monthly_details in measures
report_detailed = false
runner.workflow.workflowSteps.each do |step|
next if !step.to_MeasureStep.is_initialized
measure_step = step.to_MeasureStep.get
next if measure_step.result.is_initialized # don't look at upstream measures that have already run
arg_test = measure_step.getArgument('reg_monthly_details')
if arg_test.is_initialized
report_detailed = arg_test.get.valueAsBoolean
end
break # only want to check the first measure that doesn't have results yet
end
# alert user if report_detailed is requested
if report_detailed then runner.registerInfo('Monthly End Use by Fuel registerValues have been requested.') end
# end use colors by index
end_use_colors = ['#EF1C21', '#0071BD', '#F7DF10', '#DEC310', '#4A4D4A', '#B5B2B5', '#FF79AD', '#632C94', '#F75921', '#293094', '#CE5921', '#FFB239', '#29AAE7', '#8CC739']
# sorted end use array to pass in for stacked bar chart order
end_use_order = []
month_order = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
# loop through fuels for consumption tables
fuel_type_names.each do |fuel_type|
# get fuel type and units
if fuel_type == 'Electricity'
units = '"kWh"'
unit_str = 'kWh'
else
if is_ip_units
units = '"Million Btu"'
unit_str = 'MBtu'
else
units = '"kWh"'
unit_str = 'kWh'
end
end
# create table
monthly_fuel = {}
monthly_fuel[:title] = "#{fuel_type} Consumption (#{unit_str})"
monthly_fuel[:header] = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Total']
monthly_fuel[:units] = []
monthly_fuel[:data] = []
monthly_fuel[:chart_type] = 'vertical_stacked_bar'
monthly_fuel[:chart_attributes] = { value: monthly_fuel[:title], label_x: 'Month', sort_yaxis: end_use_order, sort_xaxis: month_order }
monthly_fuel[:chart] = []
# has to hold monthly totals for fuel
monthly_total = {}
# rest counter for each fuel type
site_energy_use = 0.0
fuel_type_aggregation = 0.0
# loop through end uses
OpenStudio::EndUseCategoryType.getValues.each do |category_type|
category_str = OpenStudio::EndUseCategoryType.new(category_type).valueDescription
end_use_order << category_str
row_data = [category_str]
fuel_and_category_aggregation = 0.0
OpenStudio::MonthOfYear.getValues.each do |month|
if month >= 1 && month <= 12
valInJ = nil
if fuel_type_names.include?(fuel_type)
if !sqlFile.energyConsumptionByMonth(OpenStudio::EndUseFuelType.new(fuel_type),
OpenStudio::EndUseCategoryType.new(category_type),
OpenStudio::MonthOfYear.new(month)).empty?
valInJ = sqlFile.energyConsumptionByMonth(OpenStudio::EndUseFuelType.new(fuel_type),
OpenStudio::EndUseCategoryType.new(category_type),
OpenStudio::MonthOfYear.new(month)).get
end
else
# manual sql query for additional fuel types
report_name = "BUILDING ENERGY PERFORMANCE - #{fuel_type.upcase}"
row_name = OpenStudio::MonthOfYear.new(month).valueDescription
column_name = "#{category_str.upcase}:#{fuel_type.upcase}"
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and ReportForString='Meter' and RowName= '#{row_name}' and ColumnName='#{column_name}' and Units='J';"
if !sqlFile.execAndReturnFirstDouble(query).empty?
valInJ = sqlFile.execAndReturnFirstDouble(query).get
end
end
if !valInJ.nil?
fuel_and_category_aggregation += valInJ
valInUnits = OpenStudio.convert(valInJ, 'J', unit_str).get
# report values for monthly tables
if report_detailed
# return just first three characters of month
month_str = OpenStudio::MonthOfYear.new(month).valueDescription[0..2]
# this specific string chosen to match design case for a specific project
prefix_str = OpenStudio.toUnderscoreCase("end_use_#{fuel_type}_#{category_str}_#{month_str}")
runner.registerValue(prefix_str.downcase.gsub(' ', '_'), valInUnits, unit_str)
end
# populate hash for monthly totals
month = monthly_fuel[:header][month]
if monthly_total[month]
monthly_total[month] += valInJ
else
monthly_total[month] = valInJ
end
monthly_fuel[:chart] << JSON.generate(label: category_str, label_x: month, value: valInUnits, color: end_use_colors[category_type])
# for some reason sometimes 0 comes through here, show as blank of 0
if valInUnits > 0
row_data << valInUnits.round(2)
else
row_data << ''
end
else
row_data << ''
# populate hash for monthly totals
month = monthly_fuel[:header][month]
if monthly_total[month]
# do nothing
else
monthly_total[month] = 0.0
end
end
end
end
prefix_str = OpenStudio.toUnderscoreCase("#{fuel_type}_#{category_str}")
runner.registerValue("#{prefix_str}_ip", OpenStudio.convert(fuel_and_category_aggregation, 'J', unit_str).get, unit_str)
fuel_type_aggregation += fuel_and_category_aggregation
row_total = OpenStudio.convert(fuel_and_category_aggregation, 'J', unit_str).get
if row_total == 0
row_data << ''
else
row_data << row_total.round(2)
end
monthly_fuel[:data] << row_data
end
runner.registerValue(OpenStudio.toUnderscoreCase("#{fuel_type}_ip"),
OpenStudio.convert(fuel_type_aggregation, 'J', unit_str).get,
unit_str)
site_energy_use += fuel_type_aggregation
# add row for totals
row_data = ['Total']
monthly_total.each do |k, v|
if OpenStudio.convert(v, 'J', unit_str).get == 0
row_data << ''
else
row_data << OpenStudio.convert(v, 'J', unit_str).get.round(2)
end
# add monthly consumption by fuel from table to runner.registerValues
if report_detailed
# return jsut first three characters of month
month_str = k[0..2]
prefix_str = OpenStudio.toUnderscoreCase("#{fuel_type}_ip_#{month_str}")
runner.registerValue(prefix_str.downcase.gsub(' ', '_'), OpenStudio.convert(v, 'J', unit_str).get, unit_str)
end
end
table_total = OpenStudio.convert(site_energy_use, 'J', unit_str).get
row_data << table_total.round(2)
monthly_fuel[:data] << row_data
# add table to array of tables if table total is > 0
if table_total > 0
monthly_tables << monthly_fuel
end
end
# loop through fuels for peak demand tables
fuel_type_names.each do |fuel_type|
# get fuel type and units
if fuel_type == 'Electricity'
unit_str = 'kW'
else
if is_ip_units
unit_str = 'kBtu/hr' # TODO: - update units ?
else
unit_str = 'W'
end
end
# create table
monthly_fuel = {}
monthly_fuel[:title] = "#{fuel_type} Peak Demand (#{unit_str})"
monthly_fuel[:header] = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
monthly_fuel[:units] = []
monthly_fuel[:data] = []
monthly_fuel[:chart_type] = 'vertical_stacked_bar'
monthly_fuel[:chart_attributes] = { value: monthly_fuel[:title], label_x: 'Month', sort_yaxis: end_use_order, sort_xaxis: month_order }
monthly_fuel[:chart] = []
# has to hold monthly totals for fuel
monthly_total = {}
# test for non 0 value in table
value_found = false
# loop through end uses
OpenStudio::EndUseCategoryType.getValues.each do |category_type|
category_str = OpenStudio::EndUseCategoryType.new(category_type).valueDescription
row_data = [category_str]
OpenStudio::MonthOfYear.getValues.each do |month|
if month >= 1 && month <= 12
if !sqlFile.peakEnergyDemandByMonth(OpenStudio::EndUseFuelType.new(fuel_type),
OpenStudio::EndUseCategoryType.new(category_type),
OpenStudio::MonthOfYear.new(month)).empty?
valInJ = sqlFile.peakEnergyDemandByMonth(OpenStudio::EndUseFuelType.new(fuel_type),
OpenStudio::EndUseCategoryType.new(category_type),
OpenStudio::MonthOfYear.new(month)).get
valInUnits = OpenStudio.convert(valInJ, 'W', unit_str).get
# populate hash for monthly totals
month = monthly_fuel[:header][month]
if monthly_total[month]
monthly_total[month] += valInJ
else
monthly_total[month] = valInJ
end
monthly_fuel[:chart] << JSON.generate(label: category_str, label_x: month, value: valInUnits, color: end_use_colors[category_type])
# for some reason sometimes 0 comes through here, show as blank of 0
if valInUnits > 0
row_data << valInUnits.round(4)
value_found = true
else
row_data << ''
end
else
row_data << ''
month = monthly_fuel[:header][month]
if monthly_total[month]
# do nothing
else
monthly_total[month] = 0.0
end
end
end
end
monthly_fuel[:data] << row_data
end
# add row for totals
row_data = ['Total']
monthly_total.each do |k, v|
if OpenStudio.convert(v, 'W', unit_str).get == 0
row_data << ''
else
row_data << OpenStudio.convert(v, 'W', unit_str).get.round(2)
end
end
monthly_fuel[:data] << row_data
# add table if value found
if value_found
monthly_tables << monthly_fuel
end
end
return @monthly_overview_section
end
# create utility section
def self.utility_bills_rates_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
utility_bills_rates_tables = []
# gather data for section
@utility_bills_rates_section = {}
@utility_bills_rates_section[:title] = 'Utility Bills/Rates'
@utility_bills_rates_section[:tables] = utility_bills_rates_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @utility_bills_rates_section
end
# create table
utility_table = {}
utility_table[:title] = 'Energy Type Summary'
utility_table[:header] = ['', 'Utility Rate', 'Average Rate', 'Units of Energy', 'Units of Demand']
utility_table[:query_column] = ['', 'Utility Rate', 'Virtual Rate', 'Units of Energy', 'Units of Demand']
utility_table[:units] = ['', '', '$/unit energy', '', '']
utility_table[:data] = []
# populate dynamic rows
rows_name_query = "SELECT DISTINCT RowName FROM tabulardatawithstrings WHERE ReportName='LEEDsummary' and TableName='EAp2-3. Energy Type Summary'"
row_names = sqlFile.execAndReturnVectorOfString(rows_name_query).get
rows = []
row_names.each do |row_name|
next if row_name == 'Other' # other currently mixes water and dist htg/clg. Don't want to show that
rows << row_name
end
# loop through rows
value_found = false
rows.each do |row|
data = [row]
utility_table[:query_column].each do |header|
next if header == ''
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='LEEDsummary' and TableName='EAp2-3. Energy Type Summary' and RowName= '#{row}' and ColumnName='#{header}';"
data << sqlFile.execAndReturnFirstString(query).get
if sqlFile.execAndReturnFirstString(query).get.to_f > 0
value_found = true
end
end
utility_table[:data] << data
end
# add table to array of tables
if value_found
utility_bills_rates_tables << utility_table
end
# create table
energy_cost_table = {}
energy_cost_table[:title] = 'Energy Cost Summary'
energy_cost_table[:header] = ['', 'Total Energy Cost'] # skipping Process Subtotal
energy_cost_table[:units] = ['', '$'] # skipping Process Subtotal
energy_cost_table[:data] = []
# populate dynamic rows
rows_name_query = "SELECT DISTINCT RowName FROM tabulardatawithstrings WHERE ReportName='LEEDsummary' and TableName='EAp2-7. Energy Cost Summary'"
row_names = sqlFile.execAndReturnVectorOfString(rows_name_query).get
rows = []
row_names.each do |row_name|
rows << row_name
end
# loop through rows
value_found = false
rows.each do |row|
data = [row]
energy_cost_table[:header].each do |header|
next if header == ''
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='LEEDsummary' and tableName = 'EAp2-7. Energy Cost Summary' and RowName= '#{row}' and ColumnName='#{header}';"
data << sqlFile.execAndReturnFirstDouble(query).get
if sqlFile.execAndReturnFirstDouble(query).get > 0
value_found = true
end
end
energy_cost_table[:data] << data
end
# add table to array of tables
if value_found
utility_bills_rates_tables << energy_cost_table
end
return @utility_bills_rates_section
end
# create unmet hours
def self.zone_condition_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
zone_condition_tables = []
# gather data for section
@zone_condition_section = {}
@zone_condition_section[:title] = 'Zone Conditions'
@zone_condition_section[:tables] = zone_condition_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @zone_condition_section
end
temperature_bins_temps_ip = [56, 61, 66, 68, 70, 72, 74, 76, 78, 83, 88]
temperature_bins_temps_si = [13, 16, 18, 20, 21, 22, 23, 24, 26, 28, 30]
# temperature_bins_temps_ip.each do |i|
# temperature_bins_temps_si << OpenStudio.convert(i, 'F', 'C').get.round(2)
# end
# hash to store hours
temperature_bins = {}
if is_ip_units
for i in 0..(temperature_bins_temps_ip.size - 1)
if i == 0
temperature_bins["< #{temperature_bins_temps_ip[i]}"] = 0
else
temperature_bins["#{temperature_bins_temps_ip[i - 1]}-#{temperature_bins_temps_ip[i]}"] = 0
end
end
# catchall bin for values over the top
temperature_bins[">= #{temperature_bins_temps_ip.last}"] = 0
else
for i in 0..(temperature_bins_temps_si.size - 1)
if i == 0
temperature_bins["< #{temperature_bins_temps_si[i]}"] = 0
else
temperature_bins["#{temperature_bins_temps_si[i - 1]}-#{temperature_bins_temps_si[i]}"] = 0
end
end
# catchall bin for values over the top
temperature_bins[">= #{temperature_bins_temps_si.last}"] = 0
end
# create table
temperature_table = {}
temperature_table[:title] = 'Temperature (Table values represent hours spent in each temperature range)'
temperature_table[:header] = ['Zone', 'Unmet Htg', 'Unmet Htg - Occ']
temperature_bins.each do |k, v|
temperature_table[:header] << k
end
temperature_table[:header] += ['Unmet Clg', 'Unmet Clg - Occ', 'Mean Temp']
temperature_table[:units] = ['', 'hr', 'hr']
temperature_bins.each do |k, v|
if is_ip_units
temperature_table[:units] << 'F'
else
temperature_table[:units] << 'C'
end
end
if is_ip_units
temperature_table[:units] += ['hr', 'hr', 'F']
else
temperature_table[:units] += ['hr', 'hr', 'C']
end
temperature_table[:data] = []
temperature_table[:data_color] = []
# get time series data for each zone
ann_env_pd = OsLib_Reporting.ann_env_pd(sqlFile)
if ann_env_pd
# get keys
keys = sqlFile.availableKeyValues(ann_env_pd, 'Hourly', 'Zone Air Temperature')
keys.each do |key|
# reset bin values
temperature_bins.each do |k, v|
temperature_bins[k] = 0
end
# get desired variable
output_timeseries = sqlFile.timeSeries(ann_env_pd, 'Hourly', 'Zone Air Temperature', key)
# loop through timeseries and move the data from an OpenStudio timeseries to a normal Ruby array (vector)
if output_timeseries.is_initialized # checks to see if time_series exists
output_timeseries = output_timeseries.get.values
temp_counter = 0
temp_sum = 0
for i in 0..(output_timeseries.size - 1)
# add to counter and sum
temp_counter += 1
temp_sum += output_timeseries[i]
found_bin = false
for j in 0..(temperature_bins_temps_si.size - 1)
if found_bin == false && output_timeseries[i] < temperature_bins_temps_si[j]
temperature_bins[temperature_bins.keys[j]] += 1
found_bin = true
end
end
# add to top if larger than all other hash values
if !found_bin
temperature_bins[temperature_bins.keys.last] += 1
end
end # end of for i in 0..(output_timeseries.size - 1)
else
runner.registerWarning("Didn't find data for Zone Air Temperature") # not getting triggered when variable missing
end # end of if output_timeseries.is_initialized
# get unmet hours for each zone from tabular data
query_htg = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='SystemSummary' and TableName = 'Time Setpoint Not Met' and RowName= '#{key}' and ColumnName='During Heating';"
unmet_htg = sqlFile.execAndReturnFirstDouble(query_htg).get
query_clg = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='SystemSummary' and TableName = 'Time Setpoint Not Met' and RowName= '#{key}' and ColumnName='During Cooling';"
unmet_clg = sqlFile.execAndReturnFirstDouble(query_clg).get
query_htg_occ = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='SystemSummary' and TableName = 'Time Setpoint Not Met' and RowName= '#{key}' and ColumnName='During Occupied Heating';"
unmet_htg_occ = sqlFile.execAndReturnFirstDouble(query_htg_occ).get
query_clg_occ = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='SystemSummary' and TableName = 'Time Setpoint Not Met' and RowName= '#{key}' and ColumnName='During Occupied Cooling';"
unmet_clg_occ = sqlFile.execAndReturnFirstDouble(query_clg_occ).get
# get mean temp
if is_ip_units
mean = OpenStudio.convert(temp_sum / temp_counter.to_f, 'C', 'F').get
else
mean = temp_sum / temp_counter.to_f
end
# add rows to table
row_data = [key, unmet_htg.round, unmet_htg_occ.round]
row_color = ['', '', '']
temperature_bins.each do |k, v|
row_data << v
if v > 2000
row_color << 'indianred'
elsif v > 1000
row_color << 'orange'
elsif v > 500
row_color << 'yellow'
else
row_color << ''
end
end
if is_ip_units
row_data += [unmet_clg.round, unmet_clg_occ.round, "#{mean.round(1)} (F)"]
else
row_data += [unmet_clg.round, unmet_clg_occ.round, "#{mean.round(1)} (C)"]
end
row_color += ['', '', '']
temperature_table[:data] << row_data
temperature_table[:data_color] << row_color
end
else
runner.registerWarning('An annual simulation was not run. Cannot get annual timeseries data')
return false
end
# add table to array of tables
zone_condition_tables << temperature_table
humidity_bins_ip = [30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80]
humidity_bins_si = humidity_bins_ip
# hash to store hours
humidity_bins = {}
for i in 0..(humidity_bins_ip.size - 1)
if i == 0
humidity_bins["< #{humidity_bins_ip[i]}"] = 0
else
humidity_bins["#{humidity_bins_ip[i - 1]}-#{humidity_bins_ip[i]}"] = 0
end
end
# add catch all bin at top
humidity_bins[">= #{humidity_bins_ip.last}"] = 0
# create table
humidity_table = {}
humidity_table[:title] = 'Humidity (Table values represent hours spent in each Humidity range)'
humidity_table[:header] = ['Zone']
humidity_bins.each do |k, v|
humidity_table[:header] << k
end
humidity_table[:header] += ['Mean Relative Humidity']
humidity_table[:units] = ['']
humidity_bins.each do |k, v|
humidity_table[:units] << '%'
end
humidity_table[:units] += ['%']
humidity_table[:data] = []
humidity_table[:data_color] = []
# get time series data for each zone
ann_env_pd = OsLib_Reporting.ann_env_pd(sqlFile)
if ann_env_pd
# store values about humidity fir reguster values
zone_max_hours_over_70_rh = 0
zone_max_hours_over_55_rh = 0
rh_hours_threshold = 10 # hr
num_zones_x_hours_over_70 = 0
num_zones_x_hours_over_55 = 0
# get keys
keys = sqlFile.availableKeyValues(ann_env_pd, 'Hourly', 'Zone Air Relative Humidity')
keys.each do |key|
# reset bin values
humidity_bins.each do |k, v|
humidity_bins[k] = 0
end
# reset humidity zone flag
zone_rh_count_hr_55 = 0.0
zone_rh_count_hr_70 = 0.0
# get desired variable
output_timeseries = sqlFile.timeSeries(ann_env_pd, 'Hourly', 'Zone Air Relative Humidity', key)
# loop through timeseries and move the data from an OpenStudio timeseries to a normal Ruby array (vector)
if output_timeseries.is_initialized # checks to see if time_series exists
output_timeseries = output_timeseries.get.values
humidity_counter = 0
humidity_sum = 0
for i in 0..(output_timeseries.size - 1)
# add to counter and sum
humidity_counter += 1
humidity_sum += output_timeseries[i]
found_bin = false
for j in 0..(humidity_bins_si.size - 1)
if found_bin == false && output_timeseries[i] < humidity_bins_si[j]
humidity_bins[humidity_bins.keys[j]] += 1
found_bin = true
end
end
# add to top if larger than all other hash values
if !found_bin
humidity_bins[humidity_bins.keys.last] += 1
end
end # end of for i in 0..(output_timeseries.size - 1)
else
runner.registerWarning("Didn't find data for Zone Air Relative Humidity") # not getting triggered when variable missing
end # end of if output_timeseries.is_initialized
# get mean humidity
mean = humidity_sum / humidity_counter.to_f
# add rows to table
row_data = [key]
row_color = ['']
humidity_bins.each do |k, v|
row_data << v
if v > 2000
row_color << 'indianred'
elsif v > 1000
row_color << 'orange'
elsif v > 500
row_color << 'yellow'
else
row_color << ''
end
# populate rh data for register_values
# catch greater than 70 and 80 for runner.registerValue
if ['55-60', '60-65', '65-70', '70-75', '75-80', '>= 80'].include?(k)
zone_rh_count_hr_55 += v
end
if ['70-75', '75-80', '>= 80'].include?(k)
zone_rh_count_hr_70 += v
end
end
row_data += ["#{mean.round(1)} (%)"]
row_color += ['']
humidity_table[:data] << row_data
humidity_table[:data_color] << row_color
# apply rh zones and max hours
if zone_rh_count_hr_55 >= rh_hours_threshold then num_zones_x_hours_over_55 += 1 end
if zone_rh_count_hr_70 >= rh_hours_threshold then num_zones_x_hours_over_70 += 1 end
if zone_max_hours_over_55_rh < zone_rh_count_hr_55 then zone_max_hours_over_55_rh = zone_rh_count_hr_55 end
if zone_max_hours_over_70_rh < zone_rh_count_hr_70 then zone_max_hours_over_70_rh = zone_rh_count_hr_70 end
# add rh runner.registerValues to be used as output in analyses
runner.registerValue('zone_max_hours_over_70_rh', zone_max_hours_over_70_rh, 'hr')
runner.registerValue('zone_max_hours_over_55_rh', zone_max_hours_over_55_rh, 'hr')
runner.registerValue('num_zones_x_hours_over_70', num_zones_x_hours_over_70, 'zones')
runner.registerValue('num_zones_x_hours_over_55', num_zones_x_hours_over_55, 'zones')
end
else
runner.registerWarning('An annual simulation was not run. Cannot get annual timeseries data')
return false
end
# add table to array of tables
zone_condition_tables << humidity_table
return @zone_condition_section
end
# create interior_lighting section
def self.interior_lighting_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
interior_lighting_tables = []
# gather data for section
@interior_lighting_section = {}
@interior_lighting_section[:title] = 'Interior Lighting Summary'
@interior_lighting_section[:tables] = interior_lighting_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @interior_lighting_section
end
# data for query
report_name = 'LightingSummary'
table_name = 'Interior Lighting'
columns = ['Lights ', 'Zone', 'Lighting Power Density', 'Total Power', 'Schedule Name', 'Scheduled Hours/Week', 'Actual Load Hours/Week', 'Return Air Fraction', 'Annual Consumption']
columns_query = ['', 'Zone', 'Lighting Power Density', 'Total Power', 'Schedule Name', 'Scheduled Hours/Week', 'Full Load Hours/Week', 'Return Air Fraction', 'Consumption']
# populate dynamic rows
rows_name_query = "SELECT DISTINCT RowName FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}'"
row_names = sqlFile.execAndReturnVectorOfString(rows_name_query).get
rows = []
row_names.each do |row_name|
next if row_name == 'Interior Lighting Total' # skipping this on purpose, may give odd results in some instances
rows << row_name
end
# Zone-level Lighting Summary
table = {}
table[:title] = 'Zone Lighting'
table[:header] = columns
source_units_area = 'm^2'
source_units_lpd = 'W/m^2'
source_units_energy = 'GJ'
if is_ip_units
target_units_area = 'ft^2'
target_units_lpd = 'W/ft^2'
target_units_energy = 'kWh'
else
target_units_area = 'm^2'
target_units_lpd = 'W/m^2'
target_units_energy = 'kWh'
end
table[:source_units] = ['', '', source_units_lpd, 'W', '', 'hr', 'hr', '', source_units_energy] # used for conversion, not needed for rendering.
table[:units] = ['', '', target_units_lpd, 'W', '', 'hr', 'hr', '', target_units_energy]
table[:data] = []
# run query and populate table
rows.each do |row|
row_data = [row]
column_counter = -1
columns_query.each do |header|
column_counter += 1
next if header == ''
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{row}' and ColumnName= '#{header}'"
if table[:source_units][column_counter] != ''
results = sqlFile.execAndReturnFirstDouble(query)
row_data_ip = OpenStudio.convert(results.to_f, table[:source_units][column_counter], table[:units][column_counter]).get
row_data << row_data_ip.round(2)
else
results = sqlFile.execAndReturnFirstString(query)
row_data << results
end
end
table[:data] << row_data
end
# Space-level lighting loads table(s)
space_lighting_table = {}
space_lighting_table[:title] = 'Space Lighting Details'
space_lighting_table[:header] = ['Load Name', 'Definition Name', 'Load Type', 'Load (units)', 'Multiplier', 'Total Load (W)']
space_lighting_table[:data] = []
spaces = model.getSpaces
spaces.each do |space|
table_row = []
area = OpenStudio.convert(space.floorArea, source_units_area, target_units_area).get
lp = OpenStudio.convert(space.lightingPowerPerFloorArea, source_units_lpd, target_units_lpd).get
space_lighting_table[:data] << [{ sub_header: "Space Name: '#{space.name}', Area: #{area.round(0)} #{target_units_area},
Total LPD: #{lp.round(2)} #{target_units_lpd}" }, '', '', '', '', '']
lights_found = 0
if space.spaceType.is_initialized
space.spaceType.get.lights.each do |lights_object|
tlp = ''
def_name = lights_object.lightsDefinition.name
lights_found += 1
if lights_object.lightsDefinition.designLevelCalculationMethod == 'LightingLevel'
val = "#{lights_object.lightsDefinition.lightingLevel.to_f.round(0)} (W)"
tlp = lights_object.lightsDefinition.lightingLevel.to_f * lights_object.multiplier
end
if lights_object.lightsDefinition.designLevelCalculationMethod == 'Watts/Area'
val_conv = OpenStudio.convert(lights_object.lightsDefinition.wattsperSpaceFloorArea.to_f, source_units_lpd, target_units_lpd).get
val = "#{val_conv.to_f.round(2)} (#{target_units_lpd})"
tlp = (lights_object.lightsDefinition.wattsperSpaceFloorArea.to_f * space.floorArea) * lights_object.multiplier
end
if lights_object.lightsDefinition.designLevelCalculationMethod == 'Watts/Person'
val = "#{lights_object.lightsDefinition.wattsperPerson.to_f.round(2)} (W/Person)"
tlp = (lights_object.lightsDefinition.wattsperPerson.to_f * space.numberOfPeople) * lights_object.multiplier
end
space_lighting_table[:data] << [lights_object.name.to_s, def_name, 'Spacetype', val, lights_object.multiplier.round(0), tlp.round(0)]
end
end
space.lights.each do |sl|
tlp = ''
def_name = sl.lightsDefinition.name
lights_found += 1
if sl.lightsDefinition.designLevelCalculationMethod == 'LightingLevel'
val = "#{sl.lightsDefinition.lightingLevel.to_f.round(0)} (W)"
tlp = sl.lightsDefinition.lightingLevel.to_f * sl.multiplier
end
if sl.lightsDefinition.designLevelCalculationMethod == 'Watts/Area'
val_conv = OpenStudio.convert(sl.lightsDefinition.wattsperSpaceFloorArea.to_f, source_units_lpd, target_units_lpd).get
val = "#{val_conv.to_f.round(2)} (#{target_units_lpd})"
tlp = (sl.lightsDefinition.wattsperSpaceFloorArea.to_f * space.floorArea) * sl.multiplier
end
if sl.lightsDefinition.designLevelCalculationMethod == 'Watts/Person'
val = "#{sl.lightsDefinition.wattsperPerson.to_f.round(2)} (W/Person)"
tlp = (sl.lightsDefinition.wattsperPerson.to_f * space.numberOfPeople) * sl.multiplier
end
space_lighting_table[:data] << [sl.name.to_s, def_name, 'Space', val, sl.multiplier.round(0), tlp.round(0)]
end
space_lighting_table[:data] << ['-', '-', '-', '-', '-', '-'] if lights_found == 0
end
# Lighting Controls
source_units_illuminance = 'lux'
if is_ip_units
target_units_illuminance = 'fc'
else
target_units_illuminance = source_units_illuminance
end
lighting_controls_table = {}
lighting_controls_table[:title] = 'Lighting Controls Details'
lighting_controls_table[:header] = ['Space Name', 'Control Name', 'Zone Controlled (type, fraction)', "Illuminance Setpoint (#{target_units_illuminance})"]
lighting_controls_table[:data] = []
model.getSpaces.sort.each do |space|
thermal_zone = space.thermalZone.get
zone_control = 'n/a'
space.daylightingControls.each do |dc|
if thermal_zone.primaryDaylightingControl.is_initialized && dc.isPrimaryDaylightingControl
zone_control = "#{thermal_zone.name} (primary, #{thermal_zone.fractionofZoneControlledbyPrimaryDaylightingControl.round(1)})"
end
if thermal_zone.secondaryDaylightingControl.is_initialized && dc.isSecondaryDaylightingControl
zone_control = "#{thermal_zone.name} (secondary, #{thermal_zone.fractionofZoneControlledbySecondaryDaylightingControl.round(1)})"
end
illuminance_conv = OpenStudio.convert(dc.illuminanceSetpoint, source_units_illuminance, target_units_illuminance).get
lighting_controls_table[:data] << [space.name, dc.name, zone_control, illuminance_conv.round(0)]
end
end
# add tables to report
interior_lighting_tables << table
interior_lighting_tables << space_lighting_table
interior_lighting_tables << lighting_controls_table
return @interior_lighting_section
end
# create plug_loads section
def self.plug_loads_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
plug_loads_tables = []
# gather data for section
@plug_loads_section = {}
@plug_loads_section[:title] = 'Plug Loads Summary'
@plug_loads_section[:tables] = plug_loads_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @plug_loads_section
end
# data for query
report_name = 'EnergyMeters'
table_name = 'Annual and Peak Values - Electricity'
columns = ['', 'Electricity Annual Value'] # TODO: - would be nice to make this more like lighting summary
# populate dynamic rows
rows_name_query = "SELECT DISTINCT RowName FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}'"
row_names = sqlFile.execAndReturnVectorOfString(rows_name_query).get
rows = []
row_names.each do |row_name|
next unless row_name.include?('InteriorEquipment:Electricity:Zone:')
rows << row_name
end
# create table
table = {}
table[:title] = 'Electric Plug Load Consumption'
table[:header] = columns
source_units_energy = 'GJ'
target_units_energy = 'kWh'
table[:source_units] = ['', source_units_energy] # used for conversation, not needed for rendering.
table[:units] = ['', target_units_energy]
table[:data] = []
# run query and populate table
rows.each do |row|
row_data = [row]
column_counter = -1
table[:header].each do |header|
column_counter += 1
next if header == ''
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{row}' and ColumnName= '#{header}'"
results = sqlFile.execAndReturnFirstDouble(query)
row_data_ip = OpenStudio.convert(results.to_f, table[:source_units][column_counter], table[:units][column_counter]).get
row_data << row_data_ip.round(2)
end
table[:data] << row_data
end
# add table to array of tables
if !table[:data].empty? then plug_loads_tables << table end
# space-level electric plug loads inputs table
table = {}
table[:title] = 'Space-level Electric Plug Loads'
table[:header] = ['Equipment Name', 'Definition', 'Load (units)', 'Inheritance Level', 'Multiplier', 'Total Load (W)']
table[:data] = []
model.getSpaces.sort.each do |space|
space_elec_equip = {}
# check for equipment from both inheritance paths
space_elec_equip['spacetype'] = space.spaceType.is_initialized ? space.spaceType.get.electricEquipment : []
space_elec_equip['space'] = space.electricEquipment
# space subheading if any equipment found
if is_ip_units
area = space.floorArea * 10.7639
table[:data] << [{ sub_header: "Space Name: #{space.name}, Area: #{area.round(0)} ft^2" }, '', '', '', '', ''] if !space_elec_equip['spacetype'].empty? || !space_elec_equip['space'].empty?
else
area = space.floorArea
table[:data] << [{ sub_header: "Space Name: #{space.name}, Area: #{area.round(0)} m^2" }, '', '', '', '', ''] if !space_elec_equip['spacetype'].empty? || !space_elec_equip['space'].empty?
end
# spacetype equipment
space_elec_equip.each do |inheritance_level, elec_equip_array|
elec_equip_array.each do |elec_equip|
if elec_equip.electricEquipmentDefinition.designLevelCalculationMethod == 'Watts/Area'
if is_ip_units
ee_power = elec_equip.electricEquipmentDefinition.wattsperSpaceFloorArea.to_f * 0.092903 # IP
ee_power = "#{ee_power.round(2)} (W/ft^2)"
ee_total_power = ((elec_equip.powerPerFloorArea.to_f * space.floorArea))
else
ee_power = elec_equip.electricEquipmentDefinition.wattsperSpaceFloorArea.to_f
ee_power = "#{ee_power.round(2)} (W/m^2)"
ee_total_power = ((elec_equip.powerPerFloorArea.to_f * space.floorArea))
end
end
if elec_equip.electricEquipmentDefinition.designLevelCalculationMethod == 'Watts/Person'
ee_power = "#{elec_equip.electricEquipmentDefinition.wattsperPerson .to_f.round(2)} (W/person)"
ee_total_power = (elec_equip.powerPerPerson.to_f * space.numberOfPeople)
end
if elec_equip.electricEquipmentDefinition.designLevelCalculationMethod == 'EquipmentLevel'
ee_power = "#{elec_equip.electricEquipmentDefinition.designLevel.to_f.round(0)} (W)"
ee_total_power = elec_equip.designLevel.to_f.round(0)
end
table[:data] << [elec_equip.name, elec_equip.electricEquipmentDefinition.name, ee_power, inheritance_level, elec_equip.multiplier.round(1), ee_total_power.round(0)]
end
end
end # space-level elec plug loads table
# if data, add table to report
plug_loads_tables << table if !table[:data].empty?
# space-level gas plug loads inputs table
table = {}
table[:title] = 'Space-level Gas Plug Loads'
if is_ip_units
table[:header] = ['Equipment Name', 'Definition', 'Load (units)', 'Inheritance Level', 'Multiplier', 'Total Load (BTU/hr)']
else
table[:header] = ['Equipment Name', 'Definition', 'Load (units)', 'Inheritance Level', 'Multiplier', 'Total Load (W)']
end
table[:data] = []
model.getSpaces.sort.each do |space|
space_gas_equip = {}
# check for equipment from both inheritance paths
space_gas_equip['spacetype'] = space.spaceType.is_initialized ? space.spaceType.get.gasEquipment : []
space_gas_equip['space'] = space.gasEquipment
# space subheading if any equipment found
if is_ip_units
area = space.floorArea.to_f * 10.7639 # --> IP
else
area = space.floorArea.to_f
end
if is_ip_units
table[:data] << [{ sub_header: "Space Name: #{space.name}, Area: #{area.round(0)} ft^2" }, '', '', '', '', ''] if !space_gas_equip['spacetype'].empty? || !space_gas_equip['space'].empty?
else
table[:data] << [{ sub_header: "Space Name: #{space.name}, Area: #{area.round(0)} m^2" }, '', '', '', '', ''] if !space_gas_equip['spacetype'].empty? || !space_gas_equip['space'].empty?
end
# spacetype equipment
space_gas_equip.each do |inheritance_level, gas_equip_array|
gas_equip_array.each do |gas_equip|
if gas_equip.gasEquipmentDefinition.designLevelCalculationMethod == 'Watts/Area'
if is_ip_units
ge_power = gas_equip.powerPerFloorArea.to_f * 0.316998331 # W/m^2 --> BTU/hr/ft^2 (OpenStudio.convert() !work (!))
ge_total_power = ((ge_power * area) * gas_equip.multiplier).to_f
ge_power = "#{ge_power.to_f.round(2)} (BTU/hr/ft^2)"
else
ge_power = gas_equip.powerPerFloorArea.to_f
ge_total_power = ((ge_power * area) * gas_equip.multiplier).to_f
ge_power = "#{ge_power.to_f.round(2)} (W/m^2)"
end
end
if gas_equip.gasEquipmentDefinition.designLevelCalculationMethod == 'Watts/Person'
if is_ip_units
ge_power = gas_equip.powerPerPerson.to_f * 3.412142 # W --> BTU/hr
ge_total_power = ((ge_power * space.numberOfPeople) * gas_equip.multiplier).to_f
ge_power = "#{ge_power.to_f.round(2)} (BTU/hr/person)"
else
ge_power = gas_equip.powerPerPerson.to_f
ge_total_power = ((ge_power * space.numberOfPeople) * gas_equip.multiplier).to_f
ge_power = "#{ge_power.to_f.round(2)} (W/person)"
end
end
if gas_equip.gasEquipmentDefinition.designLevelCalculationMethod == 'EquipmentLevel'
if is_ip_units
ge_power = gas_equip.designLevel.to_f * 3.412142 # W --> BTU/hr
ge_power = "#{ge_power.to_f.round(0)} (BTU/hr)"
ge_total_power = gas_equip.designLevel.to_f * gas_equip.multiplier
else
ge_power = gas_equip.designLevel.to_f
ge_power = "#{ge_power.to_f.round(0)} (W)"
ge_total_power = gas_equip.designLevel.to_f * gas_equip.multiplier
end
end
table[:data] << [gas_equip.name, gas_equip.gasEquipmentDefinition.name, ge_power, inheritance_level, gas_equip.multiplier.round(1), ge_total_power.round(0)]
end
end
end # space-level gas plug loads table
# if data, add table to report
plug_loads_tables << table if !table[:data].empty?
# data for query
# TODO: - need to test this in model with gas equipment
report_name = 'EnergyMeters'
table_name = 'Annual and Peak Values - Gas'
columns = ['', 'Gas Annual Value'] # TODO: - would be nice to make this more like lighting summary
# populate dynamic rows
rows_name_query = "SELECT DISTINCT RowName FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}'"
row_names = sqlFile.execAndReturnVectorOfString(rows_name_query).get
rows = []
row_names.each do |row_name|
next unless row_name.include?('InteriorEquipment:Gas:Zone:')
rows << row_name
end
# create table
table = {}
table[:title] = 'Gas Plug Load Consumption'
table[:header] = columns
source_units_energy = 'GJ'
if is_ip_units
target_units_energy = 'kBtu'
else
target_units_energy = 'kWh'
end
table[:source_units] = ['', source_units_energy] # used for conversation, not needed for rendering.
table[:units] = ['', target_units_energy]
table[:data] = []
# run query and populate table
rows.each do |row|
row_data = [row]
column_counter = -1
table[:header].each do |header|
column_counter += 1
next if header == ''
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{row}' and ColumnName= '#{header}'"
results = sqlFile.execAndReturnFirstDouble(query)
row_data_ip = OpenStudio.convert(results.to_f, table[:source_units][column_counter], table[:units][column_counter]).get
row_data << row_data_ip.round(2)
end
table[:data] << row_data
end
# add table to array of tables
if !table[:data].empty? then plug_loads_tables << table end
return @plug_loads_section
end
# create unmet hours
def self.hvac_load_profile(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
hvac_load_profile_tables = []
# gather data for section
@hvac_load_profile_section = {}
@hvac_load_profile_section[:title] = 'HVAC Load Profiles'
@hvac_load_profile_section[:tables] = hvac_load_profile_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @hvac_load_profile_section
end
month_order = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
# create table
hvac_load_profile_monthly_table = {}
hvac_load_profile_monthly_table[:title] = 'Monthly Load Profiles'
hvac_load_profile_monthly_table[:header] = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
hvac_load_profile_monthly_table[:units] = []
hvac_load_profile_monthly_table[:data] = []
hvac_load_profile_monthly_table[:chart_type] = 'vertical_grouped_bar_with_comp_line'
if is_ip_units
hvac_load_profile_monthly_table[:chart_attributes] = { value_left: 'Cooling/Heating Load (MBtu)', label_x: 'Month', value_right: 'Average Outdoor Air Dry Bulb (F)', sort_xaxis: month_order }
else
hvac_load_profile_monthly_table[:chart_attributes] = { value_left: 'Cooling/Heating Load (kWh)', label_x: 'Month', value_right: 'Average Outdoor Air Dry Bulb (C)', sort_xaxis: month_order }
end
hvac_load_profile_monthly_table[:chart] = []
# hash to store monthly values
cooling_monthly = { 'Jan' => 0, 'Feb' => 0, 'Mar' => 0, 'Apr' => 0, 'May' => 0, 'Jun' => 0, 'Jul' => 0, 'Aug' => 0, 'Sep' => 0, 'Oct' => 0, 'Nov' => 0, 'Dec' => 0 }
heating_monthly = { 'Jan' => 0, 'Feb' => 0, 'Mar' => 0, 'Apr' => 0, 'May' => 0, 'Jun' => 0, 'Jul' => 0, 'Aug' => 0, 'Sep' => 0, 'Oct' => 0, 'Nov' => 0, 'Dec' => 0 }
# units for conversion
source_units = 'J'
if is_ip_units
target_units = 'MBtu'
else
target_units = 'kWh'
end
# loop through fuel types
fuel_type_names.each do |fuel_type|
OpenStudio::MonthOfYear.getValues.each do |month|
if month >= 1 && month <= 12
if fuel_type == 'Natural Gas' then fuel_type = 'Gas' end
# get cooling value for this fuel and month
if !sqlFile.energyConsumptionByMonth(OpenStudio::EndUseFuelType.new(fuel_type),
OpenStudio::EndUseCategoryType.new('Cooling'),
OpenStudio::MonthOfYear.new(month)).empty?
cooling_valInJ = sqlFile.energyConsumptionByMonth(OpenStudio::EndUseFuelType.new(fuel_type),
OpenStudio::EndUseCategoryType.new('Cooling'),
OpenStudio::MonthOfYear.new(month)).get
cooling_valInUnits = OpenStudio.convert(cooling_valInJ, source_units, target_units).get
else
cooling_valInUnits = 0
end
# get heating value for this fuel and month
if !sqlFile.energyConsumptionByMonth(OpenStudio::EndUseFuelType.new(fuel_type),
OpenStudio::EndUseCategoryType.new('Heating'),
OpenStudio::MonthOfYear.new(month)).empty?
heating_valInJ = sqlFile.energyConsumptionByMonth(OpenStudio::EndUseFuelType.new(fuel_type),
OpenStudio::EndUseCategoryType.new('Heating'),
OpenStudio::MonthOfYear.new(month)).get
heating_valInUnits = OpenStudio.convert(heating_valInJ, source_units, target_units).get
else
heating_valInUnits = 0
end
# create or add to hash to sum across fuel types
month = hvac_load_profile_monthly_table[:header][month]
if cooling_monthly.key?(month)
cooling_monthly[month] = cooling_monthly[month].to_f + cooling_valInUnits
else
cooling_monthly[month] = cooling_valInUnits
end
if heating_monthly.key?(month)
heating_monthly[month] = heating_monthly[month].to_f + heating_valInUnits
else
heating_monthly[month] = heating_monthly
end
end
end
end
# populate dry bulb data
if is_ip_units
dry_bulb_monthly = ['Average Outdoor Air Dry Bulb (F)']
else
dry_bulb_monthly = ['Average Outdoor Air Dry Bulb (C)']
end
ann_env_pd = OsLib_Reporting.ann_env_pd(sqlFile)
if ann_env_pd
# get desired variable
output_timeseries = sqlFile.timeSeries(ann_env_pd, 'Monthly', 'Site Outdoor Air Drybulb Temperature', 'Environment')
# loop through timeseries and move the data from an OpenStudio timeseries to a normal Ruby array (vector)
if output_timeseries.is_initialized # checks to see if time_series exists
# see if filler needed at start or end of table/chart
num_blanks_start = output_timeseries.get.dateTimes[0].date.monthOfYear.value - 2
num_blanks_end = 12 - output_timeseries.get.values.size - num_blanks_start
# fill in blank data for partial year simulations
for i in 0..(num_blanks_start - 1)
month = hvac_load_profile_monthly_table[:header][i + 1]
dry_bulb_monthly << ''
end
output_timeseries = output_timeseries.get.values
for i in 0..(output_timeseries.size - 1)
month = hvac_load_profile_monthly_table[:header][i + 1 + num_blanks_start]
if is_ip_units
value = OpenStudio.convert(output_timeseries[i], 'C', 'F').get
else
value = OpenStudio.convert(output_timeseries[i], 'C', 'C').get
end
dry_bulb_monthly << value.round(1)
hvac_load_profile_monthly_table[:chart] << JSON.generate(label: 'Outdoor Temp', label_x: month, value2: value, color: 'green')
end # end of for i in 0..(output_timeseries.size - 1)
# fill in blank data for partial year simulations
for i in 0..(num_blanks_end - 1)
month = hvac_load_profile_monthly_table[:header][i]
dry_bulb_monthly << ''
end
else
# TODO: - see why this is getting thrown on some models
runner.registerWarning("Didn't find data for Site Outdoor Air Drybulb Temperature")
end # end of if output_timeseries.is_initialized
else
runner.registerWarning('An annual simulation was not run. Cannot get annual timeseries data')
return false
end
# populate tables
hvac_load_profile_monthly_table[:data] << dry_bulb_monthly
if is_ip_units
cooling_array = ['Cooling Load (MBtu)']
else
cooling_array = ['Cooling Load (kWh)']
end
cooling_monthly.each do |k, v|
cooling_array << v.round(2)
# populate chart
hvac_load_profile_monthly_table[:chart] << JSON.generate(label: 'Cooling Load', label_x: k, value: v, color: '#0071BD')
end
hvac_load_profile_monthly_table[:data] << cooling_array
if is_ip_units
heating_array = ['Heating Load (MBtu)']
else
heating_array = ['Heating Load (kWh)']
end
heating_monthly.each do |k, v|
heating_array << v.round(2)
# populate chart
hvac_load_profile_monthly_table[:chart] << JSON.generate(label: 'Heating Load', label_x: k, value: v, color: '#EF1C21')
end
hvac_load_profile_monthly_table[:data] << heating_array
# add table to array of tables
hvac_load_profile_tables << hvac_load_profile_monthly_table
# create table
hvac_part_load_profile_table = {}
hvac_part_load_profile_table[:title] = 'Part Load Profiles'
hvac_part_load_profile_table[:header] = ['Load', 'Clg: Cutoff', 'Clg: Hours', 'Clg: Hours', 'Htg: Cutoff', 'Htg: Hours', 'Htg: Hours']
if is_ip_units
hvac_part_load_profile_table[:units] = ['%', 'MBtu', '%', 'hr', 'MBtu', '%', 'hr']
else
hvac_part_load_profile_table[:units] = ['%', 'kWh', '%', 'hr', 'kWh', '%', 'hr']
end
hvac_part_load_profile_table[:data] = []
# add rows to table
hvac_part_load_profile_table[:data] << ['0-5', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['5-10', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['10-25', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['15-20', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['20-25', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['25-30', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['30-35', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['35-40', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['40-45', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['45-50', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['50-55', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['55-60', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['60-65', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['65-70', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['70-75', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['75-80', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['80-85', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['85-90', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['90-95', '', '', '', '', '', '']
hvac_part_load_profile_table[:data] << ['95-100', '', '', '', '', '', '']
# TODO: - add table to array of tables
# hvac_load_profile_tables << hvac_part_load_profile_table
return @hvac_load_profile_section
end
def self.zone_summary_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
template_tables = []
# gather data for section
@zone_summary_section = {}
@zone_summary_section[:title] = 'Zone Overview'
@zone_summary_section[:tables] = template_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @zone_summary_section
end
# data for query
report_name = 'InputVerificationandResultsSummary'
table_name = 'Zone Summary'
columns = ['', 'Area', 'Conditioned (Y/N)', 'Part of Total Floor Area (Y/N)', 'Volume', 'Multiplier', 'Above Ground Gross Wall Area', 'Underground Gross Wall Area', 'Window Glass Area', 'Lighting', 'People', 'Plug and Process']
# test looking at getting entire table to get rows
# query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}'"
# results = sqlFile.execAndReturnVectorOfString(query).get
# populate dynamic rows
rows_name_query = "SELECT DISTINCT RowName FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}'"
row_names = sqlFile.execAndReturnVectorOfString(rows_name_query).get
rows = []
row_names.each do |row_name|
rows << row_name
end
# rows = ['Total','Conditioned Total','Unconditioned Total','Not Part of Total']
# create zone_summary_table
zone_summary_table = {}
zone_summary_table[:title] = table_name
zone_summary_table[:header] = columns
source_units_area = 'm^2'
source_units_area_per_person = 'm^2/person'
source_units_volume = 'm^3'
source_units_pd = 'W/m^2'
if is_ip_units
target_units_area = 'ft^2'
target_units_area_per_person = 'ft^2/person'
target_units_volume = 'ft^3'
target_units_pd = 'W/ft^2'
else
target_units_area = 'm^2'
target_units_area_per_person = 'm^2/person'
target_units_volume = 'm^3'
target_units_pd = 'W/m^2'
end
zone_summary_table[:units] = ['', target_units_area, '', '', target_units_volume, '', target_units_area, target_units_area, target_units_area, target_units_pd, target_units_area_per_person, target_units_pd]
zone_summary_table[:source_units] = ['', source_units_area, '', '', source_units_volume, '', source_units_area, source_units_area, source_units_area, source_units_pd, source_units_area_per_person, source_units_pd] # used for conversation, not needed for rendering.
zone_summary_table[:data] = []
# run query and populate zone_summary_table
rows.each do |row|
row_data = [row]
column_counter = -1
zone_summary_table[:header].each do |header|
column_counter += 1
next if header == ''
if header == 'Multiplier' then header = 'Multipliers' end # what we want to show is different than what is in E+ table
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{row}' and ColumnName= '#{header}'"
if zone_summary_table[:source_units][column_counter] != ''
results = sqlFile.execAndReturnFirstDouble(query)
row_data_ip = OpenStudio.convert(results.to_f, zone_summary_table[:source_units][column_counter], zone_summary_table[:units][column_counter]).get
row_data << row_data_ip.round(2)
else
results = sqlFile.execAndReturnFirstString(query)
row_data << results
end
end
zone_summary_table[:data] << row_data
end
# add zone_summary_table to array of tables
template_tables << zone_summary_table
# data for query
report_name = 'HVACSizingSummary'
table_01_name = 'Zone Sensible Cooling'
table_02_name = 'Zone Sensible Heating'
columns = ['', 'Heating/Cooling', 'Calculated Design Load', 'Design Load With Sizing Factor', 'Calculated Design Air Flow', 'Design Air Flow With Sizing Factor', 'Date/Time Of Peak', 'Outdoor Temperature at Peak Load', 'Outdoor Humidity Ratio at Peak Load']
columns_query = ['', 'Heating/Cooling', 'Calculated Design Load', 'User Design Load', 'Calculated Design Air Flow', 'User Design Air Flow', 'Date/Time Of Peak {TIMESTAMP}', 'Outdoor Temperature at Peak Load', 'Outdoor Humidity Ratio at Peak Load']
# populate dynamic rows
rows_name_query = "SELECT DISTINCT RowName FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_01_name}'"
row_names = sqlFile.execAndReturnVectorOfString(rows_name_query).get
rows = []
row_names.each do |row_name|
rows << row_name
end
# create zone_dd_table
zone_dd_table = {}
zone_dd_table[:title] = 'Zone Sensible Cooling and Heating Sensible Sizing'
zone_dd_table[:header] = columns
source_units_power = 'W'
source_units_air_flow = 'm^3/s'
source_units_temp = 'C'
if is_ip_units
target_units_power_clg = 'ton'
target_units_power_htg = 'kBtu/h'
target_units_air_flow = 'ft^3/min'
target_units_temp = 'F'
# This one's a ratio... so it doesn't matter really, we don't convert
# but we want to display something pretty
target_units_humrat_display = 'lbWater/lbAir'
else
target_units_power_clg = 'W'
target_units_power_htg = 'W'
target_units_air_flow = 'm^3/h'
target_units_temp = 'C'
target_units_humrat_display = 'kgWater/kgAir'
end
zone_dd_table[:units] = ['', '', '', '', target_units_air_flow, target_units_air_flow, '', target_units_temp, target_units_humrat_display]
# used for convertion, not needed for rendering.
zone_dd_table[:source_units] = ['', '', '', '', source_units_air_flow, source_units_air_flow, '', source_units_temp, target_units_humrat_display]
zone_dd_table[:data] = []
# run query and populate zone_dd_table
rows.each do |row|
# populate cooling row
row_data = [row, 'Cooling']
column_counter = -1
columns_query.each do |header|
column_counter += 1
next if header == '' || header == 'Heating/Cooling'
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_01_name}' and RowName= '#{row}' and ColumnName= '#{header}'"
if zone_dd_table[:source_units][column_counter] != ''
results = sqlFile.execAndReturnFirstDouble(query)
row_data_ip = OpenStudio.convert(results.to_f, zone_dd_table[:source_units][column_counter], zone_dd_table[:units][column_counter]).get
row_data << row_data_ip.round(2)
elsif header == 'Calculated Design Load' || header == 'User Design Load'
results = sqlFile.execAndReturnFirstDouble(query)
row_data_ip = OpenStudio.convert(results.to_f, source_units_power, target_units_power_clg).get
row_data << "#{row_data_ip.round(2)} (#{target_units_power_clg})"
else
results = sqlFile.execAndReturnFirstString(query)
row_data << results
end
end
zone_dd_table[:data] << row_data
# populate heating row
row_data = [row, 'Heating']
column_counter = -1
columns_query.each do |header|
column_counter += 1
next if header == '' || header == 'Heating/Cooling'
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_02_name}' and RowName= '#{row}' and ColumnName= '#{header}'"
if zone_dd_table[:source_units][column_counter] != ''
results = sqlFile.execAndReturnFirstDouble(query)
row_data_ip = OpenStudio.convert(results.to_f, zone_dd_table[:source_units][column_counter], zone_dd_table[:units][column_counter]).get
row_data << row_data_ip.round(2)
elsif header == 'Calculated Design Load' || header == 'User Design Load'
results = sqlFile.execAndReturnFirstDouble(query)
row_data_ip = OpenStudio.convert(results.to_f, source_units_power, target_units_power_htg).get
row_data << "#{row_data_ip.round(2)} (#{target_units_power_htg})"
else
results = sqlFile.execAndReturnFirstString(query)
row_data << results
end
end
zone_dd_table[:data] << row_data
end
# add zone_dd_table to array of tables
template_tables << zone_dd_table
return @zone_summary_section
end
# create air_loop_summary section
def self.air_loop_summary_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
air_loop_summary_tables = []
# gather data for section
@air_loop_summary_section = {}
@air_loop_summary_section[:title] = 'Air Loops Summary'
@air_loop_summary_section[:tables] = air_loop_summary_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @air_loop_summary_section
end
# create table
air_loop_summary_table = {}
air_loop_summary_table[:title] = 'Part load histograms for fans; graphical for annual, tabular for annual and monthly'
air_loop_summary_table[:header] = []
air_loop_summary_table[:units] = []
air_loop_summary_table[:data] = []
# add table to array of tables
air_loop_summary_tables << air_loop_summary_table
return @air_loop_summary_section
end
# create plant_loop_summary section
def self.plant_loop_summary_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
plant_loop_summary_tables = []
# gather data for section
@outdoor_air_section = {}
@outdoor_air_section[:title] = 'Plant Loops Summary'
@outdoor_air_section[:tables] = plant_loop_summary_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @outdoor_air_section
end
# create table
plant_loop_summary_table = {}
plant_loop_summary_table[:title] = 'Part load histograms for chillers, boilers, pumps'
plant_loop_summary_table[:header] = []
plant_loop_summary_table[:units] = []
plant_loop_summary_table[:data] = []
# add table to array of tables
plant_loop_summary_tables << plant_loop_summary_table
return @outdoor_air_section
end
# create outdoor_air_section
def self.outdoor_air_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
outdoor_air_section_tables = []
# gather data for section
@outdoor_air_section = {}
@outdoor_air_section[:title] = 'Outdoor Air'
@outdoor_air_section[:tables] = outdoor_air_section_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @outdoor_air_section
end
# data for query
report_name = 'OutdoorAirSummary'
table_name = 'Average Outdoor Air During Occupied Hours'
min_table_name = 'Minimum Outdoor Air During Occupied Hours'
columns = ['', 'Average Number of Occupants', 'Nominal Number of Occupants', 'Zone Volume', 'Avg. Mechanical Ventilation', 'Min. Mechanical Ventilation', 'Avg. Infiltration', 'Min. Infiltration', 'Avg. Simple Ventilation', 'Min. Simple Ventilation']
# populate dynamic rows
rows_name_query = "SELECT DISTINCT RowName FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}'"
row_names = sqlFile.execAndReturnVectorOfString(rows_name_query).get
rows = []
row_names.each do |row_name|
rows << row_name
end
# create table
table = {}
table[:title] = 'Average and Minimum Outdoor Air During Occupied Hours'
table[:header] = columns
source_units_volume = 'm^3'
if is_ip_units
target_units_volume = 'ft^3'
else
target_units_volume = 'm^3'
end
table[:units] = ['', '', '', target_units_volume, 'ach', 'ach', 'ach', 'ach', 'ach', 'ach']
table[:source_units] = ['', '', '', source_units_volume, 'ach', 'ach', 'ach', 'ach', 'ach', 'ach']
table[:data] = []
# run query and populate table
rows.each do |row|
row_data = [row]
column_counter = -1
table[:header].each do |header|
column_counter += 1
next if header == ''
if header.include? 'Avg. '
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{row}' and ColumnName= '#{header.gsub('Avg. ', '')}'"
results = sqlFile.execAndReturnFirstDouble(query)
row_data_ip = OpenStudio.convert(results.to_f, table[:source_units][column_counter], table[:units][column_counter]).get
row_data << row_data_ip.round(4)
elsif header.include? 'Min. '
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{min_table_name}' and RowName= '#{row}' and ColumnName= '#{header.gsub('Min. ', '')}'"
results = sqlFile.execAndReturnFirstDouble(query)
row_data_ip = OpenStudio.convert(results.to_f, table[:source_units][column_counter], table[:units][column_counter]).get
row_data << row_data_ip.round(4)
elsif header == 'Zone Volume'
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{row}' and ColumnName= '#{header}'"
results = sqlFile.execAndReturnFirstDouble(query)
row_data_ip = OpenStudio.convert(results.to_f, table[:source_units][column_counter], table[:units][column_counter]).get
row_data << row_data_ip.round(0)
else
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{row}' and ColumnName= '#{header}'"
results = sqlFile.execAndReturnFirstDouble(query)
row_data_ip = OpenStudio.convert(results.to_f, table[:source_units][column_counter], table[:units][column_counter]).get
row_data << row_data_ip.round(4)
end
end
table[:data] << row_data
end
# add table to array of tables
outdoor_air_section_tables << table
return @outdoor_air_section
end
# create cost_summary_section
def self.cost_summary_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
cost_summary_section_tables = []
# gather data for section
@cost_summary_section = {}
@cost_summary_section[:title] = 'Cash Flow'
@cost_summary_section[:tables] = cost_summary_section_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @cost_summary_section
end
# order for fuel types
yaxis_order = ['Electricity Purchased', 'Natural Gas', 'District Heating', 'District Cooling', 'Additional', 'Water', 'Capital and O&M']
# create table
cost_summary_table = {}
cost_summary_table[:title] = 'Annual Cash Flow
(Not adjusted for inflation or utility escalation)'
cost_summary_table[:header] = ['Year', 'Capital and O&M', 'Electricity', 'Natural Gas', 'District Heating', 'District Cooling', 'Additional', 'Water']
cost_summary_table[:units] = ['', '$', '$', '$', '$', '$', '$', '$']
cost_summary_table[:data] = []
cost_summary_table[:chart_type] = 'vertical_stacked_bar'
cost_summary_table[:chart_attributes] = { value: 'Annual Cash Flow ($)', label_x: 'Date', sort_yaxis: yaxis_order }
cost_summary_table[:chart] = []
# 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 = sqlFile.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
runner.registerWarning("Inflation approach: #{inf_appr.get} not recognized")
return false
end
runner.registerValue('inflation_approach', inf_appr)
else
runner.registerWarning('Could not determine inflation approach used')
return false
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 = sqlFile.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
runner.registerWarning("Could not determine the analysis start year from #{base_yr.get}")
return false
end
else
runner.registerWarning('Could not determine analysis start year')
return false
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 = sqlFile.execAndReturnFirstInt(length_yrs_query)
if length_yrs.is_initialized
length_yrs = length_yrs.get
runner.registerValue('analysis_length', length_yrs, 'yrs')
else
runner.registerWarning('Could not determine analysis length')
return false
end
# record the cash flow in these hashes
cap_cash_flow = {}
om_cash_flow = {}
energy_cash_flow = {}
electricity_cash_flow = {}
gas_cash_flow = {}
water_cash_flow = {}
tot_cash_flow = {}
additional_cash_flow = {}
data_cashFlow = []
data_running_total = []
running_total = 0
color = []
color << '#DDCC77' # Electricity
color << '#999933' # Natural Gas
color << '#AA4499' # Additional Fuel
color << '#88CCEE' # District Cooling
color << '#CC6677' # District Heating
color << '#332288' # Water (not used here but is in cash flow chart)
color << '#117733' # Capital and O&M (not used here but is in cash flow chart)
# loop through each year and record the cash flow
for i in 0..(length_yrs - 1) do
new_yr = base_yr + i
yr = "January #{new_yr.round}" # note: two spaces removed from earlier version of sql file
ann_cap_cash = 0.0
ann_om_cash = 0.0
ann_energy_cash = 0.0
ann_electricity_cash = 0.0
ann_gas_cash = 0.0
ann_dist_htg_cash = 0.0
ann_dist_clg_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 = sqlFile.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 = sqlFile.execAndReturnFirstDouble(om_cash_query)
if om_cash.is_initialized
ann_om_cash += om_cash.get
ann_tot_cash += om_cash.get
end
end
# energy cost cash flows (by fuel)
electricity_purchased_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Energy and Water Cost Cash Flows (Without Escalation)' AND RowName='#{yr}' AND ColumnName='ElectricityPurchased'"
electricity_purchased = sqlFile.execAndReturnFirstDouble(electricity_purchased_query)
if electricity_purchased.is_initialized
ann_electricity_cash += electricity_purchased.get
end
gas_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Energy and Water Cost Cash Flows (Without Escalation)' AND RowName='#{yr}' AND ColumnName='Gas'"
gas = sqlFile.execAndReturnFirstDouble(gas_query)
if gas.is_initialized
ann_gas_cash += gas.get
end
dist_htg_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Energy and Water Cost Cash Flows (Without Escalation)' AND RowName='#{yr}' AND ColumnName='DistrictHeating'"
dist_htg = sqlFile.execAndReturnFirstDouble(dist_htg_query)
if dist_htg.is_initialized
ann_dist_htg_cash += dist_htg.get
end
dist_clg_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Energy and Water Cost Cash Flows (Without Escalation)' AND RowName='#{yr}' AND ColumnName='DistrictCooling'"
dist_clg = sqlFile.execAndReturnFirstDouble(dist_clg_query)
if dist_clg.is_initialized
ann_dist_clg_cash += dist_clg.get
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 = sqlFile.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 = sqlFile.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
electricity_cash_flow[yr] = ann_electricity_cash
gas_cash_flow[yr] = ann_gas_cash
energy_cash_flow[yr] = ann_energy_cash
water_cash_flow[yr] = ann_water_cash
tot_cash_flow[yr] = ann_tot_cash
ann_additional_cash = ann_energy_cash - ann_electricity_cash - ann_gas_cash - ann_dist_htg_cash - ann_dist_clg_cash
additional_cash_flow[yr] = ann_additional_cash
# populate table row
cost_summary_table[:data] << [yr, ann_cap_cash + ann_om_cash, ann_electricity_cash, ann_gas_cash, ann_dist_htg_cash, ann_dist_clg_cash, ann_additional_cash.round(2), ann_water_cash]
# gather graph data
if ann_electricity_cash > 0
cost_summary_table[:chart] << JSON.generate(label: 'Electricity Purchased', label_x: yr, value: ann_electricity_cash, color: color[0])
end
if ann_gas_cash > 0
cost_summary_table[:chart] << JSON.generate(label: 'Natural Gas', label_x: yr, value: ann_gas_cash, color: color[1])
end
if ann_additional_cash > 0
cost_summary_table[:chart] << JSON.generate(label: 'Additional', label_x: yr, value: ann_additional_cash, color: color[2])
end
if ann_dist_clg_cash > 0
cost_summary_table[:chart] << JSON.generate(label: 'District Cooling', label_x: yr, value: ann_dist_clg_cash, color: color[3])
end
if ann_dist_htg_cash > 0
cost_summary_table[:chart] << JSON.generate(label: 'District Heating', label_x: yr, value: ann_dist_htg_cash, color: color[4])
end
if ann_water_cash > 0
cost_summary_table[:chart] << JSON.generate(label: 'Water', label_x: yr, value: ann_water_cash, color: color[5])
end
if ann_cap_cash + ann_om_cash > 0
cost_summary_table[:chart] << JSON.generate(label: 'Capital and O&M', label_x: yr, value: ann_cap_cash + ann_om_cash, color: color[6])
end
# gather running total data for line plot
running_total += ann_tot_cash
end # next year
# add table to array of tables
if running_total > 0
cost_summary_section_tables << cost_summary_table
else
# don't make chart of no data to add to it.
cost_summary_table[:chart] = []
end
return @cost_summary_section
end
# create source_energy_section
def self.source_energy_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
source_energy_section_tables = []
# gather data for section
@source_energy_section = {}
@source_energy_section[:title] = 'Site and Source Summary'
@source_energy_section[:tables] = source_energy_section_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @source_energy_section
end
# data for query
report_name = 'AnnualBuildingUtilityPerformanceSummary'
table_name = 'Site and Source Energy'
columns = ['', 'Total Energy', 'Energy Per Total Building Area', 'Energy Per Conditioned Building Area']
rows = ['Total Site Energy', 'Net Site Energy', 'Total Source Energy', 'Net Source Energy']
# create table
source_energy_table = {}
source_energy_table[:title] = table_name
source_energy_table[:header] = columns
source_units_total = 'GJ'
source_units_area = 'MJ/m^2'
if is_ip_units
target_units_total = 'kBtu'
target_units_area = 'kBtu/ft^2'
else
target_units_total = 'kWh'
target_units_area = 'kWh/m^2'
end
source_energy_table[:units] = ['', target_units_total, target_units_area, target_units_area]
source_energy_table[:source_units] = ['', source_units_total, source_units_area, source_units_area] # used for conversation, not needed for rendering.
source_energy_table[:data] = []
# run query and populate table
rows.each do |row|
row_data = [row]
column_counter = -1
source_energy_table[:header].each do |header|
column_counter += 1
next if header == ''
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{row}' and ColumnName= '#{header}'"
results = sqlFile.execAndReturnFirstDouble(query)
row_data_ip = OpenStudio.convert(results.to_f, source_energy_table[:source_units][column_counter], source_energy_table[:units][column_counter]).get
row_data << row_data_ip.round(1)
end
source_energy_table[:data] << row_data
end
# add table to array of tables
source_energy_section_tables << source_energy_table
# data for query
report_name = 'AnnualBuildingUtilityPerformanceSummary'
table_name = 'Site to Source Energy Conversion Factors'
columns = ['', 'Site=>Source Conversion Factor']
rows = ['Electricity', 'Natural Gas', 'District Cooling', 'District Heating'] # TODO: - complete this and add logic to skip row if not used in model
# create table
source_energy_table = {}
source_energy_table[:title] = table_name
source_energy_table[:header] = columns
source_energy_table[:units] = []
source_energy_table[:data] = []
# run query and populate table
rows.each do |row|
row_data = [row]
column_counter = -1
source_energy_table[:header].each do |header|
column_counter += 1
next if header == ''
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{row}' and ColumnName= '#{header}'"
results = sqlFile.execAndReturnFirstDouble(query).to_f
row_data << results.round(3)
end
source_energy_table[:data] << row_data
end
# add table to array of tables
source_energy_section_tables << source_energy_table
return @source_energy_section
end
# create co2_and_other_emissions_section
def self.co2_and_other_emissions_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
co2_and_other_emissions_section_tables = []
# gather data for section
@co2_and_other_emissions_section = {}
@co2_and_other_emissions_section[:title] = 'CO2 and Other Emissions'
@co2_and_other_emissions_section[:tables] = co2_and_other_emissions_section_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @co2_and_other_emissions_section
end
# data for query
report_name = 'EnergyMeters'
table_name = 'Annual and Peak Values - Other by Weight/Mass'
columns = ['', 'Annual Value', 'Minimum Value', 'Timestamp of Minimum', 'Maximum Value', 'Timestamp of Maximum']
rows = ['Carbon Equivalent:Facility', 'CarbonEquivalentEmissions:Carbon Equivalent']
# create table
table = {}
table[:title] = table_name
table[:header] = columns
source_units_total = 'kg'
source_units_rate = 'kg/s'
if is_ip_units
target_units_total = 'lb'
target_units_rate = 'lb/s'
else
target_units_total = 'kg'
target_units_rate = 'kg/s'
end
table[:units] = ['', target_units_total, target_units_rate, '', target_units_rate, '']
table[:source_units] = ['', source_units_total, source_units_rate, '', source_units_rate, ''] # used for conversation, not needed for rendering.
table[:data] = []
# run query and populate table
rows.each do |row|
row_data = [row]
column_counter = -1
table[:header].each do |header|
column_counter += 1
next if header == ''
query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='#{report_name}' and TableName='#{table_name}' and RowName= '#{row}' and ColumnName= '#{header}'"
if table[:source_units][column_counter] != ''
results = sqlFile.execAndReturnFirstDouble(query)
row_data_ip = OpenStudio.convert(results.to_f, table[:source_units][column_counter], table[:units][column_counter]).get
row_data << row_data_ip.round(2)
else
results = sqlFile.execAndReturnFirstString(query)
row_data << results
end
end
table[:data] << row_data
end
# add table to array of tables
co2_and_other_emissions_section_tables << table
return @co2_and_other_emissions_section
end
# create typical_load_profiles_section
def self.typical_load_profiles_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
typical_load_profiles_section_tables = []
# gather data for section
@typical_load_profiles_section = {}
@typical_load_profiles_section[:title] = 'Typical Load Profiles'
@typical_load_profiles_section[:tables] = typical_load_profiles_section_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @typical_load_profiles_section
end
# create table
typical_load_profiles_table = {}
typical_load_profiles_table[:title] = 'Content To Be Determined'
typical_load_profiles_table[:header] = []
typical_load_profiles_table[:units] = []
typical_load_profiles_table[:data] = []
# add table to array of tables
typical_load_profiles_section_tables << typical_load_profiles_table
return @typical_load_profiles_section
end
# create schedules_overview_section
def self.schedules_overview_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
schedules_overview_section_tables = []
# gather data for section
@schedules_overview_section = {}
@schedules_overview_section[:title] = 'Schedule Overview'
@schedules_overview_section[:tables] = schedules_overview_section_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @schedules_overview_section
end
# create table
schedules_overview_table = {}
schedules_overview_table[:title] = ''
schedules_overview_table[:header] = ['Schedule', 'Type Limits', 'Rules', 'Use Count']
schedules_overview_table[:units] = []
schedules_overview_table[:data] = []
schedules_overview_table[:chart_type] = 'multi_step_line_grid'
schedules_overview_table[:chart] = [] # for this chart type, this contains an array of charts
# add table to array of tables
schedules_overview_section_tables << schedules_overview_table
model.getSchedules.sort.each do |schedule|
next unless schedule.to_ScheduleRuleset.is_initialized
schedule = schedule.to_ScheduleRuleset.get
next if schedule.nonResourceObjectUseCount(true) == 0 # true excludes children
# hash to hold chart
chart = { chart_data: [], chart_attributes: {} }
# get schedule and type limits
type_limits = nil
if schedule.scheduleTypeLimits.is_initialized
type_limits = schedule.scheduleTypeLimits.get.unitType
end
# populate table with high level schedule information
schedules_overview_table[:data] << [schedule.name, type_limits, schedule.scheduleRules.size, schedule.nonResourceObjectUseCount(true)]
# array to hold profiles
profiles = []
# get default profile
profiles << [schedule.defaultDaySchedule, 'default profile']
# get design days
summer_design = schedule.summerDesignDaySchedule
profiles << [summer_design, 'summer design day']
winter_design = schedule.winterDesignDaySchedule
profiles << [winter_design, 'winter design day']
# get rules
schedule.scheduleRules.each do |rule|
# add days of week to text
rule.applySunday ? (sun = 'Sun') : (sun = '')
rule.applyMonday ? (mon = 'Mon') : (mon = '')
rule.applyTuesday ? (tue = 'Tue') : (tue = '')
rule.applyWednesday ? (wed = 'Wed') : (wed = '')
rule.applyThursday ? (thu = 'Thu') : (thu = '')
rule.applyFriday ? (fri = 'Fri') : (fri = '')
rule.applySaturday ? (sat = 'Sat') : (sat = '')
# add dates to text
if rule.startDate.is_initialized
date = rule.startDate.get
start = date
else
start = ''
end
if rule.endDate.is_initialized
date = rule.endDate.get
finish = date
else
finish = ''
end
text = "(#{sun}#{mon}#{tue}#{wed}#{thu}#{fri}#{sat}) #{start}-#{finish}"
profiles << [rule.daySchedule, text]
end
# rule colors by index (starting with default, then summer, winter, then p1,p2, etc)
rule_colors = ['#88CCEE', 'red', 'blue', '#AA4499', '#332286', '#117733', '#99995B', '#DDCC77', '#CC6677', '#882255', '#6699CC', '#661100', '#AA4466', '#505050']
# temp test of profiles
profile_counter = -2
profiles.each do |array|
profile = array[0]
text = array[1]
if profile_counter == -2
name = text.to_s
elsif profile_counter < 1
name = " #{text}"
else
name = "Priority #{profile_counter} - #{text}"
end
# update counter
profile_counter += 1
times = profile.times
values = profile.values
(1..times.size).each do |index|
# add for this index value
time_double = times[index - 1].hours + times[index - 1].minutes / 60.0
value = values[index - 1]
# populate chart with datapoint
# chart[:chart_data] << JSON.generate({:label => name, :label_x => time_double, :value => value, :color => rule_colors[profile_counter+1]})
end
# add datapoint for 24
time = OpenStudio::Time.new(0, 24, 0, 0)
val = profile.getValue(time)
# chart[:chart_data] << JSON.generate({:label => name, :label_x => 24.0, :value => val, :color => rule_colors[profile_counter+1]})
# add datapoint for 0
time = OpenStudio::Time.new(0, 0, 0, 0)
val = profile.getValue(time)
# chart[:chart_data] << JSON.generate({:label => name, :label_x => 0.0, :value => val, :color => rule_colors[profile_counter+1]})
# get datapoint every 15min (doing this because I could get step to work just with input profile values)
(1..24).each do |i|
fractional_hours = i / 1.0
hr = fractional_hours.truncate
min = ((fractional_hours - fractional_hours.truncate) * 60.0).truncate
time = OpenStudio::Time.new(0, hr, min, 0)
val = profile.getValue(time)
# add unit conversion depending on type limits
if type_limits == 'Temperature'
if is_ip_units
val = OpenStudio.convert(val, 'C', 'F').get
else
val = OpenStudio.convert(val, 'C', 'C').get
end
end
# populate chart with datapoint
chart[:chart_data] << JSON.generate(label: name, label_x: fractional_hours, value: val, color: rule_colors[profile_counter + 1])
end
end
# populate chart attributes
if type_limits == 'Temperature'
if is_ip_units
chart[:chart_attributes][:value] = "#{type_limits} (F)"
else
chart[:chart_attributes][:value] = "#{type_limits} (C)"
end
elsif type_limits == 'ActivityLevel'
chart[:chart_attributes][:value] = "#{type_limits} (W)"
else
chart[:chart_attributes][:value] = type_limits
end
chart[:chart_attributes][:label_x] = 'Hours'
chart[:chart_attributes][:title] = schedule.name
# push chart to array of charts
schedules_overview_table[:chart] << chart
end
return @schedules_overview_section
end
# create measure_warning_section (creates tables and runner.registerValues)
def self.measure_warning_section(model, sqlFile, runner, name_only = false, is_ip_units = true)
# array to hold tables
measure_tables = []
# gather data for section
@measure_warnings_section = {}
@measure_warnings_section[:title] = 'Measure Warnings'
@measure_warnings_section[:tables] = measure_tables
# stop here if only name is requested this is used to populate display name for arguments
if name_only == true
return @measure_warnings_section
end
# will be used for registerValues
num_measures_with_warnings = 0
num_warnings = 0
num_measures = 0
# loop through workflow steps
runner.workflow.workflowSteps.each do |step|
if step.to_MeasureStep.is_initialized
measure_step = step.to_MeasureStep.get
measure_name = measure_step.measureDirName
num_measures += 1
if measure_step.name.is_initialized
measure_name = measure_step.name.get # this is instance name in PAT
end
if measure_step.result.is_initialized
result = measure_step.result.get
# create and populate table if warnings exist
if !result.warnings.empty?
measure_table_01 = {}
measure_table_01[:title] = measure_name
measure_table_01[:header] = ['Warning']
measure_table_01[:data] = []
num_measures_with_warnings += 1
# step through warnings
start_counter = num_warnings
result.warnings.each do |step|
# add rows to table and register value
num_warnings += 1
if num_warnings < start_counter + 25
measure_table_01[:data] << [step.logMessage]
elsif num_warnings == start_counter + 25
measure_table_01[:data] << ["* See OSW file for full list of warnings. This measure has #{result.warnings.size} warnings."]
end
end
# add table to section
measure_tables << measure_table_01
end
else
# puts "No result for #{measure_name}"
end
else
# puts "This step is not a measure"
end
end
# check for warnings in openstudio_results and add table for it if there are warnings.
if !runner.result.stepWarnings.empty?
measure_table_os_results = {}
measure_table_os_results[:title] = 'OpenStudio Results'
measure_table_os_results[:header] = ['Warning']
measure_table_os_results[:data] = []
num_measures_with_warnings += 1
runner.result.stepWarnings.each do |warning|
measure_table_os_results[:data] << [warning]
num_warnings += 1
end
measure_tables << measure_table_os_results
end
# add summary table (even when there are no warnings)
measure_table_summary = {}
measure_table_summary[:title] = 'Measure Warning Summary'
measure_table_summary[:header] = ['Description', 'Count']
measure_table_summary[:data] = []
# add summary rows
measure_table_summary[:data] << ['Number of measures in workflow', num_measures]
measure_table_summary[:data] << ['Number of measures with warnings', num_measures_with_warnings]
measure_table_summary[:data] << ['Total number of warnings', num_warnings]
# add table to section
measure_tables.insert(0, measure_table_summary)
runner.registerValue('number_of_measures_with_warnings', num_measures_with_warnings)
runner.registerValue('number_warnings', num_warnings)
return @measure_warnings_section
end
end