Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
5 changes: 4 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ __New Features__
- Improves handling of duct leakage specified using cfm25/cfm50.
- Crankcase heating energy is now disabled during unavailable periods, e.g., power outages.
- Adds advanced research feature to model latent degradation for cooling systems, including an input to specify an HVAC blower-off delay.
- Allows "other" for `SoilType`; adds variation to dry/wet soil conductivity and diffusivity values for unknown/other/loam soil types.
- Output updates:
- **Breaking change**: Annual peak load outputs for heating and cooling now use units of Btu/h instead of kBtu/h for consistency with other outputs.
- **Breaking change**: Replaces "UnitX" prefixes with Building IDs in whole SFA/MF building timeseries outputs.
- For whole SFA/MF building simulations, reports energy/fuel use by dwelling unit (i.e., "Dwelling Unit Energy Use: \*" and "Dwelling Unit Fuel Use: \*"). Timeseries outputs are also available.
- Allows calculating utility bills for homes with HVAC distribution systems using simplified heating/cooling DSE values (previously unsupported).
- Whole SFA/MF buildings:
- Allows modeling batteries in individual dwelling units (previously unsupported).
- Utility bill calculations:
- Updated default state-average utility rates to EIA State Energy Data System (SEDS) 2024 data.
- Updates `openei_rates.zip` with the latest residential utility rates from the [OpenEI U.S. Utility Rate database](https://apps.openei.org/USURDB/).
- Allows "other" for `SoilType`; adds variation to dry/wet soil conductivity and diffusivity values for unknown/other/loam soil types.
- Updates schematron validation error messages to be more user friendly.
- Adds a `run_simulation.rb --ems-debug` argument to generate the EnergyPlus EDD file for debugging EMS programs.

Expand Down
8 changes: 4 additions & 4 deletions HPXMLtoOpenStudio/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>hpxml_to_openstudio</name>
<uid>b1543b30-9465-45ff-ba04-1d1f85e763bc</uid>
<version_id>dd8b0677-6bf7-4dad-a74d-cbb998013cd2</version_id>
<version_modified>2026-05-04T17:18:28Z</version_modified>
<version_id>b1ede868-e44d-4413-94e2-b45f3d88d902</version_id>
<version_modified>2026-05-05T16:08:07Z</version_modified>
<xml_checksum>D8922A73</xml_checksum>
<class_name>HPXMLToOpenStudio</class_name>
<display_name>HPXML to OpenStudio Translator</display_name>
Expand Down Expand Up @@ -697,7 +697,7 @@
<filename>utility_bills.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>C87C3553</checksum>
<checksum>FAE3470C</checksum>
</file>
<file>
<filename>vehicle.rb</filename>
Expand Down Expand Up @@ -751,7 +751,7 @@
<filename>test_defaults.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>D53CDCAB</checksum>
<checksum>4E7A6CA0</checksum>
</file>
<file>
<filename>test_electric_panel.rb</filename>
Expand Down
62 changes: 35 additions & 27 deletions HPXMLtoOpenStudio/resources/utility_bills.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ def self.get_fuel_units(fuel_type)
# @param marginal_rate [Double] the marginal flat rate (USD/kWh or USD/therm, etc.)
# @return [Array<Double, Double>] the marginal and average rates (USD/kWh or USD/therm, etc., USD/month)
def self.get_rates_from_eia_data(runner, state_code, fuel_type, fixed_charge, marginal_rate = nil)
msn_codes = Constants::StateCodesMap.keys
msn_codes << 'US'
return unless msn_codes.include? state_code # Check if the state_code is valid
state_codes = Constants::StateCodesMap.keys
state_codes << 'US'
return unless state_codes.include? state_code # Check if the state_code is valid

average_rate = nil

Expand Down Expand Up @@ -104,31 +104,39 @@ def self.marginal_rate_to_average_rate(marginal_rate, fixed_charge, household_co
# @param fuel_type [String] HPXML fuel type
# @return [Double] average rate for electricity or natural gas, and marginal rate for all other fuel types (USD/kWh or USD/therm, etc.)
def self.get_eia_seds_rate(runner, state_code, fuel_type)
msn_code_map = {
HPXML::FuelTypeElectricity => 'ESRCD',
HPXML::FuelTypeNaturalGas => 'NGRCD',
HPXML::FuelTypeOil => 'DFRCD',
HPXML::FuelTypePropane => 'PQRCD',
HPXML::FuelTypeCoal => 'CLRCD',
HPXML::FuelTypeWoodCord => 'WDRCD',
HPXML::FuelTypeWoodPellets => 'WDRCD'
}

CSV.foreach(File.join(File.dirname(__FILE__), '../../ReportUtilityBills/resources/simple_rates/pr_all_update.csv'), headers: true) do |row|
next if row['State'].upcase != state_code.upcase # State
next if row['MSN'].upcase != msn_code_map[fuel_type] # EIA SEDS MSN code

seds_rate = row.to_h.values.reverse.find { |rate| rate.to_f != 0 } # If the rate for the latest year is unavailable, find the last non-nil/non-zero rate.
begin
seds_rate = Float(seds_rate)
rescue ArgumentError, TypeError
seds_rate = 0.0
runner.registerWarning("No EIA SEDS rate for #{fuel_type} was found for the state of #{state_code}.") if not runner.nil?
end
csv_path = File.join(File.dirname(__FILE__), '../../ReportUtilityBills/resources/simple_rates/eia_fuel_rates_by_state.csv')

# Collect the latest matching EIA SEDS rate for this state and fuel
matching_rate = nil
csv_fuel = fuel_type.to_s.downcase == HPXML::FuelTypeWoodPellets.to_s.downcase ? 'wood' : fuel_type.to_s.downcase

CSV.foreach(csv_path, headers: true) do |row|
next if row['state'].to_s.upcase != state_code.to_s.upcase
next if row['fuel'].to_s.downcase != csv_fuel

year = row['year'].to_i
rate = row['rate_dollar_per_mmbtu'].to_f
matching_rate = { year: year, rate: rate }
break
end

# Convert $/MBtu to $/XXX
seds_rate = UnitConversions.convert(seds_rate, get_fuel_units(fuel_type), 'mbtu')
return seds_rate
if matching_rate.nil?
runner.registerWarning("No EIA SEDS rate for #{fuel_type} was found for the state of #{state_code}.") if !runner.nil?
return 0.0
end

if matching_rate[:rate] != 0.0
seds_rate = matching_rate[:rate]
else
# Rate is zero/missing
runner.registerWarning(
"EIA SEDS rate for #{fuel_type} in #{state_code} is unavailable for #{matching_rate[:year]}."
) if !runner.nil?
return 0.0
end

# Convert $/MMBtu to $/[fuel unit]
seds_rate = UnitConversions.convert(seds_rate, get_fuel_units(fuel_type), 'mbtu')
return seds_rate
end
end
4 changes: 2 additions & 2 deletions HPXMLtoOpenStudio/tests/test_defaults.rb
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ def test_utility_bills
XMLHelper.write_file(hpxml.to_doc, @tmp_hpxml_path)
default_hpxml, _default_hpxml_bldg = _test_measure()
default_hpxml.header.utility_bill_scenarios.each do |scenario|
_test_default_bills_values(scenario, 12, 12, nil, nil, nil, nil, nil, 0.1253, 0.9688, nil, nil, nil, nil, nil, HPXML::PVCompensationTypeNetMetering, HPXML::PVAnnualExcessSellbackRateTypeUserSpecified, 0.03, nil, nil, 0)
_test_default_bills_values(scenario, 12, 12, nil, nil, nil, nil, nil, 0.1315, 0.8398, nil, nil, nil, nil, nil, HPXML::PVCompensationTypeNetMetering, HPXML::PVAnnualExcessSellbackRateTypeUserSpecified, 0.03, nil, nil, 0)
end

# Test defaults w/ electricity JSON file
Expand All @@ -239,7 +239,7 @@ def test_utility_bills
XMLHelper.write_file(hpxml.to_doc, @tmp_hpxml_path)
default_hpxml, _default_hpxml_bldg = _test_measure()
default_hpxml.header.utility_bill_scenarios.each do |scenario|
_test_default_bills_values(scenario, nil, 12, nil, nil, nil, nil, nil, nil, 0.9688, nil, nil, nil, nil, nil, HPXML::PVCompensationTypeNetMetering, HPXML::PVAnnualExcessSellbackRateTypeUserSpecified, 0.03, nil, nil, 0)
_test_default_bills_values(scenario, nil, 12, nil, nil, nil, nil, nil, nil, 0.8398, nil, nil, nil, nil, nil, HPXML::PVCompensationTypeNetMetering, HPXML::PVAnnualExcessSellbackRateTypeUserSpecified, 0.03, nil, nil, 0)
end
end

Expand Down
13 changes: 6 additions & 7 deletions ReportUtilityBills/measure.xml
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<?xml version="1.0"?>
<measure>
<schema_version>3.1</schema_version>
<error>Failed to infer measure name from '/mnt/c/git/openstudio-hpxml/ReportUtilityBills/measure.rb'</error>
<name>report_utility_bills</name>
<uid>ca88a425-e59a-4bc4-af51-c7e7d1e960fe</uid>
<version_id>cb973eaa-2d81-46fa-bdcc-5b0bba033125</version_id>
<version_modified>2026-05-01T14:45:46Z</version_modified>
<version_id>f46253b9-5719-4870-91a3-1ca4fed89a57</version_id>
<version_modified>2026-05-05T20:06:56Z</version_modified>
<xml_checksum>15BF4E57</xml_checksum>
<class_name>ReportUtilityBills</class_name>
<display_name>Utility Bills Report</display_name>
Expand Down Expand Up @@ -187,7 +186,7 @@
<filename>detailed_rates/README.md</filename>
<filetype>md</filetype>
<usage_type>resource</usage_type>
<checksum>4BA8526F</checksum>
<checksum>EFDAFA72</checksum>
</file>
<file>
<filename>detailed_rates/Sample Flat Rate Fixed Daily Charge.json</filename>
Expand Down Expand Up @@ -307,13 +306,13 @@
<filename>simple_rates/README.md</filename>
<filetype>md</filetype>
<usage_type>resource</usage_type>
<checksum>C7E1E1CB</checksum>
<checksum>1DB35659</checksum>
</file>
<file>
<filename>simple_rates/pr_all_update.csv</filename>
<filename>simple_rates/eia_fuel_rates_by_state.csv</filename>
<filetype>csv</filetype>
<usage_type>resource</usage_type>
<checksum>C6074A83</checksum>
<checksum>5A5BF6C3</checksum>
</file>
<file>
<filename>util.rb</filename>
Expand Down
2 changes: 1 addition & 1 deletion ReportUtilityBills/resources/detailed_rates/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
The **openei_rates.zip** file is produced by running `openstudio tasks.rb download_utility_rates`.
The **openei_rates.zip** file is produced by running `openstudio tasks.rb download_detailed_utility_rates`.

Rates sourced from the [OpenEI U.S. Utility Rate database](https://apps.openei.org/USURDB/).
46 changes: 36 additions & 10 deletions ReportUtilityBills/resources/simple_rates/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,36 @@
EIA SEDS Prices
* https://www.eia.gov/state/seds/seds-data-fuel.php?sid=US#DataFiles
* Data files > Prices and expenditures > All price and expenditure estimates > Prices > Download Prices (CSV) link
* All lines removed other than MSN codes for the following fuel types:
* Electricity: ESRCD ($/MMBtu)
* Natural Gas: NGRCD ($/MMBtu)
* Distillate Fuel Oil: DFRCD ($/MMBtu)
* Propane: PQRCD ($/MMBtu)
* Coal: CLRCD ($/MMBtu)
* Wood: WDRCD ($/MMBtu)
# EIA SEDS Prices

This dataset is generated using the U.S. Energy Information Administration (EIA) Open Data API (SEDS):

- https://www.eia.gov/opendata/browser/seds

The data is fetched programmatically from the EIA SEDS API and processed into a simplified state-level fuel price table.

---

## Fuel types included

Only the following residential fuel types are included:

- Electricity ($/MMBtu)
- Natural Gas ($/MMBtu)
- Fuel Oil ($/MMBtu)
- Propane ($/MMBtu)
- Wood ($/MMBtu)

---

## How to generate the file

Run the following OpenStudio task:

```bash
openstudio tasks.rb download_simple_utility_rates
```

This script:

- Calls the EIA SEDS API
- Filters relevant fuel types
- Keeps latest non-zero values per state and fuel
- Writes output to: [ReportUtilityBills/resources/simple_rates/eia_fuel_rates_by_state.csv](ReportUtilityBills/resources/simple_rates/eia_fuel_rates_by_state.csv)
Loading