Skip to content
Open
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
2 changes: 1 addition & 1 deletion BuildResidentialHPXML/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,7 @@ The amount of natural ventilation from occupants opening operable windows when o

- **Required:** ``false``

- **Choices:** <br/> - `None`<br/> - `33% Operable Windows`<br/> - `50% Operable Windows`<br/> - `67% Operable Windows`<br/> - `100% Operable Windows`<br/> - `Detailed Example: 67% Operable Windows, 7 Days/Week`
- **Choices:** <br/> - `None`<br/> - `33% Operable Windows`<br/> - `50% Operable Windows`<br/> - `67% Operable Windows`<br/> - `100% Operable Windows`<br/> - `Detailed Example: 67% Operable Windows, 3 Days/Week, Cooling Months Only`


- **Default:** `67% Operable Windows`
Expand Down
1 change: 1 addition & 0 deletions BuildResidentialHPXML/measure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1569,6 +1569,7 @@ def set_building_header(hpxml_bldg, args)
end
hpxml_bldg.header.heat_pump_sizing_methodology = args[:hvac_heat_pump_capacity_autosizing_methodology]
hpxml_bldg.header.heat_pump_backup_sizing_methodology = args[:hvac_heat_pump_backup_capacity_autosizing_methodology]
hpxml_bldg.header.natvent_seasons = args[:enclosure_window_natural_ventilation_seasons]
hpxml_bldg.header.natvent_days_per_week = args[:enclosure_window_natural_ventilation_availability]

hvac_incr = args[:advanced_feature_hvac_allow_increased_fixed_capacities]
Expand Down
14 changes: 7 additions & 7 deletions BuildResidentialHPXML/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>build_residential_hpxml</name>
<uid>a13a8983-2b01-4930-8af2-42030b6e4233</uid>
<version_id>8503d96a-f627-408e-87fe-5e35f9d14712</version_id>
<version_modified>2026-01-07T01:56:28Z</version_modified>
<version_id>deb4f32e-34d2-4c8f-b53f-b59f5ebbccce</version_id>
<version_modified>2026-01-22T20:08:12Z</version_modified>
<xml_checksum>2C38F48B</xml_checksum>
<class_name>BuildResidentialHPXML</class_name>
<display_name>HPXML Builder</display_name>
Expand Down Expand Up @@ -2849,8 +2849,8 @@
<display_name>100% Operable Windows</display_name>
</choice>
<choice>
<value>Detailed Example: 67% Operable Windows, 7 Days/Week</value>
<display_name>Detailed Example: 67% Operable Windows, 7 Days/Week</display_name>
<value>Detailed Example: 67% Operable Windows, 3 Days/Week, Cooling Months Only</value>
<display_name>Detailed Example: 67% Operable Windows, 3 Days/Week, Cooling Months Only</display_name>
</choice>
</choices>
</argument>
Expand Down Expand Up @@ -11389,7 +11389,7 @@
<filename>README.md</filename>
<filetype>md</filetype>
<usage_type>readme</usage_type>
<checksum>A3747D4E</checksum>
<checksum>6BAD51D9</checksum>
</file>
<file>
<filename>README.md.erb</filename>
Expand All @@ -11406,7 +11406,7 @@
<filename>measure.rb</filename>
<filetype>rb</filetype>
<usage_type>script</usage_type>
<checksum>5A12D243</checksum>
<checksum>97B98D7B</checksum>
</file>
<file>
<filename>constants.rb</filename>
Expand Down Expand Up @@ -11682,7 +11682,7 @@
<filename>options/enclosure_window_natural_ventilation.tsv</filename>
<filetype>tsv</filetype>
<usage_type>resource</usage_type>
<checksum>391C269D</checksum>
<checksum>BEE449DC</checksum>
</file>
<file>
<filename>options/enclosure_window_storm.tsv</filename>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
Option Name Fraction Operable [frac] Availability [days/week]
None 0
33% Operable Windows 0.33
50% Operable Windows 0.5
67% Operable Windows 0.67
100% Operable Windows 1
"Detailed Example: 67% Operable Windows, 7 Days/Week" 0.67 7

"# Fraction Operable: Total window area for operable windows divided by total window area. The total open window area for natural ventilation is calculated using A) the operable fraction, B) the assumption that only some of the area of operable windows can be open, and C) the assumption that only some of that openable area is actually opened by occupants whenever outdoor conditions are favorable."
# Availability: How many days per week windows can be opened by occupants.
Option Name Fraction Operable [frac] Availability [days/week] Seasons
None 0
33% Operable Windows 0.33
50% Operable Windows 0.5
67% Operable Windows 0.67
100% Operable Windows 1
"Detailed Example: 67% Operable Windows, 3 Days/Week, Cooling Months Only" 0.67 3 cooling

"# Fraction Operable: Total window area for operable windows divided by total window area. The total open window area for natural ventilation is calculated using A) the operable fraction, B) the assumption that only some of the area of operable windows can be open, and C) the assumption that only some of that openable area is actually opened by occupants."
# Availability: How many days per week windows can be opened by occupants.
# Seasons: When during the year occupants open windows.
22 changes: 11 additions & 11 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>1b5ed401-6cf9-43e9-b51d-ddc412d3dbf1</version_id>
<version_modified>2026-01-27T20:50:02Z</version_modified>
<version_id>7ca126f2-2fe3-4ac8-8f5b-ea21ef6c9de4</version_id>
<version_modified>2026-01-28T17:43:38Z</version_modified>
<xml_checksum>D8922A73</xml_checksum>
<class_name>HPXMLtoOpenStudio</class_name>
<display_name>HPXML to OpenStudio Translator</display_name>
Expand Down Expand Up @@ -198,7 +198,7 @@
<filename>airflow.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>E73265C9</checksum>
<checksum>D44CC68F</checksum>
</file>
<file>
<filename>battery.rb</filename>
Expand All @@ -216,7 +216,7 @@
<filename>constants.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>C1C12650</checksum>
<checksum>19608F3A</checksum>
</file>
<file>
<filename>constructions.rb</filename>
Expand Down Expand Up @@ -348,7 +348,7 @@
<filename>defaults.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>0FAE1876</checksum>
<checksum>4872C953</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>B76096BB</checksum>
<checksum>EB43715B</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>71EC385E</checksum>
<checksum>2108F0C5</checksum>
</file>
<file>
<filename>hpxml_schematron/iso-schematron.xsd</filename>
Expand Down Expand Up @@ -480,7 +480,7 @@
<filename>output.rb</filename>
<filetype>rb</filetype>
<usage_type>resource</usage_type>
<checksum>9AC90250</checksum>
<checksum>C889C4B5</checksum>
</file>
<file>
<filename>psychrometrics.rb</filename>
Expand Down Expand Up @@ -720,7 +720,7 @@
<filename>test_airflow.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>AE13556A</checksum>
<checksum>6C25C32E</checksum>
</file>
<file>
<filename>test_battery.rb</filename>
Expand All @@ -732,7 +732,7 @@
<filename>test_defaults.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>A7CB3439</checksum>
<checksum>44703054</checksum>
</file>
<file>
<filename>test_electric_panel.rb</filename>
Expand Down Expand Up @@ -798,7 +798,7 @@
<filename>test_schedules.rb</filename>
<filetype>rb</filetype>
<usage_type>test</usage_type>
<checksum>D5468D70</checksum>
<checksum>F8A251CC</checksum>
</file>
<file>
<filename>test_simcontrols.rb</filename>
Expand Down
51 changes: 37 additions & 14 deletions HPXMLtoOpenStudio/resources/airflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,16 +136,23 @@ def self.create_sensors(model, weather, spaces, hpxml_bldg)
key_name: conditioned_zone.name
)

# Create cooling season schedule sensor (applies only to natural ventilation, not HVAC equipment).
# Uses BAHSP cooling season, not user-specified cooling season (which may be, e.g., year-round).
_, default_cooling_months = HVAC.get_building_america_hvac_seasons(weather, hpxml_bldg.latitude)
clg_season_sch = MonthWeekdayWeekendSchedule.new(model, 'cooling season schedule', Array.new(24, 1), Array.new(24, 1), default_cooling_months, EPlus::ScheduleTypeLimitsFraction)
# Create season schedule sensors (applies only to natural ventilation, not HVAC equipment).
# Uses BAHSP seasons, not user-specified seasons (which are typically year-round).
bahsp_heating_months, bahsp_cooling_months = HVAC.get_building_america_hvac_seasons(weather, hpxml_bldg.latitude)
clg_season_sch = MonthWeekdayWeekendSchedule.new(model, 'cooling season schedule', Array.new(24, 1), Array.new(24, 1), bahsp_cooling_months, EPlus::ScheduleTypeLimitsFraction)
sensors[:clg_ssn] = Model.add_ems_sensor(
model,
name: 'cool_season',
output_var_or_meter_name: 'Schedule Value',
key_name: clg_season_sch.schedule.name
)
htg_season_sch = MonthWeekdayWeekendSchedule.new(model, 'heating season schedule', Array.new(24, 1), Array.new(24, 1), bahsp_heating_months, EPlus::ScheduleTypeLimitsFraction)
sensors[:htg_ssn] = Model.add_ems_sensor(
model,
name: 'heat_season',
output_var_or_meter_name: 'Schedule Value',
key_name: htg_season_sch.schedule.name
)

return sensors
end
Expand Down Expand Up @@ -395,7 +402,6 @@ def self.apply_natural_ventilation_and_whole_house_fan(runner, model, spaces, hp

# Natural Ventilation availability schedule and sensor
nv_avail_sch = create_sched_from_num_days_per_week(model, Constants::ObjectTypeNaturalVentilation, hpxml_bldg.header.natvent_days_per_week, hpxml_header.unavailable_periods)

nv_avail_sensor = Model.add_ems_sensor(
model,
name: "#{Constants::ObjectTypeNaturalVentilation} s",
Expand Down Expand Up @@ -552,25 +558,42 @@ def self.apply_natural_ventilation_and_whole_house_fan(runner, model, spaces, hp
end
vent_program.addLine("Set Tnvsp = (#{default_htg_sp} + #{default_clg_sp}) / 2")
end
vent_program.addLine("Set NVavail = #{nv_avail_sensor.name}")
vent_program.addLine("Set ClgSsnAvail = #{sensors[:clg_ssn].name}")
vent_program.addLine("Set NVavailDayofWeek = #{nv_avail_sensor.name}")
if hpxml_bldg.header.natvent_seasons == HPXML::NatVentSeasonsYearRound
vent_program.addLine('Set NVavailSeasonHtg = 1')
vent_program.addLine('Set NVavailSeasonClg = 1')
elsif hpxml_bldg.header.natvent_seasons == HPXML::NatVentSeasonsCooling
vent_program.addLine("Set NVavailSeasonClg = #{sensors[:clg_ssn].name}")
vent_program.addLine('Set NVavailSeasonHtg = 0')
elsif hpxml_bldg.header.natvent_seasons == HPXML::NatVentSeasonsHeating
vent_program.addLine("Set NVavailSeasonHtg = #{sensors[:htg_ssn].name}")
vent_program.addLine('Set NVavailSeasonClg = 0')
end
vent_program.addLine('Set Qnv = 0') # Init
vent_program.addLine('Set Qwhf = 0') # Init
vent_program.addLine("Set #{cond_to_zone_flow_rate_actuator.name} = 0") unless whf_zone.nil? # Init
vent_program.addLine("Set #{whf_elec_actuator.name} = 0") # Init
infil_constraints = 'If ((Wout < MaxHR) && (Tin > Tout) && (Tin > Tnvsp) && (ClgSsnAvail > 0))'
# From ANSI/RESNET/ICC 301-2025
# allow natural ventilation when the outdoor humidity ratio is less than 0.0115 lb_w/lb_da and either:
# A) outdoor temperature is below the indoor temperature and the indoor temperature is above the average of the heating and cooling setpoints, or
# B) outdoor temperature is above the indoor temperature and the indoor temperature is below the average of the heating and cooling setpoints
infil_constraints = 'If (((Tout < Tin) && (Tin > Tnvsp) && (NVavailSeasonClg == 1)) || ((Tout > Tin) && (Tin < Tnvsp) && (NVavailSeasonHtg == 1)))'
if not clg_avail_sensor.nil?
# We are using the availability schedule, but we also constrain the window opening based on temperatures and humidity.
# We're assuming that if the HVAC is not available, you'd ignore the humidity constraints we normally put on window opening per the old HSP guidance (RH < 70% and w < 0.015).
# Without, the humidity constraints prevent the window from opening during the entire period even though the sensible cooling would have really helped.
infil_constraints += "|| ((Tin > Tout) && (Tin > Tnvsp) && (#{clg_avail_sensor.name} == 0))"
# Ignore the humidity constraint when space cooling is not available (e.g., power outage), in order to allow as much natural ventilation as possible
infil_constraints += " && ((Wout < MaxHR) || (#{clg_avail_sensor.name} == 0))"
else
infil_constraints += ' && (Wout < MaxHR)'
end
vent_program.addLine(infil_constraints)
vent_program.addLine(' Set WHF_Flow = 0')
vent_fans[:whf].each do |vent_whf|
vent_program.addLine(" Set WHF_Flow = WHF_Flow + #{UnitConversions.convert(vent_whf.flow_rate, 'cfm', 'm^3/s')} * #{whf_avail_sensors[vent_whf.id].name}")
end
vent_program.addLine(' Set Adj = (Tin-Tnvsp)/(Tin-Tout)')
vent_program.addLine(' If Tin > Tout')
vent_program.addLine(' Set Adj = (Tin-Tnvsp)/(Tin-Tout)')
vent_program.addLine(' Else')
vent_program.addLine(' Set Adj = (Tnvsp-Tin)/(Tout-Tin)')
vent_program.addLine(' EndIf')
vent_program.addLine(' Set Adj = (@Min Adj 1)')
vent_program.addLine(' Set Adj = (@Max Adj 0)')
vent_program.addLine(' If (WHF_Flow > 0)') # If available, prioritize whole house fan
Expand All @@ -583,7 +606,7 @@ def self.apply_natural_ventilation_and_whole_house_fan(runner, model, spaces, hp
end
end
vent_program.addLine(" Set #{whf_elec_actuator.name} = WHF_W*Adj")
vent_program.addLine(' ElseIf (NVavail > 0)') # Natural ventilation
vent_program.addLine(' ElseIf NVavailDayofWeek > 0') # Natural ventilation
if hpxml_bldg.building_occupancy.number_of_residents == 0
# Operational calculation w/ zero occupants, zero out natural ventilation
vent_program.addLine(' Set NVArea = 0')
Expand Down
2 changes: 1 addition & 1 deletion HPXMLtoOpenStudio/resources/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ module Constants
# Arrays/Maps
ERIVersions = ['2014', '2014A', '2014AE', '2014AEG', '2019', '2019A',
'2019AB', '2019ABC', '2019ABCD', '2022', '2022C', '2022CE',
'latest']
'2025', 'latest']
IECCZones = ['1A', '1B', '1C', '2A', '2B', '2C', '3A', '3B', '3C',
'4A', '4B', '4C', '5A', '5B', '5C', '6A', '6B', '6C', '7', '8']
StateCodesMap = { 'AK' => 'Alaska',
Expand Down
7 changes: 6 additions & 1 deletion HPXMLtoOpenStudio/resources/defaults.rb
Original file line number Diff line number Diff line change
Expand Up @@ -399,8 +399,13 @@ def self.apply_building_header_sizing(runner, hpxml_bldg, weather)
# @param weather [WeatherFile] Weather object containing EPW information
# @return [nil]
def self.apply_building_header(hpxml_header, hpxml_bldg, weather)
if hpxml_bldg.header.natvent_seasons.nil?
hpxml_bldg.header.natvent_seasons = HPXML::NatVentSeasonsYearRound
hpxml_bldg.header.natvent_seasons_isdefaulted = true
end

if hpxml_bldg.header.natvent_days_per_week.nil?
hpxml_bldg.header.natvent_days_per_week = 3
hpxml_bldg.header.natvent_days_per_week = 7
hpxml_bldg.header.natvent_days_per_week_isdefaulted = true
end

Expand Down
6 changes: 6 additions & 0 deletions HPXMLtoOpenStudio/resources/hpxml.rb
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,9 @@ class HPXML < Object
MechVentTypeExhaust = 'exhaust only'
MechVentTypeHRV = 'heat recovery ventilator'
MechVentTypeSupply = 'supply only'
NatVentSeasonsCooling = 'cooling'
NatVentSeasonsHeating = 'heating'
NatVentSeasonsYearRound = 'year-round'
OrientationEast = 'east'
OrientationNorth = 'north'
OrientationNortheast = 'northeast'
Expand Down Expand Up @@ -2539,6 +2542,7 @@ class BuildingHeader < BaseElement
:manualj_num_occupants, # [Double] HVACSizingControl/ManualJInputs/NumberofOccupants
:manualj_infiltration_shielding_class, # [Integer] HVACSizingControl/ManualJInputs/InfiltrationShieldingClass (1-5)
:manualj_infiltration_method, # [String] HVACSizingControl/ManualJInputs/InfiltrationMethod (HPXML::ManualJInfiltrationMethodXXX)
:natvent_seasons, # [String] NaturalVentilationAvailabilitySeasons
:natvent_days_per_week, # [Integer] NaturalVentilationAvailabilityDaysperWeek
:schedules_filepaths, # [Array<String>] SchedulesFilePath
:shading_summer_begin_month, # [Integer] ShadingControl/SummerBeginMonth
Expand Down Expand Up @@ -2587,6 +2591,7 @@ def to_doc(building)
XMLHelper.add_element(manualj_sizing_inputs, 'InfiltrationShieldingClass', @manualj_infiltration_shielding_class, :integer, @manualj_infiltration_shielding_class_isdefaulted) unless @manualj_infiltration_shielding_class.nil?
XMLHelper.add_element(manualj_sizing_inputs, 'InfiltrationMethod', @manualj_infiltration_method, :string, @manualj_infiltration_method_isdefaulted) unless @manualj_infiltration_method.nil?
end
XMLHelper.add_extension(building_summary, 'NaturalVentilationAvailabilitySeasons', @natvent_seasons, :string, @natvent_seasons_isdefaulted) unless @natvent_seasons.nil?
XMLHelper.add_extension(building_summary, 'NaturalVentilationAvailabilityDaysperWeek', @natvent_days_per_week, :integer, @natvent_days_per_week_isdefaulted) unless @natvent_days_per_week.nil?
if (not @schedules_filepaths.nil?) && (not @schedules_filepaths.empty?)
@schedules_filepaths.each do |schedules_filepath|
Expand Down Expand Up @@ -2622,6 +2627,7 @@ def from_doc(building)
return if building_summary.nil?

@schedules_filepaths = XMLHelper.get_values(building_summary, 'extension/SchedulesFilePath', :string)
@natvent_seasons = XMLHelper.get_value(building_summary, 'extension/NaturalVentilationAvailabilitySeasons', :string)
@natvent_days_per_week = XMLHelper.get_value(building_summary, 'extension/NaturalVentilationAvailabilityDaysperWeek', :integer)
@shading_summer_begin_month = XMLHelper.get_value(building_summary, 'extension/ShadingControl/SummerBeginMonth', :integer)
@shading_summer_begin_day = XMLHelper.get_value(building_summary, 'extension/ShadingControl/SummerBeginDayOfMonth', :integer)
Expand Down
2 changes: 2 additions & 0 deletions HPXMLtoOpenStudio/resources/hpxml_schematron/EPvalidator.sch
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,8 @@
<sch:assert role='ERROR' test='count(h:extension/h:SchedulesFilePath) &gt;= 0'>Expected 0 or more element(s) for xpath: extension/SchedulesFilePath</sch:assert>
<sch:assert role='ERROR' test='count(h:extension/h:HVACSizingControl) &lt;= 1'>Expected 0 or 1 element(s) for xpath: extension/HVACSizingControl</sch:assert> <!-- See [HVACSizingControl] -->
<sch:assert role='ERROR' test='count(h:extension/h:ShadingControl) &lt;= 1'>Expected 0 or 1 element(s) for xpath: extension/ShadingControl</sch:assert> <!-- See [ShadingControl] -->
<sch:assert role='ERROR' test='count(h:extension/h:NaturalVentilationAvailabilitySeasons) &lt;= 1'>Expected 0 or 1 element(s) for xpath: extension/NaturalVentilationAvailabilitySeasons</sch:assert>
<sch:assert role='ERROR' test='h:extension/h:NaturalVentilationAvailabilitySeasons[text()="year-round" or text()="cooling" or text()="heating"] or not(h:extension/h:NaturalVentilationAvailabilitySeasons)'>Expected extension/NaturalVentilationAvailabilitySeasons to be 'year-round' or 'cooling' or 'heating'</sch:assert>
<sch:assert role='ERROR' test='count(h:extension/h:NaturalVentilationAvailabilityDaysperWeek) &lt;= 1'>Expected 0 or 1 element(s) for xpath: extension/NaturalVentilationAvailabilityDaysperWeek</sch:assert>
<sch:assert role='ERROR' test='number(h:extension/h:NaturalVentilationAvailabilityDaysperWeek) &gt;= 0 or not(h:extension/h:NaturalVentilationAvailabilityDaysperWeek)'>Expected extension/NaturalVentilationAvailabilityDaysperWeek to be greater than or equal to 0</sch:assert>
<sch:assert role='ERROR' test='number(h:extension/h:NaturalVentilationAvailabilityDaysperWeek) &lt;= 7 or not(h:extension/h:NaturalVentilationAvailabilityDaysperWeek)'>Expected extension/NaturalVentilationAvailabilityDaysperWeek to be less than or equal to 7</sch:assert>
Expand Down
Loading