lib/openstudio/extension/core/CreateResults.rb in openstudio-extension-0.1.2 vs lib/openstudio/extension/core/CreateResults.rb in openstudio-extension-0.1.3

- old
+ new

@@ -1,886 +1,886 @@ -# ******************************************************************************* -# OpenStudio(R), Copyright (c) 2008-2019, 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. -# ******************************************************************************* - -module OsLib_CreateResults - # Reports out the detailed simulation results needed by EDAPT. - # Results are output as both OpenStudio::Attributes (for OpenStudio 1.X) - # and runner.registerValue (for OpenStudio 2.X). - # @param skip_weekends [Bool] if true, weekends will not be included in the peak demand window - # @param skip_holidays [Bool] if true, holidays will not be included in the peak demand window - # @param start_mo [String] the start month for the peak demand window - # @param start_day [Integer] the start day for the peak demand window - # @param start_hr [Integer] the start hour for the peak demand window, using 24-hr clock - # @param end_mo [String] the end month for the peak demand window - # @param end_day [Integer] the end day for the peak demand window - # @param end_hr [Integer] the end hour for the peak demand window, using 24-hr clock - # @return [OpenStudio::AttributeVector] a vector of results needed by EDAPT - def create_results(skip_weekends = true, - skip_holidays = true, - start_mo = 'June', - start_day = 1, - start_hr = 14, - end_mo = 'September', - end_day = 30, - end_hr = 18) - - # get the current version of OS being used to determine if sql query - # changes are needed (for when E+ changes). - os_version = OpenStudio::VersionString.new(OpenStudio.openStudioVersion) - - # create an attribute vector to hold results - result_elems = OpenStudio::AttributeVector.new - - # floor_area - floor_area_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='Building Area' AND RowName='Net Conditioned Building Area' AND ColumnName='Area' AND Units='m2'" - floor_area = @sql.execAndReturnFirstDouble(floor_area_query) - if floor_area.is_initialized - result_elems << OpenStudio::Attribute.new('floor_area', floor_area.get, 'm^2') - @runner.registerValue('charsfloor_area', floor_area.get, 'm^2') - else - @runner.registerWarning('Building floor area not found') - return false - end - - # inflation approach - inf_appr_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Life-Cycle Cost Parameters' AND RowName='Inflation Approach' AND ColumnName='Value'" - inf_appr = @sql.execAndReturnFirstString(inf_appr_query) - if inf_appr.is_initialized - if inf_appr.get == 'ConstantDollar' - inf_appr = 'Constant Dollar' - elsif inf_appr.get == 'CurrentDollar' - inf_appr = 'Current Dollar' - else - @runner.registerError("Inflation approach: #{inf_appr.get} not recognized") - return OpenStudio::Attribute.new('report', result_elems) - end - @runner.registerInfo("Inflation approach = #{inf_appr}") - else - @runner.registerError('Could not determine inflation approach used') - return OpenStudio::Attribute.new('report', result_elems) - end - - # base year - base_yr_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Life-Cycle Cost Parameters' AND RowName='Base Date' AND ColumnName='Value'" - base_yr = @sql.execAndReturnFirstString(base_yr_query) - if base_yr.is_initialized - if base_yr.get =~ /\d\d\d\d/ - base_yr = base_yr.get.match(/\d\d\d\d/)[0].to_f - else - @runner.registerError("Could not determine the analysis start year from #{base_yr.get}") - return OpenStudio::Attribute.new('report', result_elems) - end - else - @runner.registerError('Could not determine analysis start year') - return OpenStudio::Attribute.new('report', result_elems) - end - - # analysis length - length_yrs_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Life-Cycle Cost Parameters' AND RowName='Length of Study Period in Years' AND ColumnName='Value'" - length_yrs = @sql.execAndReturnFirstInt(length_yrs_query) - if length_yrs.is_initialized - @runner.registerInfo "Analysis length = #{length_yrs.get} yrs" - length_yrs = length_yrs.get - else - @runner.registerError('Could not determine analysis length') - return OpenStudio::Attribute.new('report', result_elems) - end - - # cash flows - cash_flow_elems = OpenStudio::AttributeVector.new - - # setup a vector for each type of cash flow - cap_cash_flow_elems = OpenStudio::AttributeVector.new - om_cash_flow_elems = OpenStudio::AttributeVector.new - energy_cash_flow_elems = OpenStudio::AttributeVector.new - water_cash_flow_elems = OpenStudio::AttributeVector.new - tot_cash_flow_elems = OpenStudio::AttributeVector.new - - # add the type to the element - cap_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Capital Costs") - om_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Operating Costs") - energy_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Energy Costs") - water_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Water Costs") - tot_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Total Costs") - - @runner.registerValue('cash_flows_capital_type', "#{inf_appr} Capital Costs") - @runner.registerValue('cash_flows_operating_type', "#{inf_appr} Operating Costs") - @runner.registerValue('cash_flows_energy_type', "#{inf_appr} Energy Costs") - @runner.registerValue('cash_flows_water_type', "#{inf_appr} Water Costs") - @runner.registerValue('cash_flows_total_type', "#{inf_appr} Total Costs") - - # record the cash flow in these hashes - cap_cash_flow = {} - om_cash_flow = {} - energy_cash_flow = {} - water_cash_flow = {} - tot_cash_flow = {} - - # loop through each year and record the cash flow - for i in 0..(length_yrs - 1) do - new_yr = base_yr + i - - yr = nil - if os_version > OpenStudio::VersionString.new('1.5.3') - yr = "January #{new_yr.round}" - else - yr = "January #{new_yr.round}" - end - - ann_cap_cash = 0.0 - ann_om_cash = 0.0 - ann_energy_cash = 0.0 - ann_water_cash = 0.0 - ann_tot_cash = 0.0 - - # capital cash flow - cap_cash_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Capital Cash Flow by Category (Without Escalation)' AND RowName='#{yr}' AND ColumnName='Total'" - cap_cash = @sql.execAndReturnFirstDouble(cap_cash_query) - if cap_cash.is_initialized - ann_cap_cash += cap_cash.get - ann_tot_cash += cap_cash.get - end - - # o&m cash flow (excluding utility costs) - om_types = ['Maintenance', 'Repair', 'Operation', 'Replacement', 'MinorOverhaul', 'MajorOverhaul', 'OtherOperational'] - om_types.each do |om_type| - om_cash_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Operating Cash Flow by Category (Without Escalation)' AND RowName='#{yr}' AND ColumnName='#{om_type}'" - om_cash = @sql.execAndReturnFirstDouble(om_cash_query) - if om_cash.is_initialized - ann_om_cash += om_cash.get - ann_tot_cash += om_cash.get - end - end - - # energy cash flow - energy_cash_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Operating Cash Flow by Category (Without Escalation)' AND RowName='#{yr}' AND ColumnName='Energy'" - energy_cash = @sql.execAndReturnFirstDouble(energy_cash_query) - if energy_cash.is_initialized - ann_energy_cash += energy_cash.get - ann_tot_cash += energy_cash.get - end - - # water cash flow - water_cash_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Operating Cash Flow by Category (Without Escalation)' AND RowName='#{yr}' AND ColumnName='Water'" - water_cash = @sql.execAndReturnFirstDouble(water_cash_query) - if water_cash.is_initialized - ann_water_cash += water_cash.get - ann_tot_cash += water_cash.get - end - - # log the values for this year - cap_cash_flow[yr] = ann_cap_cash - om_cash_flow[yr] = ann_om_cash - energy_cash_flow[yr] = ann_energy_cash - water_cash_flow[yr] = ann_water_cash - tot_cash_flow[yr] = ann_tot_cash - - cap_cash_flow_elems << OpenStudio::Attribute.new('year', ann_cap_cash, 'dollars') - om_cash_flow_elems << OpenStudio::Attribute.new('year', ann_om_cash, 'dollars') - energy_cash_flow_elems << OpenStudio::Attribute.new('year', ann_energy_cash, 'dollars') - water_cash_flow_elems << OpenStudio::Attribute.new('year', ann_water_cash, 'dollars') - tot_cash_flow_elems << OpenStudio::Attribute.new('year', ann_tot_cash, 'dollars') - - @runner.registerValue("cash_flows_capital_year_#{i + 1}", ann_cap_cash, 'dollars') - @runner.registerValue("cash_flows_operating_year_#{i + 1}", ann_om_cash, 'dollars') - @runner.registerValue("cash_flows_energy_year_#{i + 1}", ann_energy_cash, 'dollars') - @runner.registerValue("cash_flows_water_year_#{i + 1}", ann_water_cash, 'dollars') - @runner.registerValue("cash_flows_total_year_#{i + 1}", ann_tot_cash, 'dollars') - - end # next year - - # end cash flows - cash_flow_elems << OpenStudio::Attribute.new('cash_flow', cap_cash_flow_elems) - cash_flow_elems << OpenStudio::Attribute.new('cash_flow', om_cash_flow_elems) - cash_flow_elems << OpenStudio::Attribute.new('cash_flow', energy_cash_flow_elems) - cash_flow_elems << OpenStudio::Attribute.new('cash_flow', water_cash_flow_elems) - cash_flow_elems << OpenStudio::Attribute.new('cash_flow', tot_cash_flow_elems) - result_elems << OpenStudio::Attribute.new('cash_flows', cash_flow_elems) - - # list of all end uses in OpenStudio - end_use_cat_types = [] - OpenStudio::EndUseCategoryType.getValues.each do |end_use_val| - end_use_cat_types << OpenStudio::EndUseCategoryType.new(end_use_val) - end - - # list of all end use fule types in OpenStudio - end_use_fuel_types = [] - OpenStudio::EndUseFuelType.getValues.each do |end_use_fuel_type_val| - end_use_fuel_types << OpenStudio::EndUseFuelType.new(end_use_fuel_type_val) - end - - # list of the 12 months of the year in OpenStudio - months = [] - OpenStudio::MonthOfYear.getValues.each do |month_of_year_val| - if (month_of_year_val >= 1) && (month_of_year_val <= 12) - months << OpenStudio::MonthOfYear.new(month_of_year_val) - end - end - - # map each end use category type to the name that will be used in the xml - end_use_map = { - OpenStudio::EndUseCategoryType.new('Heating').value => 'heating', - OpenStudio::EndUseCategoryType.new('Cooling').value => 'cooling', - OpenStudio::EndUseCategoryType.new('InteriorLights').value => 'lighting_interior', - OpenStudio::EndUseCategoryType.new('ExteriorLights').value => 'lighting_exterior', - OpenStudio::EndUseCategoryType.new('InteriorEquipment').value => 'equipment_interior', - OpenStudio::EndUseCategoryType.new('ExteriorEquipment').value => 'equipment_exterior', - OpenStudio::EndUseCategoryType.new('Fans').value => 'fans', - OpenStudio::EndUseCategoryType.new('Pumps').value => 'pumps', - OpenStudio::EndUseCategoryType.new('HeatRejection').value => 'heat_rejection', - OpenStudio::EndUseCategoryType.new('Humidifier').value => 'humidification', - OpenStudio::EndUseCategoryType.new('HeatRecovery').value => 'heat_recovery', - OpenStudio::EndUseCategoryType.new('WaterSystems').value => 'water_systems', - OpenStudio::EndUseCategoryType.new('Refrigeration').value => 'refrigeration', - OpenStudio::EndUseCategoryType.new('Generators').value => 'generators' - } - - # map each fuel type in EndUseFuelTypes to a specific FuelTypes - fuel_type_map = { - OpenStudio::EndUseFuelType.new('Electricity').value => OpenStudio::FuelType.new('Electricity'), - OpenStudio::EndUseFuelType.new('Gas').value => OpenStudio::FuelType.new('Gas'), - OpenStudio::EndUseFuelType.new('AdditionalFuel').value => OpenStudio::FuelType.new('Diesel'), # TODO: add other fuel types - OpenStudio::EndUseFuelType.new('DistrictCooling').value => OpenStudio::FuelType.new('DistrictCooling'), - OpenStudio::EndUseFuelType.new('DistrictHeating').value => OpenStudio::FuelType.new('DistrictHeating'), - OpenStudio::EndUseFuelType.new('Water').value => OpenStudio::FuelType.new('Water') - } - - # map each fuel type in EndUseFuelTypes to a specific FuelTypes - fuel_type_alias_map = { - OpenStudio::EndUseFuelType.new('Electricity').value => 'electricity', - OpenStudio::EndUseFuelType.new('Gas').value => 'gas', - OpenStudio::EndUseFuelType.new('AdditionalFuel').value => 'other_energy', - OpenStudio::EndUseFuelType.new('DistrictCooling').value => 'district_cooling', - OpenStudio::EndUseFuelType.new('DistrictHeating').value => 'district_heating', - OpenStudio::EndUseFuelType.new('Water').value => 'water' - } - - # annual "annual" - annual_elems = OpenStudio::AttributeVector.new - - # consumption "consumption" - cons_elems = OpenStudio::AttributeVector.new - - # electricity - electricity = @sql.electricityTotalEndUses - if electricity.is_initialized - cons_elems << OpenStudio::Attribute.new('electricity', electricity.get, 'GJ') - @runner.registerValue('annual_consumption_electricity', electricity.get, 'GJ') - else - cons_elems << OpenStudio::Attribute.new('electricity', 0.0, 'GJ') - @runner.registerValue('annual_consumption_electricity', 0.0, 'GJ') - end - - # gas - gas = @sql.naturalGasTotalEndUses - if gas.is_initialized - cons_elems << OpenStudio::Attribute.new('gas', gas.get, 'GJ') - @runner.registerValue('annual_consumption_gas', gas.get, 'GJ') - else - cons_elems << OpenStudio::Attribute.new('gas', 0.0, 'GJ') - @runner.registerValue('annual_consumption_gas', 0.0, 'GJ') - end - - # other_energy - other_energy = @sql.otherFuelTotalEndUses - if other_energy.is_initialized - cons_elems << OpenStudio::Attribute.new('other_energy', other_energy.get, 'GJ') - @runner.registerValue('annual_consumption_other_energy', other_energy.get, 'GJ') - else - cons_elems << OpenStudio::Attribute.new('other_energy', 0.0, 'GJ') - @runner.registerValue('annual_consumption_other_energy', 0.0, 'GJ') - end - - # district_cooling - district_cooling = @sql.districtCoolingTotalEndUses - if district_cooling.is_initialized - cons_elems << OpenStudio::Attribute.new('district_cooling', district_cooling.get, 'GJ') - @runner.registerValue('annual_consumption_district_cooling', district_cooling.get, 'GJ') - else - cons_elems << OpenStudio::Attribute.new('district_cooling', 0.0, 'GJ') - @runner.registerValue('annual_consumption_district_cooling', 0.0, 'GJ') - end - - # district_heating - district_heating = @sql.districtHeatingTotalEndUses - if district_heating.is_initialized - cons_elems << OpenStudio::Attribute.new('district_heating', district_heating.get, 'GJ') - @runner.registerValue('annual_consumption_district_heating', district_heating.get, 'GJ') - else - cons_elems << OpenStudio::Attribute.new('district_heating', 0.0, 'GJ') - @runner.registerValue('annual_consumption_district_heating', 0.0, 'GJ') - end - - # water - water = @sql.waterTotalEndUses - if water.is_initialized - cons_elems << OpenStudio::Attribute.new('water', water.get, 'm^3') - @runner.registerValue('annual_consumption_water', water.get, 'm^3') - else - cons_elems << OpenStudio::Attribute.new('water', 0.0, 'm^3') - @runner.registerValue('annual_consumption_water', 0.0, 'm^3') - end - - # end consumption - annual_elems << OpenStudio::Attribute.new('consumption', cons_elems) - - # demand "demand" - demand_elems = OpenStudio::AttributeVector.new - - # get the weather file run period (as opposed to design day run period) - ann_env_pd = nil - @sql.availableEnvPeriods.each do |env_pd| - env_type = @sql.environmentType(env_pd) - if env_type.is_initialized - if env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod') - ann_env_pd = env_pd - end - end - end - - # only try to get the annual peak demand if an annual simulation was run - if ann_env_pd - - # create some units to use - joule_unit = OpenStudio.createUnit('J').get - gigajoule_unit = OpenStudio.createUnit('GJ').get - hrs_unit = OpenStudio.createUnit('h').get - kilowatt_unit = OpenStudio.createUnit('kW').get - - # get the annual hours simulated - hrs_sim = '(0 - no partial annual simulation)' - if @sql.hoursSimulated.is_initialized - hrs_sim = @sql.hoursSimulated.get - if hrs_sim != 8760 - @runner.registerError("Simulation was only #{hrs_sim} hrs; EDA requires an annual simulation (8760 hrs)") - return OpenStudio::Attribute.new('report', result_elems) - end - end - - # Get the electricity timeseries to determine the year used - elec = @sql.timeSeries(ann_env_pd, 'Zone Timestep', 'Electricity:Facility', '') - timeseries_yr = nil - if elec.is_initialized - timeseries_yr = elec.get.dateTimes[0].date.year - else - @runner.registerError('Peak Demand timeseries (Electricity:Facility at zone timestep) could not be found, cannot determine the informatino needed to calculate savings or incentives.') - end - # Setup the peak demand time window based on input arguments. - # Note that holidays and weekends are not excluded because - # of a bug in EnergyPlus dates. - # This will only impact corner-case buildings that have - # peak demand on weekends or holidays, which is unusual. - @runner.registerInfo("Peak Demand window is #{start_mo} #{start_day} to #{end_mo} #{end_day} from #{start_hr}:00 to #{end_hr}:00.") - start_date = OpenStudio::DateTime.new(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(start_mo), start_day, timeseries_yr), OpenStudio::Time.new(0, 0, 0, 0)) - end_date = OpenStudio::DateTime.new(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(end_mo), end_day, timeseries_yr), OpenStudio::Time.new(0, 24, 0, 0)) - start_time = OpenStudio::Time.new(0, start_hr, 0, 0) - end_time = OpenStudio::Time.new(0, end_hr, 0, 0) - - # Get the day type timeseries. - day_types = nil - day_type_indices = @sql.timeSeries(ann_env_pd, 'Zone Timestep', 'Site Day Type Index', 'Environment') - if day_type_indices.is_initialized - # Put values into array - day_types = [] - day_type_vals = day_type_indices.get.values - for i in 0..(day_type_vals.size - 1) - day_types << day_type_vals[i] - end - else - @runner.registerError('Day Type timeseries (Site Day Type Index at zone timestep) could not be found, cannot accurately determine the peak demand.') - end - - # electricity_peak_demand - electricity_peak_demand = -1.0 - electricity_peak_demand_time = nil - # deduce the timestep based on the hours simulated and the number of datapoints in the timeseries - if elec.is_initialized && day_types - elec = elec.get - num_int = elec.values.size - int_len_hrs = OpenStudio::Quantity.new(hrs_sim / num_int, hrs_unit) - - # Put timeseries into array - elec_vals = [] - ann_elec_vals = elec.values - for i in 0..(ann_elec_vals.size - 1) - elec_vals << ann_elec_vals[i] - end - - # Put values into array - elec_times = [] - ann_elec_times = elec.dateTimes - for i in 0..(ann_elec_times.size - 1) - elec_times << ann_elec_times[i] - end - - # Loop through the time/value pairs and find the peak - # excluding the times outside of the Xcel peak demand window - elec_times.zip(elec_vals).each_with_index do |vs, ind| - date_time = vs[0] - val = vs[1] - day_type = day_types[ind] - time = date_time.time - date = date_time.date - day_of_week = date.dayOfWeek - # Convert the peak demand to kW - val_J_per_hr = val / int_len_hrs.value - val_kW = OpenStudio.convert(val_J_per_hr, 'J/h', 'kW').get - - # puts("#{val_kW}kW; #{date}; #{time}; #{day_of_week.valueName}") - - # Skip times outside of the correct months - next if date_time < start_date || date_time > end_date - # Skip times before 2pm and after 6pm - next if time < start_time || time > end_time - # Skip weekends if asked - if skip_weekends - # Sunday = 1, Saturday = 7 - next if day_type == 1 || day_type == 7 - end - # Skip holidays if asked - if skip_holidays - # Holiday = 8 - next if day_type == 8 - end - - # puts("VALID #{val_kW}kW; #{date}; #{time}; #{day_of_week.valueName}") - - # Check peak demand against this timestep - # and update if this timestep is higher. - if val > electricity_peak_demand - electricity_peak_demand = val - electricity_peak_demand_time = date_time - end - end - elec_peak_demand_timestep_J = OpenStudio::Quantity.new(electricity_peak_demand, joule_unit) - num_int = elec.values.size - int_len_hrs = OpenStudio::Quantity.new(hrs_sim / num_int, hrs_unit) - elec_peak_demand_hourly_J_per_hr = elec_peak_demand_timestep_J / int_len_hrs - electricity_peak_demand = OpenStudio.convert(elec_peak_demand_hourly_J_per_hr, kilowatt_unit).get.value - demand_elems << OpenStudio::Attribute.new('electricity_peak_demand', electricity_peak_demand, 'kW') - @runner.registerValue('annual_demand_electricity_peak_demand', electricity_peak_demand, 'kW') - @runner.registerInfo("Peak Demand = #{electricity_peak_demand.round(2)}kW on #{electricity_peak_demand_time}") - else - @runner.registerError('Peak Demand timeseries (Electricity:Facility at zone timestep) could not be found, cannot determine the informatino needed to calculate savings or incentives.') - demand_elems << OpenStudio::Attribute.new('electricity_peak_demand', 0.0, 'kW') - @runner.registerValue('annual_demand_electricity_peak_demand', 0.0, 'kW') - end - - # electricity_annual_avg_peak_demand - val = @sql.electricityTotalEndUses - if val.is_initialized - ann_elec_gj = OpenStudio::Quantity.new(val.get, gigajoule_unit) - ann_hrs = OpenStudio::Quantity.new(hrs_sim, hrs_unit) - elec_ann_avg_peak_demand_hourly_GJ_per_hr = ann_elec_gj / ann_hrs - electricity_annual_avg_peak_demand = OpenStudio.convert(elec_ann_avg_peak_demand_hourly_GJ_per_hr, kilowatt_unit).get.value - demand_elems << OpenStudio::Attribute.new('electricity_annual_avg_peak_demand', electricity_annual_avg_peak_demand, 'kW') - @runner.registerValue('annual_demand_electricity_annual_avg_peak_demand', electricity_annual_avg_peak_demand, 'kW') - else - demand_elems << OpenStudio::Attribute.new('electricity_annual_avg_peak_demand', 0.0, 'kW') - @runner.registerValue('annual_demand_electricity_annual_avg_peak_demand', 0.0, 'kW') - end - - # district_cooling_peak_demand - district_cooling_peak_demand = -1.0 - ann_dist_clg_peak_demand_time = nil - dist_clg = @sql.timeSeries(ann_env_pd, 'Zone Timestep', 'DistrictCooling:Facility', '') - # deduce the timestep based on the hours simulated and the number of datapoints in the timeseries - if dist_clg.is_initialized && day_types - dist_clg = dist_clg.get - num_int = dist_clg.values.size - int_len_hrs = OpenStudio::Quantity.new(hrs_sim / num_int, hrs_unit) - - # Put timeseries into array - dist_clg_vals = [] - ann_dist_clg_vals = dist_clg.values - for i in 0..(ann_dist_clg_vals.size - 1) - dist_clg_vals << ann_dist_clg_vals[i] - end - - # Put values into array - dist_clg_times = [] - ann_dist_clg_times = dist_clg.dateTimes - for i in 0..(ann_dist_clg_times.size - 1) - dist_clg_times << ann_dist_clg_times[i] - end - - # Loop through the time/value pairs and find the peak - # excluding the times outside of the Xcel peak demand window - dist_clg_times.zip(dist_clg_vals).each_with_index do |vs, ind| - date_time = vs[0] - val = vs[1] - day_type = day_types[ind] - time = date_time.time - date = date_time.date - day_of_week = date.dayOfWeek - # Convert the peak demand to kW - val_J_per_hr = val / int_len_hrs.value - val_kW = OpenStudio.convert(val_J_per_hr, 'J/h', 'kW').get - - # puts("#{val_kW}kW; #{date}; #{time}; #{day_of_week.valueName}") - - # Skip times outside of the correct months - next if date_time < start_date || date_time > end_date - # Skip times before 2pm and after 6pm - next if time < start_time || time > end_time - # Skip weekends if asked - if skip_weekends - # Sunday = 1, Saturday = 7 - next if day_type == 1 || day_type == 7 - end - # Skip holidays if asked - if skip_holidays - # Holiday = 8 - next if day_type == 8 - end - - # puts("VALID #{val_kW}kW; #{date}; #{time}; #{day_of_week.valueName}") - - # Check peak demand against this timestep - # and update if this timestep is higher. - if val > district_cooling_peak_demand - district_cooling_peak_demand = val - ann_dist_clg_peak_demand_time = date_time - end - end - dist_clg_peak_demand_timestep_J = OpenStudio::Quantity.new(district_cooling_peak_demand, joule_unit) - num_int = dist_clg.values.size - int_len_hrs = OpenStudio::Quantity.new(hrs_sim / num_int, hrs_unit) - dist_clg_peak_demand_hourly_J_per_hr = dist_clg_peak_demand_timestep_J / int_len_hrs - district_cooling_peak_demand = OpenStudio.convert(dist_clg_peak_demand_hourly_J_per_hr, kilowatt_unit).get.value - demand_elems << OpenStudio::Attribute.new('district_cooling_peak_demand', district_cooling_peak_demand, 'kW') - @runner.registerValue('annual_demand_district_cooling_peak_demand', district_cooling_peak_demand, 'kW') - @runner.registerInfo("District Cooling Peak Demand = #{district_cooling_peak_demand.round(2)}kW on #{ann_dist_clg_peak_demand_time}") - else - demand_elems << OpenStudio::Attribute.new('district_cooling_peak_demand', 0.0, 'kW') - @runner.registerValue('annual_demand_district_cooling_peak_demand', 0.0, 'kW') - end - - else - @runner.registerError('Could not find an annual run period') - return OpenStudio::Attribute.new('report', result_elems) - end - - # end demand - annual_elems << OpenStudio::Attribute.new('demand', demand_elems) - - # utility_cost - utility_cost_elems = OpenStudio::AttributeVector.new - annual_utility_cost_map = {} - - # electricity - electricity = @sql.annualTotalCost(OpenStudio::FuelType.new('Electricity')) - if electricity.is_initialized - utility_cost_elems << OpenStudio::Attribute.new('electricity', electricity.get, 'dollars') - @runner.registerValue('annual_utility_cost_electricity', electricity.get, 'dollars') - annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Electricity').valueName] = electricity.get - else - utility_cost_elems << OpenStudio::Attribute.new('electricity', 0.0, 'dollars') - @runner.registerValue('annual_utility_cost_electricity', 0.0, 'dollars') - annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Electricity').valueName] = 0.0 - end - - # electricity_consumption_charge and electricity_demand_charge - electric_consumption_charge = 0.0 - electric_demand_charge = 0.0 - - electric_rate_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-3. Energy Type Summary' AND RowName='Electricity' AND ColumnName='Utility Rate'" - electric_rate_name = @sql.execAndReturnFirstString(electric_rate_query) - if electric_rate_name.is_initialized - electric_rate_name = electric_rate_name.get.strip - - # electricity_consumption_charge - electric_consumption_charge_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='Tariff Report' AND ReportForString='#{electric_rate_name}' AND TableName='Categories' AND RowName='EnergyCharges (~~$~~)' AND ColumnName='Sum'" - val = @sql.execAndReturnFirstDouble(electric_consumption_charge_query) - if val.is_initialized - electric_consumption_charge = val.get - end - - # electricity_demand_charge - electric_demand_charge_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='Tariff Report' AND ReportForString='#{electric_rate_name}' AND TableName='Categories' AND RowName='DemandCharges (~~$~~)' AND ColumnName='Sum'" - val = @sql.execAndReturnFirstDouble(electric_demand_charge_query) - if val.is_initialized - electric_demand_charge = val.get - end - - end - utility_cost_elems << OpenStudio::Attribute.new('electricity_consumption_charge', electric_consumption_charge, 'dollars') - @runner.registerValue('annual_utility_cost_electricity_consumption_charge', electric_consumption_charge, 'dollars') - utility_cost_elems << OpenStudio::Attribute.new('electricity_demand_charge', electric_demand_charge, 'dollars') - @runner.registerValue('annual_utility_cost_electricity_demand_charge', electric_demand_charge, 'dollars') - - # gas - gas = @sql.annualTotalCost(OpenStudio::FuelType.new('Gas')) - if gas.is_initialized - annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Gas').valueName] = gas.get - else - annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Gas').valueName] = 0.0 - end - - # district_cooling - district_cooling_charge = 0.0 - - district_cooling_rate_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-3. Energy Type Summary' AND RowName='District Cooling' AND ColumnName='Utility Rate'" - district_cooling_rate_name = @sql.execAndReturnFirstString(district_cooling_rate_query) - if district_cooling_rate_name.is_initialized - district_cooling_rate_name = district_cooling_rate_name.get.strip - - # district_cooling_charge - district_cooling_charge_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='Tariff Report' AND ReportForString='#{district_cooling_rate_name}' AND TableName='Categories' AND RowName='Basis (~~$~~)' AND ColumnName='Sum'" - val = @sql.execAndReturnFirstDouble(district_cooling_charge_query) - if val.is_initialized - district_cooling_charge = val.get - end - - end - annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictCooling').valueName] = district_cooling_charge - - # district_heating - district_heating_charge = 0.0 - - district_heating_rate_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-3. Energy Type Summary' AND RowName='District Heating' AND ColumnName='Utility Rate'" - district_heating_rate_name = @sql.execAndReturnFirstString(district_heating_rate_query) - if district_heating_rate_name.is_initialized - district_heating_rate_name = district_heating_rate_name.get.strip - - # district_heating_charge - district_heating_charge_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='Tariff Report' AND ReportForString='#{district_heating_rate_name}' AND TableName='Categories' AND RowName='Basis (~~$~~)' AND ColumnName='Sum'" - val = @sql.execAndReturnFirstDouble(district_heating_charge_query) - if val.is_initialized - district_heating_charge = val.get - end - - end - annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictHeating').valueName] = district_heating_charge - - # water - water = @sql.annualTotalCost(OpenStudio::FuelType.new('Water')) - if water.is_initialized - annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Water').valueName] = water.get - else - annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Water').valueName] = 0.0 - end - - # total - total_query = "SELECT Value from tabulardatawithstrings where (reportname = 'Economics Results Summary Report') and (ReportForString = 'Entire Facility') and (TableName = 'Annual Cost') and (ColumnName ='Total') and (((RowName = 'Cost') and (Units = '~~$~~')) or (RowName = 'Cost (~~$~~)'))" - total = @sql.execAndReturnFirstDouble(total_query) - - # other_energy - # Subtract off the already accounted for fuel types from the total - # to account for fuels on custom meters where the fuel type is not known. - prev_tot = 0.0 - annual_utility_cost_map.each do |fuel, val| - prev_tot += val - end - if total.is_initialized - other_val = total.get - prev_tot - annual_utility_cost_map[OpenStudio::EndUseFuelType.new('AdditionalFuel').valueName] = other_val - else - annual_utility_cost_map[OpenStudio::EndUseFuelType.new('AdditionalFuel').valueName] = 0.0 - end - - # export remaining costs in the correct order - # gas - utility_cost_elems << OpenStudio::Attribute.new('gas', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Gas').valueName], 'dollars') - @runner.registerValue('annual_utility_cost_gas', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Gas').valueName], 'dollars') - # other_energy - utility_cost_elems << OpenStudio::Attribute.new('other_energy', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('AdditionalFuel').valueName], 'dollars') - @runner.registerValue('annual_utility_cost_other_energy', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('AdditionalFuel').valueName], 'dollars') - # district_cooling - utility_cost_elems << OpenStudio::Attribute.new('district_cooling', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictCooling').valueName], 'dollars') - @runner.registerValue('annual_utility_cost_district_cooling', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictCooling').valueName], 'dollars') - # district_heating - utility_cost_elems << OpenStudio::Attribute.new('district_heating', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictHeating').valueName], 'dollars') - @runner.registerValue('annual_utility_cost_district_heating', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictHeating').valueName], 'dollars') - # water - utility_cost_elems << OpenStudio::Attribute.new('water', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Water').valueName], 'dollars') - @runner.registerValue('annual_utility_cost_water', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Water').valueName], 'dollars') - # total - if total.is_initialized - utility_cost_elems << OpenStudio::Attribute.new('total', total.get, 'dollars') - @runner.registerValue('annual_utility_cost_total', total.get, 'dollars') - else - utility_cost_elems << OpenStudio::Attribute.new('total', 0.0, 'dollars') - @runner.registerValue('annual_utility_cost_total', 0.0, 'dollars') - end - - # end_uses - utility costs by end use using average blended cost - end_uses_elems = OpenStudio::AttributeVector.new - # map to store the costs by end use - cost_by_end_use = {} - - # fill the map with 0.0's to start - end_use_cat_types.each do |end_use_cat_type| - cost_by_end_use[end_use_cat_type] = 0.0 - end - - # only attempt to get monthly data if enduses table is available - if @sql.endUses.is_initialized - end_uses_table = @sql.endUses.get - # loop through all the fuel types - end_use_fuel_types.each do |end_use_fuel_type| - # get the annual total cost for this fuel type - ann_cost = annual_utility_cost_map[end_use_fuel_type.valueName] - # get the total annual usage for this fuel type in all end use categories - # loop through all end uses, adding the annual usage value to the aggregator - ann_usg = 0.0 - end_use_cat_types.each do |end_use_cat_type| - ann_usg += end_uses_table.getEndUse(end_use_fuel_type, end_use_cat_type) - end - # figure out the annual blended rate for this fuel type - avg_ann_rate = 0.0 - if ann_cost > 0 && ann_usg > 0 - avg_ann_rate = ann_cost / ann_usg - end - # for each end use category, figure out the cost if using - # the avg ann rate; add this cost to the map - end_use_cat_types.each do |end_use_cat_type| - cost_by_end_use[end_use_cat_type] += end_uses_table.getEndUse(end_use_fuel_type, end_use_cat_type) * avg_ann_rate - end - end - # loop through the end uses and record the annual total cost based on the avg annual rate - end_use_cat_types.each do |end_use_cat_type| - # record the value - end_uses_elems << OpenStudio::Attribute.new(end_use_map[end_use_cat_type.value], cost_by_end_use[end_use_cat_type], 'dollars') - @runner.registerValue("annual_utility_cost_end_uses_#{end_use_map[end_use_cat_type.value]}", cost_by_end_use[end_use_cat_type], 'dollars') - end - else - @runner.registerError('End-Use table not available in results; could not retrieve monthly costs by end use') - return OpenStudio::Attribute.new('report', result_elems) - end - - # end end_uses - utility_cost_elems << OpenStudio::Attribute.new('end_uses', end_uses_elems) - - # end utility_costs - annual_elems << OpenStudio::Attribute.new('utility_cost', utility_cost_elems) - - # end annual - result_elems << OpenStudio::Attribute.new('annual', annual_elems) - - # monthly - monthly_elems = OpenStudio::AttributeVector.new - - # consumption - cons_elems = OpenStudio::AttributeVector.new - # loop through all end uses - end_use_cat_types.each do |end_use_cat| - end_use_elems = OpenStudio::AttributeVector.new - end_use_name = end_use_map[end_use_cat.value] - # in each end use, loop through all fuel types - end_use_fuel_types.each do |end_use_fuel_type| - fuel_type_elems = OpenStudio::AttributeVector.new - fuel_type_name = fuel_type_alias_map[end_use_fuel_type.value] - ann_energy_cons = 0.0 - # in each end use, loop through months and get monthly enedy consumption - months.each_with_index do |month, i| - mon_energy_cons = 0.0 - val = @sql.energyConsumptionByMonth(end_use_fuel_type, end_use_cat, month) - if val.is_initialized - monthly_consumption_J = OpenStudio::Quantity.new(val.get, joule_unit) - monthly_consumption_GJ = OpenStudio.convert(monthly_consumption_J, gigajoule_unit).get.value - mon_energy_cons = monthly_consumption_GJ - ann_energy_cons += monthly_consumption_GJ - end - # record the monthly value - if end_use_fuel_type == OpenStudio::EndUseFuelType.new('Water') - fuel_type_elems << OpenStudio::Attribute.new('month', mon_energy_cons, 'm^3') - @runner.registerValue("monthly_consumption_#{end_use_name}_#{fuel_type_name}_month_#{i + 1}", mon_energy_cons, 'm^3') - else - fuel_type_elems << OpenStudio::Attribute.new('month', mon_energy_cons, 'GJ') - @runner.registerValue("monthly_consumption_#{end_use_name}_#{fuel_type_name}_month_#{i + 1}", mon_energy_cons, 'GJ') - end - end - # record the annual total - fuel_type_elems << OpenStudio::Attribute.new('year', ann_energy_cons, 'GJ') - @runner.registerValue("monthly_consumption_#{end_use_name}_#{fuel_type_name}_year", ann_energy_cons, 'GJ') - # add this fuel type - end_use_elems << OpenStudio::Attribute.new(fuel_type_alias_map[end_use_fuel_type.value], fuel_type_elems) - end - # add this end use - cons_elems << OpenStudio::Attribute.new(end_use_map[end_use_cat.value], end_use_elems) - end - # end consumption - monthly_elems << OpenStudio::Attribute.new('consumption', cons_elems) - - # create a unit to use - watt_unit = OpenStudio.createUnit('W').get - kilowatt_unit = OpenStudio.createUnit('kW').get - - # demand - demand_elems = OpenStudio::AttributeVector.new - # loop through all end uses - end_use_cat_types.each do |end_use_cat| - end_use_elems = OpenStudio::AttributeVector.new - end_use_name = end_use_map[end_use_cat.value] - # in each end use, loop through all fuel types - end_use_fuel_types.each do |end_use_fuel_type| - fuel_type_elems = OpenStudio::AttributeVector.new - fuel_type_name = fuel_type_alias_map[end_use_fuel_type.value] - ann_peak_demand = 0.0 - # in each end use, loop through months and get monthly enedy consumption - months.each_with_index do |month, i| - mon_peak_demand = 0.0 - val = @sql.peakEnergyDemandByMonth(end_use_fuel_type, end_use_cat, month) - if val.is_initialized - mon_peak_demand_W = OpenStudio::Quantity.new(val.get, watt_unit) - mon_peak_demand = OpenStudio.convert(mon_peak_demand_W, kilowatt_unit).get.value - end - # record the monthly value - fuel_type_elems << OpenStudio::Attribute.new('month', mon_peak_demand, 'kW') - @runner.registerValue("monthly_demand_#{end_use_name}_#{fuel_type_name}_month_#{i + 1}", mon_peak_demand, 'kW') - # if month peak demand > ann peak demand make this new ann peak demand - if mon_peak_demand > ann_peak_demand - ann_peak_demand = mon_peak_demand - end - end - # record the annual peak demand - fuel_type_elems << OpenStudio::Attribute.new('year', ann_peak_demand, 'kW') - @runner.registerValue("monthly_demand_#{end_use_name}_#{fuel_type_name}_year", ann_peak_demand, 'kW') - # add this fuel type - end_use_elems << OpenStudio::Attribute.new(fuel_type_alias_map[end_use_fuel_type.value], fuel_type_elems) - end - # add this end use - demand_elems << OpenStudio::Attribute.new(end_use_map[end_use_cat.value], end_use_elems) - end - # end demand - monthly_elems << OpenStudio::Attribute.new('demand', demand_elems) - - # end monthly - result_elems << OpenStudio::Attribute.new('monthly', monthly_elems) - - result_elem = OpenStudio::Attribute.new('results', result_elems) - return result_elem - end # end create_results -end +# ******************************************************************************* +# OpenStudio(R), Copyright (c) 2008-2019, 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. +# ******************************************************************************* + +module OsLib_CreateResults + # Reports out the detailed simulation results needed by EDAPT. + # Results are output as both OpenStudio::Attributes (for OpenStudio 1.X) + # and runner.registerValue (for OpenStudio 2.X). + # @param skip_weekends [Bool] if true, weekends will not be included in the peak demand window + # @param skip_holidays [Bool] if true, holidays will not be included in the peak demand window + # @param start_mo [String] the start month for the peak demand window + # @param start_day [Integer] the start day for the peak demand window + # @param start_hr [Integer] the start hour for the peak demand window, using 24-hr clock + # @param end_mo [String] the end month for the peak demand window + # @param end_day [Integer] the end day for the peak demand window + # @param end_hr [Integer] the end hour for the peak demand window, using 24-hr clock + # @return [OpenStudio::AttributeVector] a vector of results needed by EDAPT + def create_results(skip_weekends = true, + skip_holidays = true, + start_mo = 'June', + start_day = 1, + start_hr = 14, + end_mo = 'September', + end_day = 30, + end_hr = 18) + + # get the current version of OS being used to determine if sql query + # changes are needed (for when E+ changes). + os_version = OpenStudio::VersionString.new(OpenStudio.openStudioVersion) + + # create an attribute vector to hold results + result_elems = OpenStudio::AttributeVector.new + + # floor_area + floor_area_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='AnnualBuildingUtilityPerformanceSummary' AND ReportForString='Entire Facility' AND TableName='Building Area' AND RowName='Net Conditioned Building Area' AND ColumnName='Area' AND Units='m2'" + floor_area = @sql.execAndReturnFirstDouble(floor_area_query) + if floor_area.is_initialized + result_elems << OpenStudio::Attribute.new('floor_area', floor_area.get, 'm^2') + @runner.registerValue('charsfloor_area', floor_area.get, 'm^2') + else + @runner.registerWarning('Building floor area not found') + return false + end + + # inflation approach + inf_appr_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Life-Cycle Cost Parameters' AND RowName='Inflation Approach' AND ColumnName='Value'" + inf_appr = @sql.execAndReturnFirstString(inf_appr_query) + if inf_appr.is_initialized + if inf_appr.get == 'ConstantDollar' + inf_appr = 'Constant Dollar' + elsif inf_appr.get == 'CurrentDollar' + inf_appr = 'Current Dollar' + else + @runner.registerError("Inflation approach: #{inf_appr.get} not recognized") + return OpenStudio::Attribute.new('report', result_elems) + end + @runner.registerInfo("Inflation approach = #{inf_appr}") + else + @runner.registerError('Could not determine inflation approach used') + return OpenStudio::Attribute.new('report', result_elems) + end + + # base year + base_yr_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Life-Cycle Cost Parameters' AND RowName='Base Date' AND ColumnName='Value'" + base_yr = @sql.execAndReturnFirstString(base_yr_query) + if base_yr.is_initialized + if base_yr.get =~ /\d\d\d\d/ + base_yr = base_yr.get.match(/\d\d\d\d/)[0].to_f + else + @runner.registerError("Could not determine the analysis start year from #{base_yr.get}") + return OpenStudio::Attribute.new('report', result_elems) + end + else + @runner.registerError('Could not determine analysis start year') + return OpenStudio::Attribute.new('report', result_elems) + end + + # analysis length + length_yrs_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Life-Cycle Cost Parameters' AND RowName='Length of Study Period in Years' AND ColumnName='Value'" + length_yrs = @sql.execAndReturnFirstInt(length_yrs_query) + if length_yrs.is_initialized + @runner.registerInfo "Analysis length = #{length_yrs.get} yrs" + length_yrs = length_yrs.get + else + @runner.registerError('Could not determine analysis length') + return OpenStudio::Attribute.new('report', result_elems) + end + + # cash flows + cash_flow_elems = OpenStudio::AttributeVector.new + + # setup a vector for each type of cash flow + cap_cash_flow_elems = OpenStudio::AttributeVector.new + om_cash_flow_elems = OpenStudio::AttributeVector.new + energy_cash_flow_elems = OpenStudio::AttributeVector.new + water_cash_flow_elems = OpenStudio::AttributeVector.new + tot_cash_flow_elems = OpenStudio::AttributeVector.new + + # add the type to the element + cap_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Capital Costs") + om_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Operating Costs") + energy_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Energy Costs") + water_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Water Costs") + tot_cash_flow_elems << OpenStudio::Attribute.new('type', "#{inf_appr} Total Costs") + + @runner.registerValue('cash_flows_capital_type', "#{inf_appr} Capital Costs") + @runner.registerValue('cash_flows_operating_type', "#{inf_appr} Operating Costs") + @runner.registerValue('cash_flows_energy_type', "#{inf_appr} Energy Costs") + @runner.registerValue('cash_flows_water_type', "#{inf_appr} Water Costs") + @runner.registerValue('cash_flows_total_type', "#{inf_appr} Total Costs") + + # record the cash flow in these hashes + cap_cash_flow = {} + om_cash_flow = {} + energy_cash_flow = {} + water_cash_flow = {} + tot_cash_flow = {} + + # loop through each year and record the cash flow + for i in 0..(length_yrs - 1) do + new_yr = base_yr + i + + yr = nil + if os_version > OpenStudio::VersionString.new('1.5.3') + yr = "January #{new_yr.round}" + else + yr = "January #{new_yr.round}" + end + + ann_cap_cash = 0.0 + ann_om_cash = 0.0 + ann_energy_cash = 0.0 + ann_water_cash = 0.0 + ann_tot_cash = 0.0 + + # capital cash flow + cap_cash_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Capital Cash Flow by Category (Without Escalation)' AND RowName='#{yr}' AND ColumnName='Total'" + cap_cash = @sql.execAndReturnFirstDouble(cap_cash_query) + if cap_cash.is_initialized + ann_cap_cash += cap_cash.get + ann_tot_cash += cap_cash.get + end + + # o&m cash flow (excluding utility costs) + om_types = ['Maintenance', 'Repair', 'Operation', 'Replacement', 'MinorOverhaul', 'MajorOverhaul', 'OtherOperational'] + om_types.each do |om_type| + om_cash_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Operating Cash Flow by Category (Without Escalation)' AND RowName='#{yr}' AND ColumnName='#{om_type}'" + om_cash = @sql.execAndReturnFirstDouble(om_cash_query) + if om_cash.is_initialized + ann_om_cash += om_cash.get + ann_tot_cash += om_cash.get + end + end + + # energy cash flow + energy_cash_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Operating Cash Flow by Category (Without Escalation)' AND RowName='#{yr}' AND ColumnName='Energy'" + energy_cash = @sql.execAndReturnFirstDouble(energy_cash_query) + if energy_cash.is_initialized + ann_energy_cash += energy_cash.get + ann_tot_cash += energy_cash.get + end + + # water cash flow + water_cash_query = "SELECT Value FROM tabulardatawithstrings WHERE ReportName='Life-Cycle Cost Report' AND ReportForString='Entire Facility' AND TableName='Operating Cash Flow by Category (Without Escalation)' AND RowName='#{yr}' AND ColumnName='Water'" + water_cash = @sql.execAndReturnFirstDouble(water_cash_query) + if water_cash.is_initialized + ann_water_cash += water_cash.get + ann_tot_cash += water_cash.get + end + + # log the values for this year + cap_cash_flow[yr] = ann_cap_cash + om_cash_flow[yr] = ann_om_cash + energy_cash_flow[yr] = ann_energy_cash + water_cash_flow[yr] = ann_water_cash + tot_cash_flow[yr] = ann_tot_cash + + cap_cash_flow_elems << OpenStudio::Attribute.new('year', ann_cap_cash, 'dollars') + om_cash_flow_elems << OpenStudio::Attribute.new('year', ann_om_cash, 'dollars') + energy_cash_flow_elems << OpenStudio::Attribute.new('year', ann_energy_cash, 'dollars') + water_cash_flow_elems << OpenStudio::Attribute.new('year', ann_water_cash, 'dollars') + tot_cash_flow_elems << OpenStudio::Attribute.new('year', ann_tot_cash, 'dollars') + + @runner.registerValue("cash_flows_capital_year_#{i + 1}", ann_cap_cash, 'dollars') + @runner.registerValue("cash_flows_operating_year_#{i + 1}", ann_om_cash, 'dollars') + @runner.registerValue("cash_flows_energy_year_#{i + 1}", ann_energy_cash, 'dollars') + @runner.registerValue("cash_flows_water_year_#{i + 1}", ann_water_cash, 'dollars') + @runner.registerValue("cash_flows_total_year_#{i + 1}", ann_tot_cash, 'dollars') + + end # next year + + # end cash flows + cash_flow_elems << OpenStudio::Attribute.new('cash_flow', cap_cash_flow_elems) + cash_flow_elems << OpenStudio::Attribute.new('cash_flow', om_cash_flow_elems) + cash_flow_elems << OpenStudio::Attribute.new('cash_flow', energy_cash_flow_elems) + cash_flow_elems << OpenStudio::Attribute.new('cash_flow', water_cash_flow_elems) + cash_flow_elems << OpenStudio::Attribute.new('cash_flow', tot_cash_flow_elems) + result_elems << OpenStudio::Attribute.new('cash_flows', cash_flow_elems) + + # list of all end uses in OpenStudio + end_use_cat_types = [] + OpenStudio::EndUseCategoryType.getValues.each do |end_use_val| + end_use_cat_types << OpenStudio::EndUseCategoryType.new(end_use_val) + end + + # list of all end use fule types in OpenStudio + end_use_fuel_types = [] + OpenStudio::EndUseFuelType.getValues.each do |end_use_fuel_type_val| + end_use_fuel_types << OpenStudio::EndUseFuelType.new(end_use_fuel_type_val) + end + + # list of the 12 months of the year in OpenStudio + months = [] + OpenStudio::MonthOfYear.getValues.each do |month_of_year_val| + if (month_of_year_val >= 1) && (month_of_year_val <= 12) + months << OpenStudio::MonthOfYear.new(month_of_year_val) + end + end + + # map each end use category type to the name that will be used in the xml + end_use_map = { + OpenStudio::EndUseCategoryType.new('Heating').value => 'heating', + OpenStudio::EndUseCategoryType.new('Cooling').value => 'cooling', + OpenStudio::EndUseCategoryType.new('InteriorLights').value => 'lighting_interior', + OpenStudio::EndUseCategoryType.new('ExteriorLights').value => 'lighting_exterior', + OpenStudio::EndUseCategoryType.new('InteriorEquipment').value => 'equipment_interior', + OpenStudio::EndUseCategoryType.new('ExteriorEquipment').value => 'equipment_exterior', + OpenStudio::EndUseCategoryType.new('Fans').value => 'fans', + OpenStudio::EndUseCategoryType.new('Pumps').value => 'pumps', + OpenStudio::EndUseCategoryType.new('HeatRejection').value => 'heat_rejection', + OpenStudio::EndUseCategoryType.new('Humidifier').value => 'humidification', + OpenStudio::EndUseCategoryType.new('HeatRecovery').value => 'heat_recovery', + OpenStudio::EndUseCategoryType.new('WaterSystems').value => 'water_systems', + OpenStudio::EndUseCategoryType.new('Refrigeration').value => 'refrigeration', + OpenStudio::EndUseCategoryType.new('Generators').value => 'generators' + } + + # map each fuel type in EndUseFuelTypes to a specific FuelTypes + fuel_type_map = { + OpenStudio::EndUseFuelType.new('Electricity').value => OpenStudio::FuelType.new('Electricity'), + OpenStudio::EndUseFuelType.new('Gas').value => OpenStudio::FuelType.new('Gas'), + OpenStudio::EndUseFuelType.new('AdditionalFuel').value => OpenStudio::FuelType.new('Diesel'), # TODO: add other fuel types + OpenStudio::EndUseFuelType.new('DistrictCooling').value => OpenStudio::FuelType.new('DistrictCooling'), + OpenStudio::EndUseFuelType.new('DistrictHeating').value => OpenStudio::FuelType.new('DistrictHeating'), + OpenStudio::EndUseFuelType.new('Water').value => OpenStudio::FuelType.new('Water') + } + + # map each fuel type in EndUseFuelTypes to a specific FuelTypes + fuel_type_alias_map = { + OpenStudio::EndUseFuelType.new('Electricity').value => 'electricity', + OpenStudio::EndUseFuelType.new('Gas').value => 'gas', + OpenStudio::EndUseFuelType.new('AdditionalFuel').value => 'other_energy', + OpenStudio::EndUseFuelType.new('DistrictCooling').value => 'district_cooling', + OpenStudio::EndUseFuelType.new('DistrictHeating').value => 'district_heating', + OpenStudio::EndUseFuelType.new('Water').value => 'water' + } + + # annual "annual" + annual_elems = OpenStudio::AttributeVector.new + + # consumption "consumption" + cons_elems = OpenStudio::AttributeVector.new + + # electricity + electricity = @sql.electricityTotalEndUses + if electricity.is_initialized + cons_elems << OpenStudio::Attribute.new('electricity', electricity.get, 'GJ') + @runner.registerValue('annual_consumption_electricity', electricity.get, 'GJ') + else + cons_elems << OpenStudio::Attribute.new('electricity', 0.0, 'GJ') + @runner.registerValue('annual_consumption_electricity', 0.0, 'GJ') + end + + # gas + gas = @sql.naturalGasTotalEndUses + if gas.is_initialized + cons_elems << OpenStudio::Attribute.new('gas', gas.get, 'GJ') + @runner.registerValue('annual_consumption_gas', gas.get, 'GJ') + else + cons_elems << OpenStudio::Attribute.new('gas', 0.0, 'GJ') + @runner.registerValue('annual_consumption_gas', 0.0, 'GJ') + end + + # other_energy + other_energy = @sql.otherFuelTotalEndUses + if other_energy.is_initialized + cons_elems << OpenStudio::Attribute.new('other_energy', other_energy.get, 'GJ') + @runner.registerValue('annual_consumption_other_energy', other_energy.get, 'GJ') + else + cons_elems << OpenStudio::Attribute.new('other_energy', 0.0, 'GJ') + @runner.registerValue('annual_consumption_other_energy', 0.0, 'GJ') + end + + # district_cooling + district_cooling = @sql.districtCoolingTotalEndUses + if district_cooling.is_initialized + cons_elems << OpenStudio::Attribute.new('district_cooling', district_cooling.get, 'GJ') + @runner.registerValue('annual_consumption_district_cooling', district_cooling.get, 'GJ') + else + cons_elems << OpenStudio::Attribute.new('district_cooling', 0.0, 'GJ') + @runner.registerValue('annual_consumption_district_cooling', 0.0, 'GJ') + end + + # district_heating + district_heating = @sql.districtHeatingTotalEndUses + if district_heating.is_initialized + cons_elems << OpenStudio::Attribute.new('district_heating', district_heating.get, 'GJ') + @runner.registerValue('annual_consumption_district_heating', district_heating.get, 'GJ') + else + cons_elems << OpenStudio::Attribute.new('district_heating', 0.0, 'GJ') + @runner.registerValue('annual_consumption_district_heating', 0.0, 'GJ') + end + + # water + water = @sql.waterTotalEndUses + if water.is_initialized + cons_elems << OpenStudio::Attribute.new('water', water.get, 'm^3') + @runner.registerValue('annual_consumption_water', water.get, 'm^3') + else + cons_elems << OpenStudio::Attribute.new('water', 0.0, 'm^3') + @runner.registerValue('annual_consumption_water', 0.0, 'm^3') + end + + # end consumption + annual_elems << OpenStudio::Attribute.new('consumption', cons_elems) + + # demand "demand" + demand_elems = OpenStudio::AttributeVector.new + + # get the weather file run period (as opposed to design day run period) + ann_env_pd = nil + @sql.availableEnvPeriods.each do |env_pd| + env_type = @sql.environmentType(env_pd) + if env_type.is_initialized + if env_type.get == OpenStudio::EnvironmentType.new('WeatherRunPeriod') + ann_env_pd = env_pd + end + end + end + + # only try to get the annual peak demand if an annual simulation was run + if ann_env_pd + + # create some units to use + joule_unit = OpenStudio.createUnit('J').get + gigajoule_unit = OpenStudio.createUnit('GJ').get + hrs_unit = OpenStudio.createUnit('h').get + kilowatt_unit = OpenStudio.createUnit('kW').get + + # get the annual hours simulated + hrs_sim = '(0 - no partial annual simulation)' + if @sql.hoursSimulated.is_initialized + hrs_sim = @sql.hoursSimulated.get + if hrs_sim != 8760 + @runner.registerError("Simulation was only #{hrs_sim} hrs; EDA requires an annual simulation (8760 hrs)") + return OpenStudio::Attribute.new('report', result_elems) + end + end + + # Get the electricity timeseries to determine the year used + elec = @sql.timeSeries(ann_env_pd, 'Zone Timestep', 'Electricity:Facility', '') + timeseries_yr = nil + if elec.is_initialized + timeseries_yr = elec.get.dateTimes[0].date.year + else + @runner.registerError('Peak Demand timeseries (Electricity:Facility at zone timestep) could not be found, cannot determine the informatino needed to calculate savings or incentives.') + end + # Setup the peak demand time window based on input arguments. + # Note that holidays and weekends are not excluded because + # of a bug in EnergyPlus dates. + # This will only impact corner-case buildings that have + # peak demand on weekends or holidays, which is unusual. + @runner.registerInfo("Peak Demand window is #{start_mo} #{start_day} to #{end_mo} #{end_day} from #{start_hr}:00 to #{end_hr}:00.") + start_date = OpenStudio::DateTime.new(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(start_mo), start_day, timeseries_yr), OpenStudio::Time.new(0, 0, 0, 0)) + end_date = OpenStudio::DateTime.new(OpenStudio::Date.new(OpenStudio::MonthOfYear.new(end_mo), end_day, timeseries_yr), OpenStudio::Time.new(0, 24, 0, 0)) + start_time = OpenStudio::Time.new(0, start_hr, 0, 0) + end_time = OpenStudio::Time.new(0, end_hr, 0, 0) + + # Get the day type timeseries. + day_types = nil + day_type_indices = @sql.timeSeries(ann_env_pd, 'Zone Timestep', 'Site Day Type Index', 'Environment') + if day_type_indices.is_initialized + # Put values into array + day_types = [] + day_type_vals = day_type_indices.get.values + for i in 0..(day_type_vals.size - 1) + day_types << day_type_vals[i] + end + else + @runner.registerError('Day Type timeseries (Site Day Type Index at zone timestep) could not be found, cannot accurately determine the peak demand.') + end + + # electricity_peak_demand + electricity_peak_demand = -1.0 + electricity_peak_demand_time = nil + # deduce the timestep based on the hours simulated and the number of datapoints in the timeseries + if elec.is_initialized && day_types + elec = elec.get + num_int = elec.values.size + int_len_hrs = OpenStudio::Quantity.new(hrs_sim / num_int, hrs_unit) + + # Put timeseries into array + elec_vals = [] + ann_elec_vals = elec.values + for i in 0..(ann_elec_vals.size - 1) + elec_vals << ann_elec_vals[i] + end + + # Put values into array + elec_times = [] + ann_elec_times = elec.dateTimes + for i in 0..(ann_elec_times.size - 1) + elec_times << ann_elec_times[i] + end + + # Loop through the time/value pairs and find the peak + # excluding the times outside of the Xcel peak demand window + elec_times.zip(elec_vals).each_with_index do |vs, ind| + date_time = vs[0] + val = vs[1] + day_type = day_types[ind] + time = date_time.time + date = date_time.date + day_of_week = date.dayOfWeek + # Convert the peak demand to kW + val_J_per_hr = val / int_len_hrs.value + val_kW = OpenStudio.convert(val_J_per_hr, 'J/h', 'kW').get + + # puts("#{val_kW}kW; #{date}; #{time}; #{day_of_week.valueName}") + + # Skip times outside of the correct months + next if date_time < start_date || date_time > end_date + # Skip times before 2pm and after 6pm + next if time < start_time || time > end_time + # Skip weekends if asked + if skip_weekends + # Sunday = 1, Saturday = 7 + next if day_type == 1 || day_type == 7 + end + # Skip holidays if asked + if skip_holidays + # Holiday = 8 + next if day_type == 8 + end + + # puts("VALID #{val_kW}kW; #{date}; #{time}; #{day_of_week.valueName}") + + # Check peak demand against this timestep + # and update if this timestep is higher. + if val > electricity_peak_demand + electricity_peak_demand = val + electricity_peak_demand_time = date_time + end + end + elec_peak_demand_timestep_J = OpenStudio::Quantity.new(electricity_peak_demand, joule_unit) + num_int = elec.values.size + int_len_hrs = OpenStudio::Quantity.new(hrs_sim / num_int, hrs_unit) + elec_peak_demand_hourly_J_per_hr = elec_peak_demand_timestep_J / int_len_hrs + electricity_peak_demand = OpenStudio.convert(elec_peak_demand_hourly_J_per_hr, kilowatt_unit).get.value + demand_elems << OpenStudio::Attribute.new('electricity_peak_demand', electricity_peak_demand, 'kW') + @runner.registerValue('annual_demand_electricity_peak_demand', electricity_peak_demand, 'kW') + @runner.registerInfo("Peak Demand = #{electricity_peak_demand.round(2)}kW on #{electricity_peak_demand_time}") + else + @runner.registerError('Peak Demand timeseries (Electricity:Facility at zone timestep) could not be found, cannot determine the informatino needed to calculate savings or incentives.') + demand_elems << OpenStudio::Attribute.new('electricity_peak_demand', 0.0, 'kW') + @runner.registerValue('annual_demand_electricity_peak_demand', 0.0, 'kW') + end + + # electricity_annual_avg_peak_demand + val = @sql.electricityTotalEndUses + if val.is_initialized + ann_elec_gj = OpenStudio::Quantity.new(val.get, gigajoule_unit) + ann_hrs = OpenStudio::Quantity.new(hrs_sim, hrs_unit) + elec_ann_avg_peak_demand_hourly_GJ_per_hr = ann_elec_gj / ann_hrs + electricity_annual_avg_peak_demand = OpenStudio.convert(elec_ann_avg_peak_demand_hourly_GJ_per_hr, kilowatt_unit).get.value + demand_elems << OpenStudio::Attribute.new('electricity_annual_avg_peak_demand', electricity_annual_avg_peak_demand, 'kW') + @runner.registerValue('annual_demand_electricity_annual_avg_peak_demand', electricity_annual_avg_peak_demand, 'kW') + else + demand_elems << OpenStudio::Attribute.new('electricity_annual_avg_peak_demand', 0.0, 'kW') + @runner.registerValue('annual_demand_electricity_annual_avg_peak_demand', 0.0, 'kW') + end + + # district_cooling_peak_demand + district_cooling_peak_demand = -1.0 + ann_dist_clg_peak_demand_time = nil + dist_clg = @sql.timeSeries(ann_env_pd, 'Zone Timestep', 'DistrictCooling:Facility', '') + # deduce the timestep based on the hours simulated and the number of datapoints in the timeseries + if dist_clg.is_initialized && day_types + dist_clg = dist_clg.get + num_int = dist_clg.values.size + int_len_hrs = OpenStudio::Quantity.new(hrs_sim / num_int, hrs_unit) + + # Put timeseries into array + dist_clg_vals = [] + ann_dist_clg_vals = dist_clg.values + for i in 0..(ann_dist_clg_vals.size - 1) + dist_clg_vals << ann_dist_clg_vals[i] + end + + # Put values into array + dist_clg_times = [] + ann_dist_clg_times = dist_clg.dateTimes + for i in 0..(ann_dist_clg_times.size - 1) + dist_clg_times << ann_dist_clg_times[i] + end + + # Loop through the time/value pairs and find the peak + # excluding the times outside of the Xcel peak demand window + dist_clg_times.zip(dist_clg_vals).each_with_index do |vs, ind| + date_time = vs[0] + val = vs[1] + day_type = day_types[ind] + time = date_time.time + date = date_time.date + day_of_week = date.dayOfWeek + # Convert the peak demand to kW + val_J_per_hr = val / int_len_hrs.value + val_kW = OpenStudio.convert(val_J_per_hr, 'J/h', 'kW').get + + # puts("#{val_kW}kW; #{date}; #{time}; #{day_of_week.valueName}") + + # Skip times outside of the correct months + next if date_time < start_date || date_time > end_date + # Skip times before 2pm and after 6pm + next if time < start_time || time > end_time + # Skip weekends if asked + if skip_weekends + # Sunday = 1, Saturday = 7 + next if day_type == 1 || day_type == 7 + end + # Skip holidays if asked + if skip_holidays + # Holiday = 8 + next if day_type == 8 + end + + # puts("VALID #{val_kW}kW; #{date}; #{time}; #{day_of_week.valueName}") + + # Check peak demand against this timestep + # and update if this timestep is higher. + if val > district_cooling_peak_demand + district_cooling_peak_demand = val + ann_dist_clg_peak_demand_time = date_time + end + end + dist_clg_peak_demand_timestep_J = OpenStudio::Quantity.new(district_cooling_peak_demand, joule_unit) + num_int = dist_clg.values.size + int_len_hrs = OpenStudio::Quantity.new(hrs_sim / num_int, hrs_unit) + dist_clg_peak_demand_hourly_J_per_hr = dist_clg_peak_demand_timestep_J / int_len_hrs + district_cooling_peak_demand = OpenStudio.convert(dist_clg_peak_demand_hourly_J_per_hr, kilowatt_unit).get.value + demand_elems << OpenStudio::Attribute.new('district_cooling_peak_demand', district_cooling_peak_demand, 'kW') + @runner.registerValue('annual_demand_district_cooling_peak_demand', district_cooling_peak_demand, 'kW') + @runner.registerInfo("District Cooling Peak Demand = #{district_cooling_peak_demand.round(2)}kW on #{ann_dist_clg_peak_demand_time}") + else + demand_elems << OpenStudio::Attribute.new('district_cooling_peak_demand', 0.0, 'kW') + @runner.registerValue('annual_demand_district_cooling_peak_demand', 0.0, 'kW') + end + + else + @runner.registerError('Could not find an annual run period') + return OpenStudio::Attribute.new('report', result_elems) + end + + # end demand + annual_elems << OpenStudio::Attribute.new('demand', demand_elems) + + # utility_cost + utility_cost_elems = OpenStudio::AttributeVector.new + annual_utility_cost_map = {} + + # electricity + electricity = @sql.annualTotalCost(OpenStudio::FuelType.new('Electricity')) + if electricity.is_initialized + utility_cost_elems << OpenStudio::Attribute.new('electricity', electricity.get, 'dollars') + @runner.registerValue('annual_utility_cost_electricity', electricity.get, 'dollars') + annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Electricity').valueName] = electricity.get + else + utility_cost_elems << OpenStudio::Attribute.new('electricity', 0.0, 'dollars') + @runner.registerValue('annual_utility_cost_electricity', 0.0, 'dollars') + annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Electricity').valueName] = 0.0 + end + + # electricity_consumption_charge and electricity_demand_charge + electric_consumption_charge = 0.0 + electric_demand_charge = 0.0 + + electric_rate_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-3. Energy Type Summary' AND RowName='Electricity' AND ColumnName='Utility Rate'" + electric_rate_name = @sql.execAndReturnFirstString(electric_rate_query) + if electric_rate_name.is_initialized + electric_rate_name = electric_rate_name.get.strip + + # electricity_consumption_charge + electric_consumption_charge_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='Tariff Report' AND ReportForString='#{electric_rate_name}' AND TableName='Categories' AND RowName='EnergyCharges (~~$~~)' AND ColumnName='Sum'" + val = @sql.execAndReturnFirstDouble(electric_consumption_charge_query) + if val.is_initialized + electric_consumption_charge = val.get + end + + # electricity_demand_charge + electric_demand_charge_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='Tariff Report' AND ReportForString='#{electric_rate_name}' AND TableName='Categories' AND RowName='DemandCharges (~~$~~)' AND ColumnName='Sum'" + val = @sql.execAndReturnFirstDouble(electric_demand_charge_query) + if val.is_initialized + electric_demand_charge = val.get + end + + end + utility_cost_elems << OpenStudio::Attribute.new('electricity_consumption_charge', electric_consumption_charge, 'dollars') + @runner.registerValue('annual_utility_cost_electricity_consumption_charge', electric_consumption_charge, 'dollars') + utility_cost_elems << OpenStudio::Attribute.new('electricity_demand_charge', electric_demand_charge, 'dollars') + @runner.registerValue('annual_utility_cost_electricity_demand_charge', electric_demand_charge, 'dollars') + + # gas + gas = @sql.annualTotalCost(OpenStudio::FuelType.new('Gas')) + if gas.is_initialized + annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Gas').valueName] = gas.get + else + annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Gas').valueName] = 0.0 + end + + # district_cooling + district_cooling_charge = 0.0 + + district_cooling_rate_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-3. Energy Type Summary' AND RowName='District Cooling' AND ColumnName='Utility Rate'" + district_cooling_rate_name = @sql.execAndReturnFirstString(district_cooling_rate_query) + if district_cooling_rate_name.is_initialized + district_cooling_rate_name = district_cooling_rate_name.get.strip + + # district_cooling_charge + district_cooling_charge_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='Tariff Report' AND ReportForString='#{district_cooling_rate_name}' AND TableName='Categories' AND RowName='Basis (~~$~~)' AND ColumnName='Sum'" + val = @sql.execAndReturnFirstDouble(district_cooling_charge_query) + if val.is_initialized + district_cooling_charge = val.get + end + + end + annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictCooling').valueName] = district_cooling_charge + + # district_heating + district_heating_charge = 0.0 + + district_heating_rate_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='LEEDsummary' AND ReportForString='Entire Facility' AND TableName='EAp2-3. Energy Type Summary' AND RowName='District Heating' AND ColumnName='Utility Rate'" + district_heating_rate_name = @sql.execAndReturnFirstString(district_heating_rate_query) + if district_heating_rate_name.is_initialized + district_heating_rate_name = district_heating_rate_name.get.strip + + # district_heating_charge + district_heating_charge_query = "SELECT value FROM tabulardatawithstrings WHERE ReportName='Tariff Report' AND ReportForString='#{district_heating_rate_name}' AND TableName='Categories' AND RowName='Basis (~~$~~)' AND ColumnName='Sum'" + val = @sql.execAndReturnFirstDouble(district_heating_charge_query) + if val.is_initialized + district_heating_charge = val.get + end + + end + annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictHeating').valueName] = district_heating_charge + + # water + water = @sql.annualTotalCost(OpenStudio::FuelType.new('Water')) + if water.is_initialized + annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Water').valueName] = water.get + else + annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Water').valueName] = 0.0 + end + + # total + total_query = "SELECT Value from tabulardatawithstrings where (reportname = 'Economics Results Summary Report') and (ReportForString = 'Entire Facility') and (TableName = 'Annual Cost') and (ColumnName ='Total') and (((RowName = 'Cost') and (Units = '~~$~~')) or (RowName = 'Cost (~~$~~)'))" + total = @sql.execAndReturnFirstDouble(total_query) + + # other_energy + # Subtract off the already accounted for fuel types from the total + # to account for fuels on custom meters where the fuel type is not known. + prev_tot = 0.0 + annual_utility_cost_map.each do |fuel, val| + prev_tot += val + end + if total.is_initialized + other_val = total.get - prev_tot + annual_utility_cost_map[OpenStudio::EndUseFuelType.new('AdditionalFuel').valueName] = other_val + else + annual_utility_cost_map[OpenStudio::EndUseFuelType.new('AdditionalFuel').valueName] = 0.0 + end + + # export remaining costs in the correct order + # gas + utility_cost_elems << OpenStudio::Attribute.new('gas', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Gas').valueName], 'dollars') + @runner.registerValue('annual_utility_cost_gas', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Gas').valueName], 'dollars') + # other_energy + utility_cost_elems << OpenStudio::Attribute.new('other_energy', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('AdditionalFuel').valueName], 'dollars') + @runner.registerValue('annual_utility_cost_other_energy', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('AdditionalFuel').valueName], 'dollars') + # district_cooling + utility_cost_elems << OpenStudio::Attribute.new('district_cooling', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictCooling').valueName], 'dollars') + @runner.registerValue('annual_utility_cost_district_cooling', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictCooling').valueName], 'dollars') + # district_heating + utility_cost_elems << OpenStudio::Attribute.new('district_heating', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictHeating').valueName], 'dollars') + @runner.registerValue('annual_utility_cost_district_heating', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('DistrictHeating').valueName], 'dollars') + # water + utility_cost_elems << OpenStudio::Attribute.new('water', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Water').valueName], 'dollars') + @runner.registerValue('annual_utility_cost_water', annual_utility_cost_map[OpenStudio::EndUseFuelType.new('Water').valueName], 'dollars') + # total + if total.is_initialized + utility_cost_elems << OpenStudio::Attribute.new('total', total.get, 'dollars') + @runner.registerValue('annual_utility_cost_total', total.get, 'dollars') + else + utility_cost_elems << OpenStudio::Attribute.new('total', 0.0, 'dollars') + @runner.registerValue('annual_utility_cost_total', 0.0, 'dollars') + end + + # end_uses - utility costs by end use using average blended cost + end_uses_elems = OpenStudio::AttributeVector.new + # map to store the costs by end use + cost_by_end_use = {} + + # fill the map with 0.0's to start + end_use_cat_types.each do |end_use_cat_type| + cost_by_end_use[end_use_cat_type] = 0.0 + end + + # only attempt to get monthly data if enduses table is available + if @sql.endUses.is_initialized + end_uses_table = @sql.endUses.get + # loop through all the fuel types + end_use_fuel_types.each do |end_use_fuel_type| + # get the annual total cost for this fuel type + ann_cost = annual_utility_cost_map[end_use_fuel_type.valueName] + # get the total annual usage for this fuel type in all end use categories + # loop through all end uses, adding the annual usage value to the aggregator + ann_usg = 0.0 + end_use_cat_types.each do |end_use_cat_type| + ann_usg += end_uses_table.getEndUse(end_use_fuel_type, end_use_cat_type) + end + # figure out the annual blended rate for this fuel type + avg_ann_rate = 0.0 + if ann_cost > 0 && ann_usg > 0 + avg_ann_rate = ann_cost / ann_usg + end + # for each end use category, figure out the cost if using + # the avg ann rate; add this cost to the map + end_use_cat_types.each do |end_use_cat_type| + cost_by_end_use[end_use_cat_type] += end_uses_table.getEndUse(end_use_fuel_type, end_use_cat_type) * avg_ann_rate + end + end + # loop through the end uses and record the annual total cost based on the avg annual rate + end_use_cat_types.each do |end_use_cat_type| + # record the value + end_uses_elems << OpenStudio::Attribute.new(end_use_map[end_use_cat_type.value], cost_by_end_use[end_use_cat_type], 'dollars') + @runner.registerValue("annual_utility_cost_end_uses_#{end_use_map[end_use_cat_type.value]}", cost_by_end_use[end_use_cat_type], 'dollars') + end + else + @runner.registerError('End-Use table not available in results; could not retrieve monthly costs by end use') + return OpenStudio::Attribute.new('report', result_elems) + end + + # end end_uses + utility_cost_elems << OpenStudio::Attribute.new('end_uses', end_uses_elems) + + # end utility_costs + annual_elems << OpenStudio::Attribute.new('utility_cost', utility_cost_elems) + + # end annual + result_elems << OpenStudio::Attribute.new('annual', annual_elems) + + # monthly + monthly_elems = OpenStudio::AttributeVector.new + + # consumption + cons_elems = OpenStudio::AttributeVector.new + # loop through all end uses + end_use_cat_types.each do |end_use_cat| + end_use_elems = OpenStudio::AttributeVector.new + end_use_name = end_use_map[end_use_cat.value] + # in each end use, loop through all fuel types + end_use_fuel_types.each do |end_use_fuel_type| + fuel_type_elems = OpenStudio::AttributeVector.new + fuel_type_name = fuel_type_alias_map[end_use_fuel_type.value] + ann_energy_cons = 0.0 + # in each end use, loop through months and get monthly enedy consumption + months.each_with_index do |month, i| + mon_energy_cons = 0.0 + val = @sql.energyConsumptionByMonth(end_use_fuel_type, end_use_cat, month) + if val.is_initialized + monthly_consumption_J = OpenStudio::Quantity.new(val.get, joule_unit) + monthly_consumption_GJ = OpenStudio.convert(monthly_consumption_J, gigajoule_unit).get.value + mon_energy_cons = monthly_consumption_GJ + ann_energy_cons += monthly_consumption_GJ + end + # record the monthly value + if end_use_fuel_type == OpenStudio::EndUseFuelType.new('Water') + fuel_type_elems << OpenStudio::Attribute.new('month', mon_energy_cons, 'm^3') + @runner.registerValue("monthly_consumption_#{end_use_name}_#{fuel_type_name}_month_#{i + 1}", mon_energy_cons, 'm^3') + else + fuel_type_elems << OpenStudio::Attribute.new('month', mon_energy_cons, 'GJ') + @runner.registerValue("monthly_consumption_#{end_use_name}_#{fuel_type_name}_month_#{i + 1}", mon_energy_cons, 'GJ') + end + end + # record the annual total + fuel_type_elems << OpenStudio::Attribute.new('year', ann_energy_cons, 'GJ') + @runner.registerValue("monthly_consumption_#{end_use_name}_#{fuel_type_name}_year", ann_energy_cons, 'GJ') + # add this fuel type + end_use_elems << OpenStudio::Attribute.new(fuel_type_alias_map[end_use_fuel_type.value], fuel_type_elems) + end + # add this end use + cons_elems << OpenStudio::Attribute.new(end_use_map[end_use_cat.value], end_use_elems) + end + # end consumption + monthly_elems << OpenStudio::Attribute.new('consumption', cons_elems) + + # create a unit to use + watt_unit = OpenStudio.createUnit('W').get + kilowatt_unit = OpenStudio.createUnit('kW').get + + # demand + demand_elems = OpenStudio::AttributeVector.new + # loop through all end uses + end_use_cat_types.each do |end_use_cat| + end_use_elems = OpenStudio::AttributeVector.new + end_use_name = end_use_map[end_use_cat.value] + # in each end use, loop through all fuel types + end_use_fuel_types.each do |end_use_fuel_type| + fuel_type_elems = OpenStudio::AttributeVector.new + fuel_type_name = fuel_type_alias_map[end_use_fuel_type.value] + ann_peak_demand = 0.0 + # in each end use, loop through months and get monthly enedy consumption + months.each_with_index do |month, i| + mon_peak_demand = 0.0 + val = @sql.peakEnergyDemandByMonth(end_use_fuel_type, end_use_cat, month) + if val.is_initialized + mon_peak_demand_W = OpenStudio::Quantity.new(val.get, watt_unit) + mon_peak_demand = OpenStudio.convert(mon_peak_demand_W, kilowatt_unit).get.value + end + # record the monthly value + fuel_type_elems << OpenStudio::Attribute.new('month', mon_peak_demand, 'kW') + @runner.registerValue("monthly_demand_#{end_use_name}_#{fuel_type_name}_month_#{i + 1}", mon_peak_demand, 'kW') + # if month peak demand > ann peak demand make this new ann peak demand + if mon_peak_demand > ann_peak_demand + ann_peak_demand = mon_peak_demand + end + end + # record the annual peak demand + fuel_type_elems << OpenStudio::Attribute.new('year', ann_peak_demand, 'kW') + @runner.registerValue("monthly_demand_#{end_use_name}_#{fuel_type_name}_year", ann_peak_demand, 'kW') + # add this fuel type + end_use_elems << OpenStudio::Attribute.new(fuel_type_alias_map[end_use_fuel_type.value], fuel_type_elems) + end + # add this end use + demand_elems << OpenStudio::Attribute.new(end_use_map[end_use_cat.value], end_use_elems) + end + # end demand + monthly_elems << OpenStudio::Attribute.new('demand', demand_elems) + + # end monthly + result_elems << OpenStudio::Attribute.new('monthly', monthly_elems) + + result_elem = OpenStudio::Attribute.new('results', result_elems) + return result_elem + end # end create_results +end