Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Utility bill calcs: Allow $/day fixed charges #1861

Merged
merged 3 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,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