# *******************************************************************************
# 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

  # 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

    # temp 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

    # 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
      query_elec = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= 'Electricity'"
      query_gas = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= 'Natural Gas'"
      query_add = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= 'Additional Fuel'"
      query_dc = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= 'District Cooling'"
      query_dh = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' and TableName='End Uses' and RowName= '#{end_use}' and ColumnName= 'District Heating'"
      results_elec = sqlFile.execAndReturnFirstDouble(query_elec).get
      results_gas = sqlFile.execAndReturnFirstDouble(query_gas).get
      results_add = sqlFile.execAndReturnFirstDouble(query_add).get
      results_dc = sqlFile.execAndReturnFirstDouble(query_dc).get
      results_dh = sqlFile.execAndReturnFirstDouble(query_dh).get
      total_end_use = results_elec + results_gas + results_add + results_dc + results_dh
      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
    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 fuels for consumption tables
    counter = 0
    OpenStudio::EndUseFuelType.getValues.each do |fuel_type|
      # 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}<br>(#{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']

    extended_fuel_type_names = []
    standard_fuel_types = []
    OpenStudio::EndUseFuelType.getValues.each do |fuel_type|
      standard_fuel_types << OpenStudio::EndUseFuelType.new(fuel_type).valueDescription
    end
    additional_fuel_types = ['FuelOil#1', 'FuelOil#2', 'PropaneGas', 'Coal', 'Diesel', 'Gasoline', 'OtherFuel1', 'OtherFuel2']
    extended_fuel_type_names = standard_fuel_types + additional_fuel_types

    # loop through fuels for consumption tables
    extended_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 standard_fuel_types.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.tr(' ', '_'), 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.tr(' ', '_'), 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
    OpenStudio::EndUseFuelType.getValues.each do |fuel_type|
      # get fuel type and units
      fuel_type = OpenStudio::EndUseFuelType.new(fuel_type).valueDescription
      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")
        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")
        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.powerPerFloorArea.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.powerPerFloorArea.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.powerPerPerson.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.designLevel.to_f.round(0)} (W)"
            ee_total_power = elec_equip.designLevel.to_f.round(0) * elec_equip.multiplier
          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
    OpenStudio::EndUseFuelType.getValues.each do |fuel_type|
      OpenStudio::MonthOfYear.getValues.each do |month|
        if month >= 1 && month <= 12

          # 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
        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 <br>(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]
              else
                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

    # 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 << 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