Skip to content

Commit

Permalink
Merge pull request #1861 from NREL/utility_bills_update
Browse files Browse the repository at this point in the history
Utility bill calcs: Allow $/day fixed charges
  • Loading branch information
shorowit authored Oct 17, 2024
2 parents 2de51a2 + 2440940 commit 27dd8ea
Show file tree
Hide file tree
Showing 8 changed files with 821 additions and 163 deletions.
6 changes: 4 additions & 2 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,17 @@ __New Features__
- Adds optional `HVACSizingControl/ManualJInputs/InfiltrationMethod` input to specify which method to use for infiltration design load calculations.
- Updates heat pump HERS sizing methodology to better prevent unmet hours in warmer climates.
- Misc Manual J design load calculation improvements.
- **Breaking change**: Disaggregates "Walls" into "Above Grade Walls" and "Below Grade Walls" in results_design_load_details.csv output file.
- Advanced research features:
- Optional input `SimulationControl/AdvancedResearchFeatures/OnOffThermostatDeadbandTemperature` to model on/off thermostat deadband with start-up degradation for single and two speed AC/ASHP systems and time-based realistic staging for two speed AC/ASHP systems.
- Optional input `SimulationControl/AdvancedResearchFeatures/HeatPumpBackupCapacityIncrement` to model multi-stage electric backup coils with time-based staging.
- Maximum power ratio detailed schedule for variable-speed HVAC systems can now be used with `NumberofUnits` dwelling unit multiplier.
- BuildResidentialHPXML measure:
- **Breaking change**: Replaced `slab_under_width` and `slab_perimeter_depth` arguments with `slab_under_insulation_width` and `slab_perimeter_insulation_depth`
- **Breaking change**: Replaced `schedules_vacancy_periods`, `schedules_power_outage_periods`, and `schedules_power_outage_periods_window_natvent_availability` arguments with `schedules_unavailable_period_types`, `schedules_unavailable_period_dates`, and `schedules_unavailable_period_window_natvent_availabilities`; this improves flexibility for handling more unavailable period types.
- **Breaking change**: Disaggregates "Walls" into "Above Grade Walls" and "Below Grade Walls" in results_design_load_details.csv output file.
- Updates `openei_rates.zip` with the latest residential utility rates from the [OpenEI U.S. Utility Rate database](https://apps.openei.org/USURDB/).
- Utility bill calculations:
- Allows OpenEI URDB tariffs that have $/day fixed charges.
- Updates `openei_rates.zip` with the latest residential utility rates from the [OpenEI U.S. Utility Rate database](https://apps.openei.org/USURDB/).
- Adds a warning if the sum of supply/return duct leakage to outside values is very high.

__Bugfixes__
Expand Down
107 changes: 54 additions & 53 deletions ReportUtilityBills/measure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -328,10 +328,10 @@ def run(runner, user_arguments)
utility_rates, utility_bills = setup_utility_outputs()

# Get PV monthly fee
monthly_fee = get_monthly_fee(utility_bill_scenario, @hpxml_buildings)
pv_monthly_fee = get_pv_monthly_fee(utility_bill_scenario, @hpxml_buildings)

# Get utility rates
warnings = get_utility_rates(hpxml_path, fuels, utility_rates, utility_bill_scenario, monthly_fee, num_units)
warnings = get_utility_rates(hpxml_path, fuels, utility_rates, utility_bill_scenario, pv_monthly_fee, num_units)
if register_warnings(runner, warnings)
next
end
Expand All @@ -352,26 +352,26 @@ def run(runner, user_arguments)
return true
end

# Get the monthly grid connection fee.
# Get the PV monthly grid connection fee.
#
# @param bill_scenario [HPXML::UtilityBillScenario] HPXML Utility Bill Scenario object
# @param hpxml_buildings [HPXML::Buildings] HPXML Buildings object
# @return [Double] the sum of the monthly grid connection fees ($) across HPXML Buildings
def get_monthly_fee(bill_scenario, hpxml_buildings)
monthly_fee = 0.0
def get_pv_monthly_fee(bill_scenario, hpxml_buildings)
pv_monthly_fee = 0.0
if not bill_scenario.pv_monthly_grid_connection_fee_dollars_per_kw.nil?
hpxml_buildings.each do |hpxml_bldg|
hpxml_bldg.pv_systems.each do |pv_system|
max_power_output_kW = UnitConversions.convert(pv_system.max_power_output, 'W', 'kW')
monthly_fee += bill_scenario.pv_monthly_grid_connection_fee_dollars_per_kw * max_power_output_kW
monthly_fee *= hpxml_bldg.building_construction.number_of_units if !hpxml_bldg.building_construction.number_of_units.nil?
pv_monthly_fee += bill_scenario.pv_monthly_grid_connection_fee_dollars_per_kw * max_power_output_kW
pv_monthly_fee *= hpxml_bldg.building_construction.number_of_units if !hpxml_bldg.building_construction.number_of_units.nil?
end
end
elsif not bill_scenario.pv_monthly_grid_connection_fee_dollars.nil?
monthly_fee = bill_scenario.pv_monthly_grid_connection_fee_dollars
pv_monthly_fee = bill_scenario.pv_monthly_grid_connection_fee_dollars
end

return monthly_fee
return pv_monthly_fee
end

# Get monthly timestamps for reporting.
Expand Down Expand Up @@ -535,21 +535,23 @@ def report_monthly_output_results(runner, args, timestamps, monthly_data, monthl
# @param fuels [Hash] Fuel type, is_production => Fuel object
# @param utility_rates [Hash] Fuel Type => UtilityRate object
# @param bill_scenario [HPXML::UtilityBillScenario] HPXML Utility Bill Scenario object
# @param monthly_fee [Double] the sum of the monthly grid connection fees ($) across HPXML Buildings
# @param pv_monthly_fee [Double] the sum of the monthly grid connection fees ($) across HPXML Buildings
# @param num_units [Integer] total number of units represented by the HPXML file
# @return [Array<String>] array of warnings
def get_utility_rates(hpxml_path, fuels, utility_rates, bill_scenario, monthly_fee, num_units = 1)
def get_utility_rates(hpxml_path, fuels, utility_rates, bill_scenario, pv_monthly_fee, num_units = 1)
warnings = []
utility_rates.each do |fuel_type, rate|
next if fuels[[fuel_type, false]].timeseries.sum == 0

if fuel_type == FT::Elec
if bill_scenario.elec_tariff_filepath.nil?
rate.fixedmonthlycharge = bill_scenario.elec_fixed_charge
rate.flatratebuy = bill_scenario.elec_marginal_rate
rate.fixed_charge_monthly = bill_scenario.elec_fixed_charge
rate.flat_rate = bill_scenario.elec_marginal_rate
else
require 'json'

tariff_name = File.basename(bill_scenario.elec_tariff_filepath)

filepath = FilePath.check_path(bill_scenario.elec_tariff_filepath,
File.dirname(hpxml_path),
'Tariff File')
Expand All @@ -558,56 +560,54 @@ def get_utility_rates(hpxml_path, fuels, utility_rates, bill_scenario, monthly_f
tariff = tariff[:items][0]
fields = tariff.keys

rate.fixedmonthlycharge = 0.0
if fields.include?(:fixedchargeunits)
rate.fixed_charge_monthly = 0.0
rate.fixed_charge_daily = 0.0
if fields.include?(:fixedchargeunits) && tariff[:fixedchargefirstmeter].to_f > 0
if tariff[:fixedchargeunits] == '$/month'
rate.fixedmonthlycharge += tariff[:fixedchargefirstmeter] if fields.include?(:fixedchargefirstmeter)
rate.fixedmonthlycharge += tariff[:fixedchargeeaaddl] if fields.include?(:fixedchargeeaaddl)
rate.fixed_charge_monthly += tariff[:fixedchargefirstmeter].to_f
elsif tariff[:fixedchargeunits] == '$/day'
rate.fixed_charge_daily += tariff[:fixedchargefirstmeter].to_f
else
warnings << 'Fixed charge units must be $/month.'
warnings << "#{tariff_name}: Unsupported fixed charge units (#{tariff[:fixedchargeunits]}); utility bills will not be calculated."
end
end

if fields.include?(:minchargeunits)
if fields.include?(:minchargeunits) && tariff[:mincharge].to_f > 0
if tariff[:minchargeunits] == '$/month'
rate.minmonthlycharge = tariff[:mincharge] if fields.include?(:mincharge)
rate.min_charge_monthly = tariff[:mincharge].to_f
elsif tariff[:minchargeunits] == '$/year'
rate.minannualcharge = tariff[:mincharge] if fields.include?(:mincharge)
rate.min_charge_annual = tariff[:mincharge].to_f
else
warnings << 'Min charge units must be either $/month or $/year.'
warnings << "#{tariff_name}: Unsupported min charge units (#{tariff[:minchargeunits]}); utility bills will not be calculated."
end
end

if fields.include?(:realtimepricing)
rate.realtimeprice = tariff[:realtimepricing]
rate.real_time_prices = tariff[:realtimepricing]

else
if !fields.include?(:energyweekdayschedule) || !fields.include?(:energyweekendschedule) || !fields.include?(:energyratestructure)
warnings << 'Tariff file must contain energyweekdayschedule, energyweekendschedule, and energyratestructure fields.'
warnings << "#{tariff_name}: Tariff file must contain energyweekdayschedule, energyweekendschedule, and energyratestructure fields; utility bills will not be calculated."
end

if fields.include?(:demandweekdayschedule) || fields.include?(:demandweekendschedule) || fields.include?(:demandratestructure) || fields.include?(:flatdemandstructure)
warnings << 'Demand charges are not currently supported when calculating detailed utility bills.'
warnings << "#{tariff_name}: Demand charges are not currently supported; utility bills will not be calculated."
end

rate.energyratestructure = tariff[:energyratestructure]
rate.energyweekdayschedule = tariff[:energyweekdayschedule]
rate.energyweekendschedule = tariff[:energyweekendschedule]

if rate.energyratestructure.collect { |r| r.collect { |s| s.keys.include?(:rate) } }.flatten.any? { |t| !t }
warnings << 'Every tier must contain a rate.'
end
rate.energy_rate_structure = tariff[:energyratestructure]
rate.energy_weekday_schedule = tariff[:energyweekdayschedule]
rate.energy_weekend_schedule = tariff[:energyweekendschedule]

if rate.energyratestructure.collect { |r| r.collect { |s| s.keys } }.flatten.uniq.include?(:sell)
warnings << 'No tier may contain a sell key.'
if rate.energy_rate_structure.collect { |r| r.collect { |s| !s.keys.include?(:rate) } }.flatten.any?
warnings << "#{tariff_name}: Every tier must contain a rate; utility bills will not be calculated."
end

if rate.energyratestructure.collect { |r| r.collect { |s| s.keys.include?(:unit) } }.flatten.any? { |t| !t }
warnings << 'Every tier must contain a unit'
if rate.energy_rate_structure.collect { |r| r.collect { |s| s.keys } }.flatten.uniq.include?(:sell)
warnings << "#{tariff_name}: Tariffs with sell rates are not currently supported; utility bills will not be calculated."
end

if rate.energyratestructure.collect { |r| r.collect { |s| s[:unit] == 'kWh' } }.flatten.any? { |t| !t }
warnings << 'All rates must be in units of kWh.'
if rate.energy_rate_structure.collect { |r| r.collect { |s| s[:unit] != 'kWh' && s.keys.include?(:max) } }.flatten.any?
warnings << "#{tariff_name}: Only max usage units of kWh are currently supported; utility bills will not be calculated."
end
end
end
Expand All @@ -619,32 +619,33 @@ def get_utility_rates(hpxml_path, fuels, utility_rates, bill_scenario, monthly_f
# Feed-In Tariff
rate.feed_in_tariff_rate = bill_scenario.pv_feed_in_tariff_rate if bill_scenario.pv_compensation_type == HPXML::PVCompensationTypeFeedInTariff
elsif fuel_type == FT::Gas
rate.fixedmonthlycharge = bill_scenario.natural_gas_fixed_charge
rate.flatratebuy = bill_scenario.natural_gas_marginal_rate
rate.fixed_charge_monthly = bill_scenario.natural_gas_fixed_charge
rate.flat_rate = bill_scenario.natural_gas_marginal_rate
elsif fuel_type == FT::Oil
rate.fixedmonthlycharge = bill_scenario.fuel_oil_fixed_charge
rate.flatratebuy = bill_scenario.fuel_oil_marginal_rate
rate.fixed_charge_monthly = bill_scenario.fuel_oil_fixed_charge
rate.flat_rate = bill_scenario.fuel_oil_marginal_rate
elsif fuel_type == FT::Propane
rate.fixedmonthlycharge = bill_scenario.propane_fixed_charge
rate.flatratebuy = bill_scenario.propane_marginal_rate
rate.fixed_charge_monthly = bill_scenario.propane_fixed_charge
rate.flat_rate = bill_scenario.propane_marginal_rate
elsif fuel_type == FT::WoodCord
rate.fixedmonthlycharge = bill_scenario.wood_fixed_charge
rate.flatratebuy = bill_scenario.wood_marginal_rate
rate.fixed_charge_monthly = bill_scenario.wood_fixed_charge
rate.flat_rate = bill_scenario.wood_marginal_rate
elsif fuel_type == FT::WoodPellets
rate.fixedmonthlycharge = bill_scenario.wood_pellets_fixed_charge
rate.flatratebuy = bill_scenario.wood_pellets_marginal_rate
rate.fixed_charge_monthly = bill_scenario.wood_pellets_fixed_charge
rate.flat_rate = bill_scenario.wood_pellets_marginal_rate
elsif fuel_type == FT::Coal
rate.fixedmonthlycharge = bill_scenario.coal_fixed_charge
rate.flatratebuy = bill_scenario.coal_marginal_rate
rate.fixed_charge_monthly = bill_scenario.coal_fixed_charge
rate.flat_rate = bill_scenario.coal_marginal_rate
end
rate.fixedmonthlycharge *= num_units if !rate.fixedmonthlycharge.nil?
rate.fixed_charge_monthly *= num_units if !rate.fixed_charge_monthly.nil?
rate.fixed_charge_daily *= num_units if !rate.fixed_charge_daily.nil?

warnings << "Could not find a marginal #{fuel_type} rate." if rate.flatratebuy.nil?
warnings << "Could not find a marginal #{fuel_type} rate." if rate.flat_rate.nil?

# Grid connection fee
next unless fuel_type == FT::Elec

rate.fixedmonthlycharge += monthly_fee
rate.fixed_charge_monthly += pv_monthly_fee
end
return warnings
end
Expand Down
24 changes: 12 additions & 12 deletions ReportUtilityBills/measure.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<schema_version>3.1</schema_version>
<name>report_utility_bills</name>
<uid>ca88a425-e59a-4bc4-af51-c7e7d1e960fe</uid>
<version_id>262fad2e-e205-42f3-b54d-79e1b3b2ee8e</version_id>
<version_modified>2024-10-13T19:48:31Z</version_modified>
<version_id>ec7741e4-75b1-4c66-b40a-21ef0e5aa8f1</version_id>
<version_modified>2024-10-16T22:56:39Z</version_modified>
<xml_checksum>15BF4E57</xml_checksum>
<class_name>ReportUtilityBills</class_name>
<display_name>Utility Bills Report</display_name>
Expand Down Expand Up @@ -180,19 +180,19 @@
<filename>measure.rb</filename>
<filetype>rb</filetype>
<usage_type>script</usage_type>
<checksum>007104C8</checksum>
<checksum>4D23174A</checksum>
</file>
<file>
<filename>detailed_rates/Adams Electric Cooperative Inc - Rate Schedule T1 TOD (Effective 2013-02-01).json</filename>
<filetype>json</filetype>
<filename>detailed_rates/README.md</filename>
<filetype>md</filetype>
<usage_type>resource</usage_type>
<checksum>8E644347</checksum>
<checksum>4BA8526F</checksum>
</file>
<file>
<filename>detailed_rates/README.md</filename>
<filetype>md</filetype>
<filename>detailed_rates/Sample Flat Rate Fixed Daily Charge.json</filename>
<filetype>json</filetype>
<usage_type>resource</usage_type>
<checksum>D038669F</checksum>
<checksum>03F1B3AD</checksum>
</file>
<file>
<filename>detailed_rates/Sample Flat Rate Min Annual Charge.json</filename>
Expand Down Expand Up @@ -294,7 +294,7 @@
<filename>detailed_rates/openei_rates.zip</filename>
<filetype>zip</filetype>
<usage_type>resource</usage_type>
<checksum>F41F4AA4</checksum>
<checksum>FCDE5F5D</checksum>
</file>
<file>
<filename>simple_rates/HouseholdConsumption.csv</filename>
Expand All @@ -318,7 +318,7 @@
<filename>util.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>86D22906</checksum>
<checksum>22F928DB</checksum>
</file>
<file>
<filename>Contains Demand Charges.json</filename>
Expand Down Expand Up @@ -360,7 +360,7 @@
<filename>test_report_utility_bills.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>B9C13CA4</checksum>
<checksum>B5EF620B</checksum>
</file>
</files>
</measure>
2 changes: 0 additions & 2 deletions ReportUtilityBills/resources/detailed_rates/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
The **openei_rates.zip** file is produced by running `openstudio tasks.rb download_utility_rates`.

Rates sourced from the [OpenEI U.S. Utility Rate database](https://apps.openei.org/USURDB/).

Last updated on 9/18/2024.
Loading

0 comments on commit 27dd8ea

Please sign in to comment.