From 2fb99c6dc0fd0ff7dd5755a1f72fcf687d65cd79 Mon Sep 17 00:00:00 2001 From: Jackson Jarboe <122476654+JacksonJ-KC@users.noreply.github.com> Date: Fri, 16 May 2025 11:52:20 -0400 Subject: [PATCH 1/6] check for not applicable space types --- docs/section6/Rule6-7.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/section6/Rule6-7.md b/docs/section6/Rule6-7.md index 44439ea470..35fa7ecf04 100644 --- a/docs/section6/Rule6-7.md +++ b/docs/section6/Rule6-7.md @@ -12,6 +12,7 @@ **Evaluation Context:** Each Data Element **Data Lookup:** None ## Rule Logic: +- List lighting space types that have no daylighting control requirements: `not_applicable_space_types = ["DORMITORY_LIVING_QUARTERS", "FIRE_STATION_SLEEPING_QUARTERS", "HEALTHCARE_FACILITY_OPERATING_ROOM", "OUTPATIENT_HEALTH_CARE_FACILITIES_CLASS_1_IMAGING_ROOMS", "DWELLING_UNIT", "GUEST_ROOM", "STORAGE_ROOM_SMALL", "PARKING_AREA_INTERIOR"]` - Check if each zone has window or skylight in the building segment in the Proposed model: `For zone_p in P_RMR...zones:` @@ -22,6 +23,8 @@ - Check if surface has any subsurface that is not door, set daylight flag as TRUE: `if ( subsurface.classification != "DOOR" for subsurface in surface_p.subsurfaces ): daylight_flag_p == TRUE` - For each space in zone: `space_p in zone_p.spaces:` + + - Check if the lighting space type is in the list of not applicable space types: `if space_p.lighting_space_type in not_applicable_space_types: continue` - Get interior_lighting in space: `interior_lighting_p = space_p.interior_lighting` From 6e6541a43c8cf4d04c10c07cb336e7e7bfc87ae5 Mon Sep 17 00:00:00 2001 From: Jackson Jarboe <122476654+JacksonJ-KC@users.noreply.github.com> Date: Fri, 16 May 2025 12:06:52 -0400 Subject: [PATCH 2/6] adjust to only skip when every space is not applicable --- docs/section6/Rule6-7.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/section6/Rule6-7.md b/docs/section6/Rule6-7.md index 35fa7ecf04..2ed970bc2a 100644 --- a/docs/section6/Rule6-7.md +++ b/docs/section6/Rule6-7.md @@ -16,6 +16,8 @@ - Check if each zone has window or skylight in the building segment in the Proposed model: `For zone_p in P_RMR...zones:` + - Check if all spaces in the zone are in the list of not_applicable space types, if so, skip the zone: `if all(space.lighting_space_type in not_applicable_space_types for space in zone_p.spaces): continue` + - For each surfaces in zone: `surface_p in zone_p.surfaces` - Check if surface is exterior: `if surface_p.adjacent_to == "EXTERIOR":` @@ -23,8 +25,6 @@ - Check if surface has any subsurface that is not door, set daylight flag as TRUE: `if ( subsurface.classification != "DOOR" for subsurface in surface_p.subsurfaces ): daylight_flag_p == TRUE` - For each space in zone: `space_p in zone_p.spaces:` - - - Check if the lighting space type is in the list of not applicable space types: `if space_p.lighting_space_type in not_applicable_space_types: continue` - Get interior_lighting in space: `interior_lighting_p = space_p.interior_lighting` From a79b275dfc920d3cd16dd043cd6dad3631839de8 Mon Sep 17 00:00:00 2001 From: Jackson Jarboe <122476654+JacksonJ-KC@users.noreply.github.com> Date: Mon, 21 Jul 2025 08:52:19 -0400 Subject: [PATCH 3/6] add 90.1 reference --- docs/section6/Rule6-7.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/section6/Rule6-7.md b/docs/section6/Rule6-7.md index 2ed970bc2a..9cfb38cab2 100644 --- a/docs/section6/Rule6-7.md +++ b/docs/section6/Rule6-7.md @@ -4,9 +4,9 @@ **Rule ID:** 6-7 **Rule Description:** Proposed building is modeled with daylighting controls directly or through schedule adjustments. **Appendix G Section:** Section 6 Lighting -**Appendix G Section Reference:** Section G3.1-6(h) Lighting: Modeling Requirements for the Proposed design +**Appendix G Section Reference:** Section G3.1-6(h) Lighting: Modeling Requirements for the Proposed design, Table 9.6.1 Minimum Control Requirements -**Applicability:** All required data elements exist for P_RMR +**Applicability:** All required data elements exist for P_RMD **Applicability Checks:** None **Manual Check:** Yes **Evaluation Context:** Each Data Element @@ -14,7 +14,7 @@ ## Rule Logic: - List lighting space types that have no daylighting control requirements: `not_applicable_space_types = ["DORMITORY_LIVING_QUARTERS", "FIRE_STATION_SLEEPING_QUARTERS", "HEALTHCARE_FACILITY_OPERATING_ROOM", "OUTPATIENT_HEALTH_CARE_FACILITIES_CLASS_1_IMAGING_ROOMS", "DWELLING_UNIT", "GUEST_ROOM", "STORAGE_ROOM_SMALL", "PARKING_AREA_INTERIOR"]` -- Check if each zone has window or skylight in the building segment in the Proposed model: `For zone_p in P_RMR...zones:` +- Check if each zone has window or skylight in the building segment in the Proposed model: `For zone_p in P_RMD...zones:` - Check if all spaces in the zone are in the list of not_applicable space types, if so, skip the zone: `if all(space.lighting_space_type in not_applicable_space_types for space in zone_p.spaces): continue` @@ -47,7 +47,7 @@ **Notes:** 1. Updated the Rule ID from 6-12 to 6-8 on 6/3/2022 2. Updated the Rule ID from 6-8 to 6-7 on 6/8/2022 - 3. The rule has been written to apply to user RMR, it should instead be implemented to apply to P-RMR- should discuss + 3. Lighting space types that have no daylighting control requirements are taken from Table 9.6.1 rows with no REQ under the daylighting control columns. **[Back](../_toc.md)** From be489e1b8f618ed07e4173ce7d787bac0f419fb1 Mon Sep 17 00:00:00 2001 From: Jackson Jarboe <122476654+JacksonJ-KC@users.noreply.github.com> Date: Tue, 23 Dec 2025 16:27:40 -0500 Subject: [PATCH 4/6] handle plenum, interstitial, crawlspace, NONE --- docs/section6/Rule6-7.md | 2 +- ...e_90_1_prm_2019.prm_interior_lighting.json | 13 +++ .../data_fns/table_9_6_1_fns_test.py | 1 + .../ashrae9012019/data_fns/table_G3_7_fns.py | 1 + .../ashrae9012019/section6/section6rule7.py | 85 +++++++++++++------ .../Enumerations2019ASHRAE901.schema.json | 8 +- 6 files changed, 80 insertions(+), 30 deletions(-) diff --git a/docs/section6/Rule6-7.md b/docs/section6/Rule6-7.md index 9cfb38cab2..6ee894cb31 100644 --- a/docs/section6/Rule6-7.md +++ b/docs/section6/Rule6-7.md @@ -12,7 +12,7 @@ **Evaluation Context:** Each Data Element **Data Lookup:** None ## Rule Logic: -- List lighting space types that have no daylighting control requirements: `not_applicable_space_types = ["DORMITORY_LIVING_QUARTERS", "FIRE_STATION_SLEEPING_QUARTERS", "HEALTHCARE_FACILITY_OPERATING_ROOM", "OUTPATIENT_HEALTH_CARE_FACILITIES_CLASS_1_IMAGING_ROOMS", "DWELLING_UNIT", "GUEST_ROOM", "STORAGE_ROOM_SMALL", "PARKING_AREA_INTERIOR"]` +- List lighting space types that have no daylighting control requirements: `not_applicable_space_types = ["DORMITORY_LIVING_QUARTERS", "FIRE_STATION_SLEEPING_QUARTERS", "HEALTHCARE_FACILITY_OPERATING_ROOM", "OUTPATIENT_HEALTH_CARE_FACILITIES_CLASS_1_IMAGING_ROOMS", "DWELLING_UNIT", "GUEST_ROOM", "STORAGE_ROOM_SMALL", "PARKING_AREA_INTERIOR", "NONE"]` - Check if each zone has window or skylight in the building segment in the Proposed model: `For zone_p in P_RMD...zones:` diff --git a/rct229/rulesets/ashrae9012019/data/ashrae_90_1_prm_2019.prm_interior_lighting.json b/rct229/rulesets/ashrae9012019/data/ashrae_90_1_prm_2019.prm_interior_lighting.json index ac03f7bf21..ad4a0e37c1 100644 --- a/rct229/rulesets/ashrae9012019/data/ashrae_90_1_prm_2019.prm_interior_lighting.json +++ b/rct229/rulesets/ashrae9012019/data/ashrae_90_1_prm_2019.prm_interior_lighting.json @@ -1832,6 +1832,19 @@ "occup_sensor_auto_on_svgs": 0.1, "occup_sensor_savings": 0.125, "notes": null + }, + { + "template": "90.1-PRM-2019", + "lpd_space_type": "none", + "primary_space_type": "none", + "secondary_space_type": "none", + "w/ft^2": 0, + "w/ft": null, + "isresidential": 0, + "manon_or_partauto": 0, + "occup_sensor_auto_on_svgs": 0, + "occup_sensor_savings": 0, + "notes": null } ] } diff --git a/rct229/rulesets/ashrae9012019/data_fns/table_9_6_1_fns_test.py b/rct229/rulesets/ashrae9012019/data_fns/table_9_6_1_fns_test.py index 915ae7eb7e..6d7870b155 100644 --- a/rct229/rulesets/ashrae9012019/data_fns/table_9_6_1_fns_test.py +++ b/rct229/rulesets/ashrae9012019/data_fns/table_9_6_1_fns_test.py @@ -133,5 +133,6 @@ def test__building_type_enumeration_to_lpd_map(): "TRANSPORTATION_FACILITY_TICKET_COUNTER", "WAREHOUSE_STORAGE_AREA_MEDIUM_TO_BULKY_PALLETIZED_ITEMS", "WAREHOUSE_STORAGE_AREA_SMALLER_HAND_CARRIED_ITEMS", + "NONE", ], ) diff --git a/rct229/rulesets/ashrae9012019/data_fns/table_G3_7_fns.py b/rct229/rulesets/ashrae9012019/data_fns/table_G3_7_fns.py index 09064ada10..e724ddc209 100644 --- a/rct229/rulesets/ashrae9012019/data_fns/table_G3_7_fns.py +++ b/rct229/rulesets/ashrae9012019/data_fns/table_G3_7_fns.py @@ -112,6 +112,7 @@ "TRANSPORTATION_FACILITY_TICKET_COUNTER": "transportation ticket counter", "WAREHOUSE_STORAGE_AREA_MEDIUM_TO_BULKY_PALLETIZED_ITEMS": "warehouse - bulk storage", "WAREHOUSE_STORAGE_AREA_SMALLER_HAND_CARRIED_ITEMS": "warehouse - fine storage", + "NONE": "none", } FULL_AUTO_ON = SchemaEnums.schema_enums["LightingOccupancyControlOptions"].FULL_AUTO_ON diff --git a/rct229/rulesets/ashrae9012019/section6/section6rule7.py b/rct229/rulesets/ashrae9012019/section6/section6rule7.py index 6b8402f3f9..5c21978f4d 100644 --- a/rct229/rulesets/ashrae9012019/section6/section6rule7.py +++ b/rct229/rulesets/ashrae9012019/section6/section6rule7.py @@ -9,6 +9,7 @@ MSG_WARN_DAYLIGHT = "Some of the spaces in zone are modeled with window(s) and/or skylight(s) and have daylighting controls modeled via schedule adjustment. Verify that the mandatory lighting control requirements are met, and that the supporting documentation is provided for the schedule adjustment." MSG_WARN_NO_DAYLIGHT = "Some of the spaces in zone are modeled with fenestration but no daylighting controls. The design must include mandatory daylighting controls unless any of the exceptions to 90.1 section 9.4.1.1 apply." +LightingSpaceOptions = SchemaEnums.schema_enums["LightingSpaceOptions2019ASHRAE901TG37"] DOOR = SchemaEnums.schema_enums["SubsurfaceClassificationOptions"].DOOR EXTERIOR = SchemaEnums.schema_enums["SurfaceAdjacencyOptions"].EXTERIOR NONE = SchemaEnums.schema_enums["LightingDaylightingControlOptions"].NONE @@ -32,6 +33,26 @@ def __init__(self): list_path="ruleset_model_descriptions[0].buildings[*].building_segments[*].zones[*]", ) + def list_filter(self, context_item, data): + zone_p = context_item.PROPOSED + + not_applicable_space_types = { + LightingSpaceOptions.DORMITORY_LIVING_QUARTERS, + LightingSpaceOptions.FIRE_STATION_SLEEPING_QUARTERS, + LightingSpaceOptions.HEALTHCARE_FACILITY_OPERATING_ROOM, + LightingSpaceOptions.OUTPATIENT_HEALTH_CARE_FACILITIES_CLASS_1_IMAGING_ROOMS, + LightingSpaceOptions.DWELLING_UNIT, + LightingSpaceOptions.GUEST_ROOM, + LightingSpaceOptions.STORAGE_ROOM_SMALL, + LightingSpaceOptions.PARKING_AREA_INTERIOR, + LightingSpaceOptions.NONE, + } + + return not all( + space.get("lighting_space_type") in not_applicable_space_types + for space in zone_p.get("spaces", []) + ) + class ZoneRule(RuleDefinitionBase): def __init__(self): super(PRM9012019Rule66m62.ZoneRule, self).__init__( @@ -44,10 +65,10 @@ def __init__(self): def get_calc_vals(self, context, data=None): zone_p = context.PROPOSED - daylight_flag_p = ( + is_daylighting_control_expected = ( len( find_all( - # Doors in a surface adjacent to exterior + # Subsurfaces that are not doors on exterior surfaces f'$.surfaces[*][?(@.adjacent_to = "{EXTERIOR}")].subsurfaces[*][?(@.classification != "{DOOR}")]', zone_p, ) @@ -55,60 +76,72 @@ def get_calc_vals(self, context, data=None): > 0 ) - has_daylight_control_flag = ( + is_daylighting_control_modeled = ( len( find_all( - # interior_lighting instances with daylighting_control_type set to NONE - f'$.spaces[*].interior_lighting[*][?(@.daylighting_control_type!= "{NONE}")]', + # interior_lighting instances with daylighting_control_type not set to NONE + f'$.spaces[*].interior_lighting[*][?(@.daylighting_control_type != "{NONE}")]', zone_p, ) ) > 0 ) - daylight_schedule_adjustment_flag = any( - find_all( - # interior_lighting instances with are_schedules_used_for_modeling_daylighting_control set to True - "$.spaces[*].interior_lighting[*][?(@.are_schedules_used_for_modeling_daylighting_control = true)]", - zone_p, + are_schedules_used_for_modeling_daylighting_controls = ( + len( + find_all( + "$.spaces[*].interior_lighting[*][?(@.are_schedules_used_for_modeling_daylighting_control = true)]", + zone_p, + ) ) + > 0 ) return { - "daylight_flag_p": daylight_flag_p, - "has_daylight_control_flag": has_daylight_control_flag, - "daylight_schedule_adjustment_flag": daylight_schedule_adjustment_flag, + "is_daylighting_control_expected": is_daylighting_control_expected, + "is_daylighting_control_modeled": is_daylighting_control_modeled, + "are_schedules_used_for_modeling_daylighting_controls": are_schedules_used_for_modeling_daylighting_controls, } def manual_check_required(self, context, calc_vals=None, data=None): - daylight_flag_p = calc_vals["daylight_flag_p"] - has_daylight_control_flag = calc_vals["has_daylight_control_flag"] + is_daylighting_control_expected = calc_vals[ + "is_daylighting_control_expected" + ] + is_daylighting_control_modeled = calc_vals["is_daylighting_control_modeled"] - return daylight_flag_p and has_daylight_control_flag + return is_daylighting_control_expected and is_daylighting_control_modeled def get_manual_check_required_msg(self, context, calc_vals=None, data=None): - daylight_schedule_adjustment_flag = calc_vals[ - "daylight_schedule_adjustment_flag" + are_schedules_used_for_modeling_daylighting_controls = calc_vals[ + "are_schedules_used_for_modeling_daylighting_controls" ] return ( MSG_WARN_DAYLIGHT - if daylight_schedule_adjustment_flag + if are_schedules_used_for_modeling_daylighting_controls else MSG_WARN_DAYLIGHT_NO_SCHEDULE ) - def rule_check(self, context, calc_vals, data=None): - daylight_flag_p = calc_vals["daylight_flag_p"] - has_daylight_control_flag = calc_vals["has_daylight_control_flag"] + def rule_check(self, context, calc_vals=None, data=None): + is_daylighting_control_expected = calc_vals[ + "is_daylighting_control_expected" + ] + is_daylighting_control_modeled = calc_vals["is_daylighting_control_modeled"] - return not daylight_flag_p and not has_daylight_control_flag + return ( + not is_daylighting_control_expected + and not is_daylighting_control_modeled + ) def get_fail_msg(self, context, calc_vals=None, data=None): - daylight_flag_p = calc_vals["daylight_flag_p"] - has_daylight_control_flag = calc_vals["has_daylight_control_flag"] + is_daylighting_control_expected = calc_vals[ + "is_daylighting_control_expected" + ] + is_daylighting_control_modeled = calc_vals["is_daylighting_control_modeled"] return ( MSG_WARN_NO_DAYLIGHT - if daylight_flag_p and not has_daylight_control_flag + if is_daylighting_control_expected + and not is_daylighting_control_modeled else "" ) diff --git a/rct229/schema/Enumerations2019ASHRAE901.schema.json b/rct229/schema/Enumerations2019ASHRAE901.schema.json index 119e5fe2e3..203766c9c2 100644 --- a/rct229/schema/Enumerations2019ASHRAE901.schema.json +++ b/rct229/schema/Enumerations2019ASHRAE901.schema.json @@ -257,7 +257,8 @@ "TRANSPORTATION_FACILITY_AIRPORT_CONCOURSE", "TRANSPORTATION_FACILITY_TICKET_COUNTER", "WAREHOUSE_STORAGE_AREA_MEDIUM_TO_BULKY_PALLETIZED_ITEMS", - "WAREHOUSE_STORAGE_AREA_SMALLER_HAND_CARRIED_ITEMS" + "WAREHOUSE_STORAGE_AREA_SMALLER_HAND_CARRIED_ITEMS", + "NONE" ], "descriptions": [ "Atrium - Low/Medium", @@ -362,7 +363,8 @@ "Transportation Facility - Airport concourse", "Transportation Facility - Ticket counter", "Warehouse \u00e2\u20ac\u201d Storage Area - Medium to bulky, palletized items", - "Warehouse \u00e2\u20ac\u201d Storage Area - Smaller, hand-carried items" + "Warehouse \u00e2\u20ac\u201d Storage Area - Smaller, hand-carried items", + "None" ], "notes": [ null, @@ -467,7 +469,7 @@ null, null, null, - null + "Indicates that the space will have no occupancy, such as an elevator shaft." ] }, "LightingPurposeOptions2019ASHRAE901": { From 4387519bdc11124f068c316d2bd25a5bbd8d5727 Mon Sep 17 00:00:00 2001 From: Jackson Jarboe <122476654+JacksonJ-KC@users.noreply.github.com> Date: Tue, 23 Dec 2025 16:50:04 -0500 Subject: [PATCH 5/6] handle plenum, interstitial, crawlspace, NONE --- docs/ashrae_90p1_2019/section6/Rule6-7.md | 22 ++++-- .../ashrae9012019/section6/section6rule7.py | 75 ++++++++++++++----- 2 files changed, 71 insertions(+), 26 deletions(-) diff --git a/docs/ashrae_90p1_2019/section6/Rule6-7.md b/docs/ashrae_90p1_2019/section6/Rule6-7.md index 6ee894cb31..115489191e 100644 --- a/docs/ashrae_90p1_2019/section6/Rule6-7.md +++ b/docs/ashrae_90p1_2019/section6/Rule6-7.md @@ -1,4 +1,3 @@ - # Lighting - Rule 6-7 **Rule ID:** 6-7 @@ -16,7 +15,13 @@ - Check if each zone has window or skylight in the building segment in the Proposed model: `For zone_p in P_RMD...zones:` - - Check if all spaces in the zone are in the list of not_applicable space types, if so, skip the zone: `if all(space.lighting_space_type in not_applicable_space_types for space in zone_p.spaces): continue` + - Initialize not-applicable space with lighting power flag as FALSE: `na_space_with_lighting_power_flag = FALSE` + + - For each space in zone, compute total lighting power density and set flag if not-applicable space has lighting power: + `if space.lighting_space_type in not_applicable_space_types and sum(lighting.power_per_area for lighting in space.interior_lighting) > 0: na_space_with_lighting_power_flag = TRUE` + + - Check if all spaces in the zone are in the list of not_applicable space types and no not-applicable space has lighting power, if so, skip the zone: + `if all(space.lighting_space_type in not_applicable_space_types for space in zone_p.spaces) and not na_space_with_lighting_power_flag: continue` - For each surfaces in zone: `surface_p in zone_p.surfaces` @@ -34,20 +39,21 @@ **Rule Assertion:** For each zone in the Proposed model: - - Case 1, if the zone has window or skylight and daylight control, and daylight control is not modeled using schedule: `if ( daylight_flag_p == TRUE ) AND ( has_daylight_control_flag == TRUE ) AND ( NOT daylight_schedule_adjustment_flag ): UNDETERMINED and raise_warning "SOME OF THE SPACES IN ZONE ARE MODELED WITH WINDOW(S) AND/OR SKYLIGHT(S) AND HAVE DAYLIGHTING CONTROLS MODELED EXPLICITLY IN THE SIMULATION TOOL. VERIFY THAT THE MANDATORY LIGHTING CONTROL REQUIREMENTS ARE MET."` + - Case 1, if the zone contains a not-applicable space type with lighting power: `if na_space_with_lighting_power_flag: UNDETERMINED and raise_warning "THE ZONE CONTAINS A NOT-APPLICABLE SPACE TYPE WITH LIGHTING POWER. VERIFY THAT THE MANDATORY DAYLIGHTING CONTROL REQUIREMENTS ARE MET, IF APPLICABLE."` - - Case 2, else if the zone has window or skylight and daylight control, and daylight control is modeled using schedule: `if ( daylight_flag_p == TRUE ) AND ( has_daylight_control_flag == TRUE ) AND ( daylight_schedule_adjustment_flag ): UNDETERMINED and raise_warning "SOME OF THE SPACES IN ZONE ARE MODELED WITH WINDOW(S) AND/OR SKYLIGHT(S) AND HAVE DAYLIGHTING CONTROLS MODELED VIA SCHEDULE ADJUSTMENT. VERIFY THAT THE MANDATORY LIGHTING CONTROL REQUIREMENTS ARE MET, AND THAT THE SUPPORTING DOCUMENTATION IS PROVIDED FOR THE SCHEDULE ADJUSTMENT."` + - Case 2, if the zone has window or skylight and daylight control, and daylight control is not modeled using schedule: `if ( daylight_flag_p == TRUE ) AND ( has_daylight_control_flag == TRUE ) AND ( NOT daylight_schedule_adjustment_flag ): UNDETERMINED and raise_warning "SOME OF THE SPACES IN ZONE ARE MODELED WITH WINDOW(S) AND/OR SKYLIGHT(S) AND HAVE DAYLIGHTING CONTROLS MODELED EXPLICITLY IN THE SIMULATION TOOL. VERIFY THAT THE MANDATORY LIGHTING CONTROL REQUIREMENTS ARE MET."` - - Case 3, else if the zone has window or skylight and daylight control is not modeled: `else if ( daylight_flag_p == TRUE ) AND ( has_daylight_control_flag == FALSE ): FAIL and raise_warning "SOME OF THE SPACES IN ZONE ARE MODELED WITH FENESTRATION BUT NO DAYLIGHTING CONTROLS. THE DESIGN MUST INCLUDE MANDATORY DAYLIGHTING CONTROLS UNLESS ANY OF THE EXCEPTIONS TO 90.1 SECTION 9.4.1.1(E) APPLY."` + - Case 3, else if the zone has window or skylight and daylight control, and daylight control is modeled using schedule: `if ( daylight_flag_p == TRUE ) AND ( has_daylight_control_flag == TRUE ) AND ( daylight_schedule_adjustment_flag ): UNDETERMINED and raise_warning "SOME OF THE SPACES IN ZONE ARE MODELED WITH WINDOW(S) AND/OR SKYLIGHT(S) AND HAVE DAYLIGHTING CONTROLS MODELED VIA SCHEDULE ADJUSTMENT. VERIFY THAT THE MANDATORY LIGHTING CONTROL REQUIREMENTS ARE MET, AND THAT THE SUPPORTING DOCUMENTATION IS PROVIDED FOR THE SCHEDULE ADJUSTMENT."` - - Case 4, else if the zone does not have window or skylight and daylight control is modeled: `else if ( daylight_flag_p == FALSE ) AND ( has_daylight_control_flag == TRUE ): FAIL` + - Case 4, else if the zone has window or skylight and daylight control is not modeled: `else if ( daylight_flag_p == TRUE ) AND ( has_daylight_control_flag == FALSE ): FAIL and raise_warning "SOME OF THE SPACES IN ZONE ARE MODELED WITH FENESTRATION BUT NO DAYLIGHTING CONTROLS. THE DESIGN MUST INCLUDE MANDATORY DAYLIGHTING CONTROLS UNLESS ANY OF THE EXCEPTIONS TO 90.1 SECTION 9.4.1.1(E) APPLY."` - - Case 5, else, the zone does not have window or skylight and no daylight control is modeled: `else: PASS` + - Case 5, else if the zone does not have window or skylight and daylight control is modeled: `else if ( daylight_flag_p == FALSE ) AND ( has_daylight_control_flag == TRUE ): FAIL` + + - Case 6, else, the zone does not have window or skylight and no daylight control is modeled: `else: PASS` **Notes:** 1. Updated the Rule ID from 6-12 to 6-8 on 6/3/2022 2. Updated the Rule ID from 6-8 to 6-7 on 6/8/2022 3. Lighting space types that have no daylighting control requirements are taken from Table 9.6.1 rows with no REQ under the daylighting control columns. - **[Back](../_toc.md)** diff --git a/rct229/rulesets/ashrae9012019/section6/section6rule7.py b/rct229/rulesets/ashrae9012019/section6/section6rule7.py index 5c21978f4d..85659131a5 100644 --- a/rct229/rulesets/ashrae9012019/section6/section6rule7.py +++ b/rct229/rulesets/ashrae9012019/section6/section6rule7.py @@ -5,6 +5,7 @@ from rct229.schema.schema_enums import SchemaEnums from rct229.utils.jsonpath_utils import find_all +MSG_WARN_NA_SPACE_WITH_LIGHTING = "The zone contains a not-applicable space type with lighting power. Verify that the mandatory daylighting control requirements are met, if applicable." MSG_WARN_DAYLIGHT_NO_SCHEDULE = "Some of the spaces in zone are modeled with window(s) and/or skylight(s) and have daylighting controls modeled explicitly in the simulation tool. Verify that the mandatory lighting control requirements are met." MSG_WARN_DAYLIGHT = "Some of the spaces in zone are modeled with window(s) and/or skylight(s) and have daylighting controls modeled via schedule adjustment. Verify that the mandatory lighting control requirements are met, and that the supporting documentation is provided for the schedule adjustment." MSG_WARN_NO_DAYLIGHT = "Some of the spaces in zone are modeled with fenestration but no daylighting controls. The design must include mandatory daylighting controls unless any of the exceptions to 90.1 section 9.4.1.1 apply." @@ -48,9 +49,24 @@ def list_filter(self, context_item, data): LightingSpaceOptions.NONE, } - return not all( + spaces = zone_p.get("spaces", []) + + na_space_with_lighting_power = any( space.get("lighting_space_type") in not_applicable_space_types - for space in zone_p.get("spaces", []) + and sum( + il.get("power_per_area", 0) for il in space.get("interior_lighting", []) + ) + > 0 + for space in spaces + ) + + # Skip only if all spaces are not applicable and none have lighting power + return not ( + all( + space.get("lighting_space_type") in not_applicable_space_types + for space in spaces + ) + and not na_space_with_lighting_power ) class ZoneRule(RuleDefinitionBase): @@ -65,11 +81,33 @@ def __init__(self): def get_calc_vals(self, context, data=None): zone_p = context.PROPOSED + not_applicable_space_types = { + LightingSpaceOptions.DORMITORY_LIVING_QUARTERS, + LightingSpaceOptions.FIRE_STATION_SLEEPING_QUARTERS, + LightingSpaceOptions.HEALTHCARE_FACILITY_OPERATING_ROOM, + LightingSpaceOptions.OUTPATIENT_HEALTH_CARE_FACILITIES_CLASS_1_IMAGING_ROOMS, + LightingSpaceOptions.DWELLING_UNIT, + LightingSpaceOptions.GUEST_ROOM, + LightingSpaceOptions.STORAGE_ROOM_SMALL, + LightingSpaceOptions.PARKING_AREA_INTERIOR, + LightingSpaceOptions.NONE, + } + + has_na_space_with_lighting_power = any( + space.get("lighting_space_type") in not_applicable_space_types + and sum( + il.get("power_per_area", 0) + for il in space.get("interior_lighting", []) + ) + > 0 + for space in zone_p.get("spaces", []) + ) + is_daylighting_control_expected = ( len( find_all( - # Subsurfaces that are not doors on exterior surfaces - f'$.surfaces[*][?(@.adjacent_to = "{EXTERIOR}")].subsurfaces[*][?(@.classification != "{DOOR}")]', + f'$.surfaces[*][?(@.adjacent_to = "{EXTERIOR}")].' + f'subsurfaces[*][?(@.classification != "{DOOR}")]', zone_p, ) ) @@ -79,8 +117,8 @@ def get_calc_vals(self, context, data=None): is_daylighting_control_modeled = ( len( find_all( - # interior_lighting instances with daylighting_control_type not set to NONE - f'$.spaces[*].interior_lighting[*][?(@.daylighting_control_type != "{NONE}")]', + f"$.spaces[*].interior_lighting[*]" + f'[?(@.daylighting_control_type != "{NONE}")]', zone_p, ) ) @@ -90,7 +128,8 @@ def get_calc_vals(self, context, data=None): are_schedules_used_for_modeling_daylighting_controls = ( len( find_all( - "$.spaces[*].interior_lighting[*][?(@.are_schedules_used_for_modeling_daylighting_control = true)]", + "$.spaces[*].interior_lighting[*]" + "[?(@.are_schedules_used_for_modeling_daylighting_control = true)]", zone_p, ) ) @@ -98,27 +137,27 @@ def get_calc_vals(self, context, data=None): ) return { + "na_space_with_lighting_power": has_na_space_with_lighting_power, "is_daylighting_control_expected": is_daylighting_control_expected, "is_daylighting_control_modeled": is_daylighting_control_modeled, - "are_schedules_used_for_modeling_daylighting_controls": are_schedules_used_for_modeling_daylighting_controls, + "are_schedules_used_for_modeling_daylighting_controls": ( + are_schedules_used_for_modeling_daylighting_controls + ), } def manual_check_required(self, context, calc_vals=None, data=None): - is_daylighting_control_expected = calc_vals[ - "is_daylighting_control_expected" - ] - is_daylighting_control_modeled = calc_vals["is_daylighting_control_modeled"] - - return is_daylighting_control_expected and is_daylighting_control_modeled + return calc_vals["na_space_with_lighting_power"] or ( + calc_vals["is_daylighting_control_expected"] + and calc_vals["is_daylighting_control_modeled"] + ) def get_manual_check_required_msg(self, context, calc_vals=None, data=None): - are_schedules_used_for_modeling_daylighting_controls = calc_vals[ - "are_schedules_used_for_modeling_daylighting_controls" - ] + if calc_vals["na_space_with_lighting_power"]: + return MSG_WARN_NA_SPACE_WITH_LIGHTING return ( MSG_WARN_DAYLIGHT - if are_schedules_used_for_modeling_daylighting_controls + if calc_vals["are_schedules_used_for_modeling_daylighting_controls"] else MSG_WARN_DAYLIGHT_NO_SCHEDULE ) From 956d0cc1d9ff6cd23663f3e0178dd9a332fd0819 Mon Sep 17 00:00:00 2001 From: Jackson Jarboe <122476654+JacksonJ-KC@users.noreply.github.com> Date: Wed, 24 Dec 2025 14:29:00 -0500 Subject: [PATCH 6/6] correct enum --- .../ashrae9012019/section6/section6rule7.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/rct229/rulesets/ashrae9012019/section6/section6rule7.py b/rct229/rulesets/ashrae9012019/section6/section6rule7.py index 85659131a5..6f1c8054a6 100644 --- a/rct229/rulesets/ashrae9012019/section6/section6rule7.py +++ b/rct229/rulesets/ashrae9012019/section6/section6rule7.py @@ -10,6 +10,9 @@ MSG_WARN_DAYLIGHT = "Some of the spaces in zone are modeled with window(s) and/or skylight(s) and have daylighting controls modeled via schedule adjustment. Verify that the mandatory lighting control requirements are met, and that the supporting documentation is provided for the schedule adjustment." MSG_WARN_NO_DAYLIGHT = "Some of the spaces in zone are modeled with fenestration but no daylighting controls. The design must include mandatory daylighting controls unless any of the exceptions to 90.1 section 9.4.1.1 apply." +VentilationSpaceOptions = SchemaEnums.schema_enums[ + "VentilationSpaceOptions2019ASHRAE901" +] LightingSpaceOptions = SchemaEnums.schema_enums["LightingSpaceOptions2019ASHRAE901TG37"] DOOR = SchemaEnums.schema_enums["SubsurfaceClassificationOptions"].DOOR EXTERIOR = SchemaEnums.schema_enums["SurfaceAdjacencyOptions"].EXTERIOR @@ -41,7 +44,7 @@ def list_filter(self, context_item, data): LightingSpaceOptions.DORMITORY_LIVING_QUARTERS, LightingSpaceOptions.FIRE_STATION_SLEEPING_QUARTERS, LightingSpaceOptions.HEALTHCARE_FACILITY_OPERATING_ROOM, - LightingSpaceOptions.OUTPATIENT_HEALTH_CARE_FACILITIES_CLASS_1_IMAGING_ROOMS, + VentilationSpaceOptions.OUTPATIENT_HEALTH_CARE_FACILITIES_CLASS_1_IMAGING_ROOMS, LightingSpaceOptions.DWELLING_UNIT, LightingSpaceOptions.GUEST_ROOM, LightingSpaceOptions.STORAGE_ROOM_SMALL, @@ -52,7 +55,10 @@ def list_filter(self, context_item, data): spaces = zone_p.get("spaces", []) na_space_with_lighting_power = any( - space.get("lighting_space_type") in not_applicable_space_types + ( + space.get("lighting_space_type") in not_applicable_space_types + or space.get("ventilation_space_type") in not_applicable_space_types + ) and sum( il.get("power_per_area", 0) for il in space.get("interior_lighting", []) ) @@ -85,7 +91,7 @@ def get_calc_vals(self, context, data=None): LightingSpaceOptions.DORMITORY_LIVING_QUARTERS, LightingSpaceOptions.FIRE_STATION_SLEEPING_QUARTERS, LightingSpaceOptions.HEALTHCARE_FACILITY_OPERATING_ROOM, - LightingSpaceOptions.OUTPATIENT_HEALTH_CARE_FACILITIES_CLASS_1_IMAGING_ROOMS, + VentilationSpaceOptions.OUTPATIENT_HEALTH_CARE_FACILITIES_CLASS_1_IMAGING_ROOMS, LightingSpaceOptions.DWELLING_UNIT, LightingSpaceOptions.GUEST_ROOM, LightingSpaceOptions.STORAGE_ROOM_SMALL, @@ -94,7 +100,10 @@ def get_calc_vals(self, context, data=None): } has_na_space_with_lighting_power = any( - space.get("lighting_space_type") in not_applicable_space_types + ( + space.get("lighting_space_type") in not_applicable_space_types + or space.get("ventilation_space_type") in not_applicable_space_types + ) and sum( il.get("power_per_area", 0) for il in space.get("interior_lighting", [])