diff --git a/rct229/rulesets/ashrae9012019/ruleset_functions/baseline_system_type_compare.py b/rct229/rulesets/ashrae9012019/ruleset_functions/baseline_system_type_compare.py index 184bea59e9..ca72838be9 100644 --- a/rct229/rulesets/ashrae9012019/ruleset_functions/baseline_system_type_compare.py +++ b/rct229/rulesets/ashrae9012019/ruleset_functions/baseline_system_type_compare.py @@ -11,6 +11,7 @@ def baseline_system_type_compare( system_type: HVAC_SYS, target_system_type: HVAC_SYS, exact_match: Optional[bool] = True, + use_assert: Optional[bool] = False, ) -> bool: """ Parameters @@ -41,10 +42,11 @@ def baseline_system_type_compare( ] available_system_types.extend(available_target_system_list) - assert_( - system_type in available_system_types, - f"{system_type} does not match any baseline HVAC system type", - ) + if use_assert: + assert_( + system_type in available_system_types, + f"{system_type} does not match any baseline HVAC system type", + ) assert_( target_system_type in available_target_system_list, f"{target_system_type} does not match any primary " diff --git a/rct229/rulesets/ashrae9012019/ruleset_functions/baseline_system_type_compare_test.py b/rct229/rulesets/ashrae9012019/ruleset_functions/baseline_system_type_compare_test.py index f661761aed..63431cf97c 100644 --- a/rct229/rulesets/ashrae9012019/ruleset_functions/baseline_system_type_compare_test.py +++ b/rct229/rulesets/ashrae9012019/ruleset_functions/baseline_system_type_compare_test.py @@ -5,14 +5,15 @@ from rct229.rulesets.ashrae9012019.ruleset_functions.baseline_systems.baseline_system_util import ( HVAC_SYS, ) -from rct229.utils.assertions import RCTException +from rct229.utils.assertions import RCTException, RCTFailureException def test_baseline_system_type_compare_test_exact_match__exception_sys_type(): with pytest.raises( - RCTException, match="SYS123 does not match any baseline HVAC system type" + RCTFailureException, + match="Not_Sys does not match any primary baseline HVAC system type", ): - baseline_system_type_compare("SYS123", HVAC_SYS.SYS_1) + assert baseline_system_type_compare("SYS123", HVAC_SYS.UNMATCHED) def test_baseline_system_type_compare_test_exact_match__exception_target_sys_type(): diff --git a/rct229/rulesets/ashrae9012019/ruleset_functions/get_baseline_system_types.py b/rct229/rulesets/ashrae9012019/ruleset_functions/get_baseline_system_types.py index 45ec801160..999e0e7310 100644 --- a/rct229/rulesets/ashrae9012019/ruleset_functions/get_baseline_system_types.py +++ b/rct229/rulesets/ashrae9012019/ruleset_functions/get_baseline_system_types.py @@ -54,7 +54,9 @@ @memoize -def get_baseline_system_types(rmd_b: dict) -> dict[HVAC_SYS, list[str]]: +def get_baseline_system_types( + rmd_b: dict, use_assert: bool = False +) -> dict[HVAC_SYS, list[str]]: """ Identify all the baseline system types modeled in a B-RMD. @@ -89,7 +91,7 @@ def get_baseline_system_types(rmd_b: dict) -> dict[HVAC_SYS, list[str]]: i[1] for i in inspect.getmembers(HVAC_SYS) if type(i[0]) is str and i[0].startswith("SYS") - ] + ] + [HVAC_SYS.UNMATCHED] baseline_hvac_system_dict = {sys_type: [] for sys_type in hvac_sys_list} @@ -123,9 +125,13 @@ def get_baseline_system_types(rmd_b: dict) -> dict[HVAC_SYS, list[str]]: sys_found = True # break # TODO: This line must be uncommented before we ship the software. The reason why we commented this line is because an edge case HVAC system could (potentially) match multiple HVAC basseline systems, which require RCT developers to refine the `is_baseline_system` logic. - assert_( - sys_found, - f"Error: HVAC {hvac_b_id} does not match any baseline system type.", - ) + if use_assert: + assert_( + sys_found, + f"Error: HVAC {hvac_b_id} does not match any baseline system type.", + ) + else: + if not sys_found: + baseline_hvac_system_dict[HVAC_SYS.UNMATCHED].append(hvac_b_id) return baseline_hvac_system_dict diff --git a/rct229/rulesets/ashrae9012019/section18/section18rule2.py b/rct229/rulesets/ashrae9012019/section18/section18rule2.py index 1740b1f35f..cac1901420 100644 --- a/rct229/rulesets/ashrae9012019/section18/section18rule2.py +++ b/rct229/rulesets/ashrae9012019/section18/section18rule2.py @@ -127,6 +127,51 @@ def create_data(self, context, data): "$.buildings[*].building_segments[*].heating_ventilating_air_conditioning_systems[*].id", rmd_b, ): + sys_type = next( + ( + sys_type + for sys_type, hvac_id_list in baseline_system_types_dict_b.items() + if hvac_id_b in hvac_id_list + ), + HVAC_SYS.UNMATCHED, + ) + + zones_served_by_system = zones_and_terminal_unit_list_dict_b.get( + hvac_id_b, {} + ).get("zone_list", []) + + zones_on_floor = ( + get_zones_on_same_floor_list(rmd_b, zones_served_by_system[0]) + if zones_served_by_system + else [] + ) + + hvac_lab_zones_only_b = lab_zone_hvac_systems["lab_zones_only"] + + hvac_data_b[hvac_id_b] = { + "sys_type": sys_type, + "is_sys_single_zone_sys_b": ( + len(zones_served_by_system) == 1 + if zones_served_by_system + else False + ), + "does_two_sys_exist_on_same_fl_b": "undetermined", + "does_sys_only_serve_lab_b": hvac_id_b in hvac_lab_zones_only_b, + "does_sys_part_of_serve_lab_b": ( + hvac_id_b in hvac_lab_zones_only_b + and len(hvac_lab_zones_only_b) > 1 + ), + "does_sys_serve_lab_and_other_b": ( + hvac_id_b in lab_zone_hvac_systems["lab_and_other"] + ), + "hvac_sys2_id_b": None, + "does_sys_serve_one_floor": ( + bool(zones_served_by_system) + and set(zones_served_by_system).issubset(set(zones_on_floor)) + ), + "do_multi_zone_evaluation": bool(len(zones_served_by_system) > 1), + } + if hvac_id_b in applicable_hvac_sys_ids_b: hvac_lab_zones_only_b = lab_zone_hvac_systems["lab_zones_only"] hvac_data_b[hvac_id_b] = { @@ -142,14 +187,7 @@ def create_data(self, context, data): ) ] ), - "sys_type": next( - ( - sys_type - for sys_type, hvac_id_list in baseline_system_types_dict_b.items() - if hvac_id_b in hvac_id_list - ), - None, - ), + "sys_type": sys_type, "does_sys_only_serve_lab_b": ( hvac_id_b in hvac_lab_zones_only_b and len(hvac_lab_zones_only_b) == 1 @@ -168,11 +206,7 @@ def create_data(self, context, data): } hvac_data_by_id_b = hvac_data_b[hvac_id_b] - # if a system is a single zone system -> PASS - # or the system only serves the only lab zone -> PASS/UNDETERMINED based on total exhaust - # or the system is one of the HVAC syss serving lab zones -> FAIL - # or the system serves lab and other zones -> FAIL - # All above scenario lands on a decision which allows to skip the multi-zone evaluation + hvac_data_by_id_b["do_multi_zone_evaluation"] = not any( [ hvac_data_by_id_b["is_sys_single_zone_sys_b"], @@ -200,7 +234,7 @@ def create_data(self, context, data): ).issubset(set(zones_on_floor)) and any( [ baseline_system_type_compare( - hvac_data_by_id_b["sys_type"], target_sys_type, False + sys_type, target_sys_type, False ) for target_sys_type in EXCEPTION_SYS_TYPES ] @@ -209,15 +243,7 @@ def create_data(self, context, data): hvac_data_by_id_b["do_multi_zone_evaluation"] and hvac_data_by_id_b["does_sys_serve_one_floor"] ): - # check if there are any other systems of the same system type that serve zones on this floor - same_sys_type_list = next( - ( - hvac_id_list - for hvac_id_list in baseline_system_types_dict_b.values() - if hvac_id_b in hvac_id_list - ), - None, - ) + same_sys_type_list = baseline_system_types_dict_b[sys_type] for hvac_sys2_id_b in same_sys_type_list: if hvac_sys2_id_b != hvac_id_b: zones_served_by_system2 = ( @@ -244,20 +270,14 @@ def create_data(self, context, data): # The two systems have overlaps in the same floor # But the other system is serving lab zones only # use lab zone exhaust to determine TRUE or UNDETERMINED - if ( - building_total_lab_zone_exhaust_b + hvac_data_b[hvac_id_b][ + "does_two_sys_exist_on_same_fl_b" + ] = ( + "true" + if building_total_lab_zone_exhaust_b > AIRFLOW_15000_CFM - ): - hvac_data_b[hvac_id_b][ - "does_two_sys_exist_on_same_fl_b" - ] = "true" - hvac_data_b[hvac_id_b][ - "hvac_sys2_id_b" - ] = hvac_sys2_id_b - else: - hvac_data_b[hvac_id_b][ - "does_two_sys_exist_on_same_fl_b" - ] = "undetermined" + else "undetermined" + ) hvac_data_b[hvac_id_b][ "hvac_sys2_id_b" ] = hvac_sys2_id_b @@ -328,11 +348,18 @@ def manual_check_required(self, context, calc_vals=None, data=None): return ( ( - (does_sys_only_serve_lab_b or does_sys_serve_lab_and_other_b) - and building_total_lab_zone_exhaust_b <= AIRFLOW_15000_CFM + ( + ( + does_sys_only_serve_lab_b + or does_sys_serve_lab_and_other_b + ) + and building_total_lab_zone_exhaust_b <= AIRFLOW_15000_CFM + ) + # Note: does_two_sys_exist_on_same_fl_b already did the air flow check ) - # Note: does_two_sys_exist_on_same_fl_b already did the air flow check - ) or does_two_sys_exist_on_same_fl_b == "undetermined" + or does_two_sys_exist_on_same_fl_b == "undetermined" + or calc_vals["sys_type"] == HVAC_SYS.UNMATCHED + ) def get_manual_check_required_msg(self, context, calc_vals=None, data=None): does_sys_only_serve_lab_b = calc_vals["does_sys_only_serve_lab_b"] @@ -347,6 +374,12 @@ def get_manual_check_required_msg(self, context, calc_vals=None, data=None): undetermined_msg = "" # the building total lab zone exhaust is guaranteed to be <= 15,000 CFM due to the preconditioned in the `manual_check_required` function # for does_sys_only_serve_lab_b and does_sys_serve_lab_and_other_b. + if calc_vals["sys_type"] == HVAC_SYS.UNMATCHED: + undetermined_msg = ( + "The system type for this HVAC system could not be determined from the inputs provided. " + "Please verify that the system type is one of the applicable system types (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, or 13) " + "and that the zones served by this system are correctly assigned." + ) if does_sys_only_serve_lab_b: undetermined_msg = ( "This system serves only lab zones, which is correct if the building has total lab exhaust greater than 15,000 cfm. "