diff --git a/docs/ashrae_90p1_2019/section6/Rule6-8.md b/docs/ashrae_90p1_2019/section6/Rule6-8.md index 34c841d710..361ed2d90f 100644 --- a/docs/ashrae_90p1_2019/section6/Rule6-8.md +++ b/docs/ashrae_90p1_2019/section6/Rule6-8.md @@ -3,14 +3,14 @@ **Rule ID:** 6-8 **Rule Description:** Additional occupancy sensor controls in the proposed building are modeled through schedule adjustments based on factors defined in Table G3.7. -**Rule Assertion:** Proposed RMR = expected value +**Rule Assertion:** Proposed RMD = expected value **Appendix G Section:** Section G3.1-6(i) Modeling Requirements for the Proposed design **Appendix G Section Reference:** - Table G3.7, Performance Rating Method Lighting Power Density Allowances and Occupancy Sensor Reductions Using the Space-by-Space Method -**Applicability:** All required data elements exist for P_RMR -**Applicability Checks:** None +**Applicability:** All required data elements exist for P_RMD +**Applicability Checks:** None **Manual Check:** Yes **Evaluation Context:** Each Data Element @@ -22,32 +22,35 @@ ## Rule Logic: -- Get building open schedule in the proposed model: `building_open_schedule_p = P_RMR.building.building_open_schedule` +- Get building open schedule in the proposed model: `building_open_schedule_p = P_RMD.building.building_open_schedule` -- For each building segment in building: `for building_segment_p in P_RMR.building.building_segments:` +- For each space in building: `for space_p in P_RMD...spaces:` + + - Get matching space from B_RMD: `space_b = match_data_element(B_RMD, Spaces, space_p.id)` - - For each zone in building_segments: `zone_p in building_segment_p.zones:` + - Get total lighting power density in the baseline: `total_space_LPD_b = sum(interior_lighting.power_per_area for interior_lighting in space_b.interior_lighting)` - - For each space in zone: `space_p in zone_p.spaces:` + - Skip evaluating plenums, crawlspaces, and interstitial spaces that have no lighting power in the baseline: `if total_space_LPD_b == 0 and space_b.function in [PLENUM, CRAWL_SPACE, INTERSTITIAL_SPACE]: continue` - - Get matching space from B_RMR: `space_b = match_data_element(B_RMR, Spaces, space_p.id)` + - Get normalized space lighting schedule for B_RMD: `normalized_schedule_b = normalize_space_schedules(space_b.interior_lighting)` - - Get normalized space lighting schedule for B_RMR: `normalized_schedule_b = normalize_space_schedules(space_b.interior_lighting)` + - Get normalized space lighting schedule: `normalized_schedule_p = normalize_space_schedules(space_p.interior_lighting)` - - Get normalized space lighting schedule: `normalized_schedule_p = normalize_space_schedules(space_p.interior_lighting)` + - Compare lighting schedules in P_RMD and B_RMD: `schedule_comparison_result = compare_schedules(normalized_schedule_p, normalized_schedule_b, building_open_schedule_p)` - - Compare lighting schedules in P_RMR and B_RMR: `schedule_comparison_result = compare_schedules(normalized_schedule_p, normalized_schedule_b, building_open_schedule_p)` + **Rule Assertion:** - **Rule Assertion:** + - Case 1: If the space is a crawl space, plenum or interstitial space with lighting power in the baseline model: `if space_p.function in [PLENUM, CRAWL_SPACE, INTERSTITIAL_SPACE] AND total_space_LPD_b > 0: UNDETERMINED and raise_warning "Space is a crawl space, plenum or interstitial space with lighting power in the baseline model. Verify that occupancy sensor control requirements are followed correctly, if applicable."` - - Case 1: For all hours, for each lighting, if lighting schedule in P_RMR is equal to lighting schedule in B_RMR times adjusted lighting occupancy sensor reduction factor: `if schedule_comparison_result == "MATCH": PASS` + - Case 2: For all hours, for each lighting, if lighting schedule in P_RMD is equal to lighting schedule in B_RMD times adjusted lighting occupancy sensor reduction factor: `if schedule_comparison_result == "MATCH": PASS` - - Case 2: Else if lighting schedule in P_RMR is lower than or equal to lighting schedule in B_RMR times adjusted lighting occupancy sensor reduction factor: `if schedule_comparison_result == "EQUAL AND LESS": FAIL and raise_warning "SCHEDULE ADJUSTMENT MAY BE CORRECT IF SPACE INCLUDES DAYLIGHT CONTROL MODELED BY SCHEDULE ADJUSTMENT OR INDIVIDUAL WORKSTATIONS WITH LIGHTING CONTROLLED BY OCCUPANCY SENSORS (TABLE G3.7 FOOTNOTE C)."` + - Case 3: Else if lighting schedule in P_RMD is lower than or equal to lighting schedule in B_RMD times adjusted lighting occupancy sensor reduction factor: `if schedule_comparison_result == "EQUAL AND LESS": FAIL and raise_warning "Schedule adjustment may be correct if space includes daylight control modeled by schedule adjustment or individual workstations with lighting controlled by occupancy sensors (Table G3.7 Footnote C)."` - - Case 3: Else, lighting schedule in P_RMR is higher than lighting schedule in B_RMR times adjusted lighting occupancy sensor reduction factor: `if schedule_comparison_result == "EQUAL AND MORE": UNDETERMINED and raise_message "LIGHTING SCHEDULE IN P-RMR INCLUDING ADJUSTED LIGHTING OCCUPANCY SENSOR REDUCTION FACTOR IS HIGHER THAN THAT IN B-RMR. VERIFY ADDITIONAL OCCUPANCY SENSOR CONTROL IS MODELED CORRECTLY IN P-RMR."` + - Case 4: Else, lighting schedule in P_RMD is higher than lighting schedule in B_RMD times adjusted lighting occupancy sensor reduction factor: `if schedule_comparison_result == "EQUAL AND MORE": UNDETERMINED and raise_message "Lighting schedule in the proposed model, including adjusted lighting occupancy sensor reduction, is higher than that in the baseline model. Verify additional occupancy sensor control is modeled correctly in the proposed model."` **Notes:** 1. Updated the Rule ID from 6-13 to 6-9 on 6/3/2022 2. Updated the Rule ID from 6-9 to 6-8 on 6/8/2022 + 3. Updated to exclude crawl space, plenum or interstitial space on 9/29/2025 **[Back](../_toc.md)** diff --git a/rct229/rulesets/ashrae9012019/section6/section6rule8.py b/rct229/rulesets/ashrae9012019/section6/section6rule8.py index 09ca548c72..9282e8a9c4 100644 --- a/rct229/rulesets/ashrae9012019/section6/section6rule8.py +++ b/rct229/rulesets/ashrae9012019/section6/section6rule8.py @@ -8,6 +8,7 @@ from rct229.rulesets.ashrae9012019.ruleset_functions.normalize_interior_lighting_schedules import ( normalize_interior_lighting_schedules, ) +from rct229.schema.schema_enums import SchemaEnums from rct229.utils.assertions import getattr_ from rct229.utils.jsonpath_utils import find_all, find_exactly_one_with_field_value from rct229.utils.pint_utils import ZERO @@ -21,6 +22,17 @@ "Schedule adjustment may be correct if space includes daylight control modeled by schedule adjustment or " "individual workstation with lighting controlled by occupancy sensors (TABLE G3.7 Footnote c). " ) +NA_BASELINE_LIGHTING_MSG = ( + "Space is a crawl space, plenum or interstitial space with lighting power in the baseline model. " + "Verify that occupancy sensor control requirements are followed correctly, if applicable." +) + +SpaceFunctionOptions = SchemaEnums.schema_enums["SpaceFunctionOptions"] +NA_SPACE_FUNCTIONS = [ + SpaceFunctionOptions.PLENUM, + SpaceFunctionOptions.CRAWL_SPACE, + SpaceFunctionOptions.INTERSTITIAL_SPACE, +] class PRM9012019Rule16x33(RuleDefinitionListIndexedBase): @@ -89,6 +101,29 @@ def create_data(self, context, data=None): ), } + def list_filter(self, context_item, data): + """ + exclude plenum/crawl/interstitial spaces that have no lighting power. + Also excludes NONE lighting-space-type spaces with zero lighting. + """ + space_b = context_item.BASELINE_0 + + total_space_lpd_b = sum( + find_all("$.interior_lighting[*].power_per_area", space_b), + start=ZERO.POWER_PER_AREA, + ) + + space_function_b = space_b.get("function") + lighting_space_type_b = space_b.get("lighting_space_type") + + is_na_or_none = ( + space_function_b in NA_SPACE_FUNCTIONS + or lighting_space_type_b == "NONE" + ) + + # Exclude only NA or NONE spaces with zero baseline lighting + return not (is_na_or_none and total_space_lpd_b == ZERO.POWER_PER_AREA) + class ZoneRule(RuleDefinitionListIndexedBase): def __init__(self): super( @@ -164,12 +199,63 @@ def get_calc_vals(self, context, data=None): "eflh_difference": schedule_comparison_result[ "eflh_difference" ], + "space_function_b": space_b.get("function"), + "lighting_space_type_b": getattr_( + space_b, "space", "lighting_space_type" + ), + "total_space_lpd_b": sum( + find_all("interior_lighting[*].power_per_area", space_b) + ), } def manual_check_required(self, context, calc_vals=None, data=None): eflh_difference = calc_vals["eflh_difference"] + space_function_b = calc_vals["space_function_b"] + lighting_space_type_b = calc_vals["lighting_space_type_b"] + total_space_lpd_b = calc_vals["total_space_lpd_b"] + + # Plenum, crawlspace, interstitial space with lighting + if ( + space_function_b in NA_SPACE_FUNCTIONS + and total_space_lpd_b > ZERO.POWER_PER_AREA + ): + return True + + # NONE space type with lighting + if ( + lighting_space_type_b == "NONE" + and total_space_lpd_b > ZERO.POWER_PER_AREA + ): + return True + + # Case 4: Proposed schedule higher than Baseline-adjusted return eflh_difference > 0 + def get_manual_check_required_msg( + self, context, calc_vals=None, data=None + ): + space_function_b = calc_vals["space_function_b"] + lighting_space_type_b = calc_vals["lighting_space_type_b"] + total_space_lpd_b = calc_vals["total_space_lpd_b"] + eflh_difference = calc_vals["eflh_difference"] + + if ( + space_function_b in NA_SPACE_FUNCTIONS + and total_space_lpd_b > ZERO.POWER_PER_AREA + ): + return NA_BASELINE_LIGHTING_MSG + + if ( + lighting_space_type_b == "NONE" + and total_space_lpd_b > ZERO.POWER_PER_AREA + ): + return NA_BASELINE_LIGHTING_MSG + + if eflh_difference > 0: + return MANUAL_CHECK_MSG + + return "" + def rule_check(self, context, calc_vals=None, data=None): total_hours_compared = calc_vals["total_hours_compared"] total_hours_matched = calc_vals["total_hours_matched"]