Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 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
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ __New Features__
- Water heater improvements:
- Improves electric water heater tank losses when using `EnergyFactor` as the metric; now consistent with how `UniformEnergyFactor` is handled.
- Improves HPWH tank volume defaulting, particularly when `NumberofResidents` is provided.
- Allows HPWH performance adjustment when installed in confined space per RESNET HERS Addendum 77. When `extension/HPWHInConfinedSpaceWithoutMitigation` is "true", `extension/HPWHContainmentVolume` is used to calculate the adjustment.
- Updated site defaults:
- `Address/CityMunicipality`, `Address/StateCode`, `GeoLocation/Latitude`, `GeoLocation/Longitude`, and `TimeZone/UTCOffset` now default based on zip code if available.
- `TimeZone/DSTObserved` now defaults to false if `Address/StateCode` is 'AZ' or 'HI'.
Expand Down
18 changes: 9 additions & 9 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>hpxm_lto_openstudio</name>
<uid>b1543b30-9465-45ff-ba04-1d1f85e763bc</uid>
<version_id>370efaa7-01ec-407f-92cd-2b37f15c9baa</version_id>
<version_modified>2025-10-31T17:24:17Z</version_modified>
<version_id>ebf347a8-a3b8-4096-a72c-078aa24e7d59</version_id>
<version_modified>2025-10-31T18:46:56Z</version_modified>
<xml_checksum>D8922A73</xml_checksum>
<class_name>HPXMLtoOpenStudio</class_name>
<display_name>HPXML to OpenStudio Translator</display_name>
Expand Down Expand Up @@ -348,7 +348,7 @@
<filename>defaults.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>2157E4EC</checksum>
<checksum>73012C00</checksum>
</file>
<file>
<filename>electric_panel.rb</filename>
Expand Down Expand Up @@ -384,7 +384,7 @@
<filename>hpxml.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>DBE0703D</checksum>
<checksum>26FF824B</checksum>
</file>
<file>
<filename>hpxml_schema/HPXML.xsd</filename>
Expand All @@ -402,7 +402,7 @@
<filename>hpxml_schematron/EPvalidator.sch</filename>
<filetype>sch</filetype>
<usage_type>resource</usage_type>
<checksum>4BBE1448</checksum>
<checksum>565E6550</checksum>
</file>
<file>
<filename>hpxml_schematron/iso-schematron.xsd</filename>
Expand Down Expand Up @@ -696,7 +696,7 @@
<filename>waterheater.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>003609A7</checksum>
<checksum>D373B68C</checksum>
</file>
<file>
<filename>weather.rb</filename>
Expand Down Expand Up @@ -732,7 +732,7 @@
<filename>test_defaults.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>67DD734E</checksum>
<checksum>5C9F88DA</checksum>
</file>
<file>
<filename>test_electric_panel.rb</filename>
Expand Down Expand Up @@ -810,7 +810,7 @@
<filename>test_validation.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>AC7D67C4</checksum>
<checksum>A339372A</checksum>
</file>
<file>
<filename>test_vehicle.rb</filename>
Expand All @@ -822,7 +822,7 @@
<filename>test_water_heater.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>4B3C5F26</checksum>
<checksum>CADD169C</checksum>
</file>
<file>
<filename>test_weather.rb</filename>
Expand Down
5 changes: 5 additions & 0 deletions HPXMLtoOpenStudio/resources/defaults.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3347,6 +3347,11 @@ def self.apply_water_heaters(hpxml_bldg, eri_version, schedules_file)
water_heating_system.operating_mode_isdefaulted = true
end

if water_heating_system.hpwh_confined_space_without_mitigation.nil?
water_heating_system.hpwh_confined_space_without_mitigation = false
water_heating_system.hpwh_confined_space_without_mitigation_isdefaulted = true
end

end
next unless water_heating_system.location.nil?

Expand Down
60 changes: 33 additions & 27 deletions HPXMLtoOpenStudio/resources/hpxml.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8614,32 +8614,34 @@ def from_doc(building)

# Object for /HPXML/Building/BuildingDetails/Systems/WaterHeating/WaterHeatingSystem.
class WaterHeatingSystem < BaseElement
ATTRS = [:id, # [String] SystemIdentifier/@id
:fuel_type, # [String] FuelType (HPXML::FuelTypeXXX)
:water_heater_type, # [String] WaterHeaterType (HPXML::WaterHeaterTypeXXX)
:location, # [String] Location (HPXML::LocationXXX)
:year_installed, # [Integer] YearInstalled
:is_shared_system, # [Boolean] IsSharedSystem
:performance_adjustment, # [Double] PerformanceAdjustment (frac)
:third_party_certification, # [String] ThirdPartyCertification
:tank_volume, # [Double] TankVolume (gal)
:fraction_dhw_load_served, # [Double] FractionDHWLoadServed (frac)
:heating_capacity, # [Double] HeatingCapacity (Btu/hr)
:backup_heating_capacity, # [Double] BackupHeatingCapacity (Btu/hr)
:energy_factor, # [Double] EnergyFactor (frac)
:uniform_energy_factor, # [Double] UniformEnergyFactor (frac)
:operating_mode, # [String] HPWHOperatingMode (HPXML::WaterHeaterOperatingModeXXX)
:first_hour_rating, # [Double] FirstHourRating (gal/hr)
:usage_bin, # [String] UsageBin (HPXML::WaterHeaterUsageBinXXX)
:recovery_efficiency, # [Double] RecoveryEfficiency (frac)
:jacket_r_value, # [Double] WaterHeaterInsulation/Jacket/JacketRValue (F-ft2-hr/Btu)
:standby_loss_units, # [String] StandbyLoss/Units (HPXML::UnitsXXX)
:standby_loss_value, # [Double] StandbyLoss/Value
:temperature, # [Double] HotWaterTemperature (F)
:uses_desuperheater, # [Boolean] UsesDesuperheater
:related_hvac_idref, # [String] RelatedHVACSystem/@idref
:tank_model_type, # [String] extension/TankModelType (HPXML::WaterHeaterTankModelTypeXXX)
:number_of_bedrooms_served] # [Integer] extension/NumberofBedroomsServed
ATTRS = [:id, # [String] SystemIdentifier/@id
:fuel_type, # [String] FuelType (HPXML::FuelTypeXXX)
:water_heater_type, # [String] WaterHeaterType (HPXML::WaterHeaterTypeXXX)
:location, # [String] Location (HPXML::LocationXXX)
:year_installed, # [Integer] YearInstalled
:is_shared_system, # [Boolean] IsSharedSystem
:performance_adjustment, # [Double] PerformanceAdjustment (frac)
:third_party_certification, # [String] ThirdPartyCertification
:tank_volume, # [Double] TankVolume (gal)
:fraction_dhw_load_served, # [Double] FractionDHWLoadServed (frac)
:heating_capacity, # [Double] HeatingCapacity (Btu/hr)
:backup_heating_capacity, # [Double] BackupHeatingCapacity (Btu/hr)
:energy_factor, # [Double] EnergyFactor (frac)
:uniform_energy_factor, # [Double] UniformEnergyFactor (frac)
:operating_mode, # [String] HPWHOperatingMode (HPXML::WaterHeaterOperatingModeXXX)
:first_hour_rating, # [Double] FirstHourRating (gal/hr)
:usage_bin, # [String] UsageBin (HPXML::WaterHeaterUsageBinXXX)
:recovery_efficiency, # [Double] RecoveryEfficiency (frac)
:jacket_r_value, # [Double] WaterHeaterInsulation/Jacket/JacketRValue (F-ft2-hr/Btu)
:standby_loss_units, # [String] StandbyLoss/Units (HPXML::UnitsXXX)
:standby_loss_value, # [Double] StandbyLoss/Value
:temperature, # [Double] HotWaterTemperature (F)
:uses_desuperheater, # [Boolean] UsesDesuperheater
:related_hvac_idref, # [String] RelatedHVACSystem/@idref
:tank_model_type, # [String] extension/TankModelType (HPXML::WaterHeaterTankModelTypeXXX)
:number_of_bedrooms_served, # [Integer] extension/NumberofBedroomsServed
:hpwh_confined_space_without_mitigation, # [Boolean] extension/HPWHInConfinedSpaceWithoutMitigation
:hpwh_containment_volume] # [Double] extension/HPWHContainmentVolume
attr_accessor(*ATTRS)

# Returns any branch circuits that the component may be attached to.
Expand Down Expand Up @@ -8746,10 +8748,12 @@ def to_doc(building)
related_hvac_idref_el = XMLHelper.add_element(water_heating_system, 'RelatedHVACSystem')
XMLHelper.add_attribute(related_hvac_idref_el, 'idref', @related_hvac_idref)
end
if (not @tank_model_type.nil?) || (not @number_of_bedrooms_served.nil?)
if (not @tank_model_type.nil?) || (not @number_of_bedrooms_served.nil?) || (not @hpwh_confined_space_without_mitigation.nil?) || (not @hpwh_containment_volume.nil?)
extension = XMLHelper.create_elements_as_needed(water_heating_system, ['extension'])
XMLHelper.add_element(extension, 'TankModelType', @tank_model_type, :string, @tank_model_type_isdefaulted) unless @tank_model_type.nil?
XMLHelper.add_element(extension, 'NumberofBedroomsServed', @number_of_bedrooms_served, :integer, @number_of_bedrooms_served_isdefaulted) unless @number_of_bedrooms_served.nil?
XMLHelper.add_element(extension, 'HPWHInConfinedSpaceWithoutMitigation', @hpwh_confined_space_without_mitigation, :boolean, @hpwh_confined_space_without_mitigation_isdefaulted) unless @hpwh_confined_space_without_mitigation.nil?
XMLHelper.add_element(extension, 'HPWHContainmentVolume', @hpwh_containment_volume, :float, @hpwh_containment_volume_isdefaulted) unless @hpwh_containment_volume.nil?
end
end

Expand Down Expand Up @@ -8786,6 +8790,8 @@ def from_doc(water_heating_system)
@related_hvac_idref = HPXML::get_idref(XMLHelper.get_element(water_heating_system, 'RelatedHVACSystem'))
@tank_model_type = XMLHelper.get_value(water_heating_system, 'extension/TankModelType', :string)
@number_of_bedrooms_served = XMLHelper.get_value(water_heating_system, 'extension/NumberofBedroomsServed', :integer)
@hpwh_confined_space_without_mitigation = XMLHelper.get_value(water_heating_system, 'extension/HPWHInConfinedSpaceWithoutMitigation', :boolean)
@hpwh_containment_volume = XMLHelper.get_value(water_heating_system, 'extension/HPWHContainmentVolume', :float)
end
end

Expand Down
12 changes: 12 additions & 0 deletions HPXMLtoOpenStudio/resources/hpxml_schematron/EPvalidator.sch
Original file line number Diff line number Diff line change
Expand Up @@ -2571,13 +2571,25 @@
<sch:assert role='ERROR' test='count(h:WaterHeaterInsulation/h:Jacket/h:JacketRValue) &lt;= 1'>Expected 0 or 1 element(s) for xpath: WaterHeaterInsulation/Jacket/JacketRValue</sch:assert>
<sch:assert role='ERROR' test='count(h:HotWaterTemperature) &lt;= 1'>Expected 0 or 1 element(s) for xpath: HotWaterTemperature</sch:assert>
<sch:assert role='ERROR' test='count(h:UsesDesuperheater) &lt;= 1'>Expected 0 or 1 element(s) for xpath: UsesDesuperheater</sch:assert> <!-- See [Desuperheater] -->
<sch:assert role='ERROR' test='count(h:extension/h:HPWHInConfinedSpaceWithoutMitigation) &lt;= 1'>Expected 0 or 1 element(s) for xpath: extension/HPWHInConfinedSpaceWithoutMitigation</sch:assert> <!-- See [HPWHInConfinedSpaceWithoutMitigation] -->
<sch:assert role='ERROR' test='h:extension/h:HPWHInConfinedSpaceWithoutMitigation[text()="true" or text()="false"] or not(h:extension/h:HPWHInConfinedSpaceWithoutMitigation)'>Expected extension/HPWHInConfinedSpaceWithoutMitigation to be 'true' or 'false'</sch:assert>
<!-- Moved/deprecated extension/OperatingMode input; see https://github.com/NREL/OpenStudio-HPXML/pull/1289 -->
<sch:assert role='ERROR' test='count(h:extension/h:OperatingMode) = 0'>extension/OperatingMode has been replaced by HPWHOperatingMode</sch:assert>
<!-- Warnings -->
<sch:report role='WARN' test='number(h:HotWaterTemperature) &lt; 110'>Hot water setpoint should typically be greater than or equal to 110 deg-F.</sch:report>
</sch:rule>
</sch:pattern>

<sch:pattern>
<sch:title>[HPWHInConfinedSpaceWithoutMitigation]</sch:title>
<sch:rule context='/h:HPXML/h:Building/h:BuildingDetails/h:Systems/h:WaterHeating/h:WaterHeatingSystem/h:extension[h:HPWHInConfinedSpaceWithoutMitigation="true"]'>
<sch:assert role='ERROR' test='count(h:HPWHContainmentVolume) = 1'>Expected 1 element(s) for xpath: HPWHContainmentVolume</sch:assert>
<sch:assert role='ERROR' test='number(h:HPWHContainmentVolume) &gt; 0 or not(h:HPWHContainmentVolume)'>Expected HPWHContainmentVolume to be greater than 0.</sch:assert>
<!-- Warnings -->
<sch:report role='WARN' test='number(h:HPWHContainmentVolume) &gt; 1500'>HPWHContainmentVolume should typically be less than 1500 cuft when the HPWH is in confined space.</sch:report>
</sch:rule>
</sch:pattern>

<sch:pattern>
<sch:title>[WaterHeatingSystemType=CombiIndirect]</sch:title>
<sch:rule context='/h:HPXML/h:Building/h:BuildingDetails/h:Systems/h:WaterHeating/h:WaterHeatingSystem[h:WaterHeaterType="space-heating boiler with storage tank"]'>
Expand Down
16 changes: 16 additions & 0 deletions HPXMLtoOpenStudio/resources/waterheater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,22 @@ def self.apply_hpwh_dxcoil(runner, model, water_heating_system, elevation, obj_n

cop = water_heating_system.additional_properties.cop

# Adjust COP based on RESNET HERS Addendum 77
if not water_heating_system.hpwh_containment_volume.nil?
if not water_heating_system.hpwh_confined_space_without_mitigation
if water_heating_system.hpwh_containment_volume < 1000.0
runner.registerWarning("HPWH COP adjustment based on HPWHContainmentVolume will not be applied to #{water_heating_system.id} because HPWHInConfinedSpaceWithoutMitigation is not 'true'.")
end
else
# FUTURE: apply for 120V HPWH and other system types that the correction may not be accurate for
if water_heating_system.hpwh_containment_volume < 450.0 && (water_heating_system.backup_heating_capacity == 0.0)
runner.registerWarning("Heat pump water heater: #{water_heating_system.id} has no backup electric resistance element, COP adjustment for confined space may not be accurate when the containment space volume is below 450 cubic feet.")
end
rv = [water_heating_system.hpwh_containment_volume / 1500.0, 1.0].min
cop = (cop - 0.92) * (1 - (1.009 * Math.exp(-5.492 * rv))) + 0.92
end
end

coil = OpenStudio::Model::CoilWaterHeatingAirToWaterHeatPumpWrapped.new(model)
coil.setName("#{obj_name} coil")
coil.setRatedHeatingCapacity(cap)
Expand Down
14 changes: 9 additions & 5 deletions HPXMLtoOpenStudio/tests/test_defaults.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3635,32 +3635,35 @@ def test_heat_pump_water_heaters
hpxml_bldg.water_heating_systems[0].operating_mode = HPXML::WaterHeaterOperatingModeHeatPumpOnly
hpxml_bldg.water_heating_systems[0].heating_capacity = 4000.0
hpxml_bldg.water_heating_systems[0].backup_heating_capacity = 5000.0
hpxml_bldg.water_heating_systems[0].hpwh_confined_space_without_mitigation = true
hpxml_bldg.water_heating_systems[0].hpwh_containment_volume = 800.0
XMLHelper.write_file(hpxml.to_doc, @tmp_hpxml_path)
_default_hpxml, default_hpxml_bldg = _test_measure()
_test_default_heat_pump_water_heater_values(default_hpxml_bldg, [44.0, HPXML::WaterHeaterOperatingModeHeatPumpOnly, 4000.0, 5000.0])
_test_default_heat_pump_water_heater_values(default_hpxml_bldg, [44.0, HPXML::WaterHeaterOperatingModeHeatPumpOnly, 4000.0, 5000.0, true])

# Test defaults
hpxml_bldg.water_heating_systems[0].tank_volume = nil
hpxml_bldg.water_heating_systems[0].operating_mode = nil
hpxml_bldg.water_heating_systems[0].heating_capacity = nil
hpxml_bldg.water_heating_systems[0].backup_heating_capacity = nil
hpxml_bldg.water_heating_systems[0].hpwh_confined_space_without_mitigation = nil
XMLHelper.write_file(hpxml.to_doc, @tmp_hpxml_path)
_default_hpxml, default_hpxml_bldg = _test_measure()
_test_default_heat_pump_water_heater_values(default_hpxml_bldg, [66.0, HPXML::WaterHeaterOperatingModeHybridAuto, 6366.0, 15355.0])
_test_default_heat_pump_water_heater_values(default_hpxml_bldg, [66.0, HPXML::WaterHeaterOperatingModeHybridAuto, 6366.0, 15355.0, false])

# Test defaults w/ num occupants = 1, num bedrooms = 1
hpxml_bldg.building_construction.number_of_bedrooms = 1
hpxml_bldg.building_occupancy.number_of_residents = 1
XMLHelper.write_file(hpxml.to_doc, @tmp_hpxml_path)
_default_hpxml, default_hpxml_bldg = _test_measure()
_test_default_heat_pump_water_heater_values(default_hpxml_bldg, [50.0, HPXML::WaterHeaterOperatingModeHybridAuto, 6366.0, 15355.0])
_test_default_heat_pump_water_heater_values(default_hpxml_bldg, [50.0, HPXML::WaterHeaterOperatingModeHybridAuto, 6366.0, 15355.0, false])

# Test defaults w/ num occupants = 10, num bedrooms = 1
hpxml_bldg.building_construction.number_of_bedrooms = 1
hpxml_bldg.building_occupancy.number_of_residents = 10
XMLHelper.write_file(hpxml.to_doc, @tmp_hpxml_path)
_default_hpxml, default_hpxml_bldg = _test_measure()
_test_default_heat_pump_water_heater_values(default_hpxml_bldg, [80.0, HPXML::WaterHeaterOperatingModeHybridAuto, 6366.0, 15355.0])
_test_default_heat_pump_water_heater_values(default_hpxml_bldg, [80.0, HPXML::WaterHeaterOperatingModeHybridAuto, 6366.0, 15355.0, false])
end

def test_indirect_water_heaters
Expand Down Expand Up @@ -6302,12 +6305,13 @@ def _test_default_heat_pump_water_heater_values(hpxml_bldg, *expected_wh_values)
heat_pump_water_heaters = hpxml_bldg.water_heating_systems.select { |w| w.water_heater_type == HPXML::WaterHeaterTypeHeatPump }
assert_equal(expected_wh_values.size, heat_pump_water_heaters.size)
heat_pump_water_heaters.each_with_index do |wh_system, idx|
tank_volume, operating_mode, htg_cap, backup_htg_cap = expected_wh_values[idx]
tank_volume, operating_mode, htg_cap, backup_htg_cap, hpwh_confined_space_without_mitigation = expected_wh_values[idx]

assert_equal(tank_volume, wh_system.tank_volume)
assert_equal(operating_mode, wh_system.operating_mode)
assert_in_epsilon(htg_cap, wh_system.heating_capacity, 0.01)
assert_in_epsilon(backup_htg_cap, wh_system.backup_heating_capacity, 0.01)
assert_equal(hpwh_confined_space_without_mitigation, wh_system.hpwh_confined_space_without_mitigation)
end
end

Expand Down
Loading