diff --git a/README.md b/README.md index 540be3f..4ddb142 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # open-fdd +![MIT License](https://img.shields.io/badge/license-MIT-green.svg) +![CI](https://github.com/bbartling/open-fdd/actions/workflows/ci.yml/badge.svg) +![Black](https://img.shields.io/badge/code%20style-black-000000.svg) + + ![Alt text](open_fdd/air_handling_unit/images/plot_for_repo.png) This is a Python-based Fault Detection and Diagnostics (FDD) tool for running fault equations inspired by ASHRAE and NIST standards for HVAC systems across historical datasets using the Pandas computing library. The tool evaluates various fault conditions and outputs fault flags as boolean columns within typical Pandas DataFrames. These fault flags indicate the presence (True) or absence (False) of specific issues identified by the fault equations. This approach integrates seamlessly into standard data science and computer science workflows, allowing for efficient analysis, visualization, and further processing of fault conditions within familiar data structures like DataFrames. diff --git a/open_fdd/__init__.py b/open_fdd/__init__.py index 2801d8d..8baa2bf 100644 --- a/open_fdd/__init__.py +++ b/open_fdd/__init__.py @@ -1,5 +1,4 @@ - -''' +""" open-fdd/ # Repository root ├── open_fdd/ # Python package root │ ├── __init__.py @@ -7,7 +6,7 @@ │ │ ├── __init__.py │ │ ├── faults/ │ │ │ ├── __init__.py -│ │ │ ├── helper_utils.py +│ │ │ ├── helper_utils.pya │ │ │ ├── fault_condition.py │ │ │ ├── fault_condition_one.py │ │ │ ├── fault_condition_two.py @@ -37,23 +36,4 @@ ├── LICENSE └── requirements.txt -''' - """ -# test_formatting.py - -import subprocess -import sys - -def test_black_formatting(): - # Run the Black formatter check - result = subprocess.run( - [sys.executable, "-m", "black", "--check", "."], - capture_output=True, - text=True - ) - - # Assert that the command was successful - assert result.returncode == 0, f"Black formatting issues found:\n{result.stdout}\n{result.stderr}" - -""" \ No newline at end of file diff --git a/open_fdd/air_handling_unit/faults/fault_condition.py b/open_fdd/air_handling_unit/faults/fault_condition.py index 8f00bcf..f9934ad 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition.py +++ b/open_fdd/air_handling_unit/faults/fault_condition.py @@ -4,7 +4,6 @@ class FaultCondition: - """Parent class for Fault Conditions. Methods are inherited to all children.""" def set_attributes(self, dict_): @@ -48,5 +47,3 @@ def check_analog_pct(self, df, columns): df = helper.convert_to_float(df, col) if df[col].max() > 1.0: raise TypeError(helper.float_max_check_err(col)) - - diff --git a/open_fdd/air_handling_unit/faults/fault_condition_eight.py b/open_fdd/air_handling_unit/faults/fault_condition_eight.py index f1c2652..cda8a58 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_eight.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_eight.py @@ -4,10 +4,11 @@ from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils import sys + class FaultConditionEight(FaultCondition): - """ Class provides the definitions for Fault Condition 8. - Supply air temperature and mix air temperature should - be approx equal in economizer mode. + """Class provides the definitions for Fault Condition 8. + Supply air temperature and mix air temperature should + be approx equal in economizer mode. """ def __init__(self, dict_): @@ -36,8 +37,12 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: self.check_analog_pct(df, columns_to_check) - df["sat_fan_mat"] = abs(df[self.sat_col] - self.delta_t_supply_fan - df[self.mat_col]) - df["sat_mat_sqrted"] = np.sqrt(self.supply_degf_err_thres ** 2 + self.mix_degf_err_thres ** 2) + df["sat_fan_mat"] = abs( + df[self.sat_col] - self.delta_t_supply_fan - df[self.mat_col] + ) + df["sat_mat_sqrted"] = np.sqrt( + self.supply_degf_err_thres**2 + self.mix_degf_err_thres**2 + ) df["combined_check"] = ( (df["sat_fan_mat"] > df["sat_mat_sqrted"]) @@ -46,7 +51,9 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: ) # Rolling sum to count consecutive trues - rolling_sum = df["combined_check"].rolling(window=self.rolling_window_size).sum() + rolling_sum = ( + df["combined_check"].rolling(window=self.rolling_window_size).sum() + ) # Set flag to 1 if rolling sum equals the window size df["fc8_flag"] = (rolling_sum >= self.rolling_window_size).astype(int) diff --git a/open_fdd/air_handling_unit/faults/fault_condition_eleven.py b/open_fdd/air_handling_unit/faults/fault_condition_eleven.py index 62ce438..7655fce 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_eleven.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_eleven.py @@ -4,12 +4,13 @@ from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils import sys + class FaultConditionEleven(FaultCondition): - """ Class provides the definitions for Fault Condition 11. - Outside air temperature too low for 100% outdoor - air cooling in economizer cooling mode. + """Class provides the definitions for Fault Condition 11. + Outside air temperature too low for 100% outdoor + air cooling in economizer cooling mode. - Economizer performance fault + Economizer performance fault """ def __init__(self, dict_): @@ -38,7 +39,9 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: df["oat_plus_oaterror"] = df[self.oat_col] + self.outdoor_degf_err_thres df["satsp_delta_saterr"] = ( - df[self.sat_setpoint_col] - self.delta_t_supply_fan - self.supply_degf_err_thres + df[self.sat_setpoint_col] + - self.delta_t_supply_fan + - self.supply_degf_err_thres ) df["combined_check"] = ( @@ -49,7 +52,9 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: ) # Rolling sum to count consecutive trues - rolling_sum = df["combined_check"].rolling(window=self.rolling_window_size).sum() + rolling_sum = ( + df["combined_check"].rolling(window=self.rolling_window_size).sum() + ) # Set flag to 1 if rolling sum equals the window size df["fc11_flag"] = (rolling_sum >= self.rolling_window_size).astype(int) diff --git a/open_fdd/air_handling_unit/faults/fault_condition_fifteen.py b/open_fdd/air_handling_unit/faults/fault_condition_fifteen.py index 3eeff15..137355d 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_fifteen.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_fifteen.py @@ -4,10 +4,11 @@ from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils import sys + class FaultConditionFifteen(FaultCondition): - """ Class provides the definitions for Fault Condition 15. - Temperature rise across inactive heating coi. - Requires coil leaving temp sensor. + """Class provides the definitions for Fault Condition 15. + Temperature rise across inactive heating coi. + Requires coil leaving temp sensor. """ def __init__(self, dict_): @@ -41,35 +42,41 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: # Create helper columns df["htg_delta_temp"] = ( - df[self.htg_coil_leave_temp_col] - df[self.htg_coil_enter_temp_col] + df[self.htg_coil_leave_temp_col] - df[self.htg_coil_enter_temp_col] ) df["htg_delta_sqrted"] = ( np.sqrt( - self.coil_temp_enter_err_thres ** 2 + self.coil_temp_leav_err_thres ** 2 + self.coil_temp_enter_err_thres**2 + self.coil_temp_leav_err_thres**2 ) + self.delta_supply_fan ) df["combined_check"] = ( - (df["htg_delta_temp"] >= df["htg_delta_sqrted"]) - # verify AHU is in OS2 only free cooling mode - & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr) - & (df[self.cooling_sig_col] < 0.1) - ) | ( - (df["htg_delta_temp"] >= df["htg_delta_sqrted"]) - # OS4 AHU state clg @ min OA - & (df[self.cooling_sig_col] > 0.01) - & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr) - ) | ( - (df["htg_delta_temp"] >= df["htg_delta_sqrted"]) - # verify AHU is running in OS 3 clg mode in 100 OA - & (df[self.cooling_sig_col] > 0.01) - & (df[self.economizer_sig_col] > 0.9) + ( + (df["htg_delta_temp"] >= df["htg_delta_sqrted"]) + # verify AHU is in OS2 only free cooling mode + & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr) + & (df[self.cooling_sig_col] < 0.1) + ) + | ( + (df["htg_delta_temp"] >= df["htg_delta_sqrted"]) + # OS4 AHU state clg @ min OA + & (df[self.cooling_sig_col] > 0.01) + & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr) + ) + | ( + (df["htg_delta_temp"] >= df["htg_delta_sqrted"]) + # verify AHU is running in OS 3 clg mode in 100 OA + & (df[self.cooling_sig_col] > 0.01) + & (df[self.economizer_sig_col] > 0.9) + ) ) # Rolling sum to count consecutive trues - rolling_sum = df["combined_check"].rolling(window=self.rolling_window_size).sum() + rolling_sum = ( + df["combined_check"].rolling(window=self.rolling_window_size).sum() + ) # Set flag to 1 if rolling sum equals the window size df["fc15_flag"] = (rolling_sum >= self.rolling_window_size).astype(int) diff --git a/open_fdd/air_handling_unit/faults/fault_condition_five.py b/open_fdd/air_handling_unit/faults/fault_condition_five.py index 74e6019..fe0f522 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_five.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_five.py @@ -4,11 +4,12 @@ from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils import sys + class FaultConditionFive(FaultCondition): - """ Class provides the definitions for Fault Condition 5. - SAT too low; should be higher than MAT in HTG MODE - --Broken heating valve or other mechanical issue - related to heat valve not working as designed + """Class provides the definitions for Fault Condition 5. + SAT too low; should be higher than MAT in HTG MODE + --Broken heating valve or other mechanical issue + related to heat valve not working as designed """ def __init__(self, dict_): @@ -30,7 +31,7 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: if self.troubleshoot_mode: self.troubleshoot_cols(df) - # check analog outputs [data with units of %] are floats only + # check analog outputs [data with units of %] are floats only columns_to_check = [self.supply_vfd_speed_col, self.heating_sig_col] for col in columns_to_check: @@ -38,20 +39,22 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: df["sat_check"] = df[self.sat_col] + self.supply_degf_err_thres df["mat_check"] = ( - df[self.mat_col] - self.mix_degf_err_thres + self.delta_t_supply_fan + df[self.mat_col] - self.mix_degf_err_thres + self.delta_t_supply_fan ) df["combined_check"] = ( - (df["sat_check"] <= df["mat_check"]) - # this is to make fault only active in OS1 for htg mode only - # and fan is running. Some control programming may use htg - # vlv when AHU is off to prevent low limit freeze alarms - & (df[self.heating_sig_col] > 0.01) - & (df[self.supply_vfd_speed_col] > 0.01) + (df["sat_check"] <= df["mat_check"]) + # this is to make fault only active in OS1 for htg mode only + # and fan is running. Some control programming may use htg + # vlv when AHU is off to prevent low limit freeze alarms + & (df[self.heating_sig_col] > 0.01) + & (df[self.supply_vfd_speed_col] > 0.01) ) # Rolling sum to count consecutive trues - rolling_sum = df["combined_check"].rolling(window=self.rolling_window_size).sum() + rolling_sum = ( + df["combined_check"].rolling(window=self.rolling_window_size).sum() + ) # Set flag to 1 if rolling sum equals the window size df["fc5_flag"] = (rolling_sum == self.rolling_window_size).astype(int) diff --git a/open_fdd/air_handling_unit/faults/fault_condition_four.py b/open_fdd/air_handling_unit/faults/fault_condition_four.py index 40da2c3..cba12cc 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_four.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_four.py @@ -3,15 +3,16 @@ from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils import sys + class FaultConditionFour(FaultCondition): """Class provides the definitions for Fault Condition 4. - This fault flags excessive operating states on the AHU - if its hunting between heating, econ, econ+mech, and - a mech clg modes. The code counts how many operating - changes in an hour and will throw a fault if there is - excessive OS changes to flag control sys hunting. - + This fault flags excessive operating states on the AHU + if its hunting between heating, econ, econ+mech, and + a mech clg modes. The code counts how many operating + changes in an hour and will throw a fault if there is + excessive OS changes to flag control sys hunting. + """ def __init__(self, dict_): @@ -52,34 +53,34 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: # AHU htg only mode based on OA damper @ min oa and only htg pid/vlv modulating df["heating_mode"] = ( - (df[self.heating_sig_col] > 0) - & (df[self.cooling_sig_col] == 0) - & (df[self.supply_vfd_speed_col] > 0) - & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr) + (df[self.heating_sig_col] > 0) + & (df[self.cooling_sig_col] == 0) + & (df[self.supply_vfd_speed_col] > 0) + & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr) ) # AHU econ only mode based on OA damper modulating and clg htg = zero df["econ_only_cooling_mode"] = ( - (df[self.heating_sig_col] == 0) - & (df[self.cooling_sig_col] == 0) - & (df[self.supply_vfd_speed_col] > 0) - & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr) + (df[self.heating_sig_col] == 0) + & (df[self.cooling_sig_col] == 0) + & (df[self.supply_vfd_speed_col] > 0) + & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr) ) # AHU econ+mech clg mode based on OA damper modulating for cooling and clg pid/vlv modulating df["econ_plus_mech_cooling_mode"] = ( - (df[self.heating_sig_col] == 0) - & (df[self.cooling_sig_col] > 0) - & (df[self.supply_vfd_speed_col] > 0) - & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr) + (df[self.heating_sig_col] == 0) + & (df[self.cooling_sig_col] > 0) + & (df[self.supply_vfd_speed_col] > 0) + & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr) ) # AHU mech mode based on OA damper @ min OA and clg pid/vlv modulating df["mech_cooling_only_mode"] = ( - (df[self.heating_sig_col] == 0) - & (df[self.cooling_sig_col] > 0) - & (df[self.supply_vfd_speed_col] > 0) - & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr) + (df[self.heating_sig_col] == 0) + & (df[self.cooling_sig_col] > 0) + & (df[self.supply_vfd_speed_col] > 0) + & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr) ) # Fill non-finite values with zero or drop them diff --git a/open_fdd/air_handling_unit/faults/fault_condition_fourteen.py b/open_fdd/air_handling_unit/faults/fault_condition_fourteen.py index 640396e..b0c3bb2 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_fourteen.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_fourteen.py @@ -5,11 +5,12 @@ import operator import sys + class FaultConditionFourteen(FaultCondition): - """ Class provides the definitions for Fault Condition 14. - Temperature drop across inactive cooling coil. - Requires coil leaving temp sensor. - """ + """Class provides the definitions for Fault Condition 14. + Temperature drop across inactive cooling coil. + Requires coil leaving temp sensor. + """ def __init__(self, dict_): self.delta_t_supply_fan = float @@ -47,7 +48,7 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: df["clg_delta_sqrted"] = ( np.sqrt( - self.coil_temp_enter_err_thres ** 2 + self.coil_temp_leav_err_thres ** 2 + self.coil_temp_enter_err_thres**2 + self.coil_temp_leav_err_thres**2 ) + self.delta_t_supply_fan ) @@ -63,7 +64,9 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: ) # Rolling sum to count consecutive trues - rolling_sum = df["combined_check"].rolling(window=self.rolling_window_size).sum() + rolling_sum = ( + df["combined_check"].rolling(window=self.rolling_window_size).sum() + ) # Set flag to 1 if rolling sum equals the window size df["fc14_flag"] = (rolling_sum >= self.rolling_window_size).astype(int) diff --git a/open_fdd/air_handling_unit/faults/fault_condition_nine.py b/open_fdd/air_handling_unit/faults/fault_condition_nine.py index c10b47b..00af73c 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_nine.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_nine.py @@ -4,9 +4,10 @@ from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils import sys + class FaultConditionNine(FaultCondition): """Class provides the definitions for Fault Condition 9. - Outside air temperature too high in free cooling without + Outside air temperature too high in free cooling without additional mechanical cooling in economizer mode. """ @@ -38,7 +39,9 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: # Create helper columns df["oat_minus_oaterror"] = df[self.oat_col] - self.outdoor_degf_err_thres df["satsp_delta_saterr"] = ( - df[self.sat_setpoint_col] - self.delta_t_supply_fan + self.supply_degf_err_thres + df[self.sat_setpoint_col] + - self.delta_t_supply_fan + + self.supply_degf_err_thres ) df["combined_check"] = ( @@ -49,7 +52,9 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: ) # Rolling sum to count consecutive trues - rolling_sum = df["combined_check"].rolling(window=self.rolling_window_size).sum() + rolling_sum = ( + df["combined_check"].rolling(window=self.rolling_window_size).sum() + ) # Set flag to 1 if rolling sum equals the window size df["fc9_flag"] = (rolling_sum >= self.rolling_window_size).astype(int) diff --git a/open_fdd/air_handling_unit/faults/fault_condition_one.py b/open_fdd/air_handling_unit/faults/fault_condition_one.py index 6a71c27..c565565 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_one.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_one.py @@ -2,9 +2,10 @@ from open_fdd.air_handling_unit.faults.fault_condition import FaultCondition import sys + class FaultConditionOne(FaultCondition): - """ Class provides the definitions for Fault Condition 1. - AHU low duct static pressure fan fault. + """Class provides the definitions for Fault Condition 1. + AHU low duct static pressure fan fault. """ def __init__(self, dict_): @@ -30,16 +31,22 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: columns_to_check = [self.supply_vfd_speed_col] self.check_analog_pct(df, columns_to_check) - df['static_check_'] = ( - df[self.duct_static_col] < df[self.duct_static_setpoint_col] - self.duct_static_inches_err_thres) - df['fan_check_'] = ( - df[self.supply_vfd_speed_col] >= self.vfd_speed_percent_max - self.vfd_speed_percent_err_thres) + df["static_check_"] = ( + df[self.duct_static_col] + < df[self.duct_static_setpoint_col] - self.duct_static_inches_err_thres + ) + df["fan_check_"] = ( + df[self.supply_vfd_speed_col] + >= self.vfd_speed_percent_max - self.vfd_speed_percent_err_thres + ) # Combined condition check - df["combined_check"] = df['static_check_'] & df['fan_check_'] + df["combined_check"] = df["static_check_"] & df["fan_check_"] # Rolling sum to count consecutive trues - rolling_sum = df["combined_check"].rolling(window=self.rolling_window_size).sum() + rolling_sum = ( + df["combined_check"].rolling(window=self.rolling_window_size).sum() + ) # Set flag to 1 if rolling sum equals the window size df["fc1_flag"] = (rolling_sum == self.rolling_window_size).astype(int) diff --git a/open_fdd/air_handling_unit/faults/fault_condition_seven.py b/open_fdd/air_handling_unit/faults/fault_condition_seven.py index 1ee0e84..9c479f2 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_seven.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_seven.py @@ -4,6 +4,7 @@ from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils import sys + class FaultConditionSeven(FaultCondition): """Class provides the definitions for Fault Condition 7. Very similar to FC 13 but uses heating valve. @@ -39,7 +40,9 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: ) # Rolling sum to count consecutive trues - rolling_sum = df["combined_check"].rolling(window=self.rolling_window_size).sum() + rolling_sum = ( + df["combined_check"].rolling(window=self.rolling_window_size).sum() + ) # Set flag to 1 if rolling sum equals the window size df["fc7_flag"] = (rolling_sum >= self.rolling_window_size).astype(int) diff --git a/open_fdd/air_handling_unit/faults/fault_condition_six.py b/open_fdd/air_handling_unit/faults/fault_condition_six.py index b71f529..f03527b 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_six.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_six.py @@ -5,25 +5,26 @@ import operator import sys + class FaultConditionSix(FaultCondition): - """ Class provides the definitions for Fault Condition 6. - - This fault related to knowing the design air flow for - ventilation AHU_MIN_CFM_DESIGN which comes from the - design mech engineered records where then the fault - tries to calculate that based on totalized measured - AHU air flow and outside air fraction calc from - AHU temp sensors. The fault could flag issues where - flow stations are either not in calibration, temp - sensors used in the OA frac calc, or possibly the AHU - not bringing in design air flow when not operating in - economizer free cooling modes. Troubleshoot by TAB tech - verifying flow sensor and temp sensor precisions from - 3rd party sensing tools. - - this fault is confusing if you want to play around - in py code sandbox try this: -https://gist.github.com/bbartling/e0fb8427b1e0d148a06e3f09121ed5dc#file-fc6-py + """Class provides the definitions for Fault Condition 6. + + This fault related to knowing the design air flow for + ventilation AHU_MIN_CFM_DESIGN which comes from the + design mech engineered records where then the fault + tries to calculate that based on totalized measured + AHU air flow and outside air fraction calc from + AHU temp sensors. The fault could flag issues where + flow stations are either not in calibration, temp + sensors used in the OA frac calc, or possibly the AHU + not bringing in design air flow when not operating in + economizer free cooling modes. Troubleshoot by TAB tech + verifying flow sensor and temp sensor precisions from + 3rd party sensing tools. + + this fault is confusing if you want to play around + in py code sandbox try this: + https://gist.github.com/bbartling/e0fb8427b1e0d148a06e3f09121ed5dc#file-fc6-py """ def __init__(self, dict_): @@ -64,13 +65,15 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: # create helper columns df["rat_minus_oat"] = abs(df[self.rat_col] - df[self.oat_col]) df["percent_oa_calc"] = (df[self.mat_col] - df[self.rat_col]) / ( - df[self.oat_col] - df[self.rat_col] + df[self.oat_col] - df[self.rat_col] ) # weed out any negative values df["percent_oa_calc"] = df["percent_oa_calc"].apply(lambda x: x if x > 0 else 0) - df["perc_OAmin"] = self.ahu_min_oa_cfm_design / df[self.supply_fan_air_volume_col] + df["perc_OAmin"] = ( + self.ahu_min_oa_cfm_design / df[self.supply_fan_air_volume_col] + ) df["percent_oa_calc_minus_perc_OAmin"] = abs( df["percent_oa_calc"] - df["perc_OAmin"] @@ -79,17 +82,17 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: df["combined_check"] = operator.or_( # OS 1 htg mode ( - (df["rat_minus_oat"] >= self.oat_rat_delta_min) - & (df["percent_oa_calc_minus_perc_OAmin"] > self.airflow_err_thres) + (df["rat_minus_oat"] >= self.oat_rat_delta_min) + & (df["percent_oa_calc_minus_perc_OAmin"] > self.airflow_err_thres) ) # verify ahu is running in OS 1 htg mode in min OA & ( - (df[self.heating_sig_col] > 0.0) & (df[self.supply_vfd_speed_col] > 0.0) + (df[self.heating_sig_col] > 0.0) & (df[self.supply_vfd_speed_col] > 0.0) ), # OR # OS 4 mech clg mode ( - (df["rat_minus_oat"] >= self.oat_rat_delta_min) - & (df["percent_oa_calc_minus_perc_OAmin"] > self.airflow_err_thres) + (df["rat_minus_oat"] >= self.oat_rat_delta_min) + & (df["percent_oa_calc_minus_perc_OAmin"] > self.airflow_err_thres) ) # verify ahu is running in OS 4 clg mode in min OA & (df[self.heating_sig_col] == 0.0) @@ -99,7 +102,9 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: ) # Rolling sum to count consecutive trues - rolling_sum = df["combined_check"].rolling(window=self.rolling_window_size).sum() + rolling_sum = ( + df["combined_check"].rolling(window=self.rolling_window_size).sum() + ) # Set flag to 1 if rolling sum equals the window size df["fc6_flag"] = (rolling_sum == self.rolling_window_size).astype(int) diff --git a/open_fdd/air_handling_unit/faults/fault_condition_ten.py b/open_fdd/air_handling_unit/faults/fault_condition_ten.py index b77cbff..c0cf0e2 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_ten.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_ten.py @@ -4,10 +4,11 @@ from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils import sys + class FaultConditionTen(FaultCondition): - """ Class provides the definitions for Fault Condition 10. - Outdoor air temperature and mix air temperature should - be approx equal in economizer plus mech cooling mode. + """Class provides the definitions for Fault Condition 10. + Outdoor air temperature and mix air temperature should + be approx equal in economizer plus mech cooling mode. """ def __init__(self, dict_): @@ -35,7 +36,7 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: df["abs_mat_minus_oat"] = abs(df[self.mat_col] - df[self.oat_col]) df["mat_oat_sqrted"] = np.sqrt( - self.mix_degf_err_thres ** 2 + self.outdoor_degf_err_thres ** 2 + self.mix_degf_err_thres**2 + self.outdoor_degf_err_thres**2 ) df["combined_check"] = ( @@ -46,7 +47,9 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: ) # Rolling sum to count consecutive trues - rolling_sum = df["combined_check"].rolling(window=self.rolling_window_size).sum() + rolling_sum = ( + df["combined_check"].rolling(window=self.rolling_window_size).sum() + ) # Set flag to 1 if rolling sum equals the window size df["fc10_flag"] = (rolling_sum >= self.rolling_window_size).astype(int) diff --git a/open_fdd/air_handling_unit/faults/fault_condition_thirteen.py b/open_fdd/air_handling_unit/faults/fault_condition_thirteen.py index a37ca56..192fb39 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_thirteen.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_thirteen.py @@ -5,10 +5,11 @@ from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils import sys + class FaultConditionThirteen(FaultCondition): - """ Class provides the definitions for Fault Condition 13. - Supply air temperature too high in full cooling - in economizer plus mech cooling mode + """Class provides the definitions for Fault Condition 13. + Supply air temperature too high in full cooling + in economizer plus mech cooling mode """ def __init__(self, dict_): @@ -50,7 +51,9 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: ) # Rolling sum to count consecutive trues - rolling_sum = df["combined_check"].rolling(window=self.rolling_window_size).sum() + rolling_sum = ( + df["combined_check"].rolling(window=self.rolling_window_size).sum() + ) # Set flag to 1 if rolling sum equals the window size df["fc13_flag"] = (rolling_sum >= self.rolling_window_size).astype(int) diff --git a/open_fdd/air_handling_unit/faults/fault_condition_twelve.py b/open_fdd/air_handling_unit/faults/fault_condition_twelve.py index 5b249ff..d9fb274 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_twelve.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_twelve.py @@ -5,10 +5,11 @@ from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils import sys + class FaultConditionTwelve(FaultCondition): - """ Class provides the definitions for Fault Condition 12. - Supply air temperature too high; should be less than - mix air temperature in economizer plus mech cooling mode. + """Class provides the definitions for Fault Condition 12. + Supply air temperature too high; should be less than + mix air temperature in economizer plus mech cooling mode. """ def __init__(self, dict_): @@ -54,7 +55,9 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: ) # Rolling sum to count consecutive trues - rolling_sum = df["combined_check"].rolling(window=self.rolling_window_size).sum() + rolling_sum = ( + df["combined_check"].rolling(window=self.rolling_window_size).sum() + ) # Set flag to 1 if rolling sum equals the window size df["fc12_flag"] = (rolling_sum >= self.rolling_window_size).astype(int) diff --git a/open_fdd/air_handling_unit/faults/fault_condition_two.py b/open_fdd/air_handling_unit/faults/fault_condition_two.py index c6195eb..2c822f7 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_two.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_two.py @@ -3,9 +3,10 @@ from open_fdd.air_handling_unit.faults.fault_condition import FaultCondition import sys + class FaultConditionTwo(FaultCondition): - """ Class provides the definitions for Fault Condition 2. - Mix temperature too low; should be between outside and return air. + """Class provides the definitions for Fault Condition 2. + Mix temperature too low; should be between outside and return air. """ def __init__(self, dict_): @@ -39,13 +40,14 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: df[self.oat_col] - self.outdoor_degf_err_thres, ) - df["combined_check"] = ( - (df["mat_check"] < df["temp_min_check"]) - & (df[self.supply_vfd_speed_col] > 0.01) + df["combined_check"] = (df["mat_check"] < df["temp_min_check"]) & ( + df[self.supply_vfd_speed_col] > 0.01 ) # Rolling sum to count consecutive trues - rolling_sum = df["combined_check"].rolling(window=self.rolling_window_size).sum() + rolling_sum = ( + df["combined_check"].rolling(window=self.rolling_window_size).sum() + ) # Set flag to 1 if rolling sum equals the window size df["fc2_flag"] = (rolling_sum >= self.rolling_window_size).astype(int) diff --git a/open_fdd/air_handling_unit/faults/helper_utils.py b/open_fdd/air_handling_unit/faults/helper_utils.py index d4aaafe..f6e7dc2 100644 --- a/open_fdd/air_handling_unit/faults/helper_utils.py +++ b/open_fdd/air_handling_unit/faults/helper_utils.py @@ -2,6 +2,7 @@ from open_fdd.air_handling_unit.faults.shared_utils import SharedUtils import pandas as pd + class HelperUtils: def float_int_check_err(self, col): return SharedUtils.float_int_check_err(col) @@ -23,21 +24,51 @@ def apply_rolling_average_if_needed(self, df, freq="1min", rolling_window="5min" def process_all_faults(self, df, config_dict): # Import fault conditions - from open_fdd.air_handling_unit.faults.fault_condition_one import FaultConditionOne - from open_fdd.air_handling_unit.faults.fault_condition_two import FaultConditionTwo - from open_fdd.air_handling_unit.faults.fault_condition_three import FaultConditionThree - from open_fdd.air_handling_unit.faults.fault_condition_four import FaultConditionFour - from open_fdd.air_handling_unit.faults.fault_condition_five import FaultConditionFive - from open_fdd.air_handling_unit.faults.fault_condition_six import FaultConditionSix - from open_fdd.air_handling_unit.faults.fault_condition_seven import FaultConditionSeven - from open_fdd.air_handling_unit.faults.fault_condition_eight import FaultConditionEight - from open_fdd.air_handling_unit.faults.fault_condition_nine import FaultConditionNine - from open_fdd.air_handling_unit.faults.fault_condition_ten import FaultConditionTen - from open_fdd.air_handling_unit.faults.fault_condition_eleven import FaultConditionEleven - from open_fdd.air_handling_unit.faults.fault_condition_twelve import FaultConditionTwelve - from open_fdd.air_handling_unit.faults.fault_condition_thirteen import FaultConditionThirteen - from open_fdd.air_handling_unit.faults.fault_condition_fourteen import FaultConditionFourteen - from open_fdd.air_handling_unit.faults.fault_condition_fifteen import FaultConditionFifteen + from open_fdd.air_handling_unit.faults.fault_condition_one import ( + FaultConditionOne, + ) + from open_fdd.air_handling_unit.faults.fault_condition_two import ( + FaultConditionTwo, + ) + from open_fdd.air_handling_unit.faults.fault_condition_three import ( + FaultConditionThree, + ) + from open_fdd.air_handling_unit.faults.fault_condition_four import ( + FaultConditionFour, + ) + from open_fdd.air_handling_unit.faults.fault_condition_five import ( + FaultConditionFive, + ) + from open_fdd.air_handling_unit.faults.fault_condition_six import ( + FaultConditionSix, + ) + from open_fdd.air_handling_unit.faults.fault_condition_seven import ( + FaultConditionSeven, + ) + from open_fdd.air_handling_unit.faults.fault_condition_eight import ( + FaultConditionEight, + ) + from open_fdd.air_handling_unit.faults.fault_condition_nine import ( + FaultConditionNine, + ) + from open_fdd.air_handling_unit.faults.fault_condition_ten import ( + FaultConditionTen, + ) + from open_fdd.air_handling_unit.faults.fault_condition_eleven import ( + FaultConditionEleven, + ) + from open_fdd.air_handling_unit.faults.fault_condition_twelve import ( + FaultConditionTwelve, + ) + from open_fdd.air_handling_unit.faults.fault_condition_thirteen import ( + FaultConditionThirteen, + ) + from open_fdd.air_handling_unit.faults.fault_condition_fourteen import ( + FaultConditionFourteen, + ) + from open_fdd.air_handling_unit.faults.fault_condition_fifteen import ( + FaultConditionFifteen, + ) fault_counts = {} @@ -65,12 +96,18 @@ def process_all_faults(self, df, config_dict): # Optionally initialize Fault Condition Fourteen fc14 = None - if config_dict.get("COOLING_SIG_COL") is not None and config_dict.get("CLG_COIL_LEAVE_TEMP_COL") is not None: + if ( + config_dict.get("COOLING_SIG_COL") is not None + and config_dict.get("CLG_COIL_LEAVE_TEMP_COL") is not None + ): fc14 = FaultConditionFourteen(config_dict) # Optionally initialize Fault Condition Fifteen fc15 = None - if config_dict.get("HTG_COIL_ENTER_TEMP_COL") is not None and config_dict.get("HTG_COIL_LEAVE_TEMP_COL") is not None: + if ( + config_dict.get("HTG_COIL_ENTER_TEMP_COL") is not None + and config_dict.get("HTG_COIL_LEAVE_TEMP_COL") is not None + ): fc15 = FaultConditionFifteen(config_dict) # Apply fault conditions and calculate fault counts diff --git a/open_fdd/air_handling_unit/faults/shared_utils.py b/open_fdd/air_handling_unit/faults/shared_utils.py index 6f4ff75..ebba5f2 100644 --- a/open_fdd/air_handling_unit/faults/shared_utils.py +++ b/open_fdd/air_handling_unit/faults/shared_utils.py @@ -2,6 +2,7 @@ import pandas.api.types as pdtypes import sys + class SharedUtils: @staticmethod def float_int_check_err(col): @@ -42,19 +43,23 @@ def convert_to_float(df, col): @staticmethod def apply_rolling_average_if_needed(df, freq="1min", rolling_window="5min"): - """ Apply rolling average if time difference between consecutive - timestamps is not greater than the specified frequency. + """Apply rolling average if time difference between consecutive + timestamps is not greater than the specified frequency. """ - print("Warning: If data has a one minute or less sampling frequency a rolling average will be automatically applied") + print( + "Warning: If data has a one minute or less sampling frequency a rolling average will be automatically applied" + ) sys.stdout.flush() time_diff = df.index.to_series().diff().iloc[1:] - + # Calculate median time difference to avoid being affected by outliers median_diff = time_diff.median() - print(f"Warning: Median time difference between consecutive timestamps is {median_diff}.") + print( + f"Warning: Median time difference between consecutive timestamps is {median_diff}." + ) sys.stdout.flush() if median_diff > pd.Timedelta(freq): @@ -63,6 +68,8 @@ def apply_rolling_average_if_needed(df, freq="1min", rolling_window="5min"): else: df = df.rolling(rolling_window).mean() - print(f"Warning: A {rolling_window} rolling average has been applied to the data.") + print( + f"Warning: A {rolling_window} rolling average has been applied to the data." + ) sys.stdout.flush() return df diff --git a/open_fdd/air_handling_unit/images/latex_generator.py b/open_fdd/air_handling_unit/images/latex_generator.py index d693aa6..dba7eee 100644 --- a/open_fdd/air_handling_unit/images/latex_generator.py +++ b/open_fdd/air_handling_unit/images/latex_generator.py @@ -23,105 +23,134 @@ # display Fault Equation 2 st.title("Fault Equation 2") -st.caption('Mix air temperature too low; should be between outside and return') -st.latex(r''' +st.caption("Mix air temperature too low; should be between outside and return") +st.latex( + r""" MAT_{avg} + eMAT < \min[(RAT_{avg} - eRAT), - OAT_{avg} - eOAT)] - ''') + """ +) # display Fault Equation 3 st.title("Fault Equation 3") -st.caption('Mix air temperature too high; should be between outside and return') -st.latex(r''' +st.caption("Mix air temperature too high; should be between outside and return") +st.latex( + r""" MAT_{avg} - eMAT > \min[(RAT_{avg} + eRAT), - OAT_{avg} + eOAT)] - ''') + """ +) + - # display Fault Equation 4 st.title("Fault Equation 4") -st.caption('Too many AHU operating state changes due to PID hunting and/or excessive cycling during low load conditions.') -st.latex(r''' +st.caption( + "Too many AHU operating state changes due to PID hunting and/or excessive cycling during low load conditions." +) +st.latex( + r""" \Delta OS > \Delta OS_{max} - ''') + """ +) # display Fault Equation 5 st.title("Fault Equation 5") -st.caption('Supply air temperature too high') -st.latex(r''' +st.caption("Supply air temperature too high") +st.latex( + r""" SAT_{avg} + eSAT \leq MAT_{avg} - eMAT + \Delta TSF - ''') + """ +) # display Fault Equation 6 st.title("Fault Equation 6") -st.caption('Temperature and outside air percentage deviation from setpoints') -st.latex(r''' +st.caption("Temperature and outside air percentage deviation from setpoints") +st.latex( + r""" |\text{RAT}_{\text{avg}} - \text{OAT}_{\text{avg}}| \geq \Delta T_{\text{min}} \quad \text{and} \quad |\%OA - \%OA_{\text{min}}| > eF - ''') + """ +) # display Fault Equation 7 st.title("Fault Equation 7") -st.caption('Supply air temperature too low and heating coil status') -st.latex(r''' +st.caption("Supply air temperature too low and heating coil status") +st.latex( + r""" \text{SAT}_{\text{avg}} < \text{SATSP} - eSAT \quad \text{and} \quad \text{HC} \geq 99\% - ''') + """ +) # display Fault Equation 8 st.title("Fault Equation 8") -st.caption('Deviation between supply air temperature and mixed air temperature') -st.latex(r''' +st.caption("Deviation between supply air temperature and mixed air temperature") +st.latex( + r""" | \text{SAT}_{\text{avg}} - \Delta \text{TSF} - \text{MAT}_{\text{avg}} | > \sqrt{{eSAT}^2 + {eMAT}^2} - ''') + """ +) # display Fault Equation 9 st.title("Fault Equation 9") -st.caption('Outside air temperature deviation from setpoint') -st.latex(r''' +st.caption("Outside air temperature deviation from setpoint") +st.latex( + r""" \text{OAT}_{\text{avg}} - eOAT > \text{SATSP} - \Delta \text{SF} + eSAT - ''') + """ +) # display Fault Equation 10 st.title("Fault Equation 10") -st.caption('Temperature difference between mixed air and outside air') -st.latex(r''' +st.caption("Temperature difference between mixed air and outside air") +st.latex( + r""" | \text{MAT}_{\text{avg}} - \text{OAT}_{\text{avg}} | > \sqrt{eMAT^2 + eOAT^2} - ''') + """ +) # display Fault Equation 11 st.title("Fault Equation 11") -st.caption('Outside air temperature and supply air temperature deviation') -st.latex(r''' +st.caption("Outside air temperature and supply air temperature deviation") +st.latex( + r""" \text{OAT}_{\text{avg}} + eOAT < \text{SATSP} - \Delta \text{TSF} - eSAT - ''') + """ +) # display Fault Equation 12 st.title("Fault Equation 12") -st.caption('Supply air temperature deviation from mixed air temperature') -st.latex(r''' +st.caption("Supply air temperature deviation from mixed air temperature") +st.latex( + r""" \text{SAT}_{\text{avg}} - eSAT - \Delta \text{TSF} \geq \text{MAT}_{\text{avg}} + eMAT - ''') + """ +) # display Fault Equation 13 st.title("Fault Equation 13") -st.caption('Supply air temperature too high') -st.latex(r''' +st.caption("Supply air temperature too high") +st.latex( + r""" \text{SAT}_{\text{avg}} < \text{SATSP} + eSAT \quad \text{and} \quad \text{CC} \geq 99\% - ''') + """ +) st.title("find_closest_weather_dates Function") -st.caption('Finding closest weather dates based on given criteria') -st.latex(r''' +st.caption("Finding closest weather dates based on given criteria") +st.latex( + r""" 1. A' = \{a \in A : a < d_{test}\} \\ 2. B' = \{b \in B : b < d_{test}\} \\ 3. C = \{a \in A' : a \notin B'\} \\ 4. \text{if } |C| < 10 \text{ then remove } \max(A') \text{ and repeat step 3} \\ 5. A = A \cap C, \text{calculate } \mu(A) -''') -st.caption(''' +""" +) +st.caption( + """ In this notation: - $A$ represents the "all_data" dataset. - $B$ represents the "suitable_baseline_no" dataset. @@ -131,19 +160,23 @@ - $|C|$ represents the count of elements in set $C$. - $\max(A')$ is the latest date in $A'$. - $\mu(A)$ is the mean of the remaining elements in $A$ after filtering by set $C$. -''') +""" +) st.title("find_previous_10_days Function") -st.caption('Finding previous 10 weekdays based on given criteria') -st.latex(r''' +st.caption("Finding previous 10 weekdays based on given criteria") +st.latex( + r""" 1. A' = \{a \in A : a < d_{test}\} \\ 2. B' = \{b \in B : b < d_{test}\} \\ 3. C = \{a \in A' : a \notin B'\} \\ 4. \text{if } |C| < 10 \text{ then remove } \max(A') \text{ and repeat step 3} \\ 5. A = A \cap C -''') -st.caption(''' +""" +) +st.caption( + """ In this notation: - $A$ represents the "all_data" dataset. - $B$ represents the "suitable_baseline_no" dataset. @@ -152,24 +185,24 @@ - $C$ is a set of dates in $A'$ not found in $B'$. - $|C|$ represents the count of elements in set $C$. - $\max(A')$ is the latest date in $A'$. -''') +""" +) st.title("calculate_power_averages Function") -st.caption('Calculating average power for each type and time step') -st.latex(r''' +st.caption("Calculating average power for each type and time step") +st.latex( + r""" 1. P_{type,i} = \{p : p \text{ is a power value at time step } t_i\} \quad \text{for each type and } i \in \{1,2,\dots,96\} \\ 2. A_{type,i} = \frac{1}{|P_{type,i}|}\sum_{p \in P_{type,i}} p \quad \text{for each type and } i \in \{1,2,\dots,96\} -''') -st.caption(''' +""" +) +st.caption( + """ In this notation: - $P_{type,i}$ represents the set of power values at time step $t_i$ for a specific power type (main, ahu, or solar). - $p$ represents a power value in the set $P_{type,i}$. - $|P_{type,i}|$ represents the count of elements in set $P_{type,i}$. - $A_{type,i}$ represents the average power at time step $t_i$ for a specific power type. -''') - - - - - +""" +) diff --git a/open_fdd/air_handling_unit/reports/report_fc1.py b/open_fdd/air_handling_unit/reports/report_fc1.py index 0dddec7..047d349 100644 --- a/open_fdd/air_handling_unit/reports/report_fc1.py +++ b/open_fdd/air_handling_unit/reports/report_fc1.py @@ -5,30 +5,30 @@ class FaultCodeOneReport: def __init__(self, config): - self.vfd_speed_percent_err_thres = config['VFD_SPEED_PERCENT_ERR_THRES'] - self.duct_static_col = config['DUCT_STATIC_COL'] - self.supply_vfd_speed_col = config['SUPPLY_VFD_SPEED_COL'] - self.duct_static_setpoint_col = config['DUCT_STATIC_SETPOINT_COL'] + self.vfd_speed_percent_err_thres = config["VFD_SPEED_PERCENT_ERR_THRES"] + self.duct_static_col = config["DUCT_STATIC_COL"] + self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"] + self.duct_static_setpoint_col = config["DUCT_STATIC_SETPOINT_COL"] def create_plot(self, df: pd.DataFrame, output_col: str = None): if output_col is None: output_col = "fc1_flag" fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(25, 8)) - fig.suptitle('Fault Conditions 1 Plot') + fig.suptitle("Fault Conditions 1 Plot") ax1.plot(df.index, df[self.duct_static_col], label="STATIC") - ax1.legend(loc='best') + ax1.legend(loc="best") ax1.set_ylabel("Inch WC") ax2.plot(df.index, df[self.supply_vfd_speed_col], color="g", label="FAN") - ax2.legend(loc='best') - ax2.set_ylabel('%') + ax2.legend(loc="best") + ax2.set_ylabel("%") ax3.plot(df.index, df[output_col], label="Fault", color="k") - ax3.set_xlabel('Date') - ax3.set_ylabel('Fault Flags') - ax3.legend(loc='best') + ax3.set_xlabel("Date") + ax3.set_ylabel("Fault Flags") + ax3.legend(loc="best") plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.show() @@ -40,14 +40,24 @@ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dic delta = df.index.to_series().diff() summary = { - 'total_days': round(delta.sum() / pd.Timedelta(days=1), 2), - 'total_hours': round(delta.sum() / pd.Timedelta(hours=1)), - 'hours_fc1_mode': round((delta * df[output_col]).sum() / pd.Timedelta(hours=1)), - 'percent_true': round(df[output_col].mean() * 100, 2), - 'percent_false': round((100 - round(df[output_col].mean() * 100, 2)), 2), - 'flag_true_duct_static': round(df[self.duct_static_col].where(df[output_col] == 1).mean(), 2), - 'flag_true_duct_static_spt': round(df[self.duct_static_setpoint_col].where(df[output_col] == 1).mean(), 2), - 'hours_motor_runtime': round((delta * df[self.supply_vfd_speed_col].gt(.01).astype(int)).sum() / pd.Timedelta(hours=1), 2) + "total_days": round(delta.sum() / pd.Timedelta(days=1), 2), + "total_hours": round(delta.sum() / pd.Timedelta(hours=1)), + "hours_fc1_mode": round( + (delta * df[output_col]).sum() / pd.Timedelta(hours=1) + ), + "percent_true": round(df[output_col].mean() * 100, 2), + "percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2), + "flag_true_duct_static": round( + df[self.duct_static_col].where(df[output_col] == 1).mean(), 2 + ), + "flag_true_duct_static_spt": round( + df[self.duct_static_setpoint_col].where(df[output_col] == 1).mean(), 2 + ), + "hours_motor_runtime": round( + (delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum() + / pd.Timedelta(hours=1), + 2, + ), } return summary @@ -74,7 +84,7 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc1_fla summary = self.summarize_fault_times(df, output_col) for key, value in summary.items(): - formatted_key = key.replace('_', ' ') + formatted_key = key.replace("_", " ") print(f"{formatted_key}: {value}") sys.stdout.flush() @@ -84,17 +94,20 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc1_fla if fc_max_faults_found != 0: self.create_hist_plot(df, output_col) - - print("Duct Static Mean When In Fault: ", summary['flag_true_duct_static']) - print("Duct Static Setpoint Mean When In Fault: ", summary['flag_true_duct_static_spt']) - - if summary['percent_true'] > 5.0: + + print("Duct Static Mean When In Fault: ", summary["flag_true_duct_static"]) + print( + "Duct Static Setpoint Mean When In Fault: ", + summary["flag_true_duct_static_spt"], + ) + + if summary["percent_true"] > 5.0: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is high, indicating potential duct issues. Verify system calibration and investigate possible mechanical problems.' + "The percent True metric that represents the amount of time for when the fault flag is True is high, indicating potential duct issues. Verify system calibration and investigate possible mechanical problems." ) else: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the system is likely functioning correctly.' + "The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the system is likely functioning correctly." ) else: diff --git a/open_fdd/air_handling_unit/reports/report_fc10.py b/open_fdd/air_handling_unit/reports/report_fc10.py index 60ce465..c548039 100644 --- a/open_fdd/air_handling_unit/reports/report_fc10.py +++ b/open_fdd/air_handling_unit/reports/report_fc10.py @@ -7,33 +7,33 @@ class FaultCodeTenReport: """Class provides the definitions for Fault Condition 10 Report.""" def __init__(self, config): - self.oat_col = config['OAT_COL'] - self.mat_col = config['MAT_COL'] - self.cooling_sig_col = config['COOLING_SIG_COL'] - self.economizer_sig_col = config['ECONOMIZER_SIG_COL'] - self.supply_vfd_speed_col = config['SUPPLY_VFD_SPEED_COL'] + self.oat_col = config["OAT_COL"] + self.mat_col = config["MAT_COL"] + self.cooling_sig_col = config["COOLING_SIG_COL"] + self.economizer_sig_col = config["ECONOMIZER_SIG_COL"] + self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"] def create_plot(self, df: pd.DataFrame, output_col: str = None): if output_col is None: output_col = "fc10_flag" fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(25, 8)) - fig.suptitle('Fault Conditions 10 Plot') + fig.suptitle("Fault Conditions 10 Plot") ax1.plot(df.index, df[self.mat_col], label="MAT") ax1.plot(df.index, df[self.oat_col], label="OAT") - ax1.legend(loc='best') - ax1.set_ylabel('AHU Temps °F') + ax1.legend(loc="best") + ax1.set_ylabel("AHU Temps °F") ax2.plot(df.index, df[self.cooling_sig_col], label="AHU Cool Vlv", color="r") ax2.plot(df.index, df[self.economizer_sig_col], label="AHU Dpr Cmd", color="g") - ax2.legend(loc='best') - ax2.set_ylabel('%') + ax2.legend(loc="best") + ax2.set_ylabel("%") ax3.plot(df.index, df[output_col], label="Fault", color="k") - ax3.set_xlabel('Date') - ax3.set_ylabel('Fault Flags') - ax3.legend(loc='best') + ax3.set_xlabel("Date") + ax3.set_ylabel("Fault Flags") + ax3.legend(loc="best") plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.show() @@ -45,14 +45,24 @@ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dic delta = df.index.to_series().diff() summary = { - 'total_days': round(delta.sum() / pd.Timedelta(days=1), 2), - 'total_hours': round(delta.sum() / pd.Timedelta(hours=1)), - 'hours_fc10_mode': round((delta * df[output_col]).sum() / pd.Timedelta(hours=1)), - 'percent_true': round(df[output_col].mean() * 100, 2), - 'percent_false': round((100 - round(df[output_col].mean() * 100, 2)), 2), - 'flag_true_oat': round(df[self.oat_col].where(df[output_col] == 1).mean(), 2), - 'flag_true_mat': round(df[self.mat_col].where(df[output_col] == 1).mean(), 2), - 'hours_motor_runtime': round((delta * df[self.supply_vfd_speed_col].gt(.01).astype(int)).sum() / pd.Timedelta(hours=1), 2) + "total_days": round(delta.sum() / pd.Timedelta(days=1), 2), + "total_hours": round(delta.sum() / pd.Timedelta(hours=1)), + "hours_fc10_mode": round( + (delta * df[output_col]).sum() / pd.Timedelta(hours=1) + ), + "percent_true": round(df[output_col].mean() * 100, 2), + "percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2), + "flag_true_oat": round( + df[self.oat_col].where(df[output_col] == 1).mean(), 2 + ), + "flag_true_mat": round( + df[self.mat_col].where(df[output_col] == 1).mean(), 2 + ), + "hours_motor_runtime": round( + (delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum() + / pd.Timedelta(hours=1), + 2, + ), } return summary @@ -71,15 +81,19 @@ def create_hist_plot(self, df: pd.DataFrame, output_col: str = None): plt.show() plt.close() - def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc10_flag"): - print("Fault Condition 10: Outdoor air temperature and mix air temperature should be approximately equal in economizer plus mech cooling mode") + def display_report_in_ipython( + self, df: pd.DataFrame, output_col: str = "fc10_flag" + ): + print( + "Fault Condition 10: Outdoor air temperature and mix air temperature should be approximately equal in economizer plus mech cooling mode" + ) self.create_plot(df, output_col) summary = self.summarize_fault_times(df, output_col) for key, value in summary.items(): - formatted_key = key.replace('_', ' ') + formatted_key = key.replace("_", " ") print(f"{formatted_key}: {value}") sys.stdout.flush() @@ -98,13 +112,13 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc10_fl sys.stdout.flush() - if summary['percent_true'] > 5.0: + if summary["percent_true"] > 5.0: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is high indicating temperature sensor error or the mixing air dampers are stuck or broken with the inability for the AHU to go into a proper 100 percent outside air mode. Verify the actual installation of temperature sensors, damper operation, and related equipment to address potential issues.' + "The percent True metric that represents the amount of time for when the fault flag is True is high indicating temperature sensor error or the mixing air dampers are stuck or broken with the inability for the AHU to go into a proper 100 percent outside air mode. Verify the actual installation of temperature sensors, damper operation, and related equipment to address potential issues." ) else: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is low indicating the AHU components are within calibration for this fault equation.' + "The percent True metric that represents the amount of time for when the fault flag is True is low indicating the AHU components are within calibration for this fault equation." ) else: diff --git a/open_fdd/air_handling_unit/reports/report_fc11.py b/open_fdd/air_handling_unit/reports/report_fc11.py index 682fbeb..154686a 100644 --- a/open_fdd/air_handling_unit/reports/report_fc11.py +++ b/open_fdd/air_handling_unit/reports/report_fc11.py @@ -7,33 +7,33 @@ class FaultCodeElevenReport: """Class provides the definitions for Fault Condition 11 Report.""" def __init__(self, config): - self.sat_setpoint_col = config['SAT_SETPOINT_COL'] - self.oat_col = config['OAT_COL'] - self.cooling_sig_col = config['COOLING_SIG_COL'] - self.economizer_sig_col = config['ECONOMIZER_SIG_COL'] - self.supply_vfd_speed_col = config['SUPPLY_VFD_SPEED_COL'] + self.sat_setpoint_col = config["SAT_SETPOINT_COL"] + self.oat_col = config["OAT_COL"] + self.cooling_sig_col = config["COOLING_SIG_COL"] + self.economizer_sig_col = config["ECONOMIZER_SIG_COL"] + self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"] def create_plot(self, df: pd.DataFrame, output_col: str = None): if output_col is None: output_col = "fc11_flag" fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(25, 8)) - fig.suptitle('Fault Conditions 11 Plot') + fig.suptitle("Fault Conditions 11 Plot") ax1.plot(df.index, df[self.sat_setpoint_col], label="SATSP") ax1.plot(df.index, df[self.oat_col], label="OAT") - ax1.legend(loc='best') - ax1.set_ylabel('AHU Temps °F') + ax1.legend(loc="best") + ax1.set_ylabel("AHU Temps °F") ax2.plot(df.index, df[self.cooling_sig_col], label="AHU Cool Vlv", color="r") ax2.plot(df.index, df[self.economizer_sig_col], label="AHU Dpr Cmd", color="g") - ax2.legend(loc='best') - ax2.set_ylabel('%') + ax2.legend(loc="best") + ax2.set_ylabel("%") ax3.plot(df.index, df[output_col], label="Fault", color="k") - ax3.set_xlabel('Date') - ax3.set_ylabel('Fault Flags') - ax3.legend(loc='best') + ax3.set_xlabel("Date") + ax3.set_ylabel("Fault Flags") + ax3.legend(loc="best") plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.show() @@ -45,14 +45,24 @@ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dic delta = df.index.to_series().diff() summary = { - 'total_days': round(delta.sum() / pd.Timedelta(days=1), 2), - 'total_hours': round(delta.sum() / pd.Timedelta(hours=1)), - 'hours_fc11_mode': round((delta * df[output_col]).sum() / pd.Timedelta(hours=1)), - 'percent_true': round(df[output_col].mean() * 100, 2), - 'percent_false': round((100 - round(df[output_col].mean() * 100, 2)), 2), - 'flag_true_oat': round(df[self.oat_col].where(df[output_col] == 1).mean(), 2), - 'flag_true_sat_sp': round(df[self.sat_setpoint_col].where(df[output_col] == 1).mean(), 2), - 'hours_motor_runtime': round((delta * df[self.supply_vfd_speed_col].gt(.01).astype(int)).sum() / pd.Timedelta(hours=1), 2) + "total_days": round(delta.sum() / pd.Timedelta(days=1), 2), + "total_hours": round(delta.sum() / pd.Timedelta(hours=1)), + "hours_fc11_mode": round( + (delta * df[output_col]).sum() / pd.Timedelta(hours=1) + ), + "percent_true": round(df[output_col].mean() * 100, 2), + "percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2), + "flag_true_oat": round( + df[self.oat_col].where(df[output_col] == 1).mean(), 2 + ), + "flag_true_sat_sp": round( + df[self.sat_setpoint_col].where(df[output_col] == 1).mean(), 2 + ), + "hours_motor_runtime": round( + (delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum() + / pd.Timedelta(hours=1), + 2, + ), } return summary @@ -71,15 +81,19 @@ def create_hist_plot(self, df: pd.DataFrame, output_col: str = None): plt.show() plt.close() - def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc11_flag"): - print("Fault Condition 11: Outside air temperature too low for 100% outside air cooling in economizer mode") + def display_report_in_ipython( + self, df: pd.DataFrame, output_col: str = "fc11_flag" + ): + print( + "Fault Condition 11: Outside air temperature too low for 100% outside air cooling in economizer mode" + ) self.create_plot(df, output_col) summary = self.summarize_fault_times(df, output_col) for key, value in summary.items(): - formatted_key = key.replace('_', ' ') + formatted_key = key.replace("_", " ") print(f"{formatted_key}: {value}") sys.stdout.flush() @@ -93,18 +107,20 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc11_fl flag_true_oat = round(df[self.oat_col].where(df[output_col] == 1).mean(), 2) print("Outside Air Temp Mean When In Fault: ", flag_true_oat) - flag_true_sat_sp = round(df[self.sat_setpoint_col].where(df[output_col] == 1).mean(), 2) + flag_true_sat_sp = round( + df[self.sat_setpoint_col].where(df[output_col] == 1).mean(), 2 + ) print("Supply Air Temp Setpoint Mean When In Fault: ", flag_true_sat_sp) sys.stdout.flush() - if summary['percent_true'] > 5.0: + if summary["percent_true"] > 5.0: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is high, indicating temperature sensor error or the heating coil could be leaking, potentially creating simultaneous heating/cooling scenarios. Visually verify damper operation, and consider tuning the BAS programming for proper AHU operation.' + "The percent True metric that represents the amount of time for when the fault flag is True is high, indicating temperature sensor error or the heating coil could be leaking, potentially creating simultaneous heating/cooling scenarios. Visually verify damper operation, and consider tuning the BAS programming for proper AHU operation." ) else: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the AHU components are within calibration for this fault equation.' + "The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the AHU components are within calibration for this fault equation." ) else: diff --git a/open_fdd/air_handling_unit/reports/report_fc12.py b/open_fdd/air_handling_unit/reports/report_fc12.py index 24d2160..d3d2fe4 100644 --- a/open_fdd/air_handling_unit/reports/report_fc12.py +++ b/open_fdd/air_handling_unit/reports/report_fc12.py @@ -7,33 +7,33 @@ class FaultCodeTwelveReport: """Class provides the definitions for Fault Condition 12 Report.""" def __init__(self, config): - self.sat_col = config['SAT_COL'] - self.mat_col = config['MAT_COL'] - self.cooling_sig_col = config['COOLING_SIG_COL'] - self.economizer_sig_col = config['ECONOMIZER_SIG_COL'] - self.supply_vfd_speed_col = config['SUPPLY_VFD_SPEED_COL'] + self.sat_col = config["SAT_COL"] + self.mat_col = config["MAT_COL"] + self.cooling_sig_col = config["COOLING_SIG_COL"] + self.economizer_sig_col = config["ECONOMIZER_SIG_COL"] + self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"] def create_plot(self, df: pd.DataFrame, output_col: str = None): if output_col is None: output_col = "fc12_flag" fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(25, 8)) - fig.suptitle('Fault Conditions 12 Plot') + fig.suptitle("Fault Conditions 12 Plot") ax1.plot(df.index, df[self.sat_col], label="SAT") ax1.plot(df.index, df[self.mat_col], label="MAT") - ax1.legend(loc='best') - ax1.set_ylabel('AHU Temps °F') + ax1.legend(loc="best") + ax1.set_ylabel("AHU Temps °F") ax2.plot(df.index, df[self.cooling_sig_col], label="AHU Cool Vlv", color="r") ax2.plot(df.index, df[self.economizer_sig_col], label="AHU Dpr Cmd", color="g") - ax2.legend(loc='best') - ax2.set_ylabel('%') + ax2.legend(loc="best") + ax2.set_ylabel("%") ax3.plot(df.index, df[output_col], label="Fault", color="k") - ax3.set_xlabel('Date') - ax3.set_ylabel('Fault Flags') - ax3.legend(loc='best') + ax3.set_xlabel("Date") + ax3.set_ylabel("Fault Flags") + ax3.legend(loc="best") plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.show() @@ -45,14 +45,24 @@ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dic delta = df.index.to_series().diff() summary = { - 'total_days': round(delta.sum() / pd.Timedelta(days=1), 2), - 'total_hours': round(delta.sum() / pd.Timedelta(hours=1)), - 'hours_fc12_mode': round((delta * df[output_col]).sum() / pd.Timedelta(hours=1)), - 'percent_true': round(df[output_col].mean() * 100, 2), - 'percent_false': round((100 - round(df[output_col].mean() * 100, 2)), 2), - 'flag_true_mat': round(df[self.mat_col].where(df[output_col] == 1).mean(), 2), - 'flag_true_sat': round(df[self.sat_col].where(df[output_col] == 1).mean(), 2), - 'hours_motor_runtime': round((delta * df[self.supply_vfd_speed_col].gt(.01).astype(int)).sum() / pd.Timedelta(hours=1), 2) + "total_days": round(delta.sum() / pd.Timedelta(days=1), 2), + "total_hours": round(delta.sum() / pd.Timedelta(hours=1)), + "hours_fc12_mode": round( + (delta * df[output_col]).sum() / pd.Timedelta(hours=1) + ), + "percent_true": round(df[output_col].mean() * 100, 2), + "percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2), + "flag_true_mat": round( + df[self.mat_col].where(df[output_col] == 1).mean(), 2 + ), + "flag_true_sat": round( + df[self.sat_col].where(df[output_col] == 1).mean(), 2 + ), + "hours_motor_runtime": round( + (delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum() + / pd.Timedelta(hours=1), + 2, + ), } return summary @@ -71,15 +81,19 @@ def create_hist_plot(self, df: pd.DataFrame, output_col: str = None): plt.show() plt.close() - def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc12_flag"): - print("Fault Condition 12: Supply air temperature too high; should be less than mix air temperature in economizer plus mech cooling mode") + def display_report_in_ipython( + self, df: pd.DataFrame, output_col: str = "fc12_flag" + ): + print( + "Fault Condition 12: Supply air temperature too high; should be less than mix air temperature in economizer plus mech cooling mode" + ) self.create_plot(df, output_col) summary = self.summarize_fault_times(df, output_col) for key, value in summary.items(): - formatted_key = key.replace('_', ' ') + formatted_key = key.replace("_", " ") print(f"{formatted_key}: {value}") sys.stdout.flush() @@ -98,13 +112,13 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc12_fl sys.stdout.flush() - if summary['percent_true'] > 5.0: + if summary["percent_true"] > 5.0: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is high, indicating temperature sensor error or the heating/cooling coils may be leaking. Verify AHU mix/supply temperature sensor calibration and investigate potential mechanical issues like leaking valves.' + "The percent True metric that represents the amount of time for when the fault flag is True is high, indicating temperature sensor error or the heating/cooling coils may be leaking. Verify AHU mix/supply temperature sensor calibration and investigate potential mechanical issues like leaking valves." ) else: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the AHU components are within calibration for this fault equation.' + "The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the AHU components are within calibration for this fault equation." ) else: diff --git a/open_fdd/air_handling_unit/reports/report_fc13.py b/open_fdd/air_handling_unit/reports/report_fc13.py index 2d63b13..fca2984 100644 --- a/open_fdd/air_handling_unit/reports/report_fc13.py +++ b/open_fdd/air_handling_unit/reports/report_fc13.py @@ -7,33 +7,33 @@ class FaultCodeThirteenReport: """Class provides the definitions for Fault Condition 13 Report.""" def __init__(self, config): - self.sat_col = config['SAT_COL'] - self.mat_col = config['MAT_COL'] - self.cooling_sig_col = config['COOLING_SIG_COL'] - self.economizer_sig_col = config['ECONOMIZER_SIG_COL'] - self.supply_vfd_speed_col = config['SUPPLY_VFD_SPEED_COL'] + self.sat_col = config["SAT_COL"] + self.mat_col = config["MAT_COL"] + self.cooling_sig_col = config["COOLING_SIG_COL"] + self.economizer_sig_col = config["ECONOMIZER_SIG_COL"] + self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"] def create_plot(self, df: pd.DataFrame, output_col: str = None): if output_col is None: output_col = "fc13_flag" fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(25, 8)) - fig.suptitle('Fault Conditions 13 Plot') + fig.suptitle("Fault Conditions 13 Plot") ax1.plot(df.index, df[self.sat_col], label="SAT") ax1.plot(df.index, df[self.mat_col], label="MAT") - ax1.legend(loc='best') - ax1.set_ylabel('AHU Temps °F') + ax1.legend(loc="best") + ax1.set_ylabel("AHU Temps °F") ax2.plot(df.index, df[self.cooling_sig_col], label="AHU Cool Vlv", color="r") ax2.plot(df.index, df[self.economizer_sig_col], label="AHU Dpr Cmd", color="g") - ax2.legend(loc='best') - ax2.set_ylabel('%') + ax2.legend(loc="best") + ax2.set_ylabel("%") ax3.plot(df.index, df[output_col], label="Fault", color="k") - ax3.set_xlabel('Date') - ax3.set_ylabel('Fault Flags') - ax3.legend(loc='best') + ax3.set_xlabel("Date") + ax3.set_ylabel("Fault Flags") + ax3.legend(loc="best") plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.show() @@ -45,14 +45,24 @@ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dic delta = df.index.to_series().diff() summary = { - 'total_days': round(delta.sum() / pd.Timedelta(days=1), 2), - 'total_hours': round(delta.sum() / pd.Timedelta(hours=1)), - 'hours_fc13_mode': round((delta * df[output_col]).sum() / pd.Timedelta(hours=1)), - 'percent_true': round(df[output_col].mean() * 100, 2), - 'percent_false': round((100 - round(df[output_col].mean() * 100, 2)), 2), - 'flag_true_mat': round(df[self.mat_col].where(df[output_col] == 1).mean(), 2), - 'flag_true_sat': round(df[self.sat_col].where(df[output_col] == 1).mean(), 2), - 'hours_motor_runtime': round((delta * df[self.supply_vfd_speed_col].gt(.01).astype(int)).sum() / pd.Timedelta(hours=1), 2) + "total_days": round(delta.sum() / pd.Timedelta(days=1), 2), + "total_hours": round(delta.sum() / pd.Timedelta(hours=1)), + "hours_fc13_mode": round( + (delta * df[output_col]).sum() / pd.Timedelta(hours=1) + ), + "percent_true": round(df[output_col].mean() * 100, 2), + "percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2), + "flag_true_mat": round( + df[self.mat_col].where(df[output_col] == 1).mean(), 2 + ), + "flag_true_sat": round( + df[self.sat_col].where(df[output_col] == 1).mean(), 2 + ), + "hours_motor_runtime": round( + (delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum() + / pd.Timedelta(hours=1), + 2, + ), } return summary @@ -71,15 +81,19 @@ def create_hist_plot(self, df: pd.DataFrame, output_col: str = None): plt.show() plt.close() - def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc13_flag"): - print("Fault Condition 13: Supply air temperature too high in full cooling in economizer plus mech cooling mode") + def display_report_in_ipython( + self, df: pd.DataFrame, output_col: str = "fc13_flag" + ): + print( + "Fault Condition 13: Supply air temperature too high in full cooling in economizer plus mech cooling mode" + ) self.create_plot(df, output_col) summary = self.summarize_fault_times(df, output_col) for key, value in summary.items(): - formatted_key = key.replace('_', ' ') + formatted_key = key.replace("_", " ") print(f"{formatted_key}: {value}") sys.stdout.flush() @@ -98,13 +112,13 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc13_fl sys.stdout.flush() - if summary['percent_true'] > 5.0: + if summary["percent_true"] > 5.0: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is high, indicating temperature sensor error or issues with the cooling system. Verify AHU temperature sensor calibration and investigate potential mechanical issues.' + "The percent True metric that represents the amount of time for when the fault flag is True is high, indicating temperature sensor error or issues with the cooling system. Verify AHU temperature sensor calibration and investigate potential mechanical issues." ) else: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the AHU components are within calibration for this fault equation.' + "The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the AHU components are within calibration for this fault equation." ) else: diff --git a/open_fdd/air_handling_unit/reports/report_fc14.py b/open_fdd/air_handling_unit/reports/report_fc14.py index bdf7a45..745ec83 100644 --- a/open_fdd/air_handling_unit/reports/report_fc14.py +++ b/open_fdd/air_handling_unit/reports/report_fc14.py @@ -7,31 +7,31 @@ class FaultCodeFourteenReport: """Class provides the definitions for Fault Condition 14 Report.""" def __init__(self, config): - self.sat_col = config['SAT_COL'] - self.clt_col = config['CLT_COL'] - self.cooling_sig_col = config['COOLING_SIG_COL'] - self.supply_vfd_speed_col = config['SUPPLY_VFD_SPEED_COL'] + self.sat_col = config["SAT_COL"] + self.clt_col = config["CLT_COL"] + self.cooling_sig_col = config["COOLING_SIG_COL"] + self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"] def create_plot(self, df: pd.DataFrame, output_col: str = None): if output_col is None: output_col = "fc14_flag" fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(25, 8)) - fig.suptitle('Fault Conditions 14 Plot') + fig.suptitle("Fault Conditions 14 Plot") ax1.plot(df.index, df[self.sat_col], label="SAT") ax1.plot(df.index, df[self.clt_col], label="CLT") - ax1.legend(loc='best') - ax1.set_ylabel('AHU Temps °F') + ax1.legend(loc="best") + ax1.set_ylabel("AHU Temps °F") ax2.plot(df.index, df[self.cooling_sig_col], label="AHU Cool Vlv", color="r") - ax2.legend(loc='best') - ax2.set_ylabel('%') + ax2.legend(loc="best") + ax2.set_ylabel("%") ax3.plot(df.index, df[output_col], label="Fault", color="k") - ax3.set_xlabel('Date') - ax3.set_ylabel('Fault Flags') - ax3.legend(loc='best') + ax3.set_xlabel("Date") + ax3.set_ylabel("Fault Flags") + ax3.legend(loc="best") plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.show() @@ -43,14 +43,24 @@ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dic delta = df.index.to_series().diff() summary = { - 'total_days': round(delta.sum() / pd.Timedelta(days=1), 2), - 'total_hours': round(delta.sum() / pd.Timedelta(hours=1)), - 'hours_fc14_mode': round((delta * df[output_col]).sum() / pd.Timedelta(hours=1)), - 'percent_true': round(df[output_col].mean() * 100, 2), - 'percent_false': round((100 - round(df[output_col].mean() * 100, 2)), 2), - 'flag_true_clt': round(df[self.clt_col].where(df[output_col] == 1).mean(), 2), - 'flag_true_sat': round(df[self.sat_col].where(df[output_col] == 1).mean(), 2), - 'hours_motor_runtime': round((delta * df[self.supply_vfd_speed_col].gt(.01).astype(int)).sum() / pd.Timedelta(hours=1), 2) + "total_days": round(delta.sum() / pd.Timedelta(days=1), 2), + "total_hours": round(delta.sum() / pd.Timedelta(hours=1)), + "hours_fc14_mode": round( + (delta * df[output_col]).sum() / pd.Timedelta(hours=1) + ), + "percent_true": round(df[output_col].mean() * 100, 2), + "percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2), + "flag_true_clt": round( + df[self.clt_col].where(df[output_col] == 1).mean(), 2 + ), + "flag_true_sat": round( + df[self.sat_col].where(df[output_col] == 1).mean(), 2 + ), + "hours_motor_runtime": round( + (delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum() + / pd.Timedelta(hours=1), + 2, + ), } return summary @@ -69,15 +79,19 @@ def create_hist_plot(self, df: pd.DataFrame, output_col: str = None): plt.show() plt.close() - def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc14_flag"): - print("Fault Condition 14: Temperature drop across inactive cooling coil (requires coil leaving temp sensor)") + def display_report_in_ipython( + self, df: pd.DataFrame, output_col: str = "fc14_flag" + ): + print( + "Fault Condition 14: Temperature drop across inactive cooling coil (requires coil leaving temp sensor)" + ) self.create_plot(df, output_col) summary = self.summarize_fault_times(df, output_col) for key, value in summary.items(): - formatted_key = key.replace('_', ' ') + formatted_key = key.replace("_", " ") print(f"{formatted_key}: {value}") sys.stdout.flush() @@ -96,13 +110,13 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc14_fl sys.stdout.flush() - if summary['percent_true'] > 5.0: + if summary["percent_true"] > 5.0: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is high, indicating potential leakage in the cooling coil. Verify temperature sensor calibration and investigate possible mechanical issues.' + "The percent True metric that represents the amount of time for when the fault flag is True is high, indicating potential leakage in the cooling coil. Verify temperature sensor calibration and investigate possible mechanical issues." ) else: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the cooling coil is likely functioning correctly.' + "The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the cooling coil is likely functioning correctly." ) else: diff --git a/open_fdd/air_handling_unit/reports/report_fc15.py b/open_fdd/air_handling_unit/reports/report_fc15.py index d7cafc3..fb07083 100644 --- a/open_fdd/air_handling_unit/reports/report_fc15.py +++ b/open_fdd/air_handling_unit/reports/report_fc15.py @@ -7,31 +7,31 @@ class FaultCodeFifteenReport: """Class provides the definitions for Fault Condition 15 Report.""" def __init__(self, config): - self.sat_col = config['SAT_COL'] - self.hlt_col = config['HLT_COL'] - self.heating_sig_col = config['HEATING_SIG_COL'] - self.supply_vfd_speed_col = config['SUPPLY_VFD_SPEED_COL'] + self.sat_col = config["SAT_COL"] + self.hlt_col = config["HLT_COL"] + self.heating_sig_col = config["HEATING_SIG_COL"] + self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"] def create_plot(self, df: pd.DataFrame, output_col: str = None): if output_col is None: output_col = "fc15_flag" fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(25, 8)) - fig.suptitle('Fault Conditions 15 Plot') + fig.suptitle("Fault Conditions 15 Plot") ax1.plot(df.index, df[self.sat_col], label="SAT") ax1.plot(df.index, df[self.hlt_col], label="HLT") - ax1.legend(loc='best') - ax1.set_ylabel('AHU Temps °F') + ax1.legend(loc="best") + ax1.set_ylabel("AHU Temps °F") ax2.plot(df.index, df[self.heating_sig_col], label="AHU Heat Vlv", color="r") - ax2.legend(loc='best') - ax2.set_ylabel('%') + ax2.legend(loc="best") + ax2.set_ylabel("%") ax3.plot(df.index, df[output_col], label="Fault", color="k") - ax3.set_xlabel('Date') - ax3.set_ylabel('Fault Flags') - ax3.legend(loc='best') + ax3.set_xlabel("Date") + ax3.set_ylabel("Fault Flags") + ax3.legend(loc="best") plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.show() @@ -43,14 +43,24 @@ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dic delta = df.index.to_series().diff() summary = { - 'total_days': round(delta.sum() / pd.Timedelta(days=1), 2), - 'total_hours': round(delta.sum() / pd.Timedelta(hours=1)), - 'hours_fc15_mode': round((delta * df[output_col]).sum() / pd.Timedelta(hours=1)), - 'percent_true': round(df[output_col].mean() * 100, 2), - 'percent_false': round((100 - round(df[output_col].mean() * 100, 2)), 2), - 'flag_true_hlt': round(df[self.hlt_col].where(df[output_col] == 1).mean(), 2), - 'flag_true_sat': round(df[self.sat_col].where(df[output_col] == 1).mean(), 2), - 'hours_motor_runtime': round((delta * df[self.supply_vfd_speed_col].gt(.01).astype(int)).sum() / pd.Timedelta(hours=1), 2) + "total_days": round(delta.sum() / pd.Timedelta(days=1), 2), + "total_hours": round(delta.sum() / pd.Timedelta(hours=1)), + "hours_fc15_mode": round( + (delta * df[output_col]).sum() / pd.Timedelta(hours=1) + ), + "percent_true": round(df[output_col].mean() * 100, 2), + "percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2), + "flag_true_hlt": round( + df[self.hlt_col].where(df[output_col] == 1).mean(), 2 + ), + "flag_true_sat": round( + df[self.sat_col].where(df[output_col] == 1).mean(), 2 + ), + "hours_motor_runtime": round( + (delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum() + / pd.Timedelta(hours=1), + 2, + ), } return summary @@ -69,15 +79,19 @@ def create_hist_plot(self, df: pd.DataFrame, output_col: str = None): plt.show() plt.close() - def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc15_flag"): - print("Fault Condition 15: Temperature rise across inactive heating coil (requires coil leaving temp sensor)") + def display_report_in_ipython( + self, df: pd.DataFrame, output_col: str = "fc15_flag" + ): + print( + "Fault Condition 15: Temperature rise across inactive heating coil (requires coil leaving temp sensor)" + ) self.create_plot(df, output_col) summary = self.summarize_fault_times(df, output_col) for key, value in summary.items(): - formatted_key = key.replace('_', ' ') + formatted_key = key.replace("_", " ") print(f"{formatted_key}: {value}") sys.stdout.flush() @@ -96,13 +110,13 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc15_fl sys.stdout.flush() - if summary['percent_true'] > 5.0: + if summary["percent_true"] > 5.0: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is high, indicating potential leakage in the heating coil. Verify temperature sensor calibration and investigate possible mechanical issues.' + "The percent True metric that represents the amount of time for when the fault flag is True is high, indicating potential leakage in the heating coil. Verify temperature sensor calibration and investigate possible mechanical issues." ) else: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the heating coil is likely functioning correctly.' + "The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the heating coil is likely functioning correctly." ) else: diff --git a/open_fdd/air_handling_unit/reports/report_fc2.py b/open_fdd/air_handling_unit/reports/report_fc2.py index a390d9e..ca5428f 100644 --- a/open_fdd/air_handling_unit/reports/report_fc2.py +++ b/open_fdd/air_handling_unit/reports/report_fc2.py @@ -5,31 +5,31 @@ class FaultCodeTwoReport: def __init__(self, config): - self.mix_degf_err_thres = config['MIX_DEGF_ERR_THRES'] - self.return_degf_err_thres = config['RETURN_DEGF_ERR_THRES'] - self.outdoor_degf_err_thres = config['OUTDOOR_DEGF_ERR_THRES'] - self.mat_col = config['MAT_COL'] - self.rat_col = config['RAT_COL'] - self.oat_col = config['OAT_COL'] - self.supply_vfd_speed_col = config['SUPPLY_VFD_SPEED_COL'] + self.mix_degf_err_thres = config["MIX_DEGF_ERR_THRES"] + self.return_degf_err_thres = config["RETURN_DEGF_ERR_THRES"] + self.outdoor_degf_err_thres = config["OUTDOOR_DEGF_ERR_THRES"] + self.mat_col = config["MAT_COL"] + self.rat_col = config["RAT_COL"] + self.oat_col = config["OAT_COL"] + self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"] def create_plot(self, df: pd.DataFrame, output_col: str = None): if output_col is None: output_col = "fc2_flag" fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(25, 8)) - fig.suptitle('Fault Conditions 2 Plot') + fig.suptitle("Fault Conditions 2 Plot") - ax1.plot(df.index, df[self.mat_col], color='r', label="Mix Temp") - ax1.plot(df.index, df[self.rat_col], color='b', label="Return Temp") - ax1.plot(df.index, df[self.oat_col], color='g', label="Out Temp") - ax1.legend(loc='best') + ax1.plot(df.index, df[self.mat_col], color="r", label="Mix Temp") + ax1.plot(df.index, df[self.rat_col], color="b", label="Return Temp") + ax1.plot(df.index, df[self.oat_col], color="g", label="Out Temp") + ax1.legend(loc="best") ax1.set_ylabel("°F") ax2.plot(df.index, df[output_col], label="Fault", color="k") - ax2.set_xlabel('Date') - ax2.set_ylabel('Fault Flags') - ax2.legend(loc='best') + ax2.set_xlabel("Date") + ax2.set_ylabel("Fault Flags") + ax2.legend(loc="best") plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.show() @@ -41,15 +41,27 @@ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dic delta = df.index.to_series().diff() summary = { - 'total_days': round(delta.sum() / pd.Timedelta(days=1), 2), - 'total_hours': round(delta.sum() / pd.Timedelta(hours=1)), - 'hours_fc2_mode': round((delta * df[output_col]).sum() / pd.Timedelta(hours=1)), - 'percent_true': round(df[output_col].mean() * 100, 2), - 'percent_false': round((100 - round(df[output_col].mean() * 100, 2)), 2), - 'flag_true_mat': round(df[self.mat_col].where(df[output_col] == 1).mean(), 2), - 'flag_true_oat': round(df[self.oat_col].where(df[output_col] == 1).mean(), 2), - 'flag_true_rat': round(df[self.rat_col].where(df[output_col] == 1).mean(), 2), - 'hours_motor_runtime': round((delta * df[self.supply_vfd_speed_col].gt(.01).astype(int)).sum() / pd.Timedelta(hours=1), 2) + "total_days": round(delta.sum() / pd.Timedelta(days=1), 2), + "total_hours": round(delta.sum() / pd.Timedelta(hours=1)), + "hours_fc2_mode": round( + (delta * df[output_col]).sum() / pd.Timedelta(hours=1) + ), + "percent_true": round(df[output_col].mean() * 100, 2), + "percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2), + "flag_true_mat": round( + df[self.mat_col].where(df[output_col] == 1).mean(), 2 + ), + "flag_true_oat": round( + df[self.oat_col].where(df[output_col] == 1).mean(), 2 + ), + "flag_true_rat": round( + df[self.rat_col].where(df[output_col] == 1).mean(), 2 + ), + "hours_motor_runtime": round( + (delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum() + / pd.Timedelta(hours=1), + 2, + ), } return summary @@ -69,14 +81,16 @@ def create_hist_plot(self, df: pd.DataFrame, output_col: str = None): plt.close() def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc2_flag"): - print("Fault Condition 2: Mix temperature too low; should be between outside and return air") + print( + "Fault Condition 2: Mix temperature too low; should be between outside and return air" + ) self.create_plot(df, output_col) summary = self.summarize_fault_times(df, output_col) for key, value in summary.items(): - formatted_key = key.replace('_', ' ') + formatted_key = key.replace("_", " ") print(f"{formatted_key}: {value}") sys.stdout.flush() @@ -87,17 +101,17 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc2_fla if fc_max_faults_found != 0: self.create_hist_plot(df, output_col) - print("Mix Air Temp Mean When In Fault: ", summary['flag_true_mat']) - print("Outside Air Temp Mean When In Fault: ", summary['flag_true_oat']) - print("Return Temp Mean When In Fault: ", summary['flag_true_rat']) + print("Mix Air Temp Mean When In Fault: ", summary["flag_true_mat"]) + print("Outside Air Temp Mean When In Fault: ", summary["flag_true_oat"]) + print("Return Temp Mean When In Fault: ", summary["flag_true_rat"]) - if summary['percent_true'] > 5.0: + if summary["percent_true"] > 5.0: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is high, indicating potential issues with the mix temperature. Verify sensor calibration and investigate possible mechanical problems.' + "The percent True metric that represents the amount of time for when the fault flag is True is high, indicating potential issues with the mix temperature. Verify sensor calibration and investigate possible mechanical problems." ) else: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the system is likely functioning correctly.' + "The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the system is likely functioning correctly." ) else: diff --git a/open_fdd/air_handling_unit/reports/report_fc3.py b/open_fdd/air_handling_unit/reports/report_fc3.py index b76f33f..10908f1 100644 --- a/open_fdd/air_handling_unit/reports/report_fc3.py +++ b/open_fdd/air_handling_unit/reports/report_fc3.py @@ -5,31 +5,31 @@ class FaultCodeThreeReport: def __init__(self, config): - self.mix_degf_err_thres = config['MIX_DEGF_ERR_THRES'] - self.return_degf_err_thres = config['RETURN_DEGF_ERR_THRES'] - self.outdoor_degf_err_thres = config['OUTDOOR_DEGF_ERR_THRES'] - self.mat_col = config['MAT_COL'] - self.rat_col = config['RAT_COL'] - self.oat_col = config['OAT_COL'] - self.supply_vfd_speed_col = config['SUPPLY_VFD_SPEED_COL'] + self.mix_degf_err_thres = config["MIX_DEGF_ERR_THRES"] + self.return_degf_err_thres = config["RETURN_DEGF_ERR_THRES"] + self.outdoor_degf_err_thres = config["OUTDOOR_DEGF_ERR_THRES"] + self.mat_col = config["MAT_COL"] + self.rat_col = config["RAT_COL"] + self.oat_col = config["OAT_COL"] + self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"] def create_plot(self, df: pd.DataFrame, output_col: str = None): if output_col is None: output_col = "fc3_flag" fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(25, 8)) - fig.suptitle('Fault Conditions 3 Plot') + fig.suptitle("Fault Conditions 3 Plot") - ax1.plot(df.index, df[self.mat_col], color='r', label="Mix Temp") - ax1.plot(df.index, df[self.rat_col], color='b', label="Return Temp") - ax1.plot(df.index, df[self.oat_col], color='g', label="Out Temp") - ax1.legend(loc='best') + ax1.plot(df.index, df[self.mat_col], color="r", label="Mix Temp") + ax1.plot(df.index, df[self.rat_col], color="b", label="Return Temp") + ax1.plot(df.index, df[self.oat_col], color="g", label="Out Temp") + ax1.legend(loc="best") ax1.set_ylabel("°F") ax2.plot(df.index, df[output_col], label="Fault", color="k") - ax2.set_xlabel('Date') - ax2.set_ylabel('Fault Flags') - ax2.legend(loc='best') + ax2.set_xlabel("Date") + ax2.set_ylabel("Fault Flags") + ax2.legend(loc="best") plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.show() @@ -41,15 +41,27 @@ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dic delta = df.index.to_series().diff() summary = { - 'total_days': round(delta.sum() / pd.Timedelta(days=1), 2), - 'total_hours': round(delta.sum() / pd.Timedelta(hours=1)), - 'hours_fc3_mode': round((delta * df[output_col]).sum() / pd.Timedelta(hours=1)), - 'percent_true': round(df[output_col].mean() * 100, 2), - 'percent_false': round((100 - round(df[output_col].mean() * 100, 2)), 2), - 'flag_true_mat': round(df[self.mat_col].where(df[output_col] == 1).mean(), 2), - 'flag_true_oat': round(df[self.oat_col].where(df[output_col] == 1).mean(), 2), - 'flag_true_rat': round(df[self.rat_col].where(df[output_col] == 1).mean(), 2), - 'hours_motor_runtime': round((delta * df[self.supply_vfd_speed_col].gt(.01).astype(int)).sum() / pd.Timedelta(hours=1), 2) + "total_days": round(delta.sum() / pd.Timedelta(days=1), 2), + "total_hours": round(delta.sum() / pd.Timedelta(hours=1)), + "hours_fc3_mode": round( + (delta * df[output_col]).sum() / pd.Timedelta(hours=1) + ), + "percent_true": round(df[output_col].mean() * 100, 2), + "percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2), + "flag_true_mat": round( + df[self.mat_col].where(df[output_col] == 1).mean(), 2 + ), + "flag_true_oat": round( + df[self.oat_col].where(df[output_col] == 1).mean(), 2 + ), + "flag_true_rat": round( + df[self.rat_col].where(df[output_col] == 1).mean(), 2 + ), + "hours_motor_runtime": round( + (delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum() + / pd.Timedelta(hours=1), + 2, + ), } return summary @@ -69,14 +81,16 @@ def create_hist_plot(self, df: pd.DataFrame, output_col: str = None): plt.close() def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc3_flag"): - print("Fault Condition 3: Mix temperature too high; should be between outside and return air") + print( + "Fault Condition 3: Mix temperature too high; should be between outside and return air" + ) self.create_plot(df, output_col) summary = self.summarize_fault_times(df, output_col) for key, value in summary.items(): - formatted_key = key.replace('_', ' ') + formatted_key = key.replace("_", " ") print(f"{formatted_key}: {value}") sys.stdout.flush() @@ -87,17 +101,17 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc3_fla if fc_max_faults_found != 0: self.create_hist_plot(df, output_col) - print("Mix Air Temp Mean When In Fault: ", summary['flag_true_mat']) - print("Outside Air Temp Mean When In Fault: ", summary['flag_true_oat']) - print("Return Temp Mean When In Fault: ", summary['flag_true_rat']) + print("Mix Air Temp Mean When In Fault: ", summary["flag_true_mat"]) + print("Outside Air Temp Mean When In Fault: ", summary["flag_true_oat"]) + print("Return Temp Mean When In Fault: ", summary["flag_true_rat"]) - if summary['percent_true'] > 5.0: + if summary["percent_true"] > 5.0: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is high, indicating potential issues with the mix temperature. Verify sensor calibration and investigate possible mechanical problems.' + "The percent True metric that represents the amount of time for when the fault flag is True is high, indicating potential issues with the mix temperature. Verify sensor calibration and investigate possible mechanical problems." ) else: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the system is likely functioning correctly.' + "The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the system is likely functioning correctly." ) else: diff --git a/open_fdd/air_handling_unit/reports/report_fc4.py b/open_fdd/air_handling_unit/reports/report_fc4.py index fa8d55a..9206421 100644 --- a/open_fdd/air_handling_unit/reports/report_fc4.py +++ b/open_fdd/air_handling_unit/reports/report_fc4.py @@ -2,45 +2,62 @@ import pandas as pd import sys + class FaultCodeFourReport: """Class provides the definitions for Fault Code 4 Report. Reporting the time series average dataframe that calculates control states per hour. """ def __init__(self, config): - self.delta_os_max = config['DELTA_OS_MAX'] - self.heating_mode_calc_col = 'heating_mode' - self.econ_only_cooling_mode_calc_col = 'econ_only_cooling_mode' - self.econ_plus_mech_cooling_mode_calc_col = 'econ_plus_mech_cooling_mode' - self.mech_cooling_only_mode_calc_col = 'mech_cooling_only_mode' - - def summarize_fault_times(self, df: pd.DataFrame, output_col: str = "fc4_flag") -> dict: + self.delta_os_max = config["DELTA_OS_MAX"] + self.heating_mode_calc_col = "heating_mode" + self.econ_only_cooling_mode_calc_col = "econ_only_cooling_mode" + self.econ_plus_mech_cooling_mode_calc_col = "econ_plus_mech_cooling_mode" + self.mech_cooling_only_mode_calc_col = "mech_cooling_only_mode" + + def summarize_fault_times( + self, df: pd.DataFrame, output_col: str = "fc4_flag" + ) -> dict: delta_all_data = df.index.to_series().diff() total_days_all_data = round(delta_all_data.sum() / pd.Timedelta(days=1), 2) total_hours_all_data = round(delta_all_data.sum() / pd.Timedelta(hours=1)) - hours_fc4_mode = round((delta_all_data * df[output_col]).sum() / pd.Timedelta(hours=1)) + hours_fc4_mode = round( + (delta_all_data * df[output_col]).sum() / pd.Timedelta(hours=1) + ) percent_true_fc4 = round(df[output_col].mean() * 100, 2) percent_false_fc4 = round((100 - percent_true_fc4), 2) # Heating mode runtime stats delta_heating = df[self.heating_mode_calc_col].index.to_series().diff() - total_hours_heating = (delta_heating * df[self.heating_mode_calc_col]).sum() / pd.Timedelta(hours=1) + total_hours_heating = ( + delta_heating * df[self.heating_mode_calc_col] + ).sum() / pd.Timedelta(hours=1) percent_heating = round(df[self.heating_mode_calc_col].mean() * 100, 2) # Econ mode runtime stats delta_econ = df[self.econ_only_cooling_mode_calc_col].index.to_series().diff() - total_hours_econ = (delta_econ * df[self.econ_only_cooling_mode_calc_col]).sum() / pd.Timedelta(hours=1) + total_hours_econ = ( + delta_econ * df[self.econ_only_cooling_mode_calc_col] + ).sum() / pd.Timedelta(hours=1) percent_econ = round(df[self.econ_only_cooling_mode_calc_col].mean() * 100, 2) # Econ plus mech cooling mode runtime stats - delta_econ_clg = df[self.econ_plus_mech_cooling_mode_calc_col].index.to_series().diff() - total_hours_econ_clg = (delta_econ_clg * df[self.econ_plus_mech_cooling_mode_calc_col]).sum() / pd.Timedelta(hours=1) - percent_econ_clg = round(df[self.econ_plus_mech_cooling_mode_calc_col].mean() * 100, 2) + delta_econ_clg = ( + df[self.econ_plus_mech_cooling_mode_calc_col].index.to_series().diff() + ) + total_hours_econ_clg = ( + delta_econ_clg * df[self.econ_plus_mech_cooling_mode_calc_col] + ).sum() / pd.Timedelta(hours=1) + percent_econ_clg = round( + df[self.econ_plus_mech_cooling_mode_calc_col].mean() * 100, 2 + ) # Mech cooling mode runtime stats delta_clg = df[self.mech_cooling_only_mode_calc_col].index.to_series().diff() - total_hours_clg = (delta_clg * df[self.mech_cooling_only_mode_calc_col]).sum() / pd.Timedelta(hours=1) + total_hours_clg = ( + delta_clg * df[self.mech_cooling_only_mode_calc_col] + ).sum() / pd.Timedelta(hours=1) percent_clg = round(df[self.mech_cooling_only_mode_calc_col].mean() * 100, 2) return { @@ -56,25 +73,40 @@ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = "fc4_flag") "total_hours_heating_mode": total_hours_heating, "total_hours_econ_mode": total_hours_econ, "total_hours_econ_mech_clg_mode": total_hours_econ_clg, - "total_hours_mech_clg_mode": total_hours_clg + "total_hours_mech_clg_mode": total_hours_clg, } def create_plot(self, df: pd.DataFrame, output_col: str = "fc4_flag"): fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(25, 8)) - fig.suptitle('Fault Condition 4 Plots') - - ax1.plot(df.index, df[self.heating_mode_calc_col], label="Heat", color='orange') - ax1.plot(df.index, df[self.econ_only_cooling_mode_calc_col], label="Econ Clg", color='olive') - ax1.plot(df.index, df[self.econ_plus_mech_cooling_mode_calc_col], label="Econ + Mech Clg", color='c') - ax1.plot(df.index, df[self.mech_cooling_only_mode_calc_col], label="Mech Clg", color='m') - ax1.set_xlabel('Date') - ax1.set_ylabel('Calculated AHU Operating States') - ax1.legend(loc='best') + fig.suptitle("Fault Condition 4 Plots") + + ax1.plot(df.index, df[self.heating_mode_calc_col], label="Heat", color="orange") + ax1.plot( + df.index, + df[self.econ_only_cooling_mode_calc_col], + label="Econ Clg", + color="olive", + ) + ax1.plot( + df.index, + df[self.econ_plus_mech_cooling_mode_calc_col], + label="Econ + Mech Clg", + color="c", + ) + ax1.plot( + df.index, + df[self.mech_cooling_only_mode_calc_col], + label="Mech Clg", + color="m", + ) + ax1.set_xlabel("Date") + ax1.set_ylabel("Calculated AHU Operating States") + ax1.legend(loc="best") ax2.plot(df.index, df[output_col], label="Fault", color="k") - ax2.set_xlabel('Date') - ax2.set_ylabel('Fault Flags') - ax2.legend(loc='best') + ax2.set_xlabel("Date") + ax2.set_ylabel("Fault Flags") + ax2.legend(loc="best") plt.tight_layout() plt.show() @@ -103,7 +135,7 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc4_fla summary = self.summarize_fault_times(df, output_col) for key, value in summary.items(): - formatted_key = key.replace('_', ' ') + formatted_key = key.replace("_", " ") print(f"{formatted_key}: {value}") sys.stdout.flush() diff --git a/open_fdd/air_handling_unit/reports/report_fc5.py b/open_fdd/air_handling_unit/reports/report_fc5.py index 7f735d7..f287839 100644 --- a/open_fdd/air_handling_unit/reports/report_fc5.py +++ b/open_fdd/air_handling_unit/reports/report_fc5.py @@ -8,35 +8,35 @@ class FaultCodeFiveReport: """Class provides the definitions for Fault Condition 5 Report.""" def __init__(self, config): - self.mix_degf_err_thres = config['MIX_DEGF_ERR_THRES'] - self.supply_degf_err_thres = config['SUPPLY_DEGF_ERR_THRES'] - self.delta_t_supply_fan = config['DELTA_T_SUPPLY_FAN'] - self.mat_col = config['MAT_COL'] - self.sat_col = config['SAT_COL'] - self.heating_sig_col = config['HEATING_SIG_COL'] - self.supply_vfd_speed_col = config['SUPPLY_VFD_SPEED_COL'] + self.mix_degf_err_thres = config["MIX_DEGF_ERR_THRES"] + self.supply_degf_err_thres = config["SUPPLY_DEGF_ERR_THRES"] + self.delta_t_supply_fan = config["DELTA_T_SUPPLY_FAN"] + self.mat_col = config["MAT_COL"] + self.sat_col = config["SAT_COL"] + self.heating_sig_col = config["HEATING_SIG_COL"] + self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"] def create_plot(self, df: pd.DataFrame, output_col: str = None): if output_col is None: output_col = "fc5_flag" fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(25, 8)) - fig.suptitle('Fault Conditions 5 Plot') + fig.suptitle("Fault Conditions 5 Plot") - ax1.plot(df.index, df[self.mat_col], color='g', label="Mix Temp") - ax1.plot(df.index, df[self.sat_col], color='b', label="Supply Temp") - ax1.legend(loc='best') + ax1.plot(df.index, df[self.mat_col], color="g", label="Mix Temp") + ax1.plot(df.index, df[self.sat_col], color="b", label="Supply Temp") + ax1.legend(loc="best") ax1.set_ylabel("°F") ax2.plot(df.index, df[self.heating_sig_col], label="Htg Valve", color="r") - ax2.set_xlabel('Date') - ax2.set_ylabel('%') - ax2.legend(loc='best') + ax2.set_xlabel("Date") + ax2.set_ylabel("%") + ax2.legend(loc="best") ax3.plot(df.index, df[output_col], label="Fault", color="k") - ax3.set_xlabel('Date') - ax3.set_ylabel('Fault Flags') - ax3.legend(loc='best') + ax3.set_xlabel("Date") + ax3.set_ylabel("Fault Flags") + ax3.legend(loc="best") plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.show() @@ -48,14 +48,24 @@ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dic delta = df.index.to_series().diff() summary = { - 'total_days': round(delta.sum() / pd.Timedelta(days=1), 2), - 'total_hours': round(delta.sum() / pd.Timedelta(hours=1)), - 'hours_fc5_mode': round((delta * df[output_col]).sum() / pd.Timedelta(hours=1)), - 'percent_true': round(df[output_col].mean() * 100, 2), - 'percent_false': round((100 - round(df[output_col].mean() * 100, 2)), 2), - 'flag_true_mat': round(df[self.mat_col].where(df[output_col] == 1).mean(), 2), - 'flag_true_sat': round(df[self.sat_col].where(df[output_col] == 1).mean(), 2), - 'hours_motor_runtime': round((delta * df[self.supply_vfd_speed_col].gt(.01).astype(int)).sum() / pd.Timedelta(hours=1), 2) + "total_days": round(delta.sum() / pd.Timedelta(days=1), 2), + "total_hours": round(delta.sum() / pd.Timedelta(hours=1)), + "hours_fc5_mode": round( + (delta * df[output_col]).sum() / pd.Timedelta(hours=1) + ), + "percent_true": round(df[output_col].mean() * 100, 2), + "percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2), + "flag_true_mat": round( + df[self.mat_col].where(df[output_col] == 1).mean(), 2 + ), + "flag_true_sat": round( + df[self.sat_col].where(df[output_col] == 1).mean(), 2 + ), + "hours_motor_runtime": round( + (delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum() + / pd.Timedelta(hours=1), + 2, + ), } return summary @@ -78,7 +88,9 @@ def create_hist_plot(self, df: pd.DataFrame, output_col: str = None): def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc5_flag"): # Display report content in IPython - print("Fault Condition 5: Supply air temperature too low; should be higher than mix air") + print( + "Fault Condition 5: Supply air temperature too low; should be higher than mix air" + ) # Display plot self.create_plot(df, output_col) @@ -87,7 +99,7 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc5_fla summary = self.summarize_fault_times(df, output_col) for key, value in summary.items(): - formatted_key = key.replace('_', ' ') + formatted_key = key.replace("_", " ") print(f"{formatted_key}: {value}") sys.stdout.flush() @@ -98,25 +110,21 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc5_fla if fc_max_faults_found != 0: self.create_hist_plot(df, output_col) - flag_true_mat = round( - df[self.mat_col].where(df[output_col] == 1).mean(), 2 - ) + flag_true_mat = round(df[self.mat_col].where(df[output_col] == 1).mean(), 2) print("Mix Air Temp Mean When In Fault: ", flag_true_mat) - flag_true_sat = round( - df[self.sat_col].where(df[output_col] == 1).mean(), 2 - ) + flag_true_sat = round(df[self.sat_col].where(df[output_col] == 1).mean(), 2) print("Supply Air Temp Mean When In Fault: ", flag_true_sat) sys.stdout.flush() - if summary['percent_true'] > 5.0: + if summary["percent_true"] > 5.0: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is high indicating the AHU temperature sensors for either the supply or mix temperature are out of calibration. Verify the mixing temperature sensor is not a probe type sensor but a long averaging type sensor that is installed properly inside the AHU mixing chamber to get a good solid true reading of the actual air mixing temperature. Poor duct design may also contribute to not having good air mixing, to troubleshoot install data loggers inside the mixing chamber or take measurements when the AHU is running of different locations in the mixing chamber to spot where better air blending needs to take place.' + "The percent True metric that represents the amount of time for when the fault flag is True is high indicating the AHU temperature sensors for either the supply or mix temperature are out of calibration. Verify the mixing temperature sensor is not a probe type sensor but a long averaging type sensor that is installed properly inside the AHU mixing chamber to get a good solid true reading of the actual air mixing temperature. Poor duct design may also contribute to not having good air mixing, to troubleshoot install data loggers inside the mixing chamber or take measurements when the AHU is running of different locations in the mixing chamber to spot where better air blending needs to take place." ) else: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is low indicating the AHU temperature sensors are within calibration.' + "The percent True metric that represents the amount of time for when the fault flag is True is low indicating the AHU temperature sensors are within calibration." ) else: diff --git a/open_fdd/air_handling_unit/reports/report_fc6.py b/open_fdd/air_handling_unit/reports/report_fc6.py index 663ef6c..8b510b2 100644 --- a/open_fdd/air_handling_unit/reports/report_fc6.py +++ b/open_fdd/air_handling_unit/reports/report_fc6.py @@ -8,43 +8,53 @@ class FaultCodeSixReport: """Class provides the definitions for Fault Condition 6 Report.""" def __init__(self, config): - self.supply_fan_air_volume_col = config['SUPPLY_FAN_AIR_VOLUME_COL'] - self.mat_col = config['MAT_COL'] - self.oat_col = config['OAT_COL'] - self.rat_col = config['RAT_COL'] - self.supply_vfd_speed_col = config['SUPPLY_VFD_SPEED_COL'] + self.supply_fan_air_volume_col = config["SUPPLY_FAN_AIR_VOLUME_COL"] + self.mat_col = config["MAT_COL"] + self.oat_col = config["OAT_COL"] + self.rat_col = config["RAT_COL"] + self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"] def create_plot(self, df: pd.DataFrame, output_col: str = None): if output_col is None: output_col = "fc6_flag" fig, (ax1, ax2, ax3, ax4, ax5) = plt.subplots(5, 1, figsize=(25, 8)) - fig.suptitle('Fault Conditions 6 Plot') + fig.suptitle("Fault Conditions 6 Plot") - ax1.plot(df.index, df['rat_minus_oat'], label="Rat Minus Oat") - ax1.legend(loc='best') + ax1.plot(df.index, df["rat_minus_oat"], label="Rat Minus Oat") + ax1.legend(loc="best") ax1.set_ylabel("°F") - ax2.plot(df.index, df[self.supply_fan_air_volume_col], label="Total Air Flow", color="r") - ax2.set_xlabel('Date') - ax2.set_ylabel('CFM') - ax2.legend(loc='best') - - ax3.plot(df.index, df['percent_oa_calc'], label="OA Frac Calc", color="m") - ax3.plot(df.index, df['perc_OAmin'], label="OA Perc Min Calc", color="y") - ax3.set_xlabel('Date') - ax3.set_ylabel('%') - ax3.legend(loc='best') - - ax4.plot(df.index, df['percent_oa_calc_minus_perc_OAmin'], label="OA Error Frac Vs Perc Min Calc", color="g") - ax4.set_xlabel('Date') - ax4.set_ylabel('%') - ax4.legend(loc='best') + ax2.plot( + df.index, + df[self.supply_fan_air_volume_col], + label="Total Air Flow", + color="r", + ) + ax2.set_xlabel("Date") + ax2.set_ylabel("CFM") + ax2.legend(loc="best") + + ax3.plot(df.index, df["percent_oa_calc"], label="OA Frac Calc", color="m") + ax3.plot(df.index, df["perc_OAmin"], label="OA Perc Min Calc", color="y") + ax3.set_xlabel("Date") + ax3.set_ylabel("%") + ax3.legend(loc="best") + + ax4.plot( + df.index, + df["percent_oa_calc_minus_perc_OAmin"], + label="OA Error Frac Vs Perc Min Calc", + color="g", + ) + ax4.set_xlabel("Date") + ax4.set_ylabel("%") + ax4.legend(loc="best") ax5.plot(df.index, df[output_col], label="Fault", color="k") - ax5.set_xlabel('Date') - ax5.set_ylabel('Fault Flags') - ax5.legend(loc='best') + ax5.set_xlabel("Date") + ax5.set_ylabel("Fault Flags") + ax5.legend(loc="best") plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.show() @@ -56,15 +66,27 @@ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dic delta = df.index.to_series().diff() summary = { - 'total_days': round(delta.sum() / pd.Timedelta(days=1), 2), - 'total_hours': round(delta.sum() / pd.Timedelta(hours=1)), - 'hours_fc6_mode': round((delta * df[output_col]).sum() / pd.Timedelta(hours=1)), - 'percent_true': round(df[output_col].mean() * 100, 2), - 'percent_false': round((100 - round(df[output_col].mean() * 100, 2)), 2), - 'flag_true_mat': round(df[self.mat_col].where(df[output_col] == 1).mean(), 2), - 'flag_true_rat': round(df[self.rat_col].where(df[output_col] == 1).mean(), 2), - 'flag_true_oat': round(df[self.oat_col].where(df[output_col] == 1).mean(), 2), - 'hours_motor_runtime': round((delta * df[self.supply_vfd_speed_col].gt(.01).astype(int)).sum() / pd.Timedelta(hours=1), 2) + "total_days": round(delta.sum() / pd.Timedelta(days=1), 2), + "total_hours": round(delta.sum() / pd.Timedelta(hours=1)), + "hours_fc6_mode": round( + (delta * df[output_col]).sum() / pd.Timedelta(hours=1) + ), + "percent_true": round(df[output_col].mean() * 100, 2), + "percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2), + "flag_true_mat": round( + df[self.mat_col].where(df[output_col] == 1).mean(), 2 + ), + "flag_true_rat": round( + df[self.rat_col].where(df[output_col] == 1).mean(), 2 + ), + "flag_true_oat": round( + df[self.oat_col].where(df[output_col] == 1).mean(), 2 + ), + "hours_motor_runtime": round( + (delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum() + / pd.Timedelta(hours=1), + 2, + ), } return summary @@ -87,7 +109,9 @@ def create_hist_plot(self, df: pd.DataFrame, output_col: str = None): def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc6_flag"): # Display report content in IPython - print("Fault Condition 6: OA fraction too low or too high; should equal to design % outdoor air requirement") + print( + "Fault Condition 6: OA fraction too low or too high; should equal to design % outdoor air requirement" + ) # Display plot self.create_plot(df, output_col) @@ -96,7 +120,7 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc6_fla summary = self.summarize_fault_times(df, output_col) for key, value in summary.items(): - formatted_key = key.replace('_', ' ') + formatted_key = key.replace("_", " ") print(f"{formatted_key}: {value}") sys.stdout.flush() @@ -107,30 +131,24 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc6_fla if fc_max_faults_found != 0: self.create_hist_plot(df, output_col) - flag_true_mat = round( - df[self.mat_col].where(df[output_col] == 1).mean(), 2 - ) + flag_true_mat = round(df[self.mat_col].where(df[output_col] == 1).mean(), 2) print("Mix Air Temp Mean When In Fault: ", flag_true_mat) - flag_true_rat = round( - df[self.rat_col].where(df[output_col] == 1).mean(), 2 - ) + flag_true_rat = round(df[self.rat_col].where(df[output_col] == 1).mean(), 2) print("Return Air Temp Mean When In Fault: ", flag_true_rat) - flag_true_oat = round( - df[self.oat_col].where(df[output_col] == 1).mean(), 2 - ) + flag_true_oat = round(df[self.oat_col].where(df[output_col] == 1).mean(), 2) print("Outside Air Temp Mean When In Fault: ", flag_true_oat) sys.stdout.flush() - if summary['percent_true'] > 5.0: + if summary["percent_true"] > 5.0: print( - 'The percent True metric maybe yielding sensors are out of calibration either on the AHU outside, mix, or return air temperature sensors that handle the OA fraction calculation or the totalized air flow calculation handled by a totalizing all VAV box air flows or AHU AFMS. Air flow and/or AHU temperature sensor may require recalibration.' + "The percent True metric maybe yielding sensors are out of calibration either on the AHU outside, mix, or return air temperature sensors that handle the OA fraction calculation or the totalized air flow calculation handled by a totalizing all VAV box air flows or AHU AFMS. Air flow and/or AHU temperature sensor may require recalibration." ) else: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is low indicating the sensors are within calibration.' + "The percent True metric that represents the amount of time for when the fault flag is True is low indicating the sensors are within calibration." ) else: diff --git a/open_fdd/air_handling_unit/reports/report_fc7.py b/open_fdd/air_handling_unit/reports/report_fc7.py index fdbf256..d69a82a 100644 --- a/open_fdd/air_handling_unit/reports/report_fc7.py +++ b/open_fdd/air_handling_unit/reports/report_fc7.py @@ -5,35 +5,35 @@ class FaultCodeSevenReport: """Class provides the definitions for Fault Condition 7 Report. - Very similar to FC 13 but uses heating valve + Very similar to FC 13 but uses heating valve """ def __init__(self, config): - self.sat_col = config['SAT_COL'] - self.sat_setpoint_col = config['SAT_SETPOINT_COL'] - self.heating_sig_col = config['HEATING_SIG_COL'] - self.supply_vfd_speed_col = config['SUPPLY_VFD_SPEED_COL'] + self.sat_col = config["SAT_COL"] + self.sat_setpoint_col = config["SAT_SETPOINT_COL"] + self.heating_sig_col = config["HEATING_SIG_COL"] + self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"] def create_plot(self, df: pd.DataFrame, output_col: str = None): if output_col is None: output_col = "fc7_flag" fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(25, 8)) - fig.suptitle('Fault Conditions 7 Plot') + fig.suptitle("Fault Conditions 7 Plot") ax1.plot(df.index, df[self.sat_col], label="SAT") ax1.plot(df.index, df[self.sat_setpoint_col], label="SATsp") - ax1.legend(loc='best') - ax1.set_ylabel('AHU Supply Temps °F') + ax1.legend(loc="best") + ax1.set_ylabel("AHU Supply Temps °F") ax2.plot(df.index, df[self.heating_sig_col], color="r", label="AHU Heat Vlv") - ax2.legend(loc='best') - ax2.set_ylabel('%') + ax2.legend(loc="best") + ax2.set_ylabel("%") ax3.plot(df.index, df[output_col], label="Fault", color="k") - ax3.set_xlabel('Date') - ax3.set_ylabel('Fault Flags') - ax3.legend(loc='best') + ax3.set_xlabel("Date") + ax3.set_ylabel("Fault Flags") + ax3.legend(loc="best") plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.show() @@ -45,14 +45,24 @@ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dic delta = df.index.to_series().diff() summary = { - 'total_days': round(delta.sum() / pd.Timedelta(days=1), 2), - 'total_hours': round(delta.sum() / pd.Timedelta(hours=1)), - 'hours_fc7_mode': round((delta * df[output_col]).sum() / pd.Timedelta(hours=1)), - 'percent_true': round(df[output_col].mean() * 100, 2), - 'percent_false': round((100 - round(df[output_col].mean() * 100, 2)), 2), - 'flag_true_satsp': round(df[self.sat_setpoint_col].where(df[output_col] == 1).mean(), 2), - 'flag_true_sat': round(df[self.sat_col].where(df[output_col] == 1).mean(), 2), - 'hours_motor_runtime': round((delta * df[self.supply_vfd_speed_col].gt(.01).astype(int)).sum() / pd.Timedelta(hours=1), 2) + "total_days": round(delta.sum() / pd.Timedelta(days=1), 2), + "total_hours": round(delta.sum() / pd.Timedelta(hours=1)), + "hours_fc7_mode": round( + (delta * df[output_col]).sum() / pd.Timedelta(hours=1) + ), + "percent_true": round(df[output_col].mean() * 100, 2), + "percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2), + "flag_true_satsp": round( + df[self.sat_setpoint_col].where(df[output_col] == 1).mean(), 2 + ), + "flag_true_sat": round( + df[self.sat_col].where(df[output_col] == 1).mean(), 2 + ), + "hours_motor_runtime": round( + (delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum() + / pd.Timedelta(hours=1), + 2, + ), } return summary @@ -79,7 +89,7 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc7_fla summary = self.summarize_fault_times(df, output_col) for key, value in summary.items(): - formatted_key = key.replace('_', ' ') + formatted_key = key.replace("_", " ") print(f"{formatted_key}: {value}") sys.stdout.flush() @@ -90,7 +100,9 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc7_fla if fc_max_faults_found != 0: self.create_hist_plot(df, output_col) - flag_true_satsp = round(df[self.sat_setpoint_col].where(df[output_col] == 1).mean(), 2) + flag_true_satsp = round( + df[self.sat_setpoint_col].where(df[output_col] == 1).mean(), 2 + ) print("Supply Air Temp Setpoint Mean When In Fault: ", flag_true_satsp) flag_true_sat = round(df[self.sat_col].where(df[output_col] == 1).mean(), 2) @@ -98,13 +110,13 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc7_fla sys.stdout.flush() - if summary['percent_true'] > 5.0: + if summary["percent_true"] > 5.0: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is high indicating the AHU heating valve may be broken or there could be a flow issue with the amount of hot water flowing through the coil or that the boiler system reset is too aggressive and there isn’t enough heat being produced by this coil. It could be worth viewing mechanical blueprints for this AHU design schedule to see what hot water temperature this coil was designed for and compare it to actual hot water supply temperatures. Consult a mechanical design engineer to rectify if needed.' + "The percent True metric that represents the amount of time for when the fault flag is True is high indicating the AHU heating valve may be broken or there could be a flow issue with the amount of hot water flowing through the coil or that the boiler system reset is too aggressive and there isn’t enough heat being produced by this coil. It could be worth viewing mechanical blueprints for this AHU design schedule to see what hot water temperature this coil was designed for and compare it to actual hot water supply temperatures. Consult a mechanical design engineer to rectify if needed." ) else: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the AHU heating valve operates correctly.' + "The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the AHU heating valve operates correctly." ) else: diff --git a/open_fdd/air_handling_unit/reports/report_fc8.py b/open_fdd/air_handling_unit/reports/report_fc8.py index 7854e14..0c51722 100644 --- a/open_fdd/air_handling_unit/reports/report_fc8.py +++ b/open_fdd/air_handling_unit/reports/report_fc8.py @@ -7,27 +7,27 @@ class FaultCodeEightReport: """Class provides the definitions for Fault Condition 8 Report.""" def __init__(self, config): - self.sat_col = config['SAT_COL'] - self.mat_col = config['MAT_COL'] - self.supply_vfd_speed_col = config['SUPPLY_VFD_SPEED_COL'] - self.economizer_sig_col = config['ECONOMIZER_SIG_COL'] + self.sat_col = config["SAT_COL"] + self.mat_col = config["MAT_COL"] + self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"] + self.economizer_sig_col = config["ECONOMIZER_SIG_COL"] def create_plot(self, df: pd.DataFrame, output_col: str = None): if output_col is None: output_col = "fc8_flag" fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(25, 8)) - fig.suptitle('Fault Conditions 8 Plot') + fig.suptitle("Fault Conditions 8 Plot") ax1.plot(df.index, df[self.sat_col], label="SAT") ax1.plot(df.index, df[self.mat_col], label="MAT") - ax1.legend(loc='best') - ax1.set_ylabel('AHU Temps °F') + ax1.legend(loc="best") + ax1.set_ylabel("AHU Temps °F") ax2.plot(df.index, df[output_col], label="Fault", color="k") - ax2.set_xlabel('Date') - ax2.set_ylabel('Fault Flags') - ax2.legend(loc='best') + ax2.set_xlabel("Date") + ax2.set_ylabel("Fault Flags") + ax2.legend(loc="best") plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.show() @@ -39,14 +39,24 @@ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dic delta = df.index.to_series().diff() summary = { - 'total_days': round(delta.sum() / pd.Timedelta(days=1), 2), - 'total_hours': round(delta.sum() / pd.Timedelta(hours=1)), - 'hours_fc8_mode': round((delta * df[output_col]).sum() / pd.Timedelta(hours=1)), - 'percent_true': round(df[output_col].mean() * 100, 2), - 'percent_false': round((100 - round(df[output_col].mean() * 100, 2)), 2), - 'flag_true_mat': round(df[self.mat_col].where(df[output_col] == 1).mean(), 2), - 'flag_true_sat': round(df[self.sat_col].where(df[output_col] == 1).mean(), 2), - 'hours_motor_runtime': round((delta * df[self.supply_vfd_speed_col].gt(.01).astype(int)).sum() / pd.Timedelta(hours=1), 2) + "total_days": round(delta.sum() / pd.Timedelta(days=1), 2), + "total_hours": round(delta.sum() / pd.Timedelta(hours=1)), + "hours_fc8_mode": round( + (delta * df[output_col]).sum() / pd.Timedelta(hours=1) + ), + "percent_true": round(df[output_col].mean() * 100, 2), + "percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2), + "flag_true_mat": round( + df[self.mat_col].where(df[output_col] == 1).mean(), 2 + ), + "flag_true_sat": round( + df[self.sat_col].where(df[output_col] == 1).mean(), 2 + ), + "hours_motor_runtime": round( + (delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum() + / pd.Timedelta(hours=1), + 2, + ), } return summary @@ -66,14 +76,16 @@ def create_hist_plot(self, df: pd.DataFrame, output_col: str = None): plt.close() def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc8_flag"): - print("Fault Condition 8: Supply air temperature and mix air temperature should be approximately equal in economizer mode") + print( + "Fault Condition 8: Supply air temperature and mix air temperature should be approximately equal in economizer mode" + ) self.create_plot(df, output_col) summary = self.summarize_fault_times(df, output_col) for key, value in summary.items(): - formatted_key = key.replace('_', ' ') + formatted_key = key.replace("_", " ") print(f"{formatted_key}: {value}") sys.stdout.flush() @@ -92,13 +104,13 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc8_fla sys.stdout.flush() - if summary['percent_true'] > 5.0: + if summary["percent_true"] > 5.0: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is high indicating temperature sensor error or the heating/cooling coils are leaking, potentially creating simultaneous heating/cooling which can be an energy penalty for running the AHU in this fashion. Verify AHU mix/supply temperature sensor calibration in addition to a potential mechanical issue of a leaking valve. A leaking valve can be troubleshot by isolating the valve closed by manual shut-off valves where piping lines enter the AHU coil and then verifying any changes in the AHU discharge air temperature.' + "The percent True metric that represents the amount of time for when the fault flag is True is high indicating temperature sensor error or the heating/cooling coils are leaking, potentially creating simultaneous heating/cooling which can be an energy penalty for running the AHU in this fashion. Verify AHU mix/supply temperature sensor calibration in addition to a potential mechanical issue of a leaking valve. A leaking valve can be troubleshot by isolating the valve closed by manual shut-off valves where piping lines enter the AHU coil and then verifying any changes in the AHU discharge air temperature." ) else: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the AHU components are within calibration for this fault equation.' + "The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the AHU components are within calibration for this fault equation." ) else: diff --git a/open_fdd/air_handling_unit/reports/report_fc9.py b/open_fdd/air_handling_unit/reports/report_fc9.py index 55d1b15..7b8d9d3 100644 --- a/open_fdd/air_handling_unit/reports/report_fc9.py +++ b/open_fdd/air_handling_unit/reports/report_fc9.py @@ -7,27 +7,27 @@ class FaultCodeNineReport: """Class provides the definitions for Fault Condition 9 Report.""" def __init__(self, config): - self.sat_setpoint_col = config['SAT_SETPOINT_COL'] - self.oat_col = config['OAT_COL'] - self.supply_vfd_speed_col = config['SUPPLY_VFD_SPEED_COL'] - self.economizer_sig_col = config['ECONOMIZER_SIG_COL'] + self.sat_setpoint_col = config["SAT_SETPOINT_COL"] + self.oat_col = config["OAT_COL"] + self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"] + self.economizer_sig_col = config["ECONOMIZER_SIG_COL"] def create_plot(self, df: pd.DataFrame, output_col: str = None): if output_col is None: output_col = "fc9_flag" fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(25, 8)) - fig.suptitle('Fault Conditions 9 Plot') + fig.suptitle("Fault Conditions 9 Plot") ax1.plot(df.index, df[self.sat_setpoint_col], label="SATSP") ax1.plot(df.index, df[self.oat_col], label="OAT") - ax1.legend(loc='best') - ax1.set_ylabel('AHU Temps °F') + ax1.legend(loc="best") + ax1.set_ylabel("AHU Temps °F") ax2.plot(df.index, df[output_col], label="Fault", color="k") - ax2.set_xlabel('Date') - ax2.set_ylabel('Fault Flags') - ax2.legend(loc='best') + ax2.set_xlabel("Date") + ax2.set_ylabel("Fault Flags") + ax2.legend(loc="best") plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.show() @@ -39,14 +39,24 @@ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dic delta = df.index.to_series().diff() summary = { - 'total_days': round(delta.sum() / pd.Timedelta(days=1), 2), - 'total_hours': round(delta.sum() / pd.Timedelta(hours=1)), - 'hours_fc9_mode': round((delta * df[output_col]).sum() / pd.Timedelta(hours=1)), - 'percent_true': round(df[output_col].mean() * 100, 2), - 'percent_false': round((100 - round(df[output_col].mean() * 100, 2)), 2), - 'flag_true_oat': round(df[self.oat_col].where(df[output_col] == 1).mean(), 2), - 'flag_true_satsp': round(df[self.sat_setpoint_col].where(df[output_col] == 1).mean(), 2), - 'hours_motor_runtime': round((delta * df[self.supply_vfd_speed_col].gt(.01).astype(int)).sum() / pd.Timedelta(hours=1), 2) + "total_days": round(delta.sum() / pd.Timedelta(days=1), 2), + "total_hours": round(delta.sum() / pd.Timedelta(hours=1)), + "hours_fc9_mode": round( + (delta * df[output_col]).sum() / pd.Timedelta(hours=1) + ), + "percent_true": round(df[output_col].mean() * 100, 2), + "percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2), + "flag_true_oat": round( + df[self.oat_col].where(df[output_col] == 1).mean(), 2 + ), + "flag_true_satsp": round( + df[self.sat_setpoint_col].where(df[output_col] == 1).mean(), 2 + ), + "hours_motor_runtime": round( + (delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum() + / pd.Timedelta(hours=1), + 2, + ), } return summary @@ -66,14 +76,16 @@ def create_hist_plot(self, df: pd.DataFrame, output_col: str = None): plt.close() def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc9_flag"): - print("Fault Condition 9: Outside air temperature too high in free cooling without additional mechanical cooling in economizer mode") + print( + "Fault Condition 9: Outside air temperature too high in free cooling without additional mechanical cooling in economizer mode" + ) self.create_plot(df, output_col) summary = self.summarize_fault_times(df, output_col) for key, value in summary.items(): - formatted_key = key.replace('_', ' ') + formatted_key = key.replace("_", " ") print(f"{formatted_key}: {value}") sys.stdout.flush() @@ -87,18 +99,20 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc9_fla flag_true_oat = round(df[self.oat_col].where(df[output_col] == 1).mean(), 2) print("Outside Air Temp Mean When In Fault: ", flag_true_oat) - flag_true_satsp = round(df[self.sat_setpoint_col].where(df[output_col] == 1).mean(), 2) + flag_true_satsp = round( + df[self.sat_setpoint_col].where(df[output_col] == 1).mean(), 2 + ) print("Supply Air Temp Setpoint Mean When In Fault: ", flag_true_satsp) sys.stdout.flush() - if summary['percent_true'] > 5.0: + if summary["percent_true"] > 5.0: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is high indicating temperature sensor error or the cooling valve is stuck open or leaking causing overcooling. Trouble shoot a leaking valve by isolating the coil with manual shutoff valves and verify a change in AHU discharge air temperature with the AHU running.' + "The percent True metric that represents the amount of time for when the fault flag is True is high indicating temperature sensor error or the cooling valve is stuck open or leaking causing overcooling. Trouble shoot a leaking valve by isolating the coil with manual shutoff valves and verify a change in AHU discharge air temperature with the AHU running." ) else: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is low indicating the AHU components are within calibration for this fault equation.' + "The percent True metric that represents the amount of time for when the fault flag is True is low indicating the AHU components are within calibration for this fault equation." ) else: diff --git a/open_fdd/black_box/unsupervised_learning/hyper_params.py b/open_fdd/black_box/unsupervised_learning/hyper_params.py index d5a12c6..9ad6b7a 100644 --- a/open_fdd/black_box/unsupervised_learning/hyper_params.py +++ b/open_fdd/black_box/unsupervised_learning/hyper_params.py @@ -6,6 +6,7 @@ from sklearn.metrics import silhouette_score import numpy as np + # Custom scoring function for GridSearchCV using silhouette score def anomaly_silhouette_score(estimator, X): anomalies = estimator.fit_predict(X) @@ -15,37 +16,68 @@ def anomaly_silhouette_score(estimator, X): score = -1 return score + # Load data data = pd.read_csv(r"C:\Users\bbartling\Documents\WPCRC_Master.csv") -data['timestamp'] = pd.to_datetime(data['timestamp']) -data['chiller_delta_temp'] = data['CWR_Temp'] - data['CWS_Temp'] -data = data.drop(columns=['CWR_Temp', 'CWS_Temp', 'VAV2_6_SpaceTemp', 'VAV2_7_SpaceTemp', 'VAV3_2_SpaceTemp', 'VAV3_5_SpaceTemp']) +data["timestamp"] = pd.to_datetime(data["timestamp"]) +data["chiller_delta_temp"] = data["CWR_Temp"] - data["CWS_Temp"] +data = data.drop( + columns=[ + "CWR_Temp", + "CWS_Temp", + "VAV2_6_SpaceTemp", + "VAV2_7_SpaceTemp", + "VAV3_2_SpaceTemp", + "VAV3_5_SpaceTemp", + ] +) # Filter data where 'Sa_FanSpeed' is less than 10.0 -data = data[data['Sa_FanSpeed'] < 10.0] +data = data[data["Sa_FanSpeed"] < 10.0] # Define groups -group_building_power = data[['timestamp', 'CurrentKW', 'OaTemp']] -group_heat_vlv_cntrl = data[['timestamp', 'HW_Valve', 'DischargeTemp', 'Ma_Temp', 'Sa_FanSpeed']] -group_cool_vlv_cntrl = data[['timestamp', 'CW_Valve', 'DischargeTemp', 'Ma_Temp', 'Sa_FanSpeed']] -group_econ_cntrl = data[['timestamp', 'MaDampers', 'RA_Temp', 'Ma_Temp', 'OaTemp', 'Sa_FanSpeed']] -fan_static_press_cntrl = data[['timestamp', 'SaStatic', 'SaStaticSPt', 'Sa_FanSpeed']] +group_building_power = data[["timestamp", "CurrentKW", "OaTemp"]] +group_heat_vlv_cntrl = data[ + ["timestamp", "HW_Valve", "DischargeTemp", "Ma_Temp", "Sa_FanSpeed"] +] +group_cool_vlv_cntrl = data[ + ["timestamp", "CW_Valve", "DischargeTemp", "Ma_Temp", "Sa_FanSpeed"] +] +group_econ_cntrl = data[ + ["timestamp", "MaDampers", "RA_Temp", "Ma_Temp", "OaTemp", "Sa_FanSpeed"] +] +fan_static_press_cntrl = data[["timestamp", "SaStatic", "SaStaticSPt", "Sa_FanSpeed"]] groups = { - 'Building Power': group_building_power, - 'Heat Valve Control': group_heat_vlv_cntrl, - 'Cool Valve Control': group_cool_vlv_cntrl, - 'Economizer Control': group_econ_cntrl, - 'Fan Static Pressure Control': fan_static_press_cntrl, + "Building Power": group_building_power, + "Heat Valve Control": group_heat_vlv_cntrl, + "Cool Valve Control": group_cool_vlv_cntrl, + "Economizer Control": group_econ_cntrl, + "Fan Static Pressure Control": fan_static_press_cntrl, } + # Rule-based fault detection def detect_faults(data): - data['fault_heat_valve'] = ((data['HW_Valve'] > 0) & (data['DischargeTemp'] <= data['Ma_Temp']) & (data['Sa_FanSpeed'] > 0)).astype(int) - data['fault_cool_valve'] = ((data['CW_Valve'] > 0) & (data['DischargeTemp'] >= data['Ma_Temp']) & (data['Sa_FanSpeed'] > 0)).astype(int) - data['fault_economizer'] = ((data['MaDampers'] > 0) & (data['RA_Temp'] <= data['Ma_Temp']) & (data['OaTemp'] < data['Ma_Temp']) & (data['Sa_FanSpeed'] > 0)).astype(int) + data["fault_heat_valve"] = ( + (data["HW_Valve"] > 0) + & (data["DischargeTemp"] <= data["Ma_Temp"]) + & (data["Sa_FanSpeed"] > 0) + ).astype(int) + data["fault_cool_valve"] = ( + (data["CW_Valve"] > 0) + & (data["DischargeTemp"] >= data["Ma_Temp"]) + & (data["Sa_FanSpeed"] > 0) + ).astype(int) + data["fault_economizer"] = ( + (data["MaDampers"] > 0) + & (data["RA_Temp"] <= data["Ma_Temp"]) + & (data["OaTemp"] < data["Ma_Temp"]) + & (data["Sa_FanSpeed"] > 0) + ).astype(int) return data + data = detect_faults(data) for group_name, group in groups.items(): @@ -54,7 +86,7 @@ def detect_faults(data): # Data preprocessing group_filtered = group.fillna(group.median()) scaler = StandardScaler() - data_normalized = scaler.fit_transform(group_filtered.drop(columns=['timestamp'])) + data_normalized = scaler.fit_transform(group_filtered.drop(columns=["timestamp"])) # Perform PCA for dimensionality reduction pca = PCA(n_components=0.95) @@ -62,19 +94,26 @@ def detect_faults(data): # Define the parameter grid for hyperparameter tuning param_grid = { - 'n_estimators': [50, 100, 150], - 'max_samples': ['auto', 0.6, 0.8, 1.0], - 'contamination': [0.01, 0.05, 0.1], - 'max_features': [0.5, 0.75, 1.0], - 'bootstrap': [False, True], - 'random_state': [42] + "n_estimators": [50, 100, 150], + "max_samples": ["auto", 0.6, 0.8, 1.0], + "contamination": [0.01, 0.05, 0.1], + "max_features": [0.5, 0.75, 1.0], + "bootstrap": [False, True], + "random_state": [42], } # Initialize the Isolation Forest model model = IsolationForest() # Set up GridSearchCV with custom scoring function - grid_search = GridSearchCV(estimator=model, param_grid=param_grid, scoring=anomaly_silhouette_score, cv=5, n_jobs=-1, verbose=2) + grid_search = GridSearchCV( + estimator=model, + param_grid=param_grid, + scoring=anomaly_silhouette_score, + cv=5, + n_jobs=-1, + verbose=2, + ) # Fit GridSearchCV grid_search.fit(data_pca) @@ -84,21 +123,26 @@ def detect_faults(data): best_model = grid_search.best_estimator_ # Predict anomalies using the best model - group['anomaly'] = best_model.predict(data_pca) - group['anomaly'] = group['anomaly'].map({1: 0, -1: 1}) + group["anomaly"] = best_model.predict(data_pca) + group["anomaly"] = group["anomaly"].map({1: 0, -1: 1}) # Combine rule-based faults with anomalies - group['combined_faults'] = group['anomaly'] | data.loc[group.index, 'fault_heat_valve'] | data.loc[group.index, 'fault_cool_valve'] | data.loc[group.index, 'fault_economizer'] + group["combined_faults"] = ( + group["anomaly"] + | data.loc[group.index, "fault_heat_valve"] + | data.loc[group.index, "fault_cool_valve"] + | data.loc[group.index, "fault_economizer"] + ) # Display the results - print(group[['timestamp', 'anomaly', 'combined_faults']]) + print(group[["timestamp", "anomaly", "combined_faults"]]) # Optional: Display the top anomalies for manual inspection - top_anomalies = group[group['anomaly'] == 1] + top_anomalies = group[group["anomaly"] == 1] print(f"Top anomalies in {group_name}:") print(top_anomalies.head()) # Optional: Display the detected faults - top_faults = group[group['combined_faults'] == 1] + top_faults = group[group["combined_faults"] == 1] print(f"Detected faults in {group_name}:") print(top_faults.head()) diff --git a/open_fdd/black_box/unsupervised_learning/main.py b/open_fdd/black_box/unsupervised_learning/main.py index fa6798c..9ff44ec 100644 --- a/open_fdd/black_box/unsupervised_learning/main.py +++ b/open_fdd/black_box/unsupervised_learning/main.py @@ -7,23 +7,33 @@ # Switch backend to non-interactive mode import matplotlib -matplotlib.use('Agg') # Use a non-interactive backend like Agg + +matplotlib.use("Agg") # Use a non-interactive backend like Agg # Load data data = pd.read_csv(r"C:\Users\bbartling\Documents\WPCRC_Master.csv") # Convert timestamp to datetime -data['timestamp'] = pd.to_datetime(data['timestamp']) +data["timestamp"] = pd.to_datetime(data["timestamp"]) # Calculate the delta temperature for the chiller before dropping the columns -data['chiller_delta_temp'] = data['CWR_Temp'] - data['CWS_Temp'] +data["chiller_delta_temp"] = data["CWR_Temp"] - data["CWS_Temp"] # Drop the CWR_Temp and CWS_Temp columns -data = data.drop(columns=['CWR_Temp', 'CWS_Temp', 'VAV2_6_SpaceTemp', 'VAV2_7_SpaceTemp', 'VAV3_2_SpaceTemp', 'VAV3_5_SpaceTemp']) +data = data.drop( + columns=[ + "CWR_Temp", + "CWS_Temp", + "VAV2_6_SpaceTemp", + "VAV2_7_SpaceTemp", + "VAV3_2_SpaceTemp", + "VAV3_5_SpaceTemp", + ] +) # Select all relevant features for analysis (you can add more features as needed) -features = data.columns.drop(['timestamp']) +features = data.columns.drop(["timestamp"]) # Filter and preprocess data data_filtered = data[features].fillna(data[features].median()) @@ -35,44 +45,59 @@ model.fit(data_normalized) # Predict anomalies -data['anomaly'] = model.predict(data_normalized) -data['anomaly'] = data['anomaly'].map({1: 0, -1: 1}) # Convert -1 (outlier) to 1 for easier interpretation +data["anomaly"] = model.predict(data_normalized) +data["anomaly"] = data["anomaly"].map( + {1: 0, -1: 1} +) # Convert -1 (outlier) to 1 for easier interpretation # Filter data for when the chiller is running (Sa_FanSpeed > 20.0) -#running_data = data[data['Sa_FanSpeed'] > 20.0] +# running_data = data[data['Sa_FanSpeed'] > 20.0] running_data = data # Separate data by anomaly status -anomaly_data = running_data[running_data['anomaly'] == 1] -normal_data = running_data[running_data['anomaly'] == 0] +anomaly_data = running_data[running_data["anomaly"] == 1] +normal_data = running_data[running_data["anomaly"] == 0] # Columns to analyze -columns_to_analyze = ['CurrentKW', 'CoolValve', 'Ma_Temp', 'HW_Valve', 'MaDampers', 'OaTemp', 'SaStatic', 'Sa_FanSpeed', 'DischargeTemp', 'SpaceTemp', 'RaHumidity', 'RA_Temp'] +columns_to_analyze = [ + "CurrentKW", + "CoolValve", + "Ma_Temp", + "HW_Valve", + "MaDampers", + "OaTemp", + "SaStatic", + "Sa_FanSpeed", + "DischargeTemp", + "SpaceTemp", + "RaHumidity", + "RA_Temp", +] # Grouped columns for subplots -group1 = ['CurrentKW'] -group2 = ['HW_Valve', 'MaDampers', 'CoolValve', 'Sa_FanSpeed'] -group3 = ['DischargeTemp', 'SpaceTemp', 'RaHumidity', 'RA_Temp', 'Ma_Temp', 'OaTemp'] -group4 = ['SaStatic'] +group1 = ["CurrentKW"] +group2 = ["HW_Valve", "MaDampers", "CoolValve", "Sa_FanSpeed"] +group3 = ["DischargeTemp", "SpaceTemp", "RaHumidity", "RA_Temp", "Ma_Temp", "OaTemp"] +group4 = ["SaStatic"] # Calculate statistics for anomaly data anomaly_stats = anomaly_data[columns_to_analyze].describe().T -anomaly_stats['anomaly_status'] = 'Anomaly' +anomaly_stats["anomaly_status"] = "Anomaly" # Calculate statistics for normal data normal_stats = normal_data[columns_to_analyze].describe().T -normal_stats['anomaly_status'] = 'Normal' +normal_stats["anomaly_status"] = "Normal" # Combine statistics combined_stats = pd.concat([anomaly_stats, normal_stats], axis=0) # Save statistics to CSV -combined_stats.to_csv('anomaly_vs_normal_statistics.csv') +combined_stats.to_csv("anomaly_vs_normal_statistics.csv") # Save timestamps of anomalies to CSV -anomaly_timestamps = anomaly_data[['timestamp', 'chiller_delta_temp']] -anomaly_timestamps.to_csv('anomaly_timestamps.csv', index=False) +anomaly_timestamps = anomaly_data[["timestamp", "chiller_delta_temp"]] +anomaly_timestamps.to_csv("anomaly_timestamps.csv", index=False) # Display statistics print(combined_stats) @@ -80,103 +105,137 @@ # Plot statistics fig, axes = plt.subplots(len(columns_to_analyze), 1, figsize=(10, 20)) for i, column in enumerate(columns_to_analyze): - axes[i].boxplot([normal_data[column], anomaly_data[column]], tick_labels=['Normal', 'Anomaly']) - axes[i].set_title(f'{column} by Anomaly Status') + axes[i].boxplot( + [normal_data[column], anomaly_data[column]], tick_labels=["Normal", "Anomaly"] + ) + axes[i].set_title(f"{column} by Anomaly Status") axes[i].set_ylabel(column) plt.tight_layout() -plt.savefig('anomaly_vs_normal_boxplots.png') +plt.savefig("anomaly_vs_normal_boxplots.png") # Plotting chiller_delta_temp over time with anomalies fig, ax = plt.subplots(figsize=(12, 6)) -ax.plot(data['timestamp'], data['chiller_delta_temp'], label='Chiller Delta Temp') -ax.plot(anomaly_data['timestamp'], anomaly_data['chiller_delta_temp'], 'ro', markersize=5) -ax.set_title('Chiller Delta Temperature (CWR_Temp - CWS_Temp) Over Time') -ax.set_xlabel('Time') -ax.set_ylabel('Chiller Delta Temperature') +ax.plot(data["timestamp"], data["chiller_delta_temp"], label="Chiller Delta Temp") +ax.plot( + anomaly_data["timestamp"], anomaly_data["chiller_delta_temp"], "ro", markersize=5 +) +ax.set_title("Chiller Delta Temperature (CWR_Temp - CWS_Temp) Over Time") +ax.set_xlabel("Time") +ax.set_ylabel("Chiller Delta Temperature") ax.legend() ax.xaxis.set_major_locator(mdates.MonthLocator()) -ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m')) -plt.savefig('chiller_delta_temp_anomalies_timeseries.png') +ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m")) +plt.savefig("chiller_delta_temp_anomalies_timeseries.png") # Extract unique anomaly dates -anomaly_dates = anomaly_data['timestamp'].dt.date.unique() +anomaly_dates = anomaly_data["timestamp"].dt.date.unique() # Create a directory for saving the plots -output_dir = 'anomaly_plots' +output_dir = "anomaly_plots" os.makedirs(output_dir, exist_ok=True) # Create combined line plots for each anomaly date for anomaly_date in anomaly_dates: day_before = pd.Timestamp(anomaly_date) - pd.Timedelta(days=1) day_after = pd.Timestamp(anomaly_date) + pd.Timedelta(days=1) - - combined_data = data[(data['timestamp'].dt.date >= day_before.date()) & (data['timestamp'].dt.date <= day_after.date())] - + + combined_data = data[ + (data["timestamp"].dt.date >= day_before.date()) + & (data["timestamp"].dt.date <= day_after.date()) + ] + fig, axes = plt.subplots(5, 1, figsize=(12, 24)) - + # Plot group1 for column in group1: - axes[0].plot(combined_data['timestamp'], combined_data[column], label=f'{column}') + axes[0].plot( + combined_data["timestamp"], combined_data[column], label=f"{column}" + ) # Highlight anomaly points - anomaly_points = combined_data[combined_data['anomaly'] == 1] + anomaly_points = combined_data[combined_data["anomaly"] == 1] if not anomaly_points.empty: - axes[0].plot(anomaly_points['timestamp'], anomaly_points[column], 'ro', markersize=5) - axes[0].set_title(f'Building Power Metrics Around Anomaly Date {anomaly_date}') - axes[0].set_ylabel('Value') - axes[0].legend(loc='upper right') + axes[0].plot( + anomaly_points["timestamp"], anomaly_points[column], "ro", markersize=5 + ) + axes[0].set_title(f"Building Power Metrics Around Anomaly Date {anomaly_date}") + axes[0].set_ylabel("Value") + axes[0].legend(loc="upper right") axes[0].xaxis.set_major_locator(mdates.HourLocator(interval=4)) - axes[0].xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d %H:%M')) + axes[0].xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d %H:%M")) axes[0].tick_params(labelbottom=False) # Hide x-axis labels for the subplot # Plot group2 for column in group2: - axes[1].plot(combined_data['timestamp'], combined_data[column], label=f'{column}') + axes[1].plot( + combined_data["timestamp"], combined_data[column], label=f"{column}" + ) if not anomaly_points.empty: - axes[1].plot(anomaly_points['timestamp'], anomaly_points[column], 'ro', markersize=5) - axes[1].set_title(f'AHU Output Metrics Around Anomaly Date {anomaly_date}') - axes[1].set_ylabel('Value') - axes[1].legend(loc='upper right') + axes[1].plot( + anomaly_points["timestamp"], anomaly_points[column], "ro", markersize=5 + ) + axes[1].set_title(f"AHU Output Metrics Around Anomaly Date {anomaly_date}") + axes[1].set_ylabel("Value") + axes[1].legend(loc="upper right") axes[1].xaxis.set_major_locator(mdates.HourLocator(interval=4)) - axes[1].xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d %H:%M')) + axes[1].xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d %H:%M")) axes[1].tick_params(labelbottom=False) # Plot group3 for column in group3: - axes[2].plot(combined_data['timestamp'], combined_data[column], label=f'{column}') + axes[2].plot( + combined_data["timestamp"], combined_data[column], label=f"{column}" + ) if not anomaly_points.empty: - axes[2].plot(anomaly_points['timestamp'], anomaly_points[column], 'ro', markersize=5) - axes[2].set_title(f'AHU Temp Sensor Metrics Around Anomaly Date {anomaly_date}') - axes[2].set_ylabel('Value') - axes[2].legend(loc='upper right') + axes[2].plot( + anomaly_points["timestamp"], anomaly_points[column], "ro", markersize=5 + ) + axes[2].set_title(f"AHU Temp Sensor Metrics Around Anomaly Date {anomaly_date}") + axes[2].set_ylabel("Value") + axes[2].legend(loc="upper right") axes[2].xaxis.set_major_locator(mdates.HourLocator(interval=4)) - axes[2].xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d %H:%M')) + axes[2].xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d %H:%M")) axes[2].tick_params(labelbottom=False) # Plot group4 for column in group4: - axes[3].plot(combined_data['timestamp'], combined_data[column], label=f'{column}') + axes[3].plot( + combined_data["timestamp"], combined_data[column], label=f"{column}" + ) if not anomaly_points.empty: - axes[3].plot(anomaly_points['timestamp'], anomaly_points[column], 'ro', markersize=5) - axes[3].set_title(f'AHU Duct Static Pressure Around Anomaly Date {anomaly_date}') - axes[3].set_ylabel('Value') - axes[3].legend(loc='upper right') + axes[3].plot( + anomaly_points["timestamp"], anomaly_points[column], "ro", markersize=5 + ) + axes[3].set_title(f"AHU Duct Static Pressure Around Anomaly Date {anomaly_date}") + axes[3].set_ylabel("Value") + axes[3].legend(loc="upper right") axes[3].xaxis.set_major_locator(mdates.HourLocator(interval=4)) - axes[3].xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d %H:%M')) + axes[3].xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d %H:%M")) axes[3].tick_params(labelbottom=False) # Plot chiller delta temp - axes[4].plot(combined_data['timestamp'], combined_data['chiller_delta_temp'], label='Chiller Delta Temp') + axes[4].plot( + combined_data["timestamp"], + combined_data["chiller_delta_temp"], + label="Chiller Delta Temp", + ) if not anomaly_points.empty: - axes[4].plot(anomaly_points['timestamp'], anomaly_points['chiller_delta_temp'], 'ro', markersize=5) - axes[4].set_title(f'Chiller Delta Temp Around Anomaly Date {anomaly_date}') - axes[4].set_xlabel('Time') - axes[4].set_ylabel('°F') - axes[4].legend(loc='best') + axes[4].plot( + anomaly_points["timestamp"], + anomaly_points["chiller_delta_temp"], + "ro", + markersize=5, + ) + axes[4].set_title(f"Chiller Delta Temp Around Anomaly Date {anomaly_date}") + axes[4].set_xlabel("Time") + axes[4].set_ylabel("°F") + axes[4].legend(loc="best") axes[4].xaxis.set_major_locator(mdates.HourLocator(interval=4)) - axes[4].xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d %H:%M')) + axes[4].xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d %H:%M")) plt.xticks(rotation=45) plt.tight_layout() - plt.savefig(os.path.join(output_dir, f'all_metrics_around_anomaly_{anomaly_date}.png')) + plt.savefig( + os.path.join(output_dir, f"all_metrics_around_anomaly_{anomaly_date}.png") + ) plt.close() diff --git a/open_fdd/central_plant/reports/report_chiller_low_delta_t.py b/open_fdd/central_plant/reports/report_chiller_low_delta_t.py index c5051c0..fe0c9ea 100644 --- a/open_fdd/central_plant/reports/report_chiller_low_delta_t.py +++ b/open_fdd/central_plant/reports/report_chiller_low_delta_t.py @@ -2,34 +2,39 @@ import pandas as pd import sys + class FaultCodeLowDeltaTReport: """Class provides the definitions for Low Delta T Fault Condition Report in Chiller Plant.""" def __init__(self, config): - self.chw_supply_temp_col = config['CHW_SUPPLY_TEMP_COL'] - self.chw_return_temp_col = config['CHW_RETURN_TEMP_COL'] - self.chw_flow_rate_col = config['CHW_FLOW_RATE_COL'] + self.chw_supply_temp_col = config["CHW_SUPPLY_TEMP_COL"] + self.chw_return_temp_col = config["CHW_RETURN_TEMP_COL"] + self.chw_flow_rate_col = config["CHW_FLOW_RATE_COL"] def create_plot(self, df: pd.DataFrame, output_col: str = None): if output_col is None: output_col = "low_delta_t_flag" fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(25, 8)) - fig.suptitle('Low Delta T Fault Condition Plot') - - ax1.plot(df.index, df[self.chw_supply_temp_col], label="CHW Supply Temp", color="b") - ax1.plot(df.index, df[self.chw_return_temp_col], label="CHW Return Temp", color="r") - ax1.legend(loc='best') + fig.suptitle("Low Delta T Fault Condition Plot") + + ax1.plot( + df.index, df[self.chw_supply_temp_col], label="CHW Supply Temp", color="b" + ) + ax1.plot( + df.index, df[self.chw_return_temp_col], label="CHW Return Temp", color="r" + ) + ax1.legend(loc="best") ax1.set_ylabel("Temperature (°F)") ax2.plot(df.index, df["delta_t"], label="Delta T", color="g") - ax2.legend(loc='best') + ax2.legend(loc="best") ax2.set_ylabel("Delta T (°F)") ax3.plot(df.index, df[output_col], label="Fault Flag", color="k") - ax3.set_xlabel('Date') - ax3.set_ylabel('Fault Flags') - ax3.legend(loc='best') + ax3.set_xlabel("Date") + ax3.set_ylabel("Fault Flags") + ax3.legend(loc="best") plt.tight_layout(rect=[0, 0.03, 1, 0.95]) plt.show() @@ -41,13 +46,21 @@ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dic delta = df.index.to_series().diff() summary = { - 'total_days': round(delta.sum() / pd.Timedelta(days=1), 2), - 'total_hours': round(delta.sum() / pd.Timedelta(hours=1)), - 'hours_low_delta_t': round((delta * df[output_col]).sum() / pd.Timedelta(hours=1)), - 'percent_true': round(df[output_col].mean() * 100, 2), - 'percent_false': round((100 - round(df[output_col].mean() * 100, 2)), 2), - 'flag_true_delta_t': round(df["delta_t"].where(df[output_col] == 1).mean(), 2), - 'hours_motor_runtime': round((delta * df[self.chw_flow_rate_col].gt(.01).astype(int)).sum() / pd.Timedelta(hours=1), 2) + "total_days": round(delta.sum() / pd.Timedelta(days=1), 2), + "total_hours": round(delta.sum() / pd.Timedelta(hours=1)), + "hours_low_delta_t": round( + (delta * df[output_col]).sum() / pd.Timedelta(hours=1) + ), + "percent_true": round(df[output_col].mean() * 100, 2), + "percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2), + "flag_true_delta_t": round( + df["delta_t"].where(df[output_col] == 1).mean(), 2 + ), + "hours_motor_runtime": round( + (delta * df[self.chw_flow_rate_col].gt(0.01).astype(int)).sum() + / pd.Timedelta(hours=1), + 2, + ), } return summary @@ -66,7 +79,9 @@ def create_hist_plot(self, df: pd.DataFrame, output_col: str = None): plt.show() plt.close() - def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "low_delta_t_flag"): + def display_report_in_ipython( + self, df: pd.DataFrame, output_col: str = "low_delta_t_flag" + ): print("Low Delta T Fault Condition: Chiller Plant Efficiency Reduction") self.create_plot(df, output_col) @@ -74,7 +89,7 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "low_del summary = self.summarize_fault_times(df, output_col) for key, value in summary.items(): - formatted_key = key.replace('_', ' ') + formatted_key = key.replace("_", " ") print(f"{formatted_key}: {value}") sys.stdout.flush() @@ -85,15 +100,15 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "low_del if fc_max_faults_found != 0: self.create_hist_plot(df, output_col) - print("Delta T Mean When In Fault: ", summary['flag_true_delta_t']) + print("Delta T Mean When In Fault: ", summary["flag_true_delta_t"]) - if summary['percent_true'] > 5.0: + if summary["percent_true"] > 5.0: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is high, indicating potential issues with chiller efficiency. Investigate possible mechanical problems, sensor calibration, or system design issues.' + "The percent True metric that represents the amount of time for when the fault flag is True is high, indicating potential issues with chiller efficiency. Investigate possible mechanical problems, sensor calibration, or system design issues." ) else: print( - 'The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the system is likely functioning correctly.' + "The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the system is likely functioning correctly." ) else: diff --git a/open_fdd/energy_efficiency/reports/ahu_mech_clg_tracker.py b/open_fdd/energy_efficiency/reports/ahu_mech_clg_tracker.py index f88d718..d7e0aed 100644 --- a/open_fdd/energy_efficiency/reports/ahu_mech_clg_tracker.py +++ b/open_fdd/energy_efficiency/reports/ahu_mech_clg_tracker.py @@ -3,16 +3,17 @@ import matplotlib.pyplot as plt import sys + class MechanicalCoolingTracker: def __init__(self, config): - self.static_min = config['STATIC_MIN'] - self.static_col = config['STATIC_COL'] - self.economizer_min_oa_pos = config['ECONOMIZER_MIN_OA_POS'] - self.economizer_damper_position = config['ECONOMIZER_DAMPER_POSITION'] - self.mechanical_valve_position = config['MECHANICAL_VALVE_POSITION'] - self.oat_col = config['OAT_COL'] - self.ma_dampers_col = config['MA_DAMPERS_COL'] - self.cw_valve_col = config['CW_VALVE_COL'] + self.static_min = config["STATIC_MIN"] + self.static_col = config["STATIC_COL"] + self.economizer_min_oa_pos = config["ECONOMIZER_MIN_OA_POS"] + self.economizer_damper_position = config["ECONOMIZER_DAMPER_POSITION"] + self.mechanical_valve_position = config["MECHANICAL_VALVE_POSITION"] + self.oat_col = config["OAT_COL"] + self.ma_dampers_col = config["MA_DAMPERS_COL"] + self.cw_valve_col = config["CW_VALVE_COL"] def display_report_in_ipython(self, df: pd.DataFrame): print("Mechanical Cooling Report") @@ -22,13 +23,13 @@ def display_report_in_ipython(self, df: pd.DataFrame): summary = self.summarize_fault_times(df) for key, value in summary.items(): - formatted_key = key.replace('_', ' ').title() + formatted_key = key.replace("_", " ").title() print(f"{formatted_key}: {value}") sys.stdout.flush() # Generate mode percentage plots filtered_df = df[df[self.static_col] >= self.static_min].copy() - filtered_df['Mode'] = filtered_df.apply(self._identify_mode, axis=1) + filtered_df["Mode"] = filtered_df.apply(self._identify_mode, axis=1) mode_percentages = self._calculate_mode_percentages(filtered_df) print("Mode Percentages:") @@ -42,19 +43,34 @@ def display_report_in_ipython(self, df: pd.DataFrame): def create_plots(self, df: pd.DataFrame): filtered_df = df[df[self.static_col] >= self.static_min].copy() - daily_stats = filtered_df[self.static_col].resample('D').agg(['mean', 'std']) - filtered_df['time_delta'] = filtered_df.index.to_series().diff().dt.total_seconds().fillna(0) - filtered_df = filtered_df[filtered_df['time_delta'] > 0] + daily_stats = filtered_df[self.static_col].resample("D").agg(["mean", "std"]) + filtered_df["time_delta"] = ( + filtered_df.index.to_series().diff().dt.total_seconds().fillna(0) + ) + filtered_df = filtered_df[filtered_df["time_delta"] > 0] - expected_interval = filtered_df['time_delta'].mode()[0] - filtered_df['time_delta'] = filtered_df['time_delta'].apply(lambda x: expected_interval if x > expected_interval * 2 else x) + expected_interval = filtered_df["time_delta"].mode()[0] + filtered_df["time_delta"] = filtered_df["time_delta"].apply( + lambda x: expected_interval if x > expected_interval * 2 else x + ) - motor_run_time_per_day_seconds = filtered_df.groupby(filtered_df.index.date)['time_delta'].sum() + motor_run_time_per_day_seconds = filtered_df.groupby(filtered_df.index.date)[ + "time_delta" + ].sum() motor_run_time_per_day_hours = motor_run_time_per_day_seconds / 3600 - self._display_plot(daily_stats['mean'], 'Daily Avg AHU Duct Static Press', 'Inches WC') - self._display_plot(daily_stats['std'], 'Daily Std AHU Duct Static Press', 'Inches WC') - self._display_plot(motor_run_time_per_day_hours, 'AHU Motor Run Time per Day', 'Run Time (Hours)', color='orange') + self._display_plot( + daily_stats["mean"], "Daily Avg AHU Duct Static Press", "Inches WC" + ) + self._display_plot( + daily_stats["std"], "Daily Std AHU Duct Static Press", "Inches WC" + ) + self._display_plot( + motor_run_time_per_day_hours, + "AHU Motor Run Time per Day", + "Run Time (Hours)", + color="orange", + ) print("Plots created successfully.") @@ -63,29 +79,40 @@ def summarize_fault_times(self, df: pd.DataFrame) -> dict: delta = filtered_df.index.to_series().diff().dt.total_seconds().fillna(0) summary = { - 'total_days': round(delta.sum() / 86400, 2), # Convert seconds to days - 'total_hours': round(delta.sum() / 3600), # Convert seconds to hours - 'hours_motor_runtime': round(delta.sum() / 3600, 2) # Convert seconds to hours + "total_days": round(delta.sum() / 86400, 2), # Convert seconds to days + "total_hours": round(delta.sum() / 3600), # Convert seconds to hours + "hours_motor_runtime": round( + delta.sum() / 3600, 2 + ), # Convert seconds to hours } return summary def _identify_mode(self, row): if row[self.oat_col] > 40: - if row[self.ma_dampers_col] > self.economizer_damper_position and row[self.cw_valve_col] > self.mechanical_valve_position: - return 'Economizer_plus_Mech' - elif row[self.ma_dampers_col] > self.economizer_min_oa_pos and row[self.cw_valve_col] < self.mechanical_valve_position: - return 'Economizer' - elif row[self.ma_dampers_col] <= self.economizer_min_oa_pos and row[self.cw_valve_col] > self.mechanical_valve_position: - return 'Mechanical' - return 'Not_Mechanical' + if ( + row[self.ma_dampers_col] > self.economizer_damper_position + and row[self.cw_valve_col] > self.mechanical_valve_position + ): + return "Economizer_plus_Mech" + elif ( + row[self.ma_dampers_col] > self.economizer_min_oa_pos + and row[self.cw_valve_col] < self.mechanical_valve_position + ): + return "Economizer" + elif ( + row[self.ma_dampers_col] <= self.economizer_min_oa_pos + and row[self.cw_valve_col] > self.mechanical_valve_position + ): + return "Mechanical" + return "Not_Mechanical" def _calculate_mode_percentages(self, df: pd.DataFrame) -> pd.DataFrame: - mode_counts = df.groupby([df.index.date, 'Mode']).size().unstack(fill_value=0) + mode_counts = df.groupby([df.index.date, "Mode"]).size().unstack(fill_value=0) mode_percentages = mode_counts.div(mode_counts.sum(axis=1), axis=0) * 100 return mode_percentages - def _display_plot(self, data, title, ylabel, color='blue', kind='line'): + def _display_plot(self, data, title, ylabel, color="blue", kind="line"): plt.figure(figsize=(12, 6)) data.plot(title=title, ylabel=ylabel, color=color, kind=kind) plt.xticks(rotation=45) @@ -93,22 +120,32 @@ def _display_plot(self, data, title, ylabel, color='blue', kind='line'): plt.show() plt.close() - def _display_mode_plot_with_oat(self, mode_percentages: pd.DataFrame, df: pd.DataFrame): - daily_avg_oat = df[self.oat_col].resample('D').mean() - combined_df = mode_percentages.join(daily_avg_oat, rsuffix='_OAT') + def _display_mode_plot_with_oat( + self, mode_percentages: pd.DataFrame, df: pd.DataFrame + ): + daily_avg_oat = df[self.oat_col].resample("D").mean() + combined_df = mode_percentages.join(daily_avg_oat, rsuffix="_OAT") fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 12)) - combined_df.iloc[:, :-1].plot(kind='bar', stacked=True, ax=ax1, colormap='viridis') - ax1.set_title('Mode Percentage') - ax1.set_ylabel('Mode Percentage (%)') + combined_df.iloc[:, :-1].plot( + kind="bar", stacked=True, ax=ax1, colormap="viridis" + ) + ax1.set_title("Mode Percentage") + ax1.set_ylabel("Mode Percentage (%)") ax1.set_xticks([]) - ax2.plot(combined_df.index, combined_df['OaTemp'], color='red', linestyle='-', marker='o') - ax2.set_title('Average Outside Air Temperature Over Time') - ax2.set_ylabel('Average Outside Air Temperature (°F)') - ax2.set_xlabel('Date') - ax2.tick_params(axis='x', rotation=90) + ax2.plot( + combined_df.index, + combined_df["OaTemp"], + color="red", + linestyle="-", + marker="o", + ) + ax2.set_title("Average Outside Air Temperature Over Time") + ax2.set_ylabel("Average Outside Air Temperature (°F)") + ax2.set_xlabel("Date") + ax2.tick_params(axis="x", rotation=90) ax2.grid(True) plt.tight_layout() diff --git a/open_fdd/tests/ahu/test_ahu_fc1.py b/open_fdd/tests/ahu/test_ahu_fc1.py index 8564791..f9cce65 100644 --- a/open_fdd/tests/ahu/test_ahu_fc1.py +++ b/open_fdd/tests/ahu/test_ahu_fc1.py @@ -14,55 +14,111 @@ # Initialize FaultConditionOne with a dictionary fault_condition_params = { - 'VFD_SPEED_PERCENT_ERR_THRES': TEST_VFD_ERR_THRESHOLD, - 'VFD_SPEED_PERCENT_MAX': TEST_VFD_SPEED_MAX, - 'DUCT_STATIC_INCHES_ERR_THRES': TEST_DUCT_STATIC_ERR_THRESHOLD, - 'DUCT_STATIC_COL': TEST_DUCT_STATIC_COL, - 'SUPPLY_VFD_SPEED_COL': TEST_SUPPLY_VFD_SPEED_COL, - 'DUCT_STATIC_SETPOINT_COL': TEST_DUCT_STATIC_SETPOINT_COL, - 'TROUBLESHOOT_MODE': False, # default value - 'ROLLING_WINDOW_SIZE': ROLLING_WINDOW_SIZE # rolling sum window size + "VFD_SPEED_PERCENT_ERR_THRES": TEST_VFD_ERR_THRESHOLD, + "VFD_SPEED_PERCENT_MAX": TEST_VFD_SPEED_MAX, + "DUCT_STATIC_INCHES_ERR_THRES": TEST_DUCT_STATIC_ERR_THRESHOLD, + "DUCT_STATIC_COL": TEST_DUCT_STATIC_COL, + "SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL, + "DUCT_STATIC_SETPOINT_COL": TEST_DUCT_STATIC_SETPOINT_COL, + "TROUBLESHOOT_MODE": False, # default value + "ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE, # rolling sum window size } fc1 = FaultConditionOne(fault_condition_params) + class TestNoFault: def no_fault_df(self) -> pd.DataFrame: data = { TEST_DUCT_STATIC_COL: [0.99, 0.99, 0.99, 0.99, 0.99, 0.99], TEST_DUCT_STATIC_SETPOINT_COL: [1.0, 1.0, 1.0, 1.0, 1.0, 1.0], - TEST_SUPPLY_VFD_SPEED_COL: [0.8, 0.8, 0.8, 0.8, 0.8, 0.8] + TEST_SUPPLY_VFD_SPEED_COL: [0.8, 0.8, 0.8, 0.8, 0.8, 0.8], } return pd.DataFrame(data) def test_no_fault(self): results = fc1.apply(self.no_fault_df()) - actual = results['fc1_flag'].sum() + actual = results["fc1_flag"].sum() expected = 0 message = f"FC1 no_fault_df actual is {actual} and expected is {expected}" assert actual == expected, message + class TestFault: def fault_df(self) -> pd.DataFrame: data = { - TEST_DUCT_STATIC_COL: [0.7, 0.7, 0.6, 0.7, 0.65, 0.55, 0.99, 0.99, 0.6, 0.7, 0.65, 0.55, 0.6, 0.7, 0.65, 0.55, 0.6], - TEST_DUCT_STATIC_SETPOINT_COL: [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], - TEST_SUPPLY_VFD_SPEED_COL: [0.99, 0.95, 0.96, 0.97, 0.98, 0.98, 0.5, 0.55, 0.96, 0.97, 0.98, 0.98, 0.96, 0.97, 0.98, 0.98, 0.96] + TEST_DUCT_STATIC_COL: [ + 0.7, + 0.7, + 0.6, + 0.7, + 0.65, + 0.55, + 0.99, + 0.99, + 0.6, + 0.7, + 0.65, + 0.55, + 0.6, + 0.7, + 0.65, + 0.55, + 0.6, + ], + TEST_DUCT_STATIC_SETPOINT_COL: [ + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + ], + TEST_SUPPLY_VFD_SPEED_COL: [ + 0.99, + 0.95, + 0.96, + 0.97, + 0.98, + 0.98, + 0.5, + 0.55, + 0.96, + 0.97, + 0.98, + 0.98, + 0.96, + 0.97, + 0.98, + 0.98, + 0.96, + ], } return pd.DataFrame(data) def test_fault(self): results = fc1.apply(self.fault_df()) - actual = results['fc1_flag'].sum() + actual = results["fc1_flag"].sum() # accumilated 5 faults need to happen before an "official fault" # in TEST_DUCT_STATIC_COL after the 5 first values there is 3 faults # then artificially adjust fake fan data back to normal and another 5 # needs happen per ROLLING_WINDOW_SIZE and then 4 faults after that. # so expected = 3 + 4. - expected = 3 + 4 + expected = 3 + 4 message = f"FC1 fault_df actual is {actual} and expected is {expected}" assert actual == expected, message @@ -71,28 +127,33 @@ class TestFaultOnInt: def fault_df_on_output_int(self) -> pd.DataFrame: data = { - TEST_DUCT_STATIC_COL: [.8] * 6, + TEST_DUCT_STATIC_COL: [0.8] * 6, TEST_DUCT_STATIC_SETPOINT_COL: [1.0] * 6, TEST_SUPPLY_VFD_SPEED_COL: [99] * 6, } return pd.DataFrame(data) def test_fault_on_int(self): - with pytest.raises(TypeError, - match=HelperUtils().float_int_check_err(TEST_SUPPLY_VFD_SPEED_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_int_check_err(TEST_SUPPLY_VFD_SPEED_COL), + ): fc1.apply(self.fault_df_on_output_int()) + class TestFaultOnFloatGreaterThanOne: def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: data = { - TEST_DUCT_STATIC_COL: [.8] * 6, + TEST_DUCT_STATIC_COL: [0.8] * 6, TEST_DUCT_STATIC_SETPOINT_COL: [1.0] * 6, TEST_SUPPLY_VFD_SPEED_COL: [99.0] * 6, } return pd.DataFrame(data) def test_fault_on_float_greater_than_one(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL), + ): fc1.apply(self.fault_df_on_output_greater_than_one()) diff --git a/open_fdd/tests/ahu/test_ahu_fc10.py b/open_fdd/tests/ahu/test_ahu_fc10.py index 65b1c70..4bdd45c 100644 --- a/open_fdd/tests/ahu/test_ahu_fc10.py +++ b/open_fdd/tests/ahu/test_ahu_fc10.py @@ -3,12 +3,12 @@ from open_fdd.air_handling_unit.faults.fault_condition_ten import FaultConditionTen from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils -''' +""" To see print statements in pytest run with: $ py -3.12 -m pytest tests/ahu/test_ahu_fc10.py -rP -s SAT and MAT should be approx equal in OS3 -''' +""" # Constants TEST_OAT_DEGF_ERR_THRES = 5.0 @@ -21,18 +21,19 @@ # Initialize FaultConditionTen with a dictionary fault_condition_params = { - 'OUTDOOR_DEGF_ERR_THRES': TEST_OAT_DEGF_ERR_THRES, - 'MIX_DEGF_ERR_THRES': TEST_MIX_DEGF_ERR_THRES, - 'OAT_COL': TEST_OAT_COL, - 'MAT_COL': TEST_MAT_COL, - 'COOLING_SIG_COL': TEST_COOLING_COIL_SIG_COL, - 'ECONOMIZER_SIG_COL': TEST_MIX_AIR_DAMPER_COL, - 'TROUBLESHOOT_MODE': False, - 'ROLLING_WINDOW_SIZE': ROLLING_WINDOW_SIZE + "OUTDOOR_DEGF_ERR_THRES": TEST_OAT_DEGF_ERR_THRES, + "MIX_DEGF_ERR_THRES": TEST_MIX_DEGF_ERR_THRES, + "OAT_COL": TEST_OAT_COL, + "MAT_COL": TEST_MAT_COL, + "COOLING_SIG_COL": TEST_COOLING_COIL_SIG_COL, + "ECONOMIZER_SIG_COL": TEST_MIX_AIR_DAMPER_COL, + "TROUBLESHOOT_MODE": False, + "ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE, } fc10 = FaultConditionTen(fault_condition_params) + class TestFaultConditionTen: def no_fault_df_no_econ(self) -> pd.DataFrame: @@ -55,18 +56,21 @@ def fault_df_in_econ(self) -> pd.DataFrame: def test_no_fault_no_econ(self): results = fc10.apply(self.no_fault_df_no_econ()) - actual = results['fc10_flag'].sum() + actual = results["fc10_flag"].sum() expected = 0 - message = f"FC10 no_fault_df_no_econ actual is {actual} and expected is {expected}" + message = ( + f"FC10 no_fault_df_no_econ actual is {actual} and expected is {expected}" + ) assert actual == expected, message def test_fault_in_econ(self): results = fc10.apply(self.fault_df_in_econ()) - actual = results['fc10_flag'].sum() + actual = results["fc10_flag"].sum() expected = 2 message = f"FC10 fault_df_in_econ actual is {actual} and expected is {expected}" assert actual == expected, message + class TestFaultOnInt: def fault_df_on_output_int(self) -> pd.DataFrame: @@ -79,10 +83,13 @@ def fault_df_on_output_int(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_int(self): - with pytest.raises(TypeError, - match=HelperUtils().float_int_check_err(TEST_COOLING_COIL_SIG_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_int_check_err(TEST_COOLING_COIL_SIG_COL), + ): fc10.apply(self.fault_df_on_output_int()) + class TestFaultOnFloatGreaterThanOne: def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: @@ -95,10 +102,13 @@ def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_float_greater_than_one(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_COOLING_COIL_SIG_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_max_check_err(TEST_COOLING_COIL_SIG_COL), + ): fc10.apply(self.fault_df_on_output_greater_than_one()) + class TestFaultOnMixedTypes: def fault_df_on_mixed_types(self) -> pd.DataFrame: @@ -111,9 +121,12 @@ def fault_df_on_mixed_types(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_mixed_types(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_COOLING_COIL_SIG_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_max_check_err(TEST_COOLING_COIL_SIG_COL), + ): fc10.apply(self.fault_df_on_mixed_types()) + if __name__ == "__main__": pytest.main() diff --git a/open_fdd/tests/ahu/test_ahu_fc11.py b/open_fdd/tests/ahu/test_ahu_fc11.py index 9b02a43..d6cc12b 100644 --- a/open_fdd/tests/ahu/test_ahu_fc11.py +++ b/open_fdd/tests/ahu/test_ahu_fc11.py @@ -1,14 +1,16 @@ import pandas as pd import pytest -from open_fdd.air_handling_unit.faults.fault_condition_eleven import FaultConditionEleven +from open_fdd.air_handling_unit.faults.fault_condition_eleven import ( + FaultConditionEleven, +) from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils -''' +""" To see print statements in pytest run with: $ py -3.12 -m pytest tests/ahu/test_ahu_fc11.py -rP -s OAT Temp too low for 100% cooling in OS3 -''' +""" # Constants TEST_DELTA_SUPPLY_FAN = 2.0 @@ -22,19 +24,20 @@ # Initialize FaultConditionEleven with a dictionary fault_condition_params = { - 'DELTA_T_SUPPLY_FAN': TEST_DELTA_SUPPLY_FAN, - 'OUTDOOR_DEGF_ERR_THRES': TEST_OAT_DEGF_ERR_THRES, - 'SUPPLY_DEGF_ERR_THRES': TEST_SUPPLY_DEGF_ERR_THRES, - 'SAT_SETPOINT_COL': TEST_SAT_SP_COL, - 'OAT_COL': TEST_OAT_COL, - 'COOLING_SIG_COL': TEST_COOLING_COIL_SIG_COL, - 'ECONOMIZER_SIG_COL': TEST_MIX_AIR_DAMPER_COL, - 'TROUBLESHOOT_MODE': False, - 'ROLLING_WINDOW_SIZE': ROLLING_WINDOW_SIZE + "DELTA_T_SUPPLY_FAN": TEST_DELTA_SUPPLY_FAN, + "OUTDOOR_DEGF_ERR_THRES": TEST_OAT_DEGF_ERR_THRES, + "SUPPLY_DEGF_ERR_THRES": TEST_SUPPLY_DEGF_ERR_THRES, + "SAT_SETPOINT_COL": TEST_SAT_SP_COL, + "OAT_COL": TEST_OAT_COL, + "COOLING_SIG_COL": TEST_COOLING_COIL_SIG_COL, + "ECONOMIZER_SIG_COL": TEST_MIX_AIR_DAMPER_COL, + "TROUBLESHOOT_MODE": False, + "ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE, } fc11 = FaultConditionEleven(fault_condition_params) + class TestFaultConditionEleven: def no_fault_df_no_econ(self) -> pd.DataFrame: @@ -57,18 +60,21 @@ def fault_df_in_econ(self) -> pd.DataFrame: def test_no_fault_no_econ(self): results = fc11.apply(self.no_fault_df_no_econ()) - actual = results['fc11_flag'].sum() + actual = results["fc11_flag"].sum() expected = 0 - message = f"FC11 no_fault_df_no_econ actual is {actual} and expected is {expected}" + message = ( + f"FC11 no_fault_df_no_econ actual is {actual} and expected is {expected}" + ) assert actual == expected, message def test_fault_in_econ(self): results = fc11.apply(self.fault_df_in_econ()) - actual = results['fc11_flag'].sum() + actual = results["fc11_flag"].sum() expected = 2 message = f"FC11 fault_df_in_econ actual is {actual} and expected is {expected}" assert actual == expected, message + class TestFaultOnInt: def fault_df_on_output_int(self) -> pd.DataFrame: @@ -81,10 +87,13 @@ def fault_df_on_output_int(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_int(self): - with pytest.raises(TypeError, - match=HelperUtils().float_int_check_err(TEST_COOLING_COIL_SIG_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_int_check_err(TEST_COOLING_COIL_SIG_COL), + ): fc11.apply(self.fault_df_on_output_int()) + class TestFaultOnFloatGreaterThanOne: def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: @@ -97,10 +106,13 @@ def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_float_greater_than_one(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_COOLING_COIL_SIG_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_max_check_err(TEST_COOLING_COIL_SIG_COL), + ): fc11.apply(self.fault_df_on_output_greater_than_one()) + class TestFaultOnMixedTypes: def fault_df_on_mixed_types(self) -> pd.DataFrame: @@ -113,9 +125,12 @@ def fault_df_on_mixed_types(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_mixed_types(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_COOLING_COIL_SIG_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_max_check_err(TEST_COOLING_COIL_SIG_COL), + ): fc11.apply(self.fault_df_on_mixed_types()) + if __name__ == "__main__": pytest.main() diff --git a/open_fdd/tests/ahu/test_ahu_fc12.py b/open_fdd/tests/ahu/test_ahu_fc12.py index ad94012..b8ee690 100644 --- a/open_fdd/tests/ahu/test_ahu_fc12.py +++ b/open_fdd/tests/ahu/test_ahu_fc12.py @@ -1,14 +1,16 @@ import pandas as pd import pytest -from open_fdd.air_handling_unit.faults.fault_condition_twelve import FaultConditionTwelve +from open_fdd.air_handling_unit.faults.fault_condition_twelve import ( + FaultConditionTwelve, +) from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils -''' +""" To see print statements in pytest run with: $ py -3.12 -m pytest tests/ahu/test_ahu_fc12.py -rP -s SAT too high should be less than MAT. OS3 & OS4 -''' +""" # Constants TEST_DELTA_SUPPLY_FAN = 2.0 @@ -23,20 +25,21 @@ # Initialize FaultConditionTwelve with a dictionary fault_condition_params = { - 'DELTA_T_SUPPLY_FAN': TEST_DELTA_SUPPLY_FAN, - 'MIX_DEGF_ERR_THRES': TEST_MIX_DEGF_ERR_THRES, - 'SUPPLY_DEGF_ERR_THRES': TEST_SUPPLY_DEGF_ERR_THRES, - 'AHU_MIN_OA_DPR': TEST_AHU_MIN_OA_DPR, - 'SAT_COL': TEST_SAT_COL, - 'MAT_COL': TEST_MAT_COL, - 'COOLING_SIG_COL': TEST_COOLING_COIL_SIG_COL, - 'ECONOMIZER_SIG_COL': TEST_MIX_AIR_DAMPER_COL, - 'TROUBLESHOOT_MODE': False, - 'ROLLING_WINDOW_SIZE': ROLLING_WINDOW_SIZE + "DELTA_T_SUPPLY_FAN": TEST_DELTA_SUPPLY_FAN, + "MIX_DEGF_ERR_THRES": TEST_MIX_DEGF_ERR_THRES, + "SUPPLY_DEGF_ERR_THRES": TEST_SUPPLY_DEGF_ERR_THRES, + "AHU_MIN_OA_DPR": TEST_AHU_MIN_OA_DPR, + "SAT_COL": TEST_SAT_COL, + "MAT_COL": TEST_MAT_COL, + "COOLING_SIG_COL": TEST_COOLING_COIL_SIG_COL, + "ECONOMIZER_SIG_COL": TEST_MIX_AIR_DAMPER_COL, + "TROUBLESHOOT_MODE": False, + "ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE, } fc12 = FaultConditionTwelve(fault_condition_params) + class TestFaultConditionTwelve: def no_fault_df_no_econ(self) -> pd.DataFrame: @@ -44,7 +47,14 @@ def no_fault_df_no_econ(self) -> pd.DataFrame: TEST_SAT_COL: [55, 55, 55, 55, 55, 55], TEST_MAT_COL: [56, 56, 56, 56, 56, 56], TEST_COOLING_COIL_SIG_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - TEST_MIX_AIR_DAMPER_COL: [TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR], + TEST_MIX_AIR_DAMPER_COL: [ + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + ], } return pd.DataFrame(data) @@ -62,31 +72,43 @@ def fault_df_in_mech_clg(self) -> pd.DataFrame: TEST_SAT_COL: [66, 66, 66, 66, 66, 66], TEST_MAT_COL: [56, 56, 56, 56, 56, 56], TEST_COOLING_COIL_SIG_COL: [0.99, 0.99, 0.99, 0.99, 0.99, 0.99], - TEST_MIX_AIR_DAMPER_COL: [TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR], + TEST_MIX_AIR_DAMPER_COL: [ + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + ], } return pd.DataFrame(data) def test_no_fault_no_econ(self): results = fc12.apply(self.no_fault_df_no_econ()) - actual = results['fc12_flag'].sum() + actual = results["fc12_flag"].sum() expected = 0 - message = f"FC12 no_fault_df_no_econ actual is {actual} and expected is {expected}" + message = ( + f"FC12 no_fault_df_no_econ actual is {actual} and expected is {expected}" + ) assert actual == expected, message def test_fault_in_econ_plus_mech(self): results = fc12.apply(self.fault_df_in_econ_plus_mech()) - actual = results['fc12_flag'].sum() + actual = results["fc12_flag"].sum() expected = 2 message = f"FC12 fault_df_in_econ actual is {actual} and expected is {expected}" assert actual == expected, message def test_fault_in_mech_clg(self): results = fc12.apply(self.fault_df_in_mech_clg()) - actual = results['fc12_flag'].sum() + actual = results["fc12_flag"].sum() expected = 2 - message = f"FC12 fault_df_in_mech_clg actual is {actual} and expected is {expected}" + message = ( + f"FC12 fault_df_in_mech_clg actual is {actual} and expected is {expected}" + ) assert actual == expected, message + class TestFaultOnInt: def fault_df_on_output_int(self) -> pd.DataFrame: @@ -99,10 +121,12 @@ def fault_df_on_output_int(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_int(self): - with pytest.raises(TypeError, - match=HelperUtils().float_int_check_err(TEST_MIX_AIR_DAMPER_COL)): + with pytest.raises( + TypeError, match=HelperUtils().float_int_check_err(TEST_MIX_AIR_DAMPER_COL) + ): fc12.apply(self.fault_df_on_output_int()) + class TestFaultOnFloatGreaterThanOne: def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: @@ -115,10 +139,12 @@ def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_float_greater_than_one(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_MIX_AIR_DAMPER_COL)): + with pytest.raises( + TypeError, match=HelperUtils().float_max_check_err(TEST_MIX_AIR_DAMPER_COL) + ): fc12.apply(self.fault_df_on_output_greater_than_one()) + class TestFaultOnMixedTypes: def fault_df_on_mixed_types(self) -> pd.DataFrame: @@ -131,9 +157,11 @@ def fault_df_on_mixed_types(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_mixed_types(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_MIX_AIR_DAMPER_COL)): + with pytest.raises( + TypeError, match=HelperUtils().float_max_check_err(TEST_MIX_AIR_DAMPER_COL) + ): fc12.apply(self.fault_df_on_mixed_types()) + if __name__ == "__main__": pytest.main() diff --git a/open_fdd/tests/ahu/test_ahu_fc13.py b/open_fdd/tests/ahu/test_ahu_fc13.py index 4aed9f7..13e036c 100644 --- a/open_fdd/tests/ahu/test_ahu_fc13.py +++ b/open_fdd/tests/ahu/test_ahu_fc13.py @@ -1,14 +1,16 @@ import pandas as pd import pytest -from open_fdd.air_handling_unit.faults.fault_condition_thirteen import FaultConditionThirteen +from open_fdd.air_handling_unit.faults.fault_condition_thirteen import ( + FaultConditionThirteen, +) from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils -''' +""" To see print statements in pytest run with: $ py -3.12 -m pytest tests/ahu/test_ahu_fc13.py -rP -s SAT too high in full cooling. OS3 & OS4 -''' +""" # Constants TEST_SUPPLY_DEGF_ERR_THRES = 2.0 @@ -21,18 +23,19 @@ # Initialize FaultConditionThirteen with a dictionary fault_condition_params = { - 'SUPPLY_DEGF_ERR_THRES': TEST_SUPPLY_DEGF_ERR_THRES, - 'AHU_MIN_OA_DPR': TEST_AHU_MIN_OA_DPR, - 'SAT_COL': TEST_SAT_COL, - 'SAT_SETPOINT_COL': TEST_SAT_SP_COL, - 'COOLING_SIG_COL': TEST_COOLING_COIL_SIG_COL, - 'ECONOMIZER_SIG_COL': TEST_MIX_AIR_DAMPER_COL, - 'TROUBLESHOOT_MODE': False, - 'ROLLING_WINDOW_SIZE': ROLLING_WINDOW_SIZE + "SUPPLY_DEGF_ERR_THRES": TEST_SUPPLY_DEGF_ERR_THRES, + "AHU_MIN_OA_DPR": TEST_AHU_MIN_OA_DPR, + "SAT_COL": TEST_SAT_COL, + "SAT_SETPOINT_COL": TEST_SAT_SP_COL, + "COOLING_SIG_COL": TEST_COOLING_COIL_SIG_COL, + "ECONOMIZER_SIG_COL": TEST_MIX_AIR_DAMPER_COL, + "TROUBLESHOOT_MODE": False, + "ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE, } fc13 = FaultConditionThirteen(fault_condition_params) + class TestFaultConditionThirteen: def no_fault_df_no_econ(self) -> pd.DataFrame: @@ -40,7 +43,14 @@ def no_fault_df_no_econ(self) -> pd.DataFrame: TEST_SAT_COL: [55, 55, 55, 55, 55, 55], TEST_SAT_SP_COL: [56, 56, 56, 56, 56, 56], TEST_COOLING_COIL_SIG_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - TEST_MIX_AIR_DAMPER_COL: [TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR], + TEST_MIX_AIR_DAMPER_COL: [ + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + ], } return pd.DataFrame(data) @@ -58,31 +68,43 @@ def fault_df_in_mech_clg(self) -> pd.DataFrame: TEST_SAT_COL: [66, 66, 66, 66, 66, 66], TEST_SAT_SP_COL: [56, 56, 56, 56, 56, 56], TEST_COOLING_COIL_SIG_COL: [0.99, 0.99, 0.99, 0.99, 0.99, 0.99], - TEST_MIX_AIR_DAMPER_COL: [TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR], + TEST_MIX_AIR_DAMPER_COL: [ + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + ], } return pd.DataFrame(data) def test_no_fault_no_econ(self): results = fc13.apply(self.no_fault_df_no_econ()) - actual = results['fc13_flag'].sum() + actual = results["fc13_flag"].sum() expected = 0 - message = f"FC13 no_fault_df_no_econ actual is {actual} and expected is {expected}" + message = ( + f"FC13 no_fault_df_no_econ actual is {actual} and expected is {expected}" + ) assert actual == expected, message def test_fault_in_econ_plus_mech(self): results = fc13.apply(self.fault_df_in_econ_plus_mech()) - actual = results['fc13_flag'].sum() + actual = results["fc13_flag"].sum() expected = 2 message = f"FC13 fault_df_in_econ actual is {actual} and expected is {expected}" assert actual == expected, message def test_fault_in_mech_clg(self): results = fc13.apply(self.fault_df_in_mech_clg()) - actual = results['fc13_flag'].sum() + actual = results["fc13_flag"].sum() expected = 2 - message = f"FC13 fault_df_in_mech_clg actual is {actual} and expected is {expected}" + message = ( + f"FC13 fault_df_in_mech_clg actual is {actual} and expected is {expected}" + ) assert actual == expected, message + class TestFaultOnInt: def fault_df_on_output_int(self) -> pd.DataFrame: @@ -95,10 +117,12 @@ def fault_df_on_output_int(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_int(self): - with pytest.raises(TypeError, - match=HelperUtils().float_int_check_err(TEST_MIX_AIR_DAMPER_COL)): + with pytest.raises( + TypeError, match=HelperUtils().float_int_check_err(TEST_MIX_AIR_DAMPER_COL) + ): fc13.apply(self.fault_df_on_output_int()) + class TestFaultOnFloatGreaterThanOne: def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: @@ -111,10 +135,12 @@ def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_float_greater_than_one(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_MIX_AIR_DAMPER_COL)): + with pytest.raises( + TypeError, match=HelperUtils().float_max_check_err(TEST_MIX_AIR_DAMPER_COL) + ): fc13.apply(self.fault_df_on_output_greater_than_one()) + class TestFaultOnMixedTypes: def fault_df_on_mixed_types(self) -> pd.DataFrame: @@ -127,9 +153,11 @@ def fault_df_on_mixed_types(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_mixed_types(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_MIX_AIR_DAMPER_COL)): + with pytest.raises( + TypeError, match=HelperUtils().float_max_check_err(TEST_MIX_AIR_DAMPER_COL) + ): fc13.apply(self.fault_df_on_mixed_types()) + if __name__ == "__main__": pytest.main() diff --git a/open_fdd/tests/ahu/test_ahu_fc14.py b/open_fdd/tests/ahu/test_ahu_fc14.py index 3da0e14..8df94c0 100644 --- a/open_fdd/tests/ahu/test_ahu_fc14.py +++ b/open_fdd/tests/ahu/test_ahu_fc14.py @@ -1,14 +1,16 @@ import pandas as pd import pytest -from open_fdd.air_handling_unit.faults.fault_condition_fourteen import FaultConditionFourteen +from open_fdd.air_handling_unit.faults.fault_condition_fourteen import ( + FaultConditionFourteen, +) from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils -''' +""" To see print statements in pytest run with: $ py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc14.py -rP -s Temp drop across inactive clg coil in OS1 & OS2 -''' +""" # Constants TEST_DELTA_SUPPLY_FAN = 2.0 @@ -25,22 +27,23 @@ # Initialize FaultConditionFourteen with a dictionary fault_condition_params = { - 'DELTA_T_SUPPLY_FAN': TEST_DELTA_SUPPLY_FAN, - 'COIL_TEMP_ENTER_ERR_THRES': TEST_COIL_TEMP_ENTER_ERR_THRES, - 'COIL_TEMP_LEAV_ERR_THRES': TEST_COIL_TEMP_LEAVE_ERR_THRES, - 'AHU_MIN_OA_DPR': TEST_AHU_MIN_OA_DPR, - 'CLG_COIL_ENTER_TEMP_COL': TEST_CLG_COIL_ENTER_TEMP_COL, - 'CLG_COIL_LEAVE_TEMP_COL': TEST_CLG_COIL_LEAVE_TEMP_COL, - 'COOLING_SIG_COL': TEST_CLG_COIL_CMD_COL, - 'HEATING_SIG_COL': TEST_HTG_COIL_CMD_COL, - 'ECONOMIZER_SIG_COL': TEST_MIX_AIR_DAMPER_COL, - 'SUPPLY_VFD_SPEED_COL': TEST_SUPPLY_VFD_SPEED_COL, - 'TROUBLESHOOT_MODE': False, - 'ROLLING_WINDOW_SIZE': ROLLING_WINDOW_SIZE + "DELTA_T_SUPPLY_FAN": TEST_DELTA_SUPPLY_FAN, + "COIL_TEMP_ENTER_ERR_THRES": TEST_COIL_TEMP_ENTER_ERR_THRES, + "COIL_TEMP_LEAV_ERR_THRES": TEST_COIL_TEMP_LEAVE_ERR_THRES, + "AHU_MIN_OA_DPR": TEST_AHU_MIN_OA_DPR, + "CLG_COIL_ENTER_TEMP_COL": TEST_CLG_COIL_ENTER_TEMP_COL, + "CLG_COIL_LEAVE_TEMP_COL": TEST_CLG_COIL_LEAVE_TEMP_COL, + "COOLING_SIG_COL": TEST_CLG_COIL_CMD_COL, + "HEATING_SIG_COL": TEST_HTG_COIL_CMD_COL, + "ECONOMIZER_SIG_COL": TEST_MIX_AIR_DAMPER_COL, + "SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL, + "TROUBLESHOOT_MODE": False, + "ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE, } fc14 = FaultConditionFourteen(fault_condition_params) + class TestFaultConditionFourteen: def no_fault_df_econ(self) -> pd.DataFrame: @@ -50,7 +53,7 @@ def no_fault_df_econ(self) -> pd.DataFrame: TEST_CLG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], TEST_HTG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], TEST_MIX_AIR_DAMPER_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], - TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55] + TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], } return pd.DataFrame(data) @@ -60,8 +63,15 @@ def no_fault_df_htg(self) -> pd.DataFrame: TEST_CLG_COIL_LEAVE_TEMP_COL: [56.5, 56.5, 56.5, 56.5, 56.5, 56.5], TEST_CLG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], TEST_HTG_COIL_CMD_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], - TEST_MIX_AIR_DAMPER_COL: [TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR], - TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55] + TEST_MIX_AIR_DAMPER_COL: [ + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + ], + TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], } return pd.DataFrame(data) @@ -72,7 +82,7 @@ def fault_df_in_econ(self) -> pd.DataFrame: TEST_CLG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], TEST_HTG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], TEST_MIX_AIR_DAMPER_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], - TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55] + TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], } return pd.DataFrame(data) @@ -82,39 +92,47 @@ def fault_df_in_htg(self) -> pd.DataFrame: TEST_CLG_COIL_LEAVE_TEMP_COL: [50.5, 50.5, 50.5, 50.5, 50.5, 50.5], TEST_CLG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], TEST_HTG_COIL_CMD_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], - TEST_MIX_AIR_DAMPER_COL: [TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR, TEST_AHU_MIN_OA_DPR], - TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55] + TEST_MIX_AIR_DAMPER_COL: [ + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + TEST_AHU_MIN_OA_DPR, + ], + TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], } return pd.DataFrame(data) def test_no_fault_econ(self): results = fc14.apply(self.no_fault_df_econ()) - actual = results['fc14_flag'].sum() + actual = results["fc14_flag"].sum() expected = 0 message = f"FC14 no_fault_df_econ actual is {actual} and expected is {expected}" assert actual == expected, message def test_no_fault_htg(self): results = fc14.apply(self.no_fault_df_htg()) - actual = results['fc14_flag'].sum() + actual = results["fc14_flag"].sum() expected = 0 message = f"FC14 no_fault_df_htg actual is {actual} and expected is {expected}" assert actual == expected, message def test_fault_in_econ(self): results = fc14.apply(self.fault_df_in_econ()) - actual = results['fc14_flag'].sum() + actual = results["fc14_flag"].sum() expected = 2 message = f"FC14 fault_df_in_econ actual is {actual} and expected is {expected}" assert actual == expected, message def test_fault_in_htg(self): results = fc14.apply(self.fault_df_in_htg()) - actual = results['fc14_flag'].sum() + actual = results["fc14_flag"].sum() expected = 2 message = f"FC14 fault_df_in_htg actual is {actual} and expected is {expected}" assert actual == expected, message + class TestFaultOnInt: def fault_df_on_output_int(self) -> pd.DataFrame: @@ -124,15 +142,17 @@ def fault_df_on_output_int(self) -> pd.DataFrame: TEST_CLG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], TEST_HTG_COIL_CMD_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], TEST_MIX_AIR_DAMPER_COL: [55, 55, 55, 55, 55, 55], # Incorrect type - TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55] + TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], } return pd.DataFrame(data) def test_fault_on_int(self): - with pytest.raises(TypeError, - match=HelperUtils().float_int_check_err(TEST_MIX_AIR_DAMPER_COL)): + with pytest.raises( + TypeError, match=HelperUtils().float_int_check_err(TEST_MIX_AIR_DAMPER_COL) + ): fc14.apply(self.fault_df_on_output_int()) + class TestFaultOnFloatGreaterThanOne: def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: @@ -142,15 +162,17 @@ def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: TEST_CLG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], TEST_HTG_COIL_CMD_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], TEST_MIX_AIR_DAMPER_COL: [1.1, 1.2, 1.1, 1.3, 1.1, 1.2], # Values > 1.0 - TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55] + TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], } return pd.DataFrame(data) def test_fault_on_float_greater_than_one(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_MIX_AIR_DAMPER_COL)): + with pytest.raises( + TypeError, match=HelperUtils().float_max_check_err(TEST_MIX_AIR_DAMPER_COL) + ): fc14.apply(self.fault_df_on_output_greater_than_one()) + class TestFaultOnMixedTypes: def fault_df_on_mixed_types(self) -> pd.DataFrame: @@ -160,14 +182,16 @@ def fault_df_on_mixed_types(self) -> pd.DataFrame: TEST_CLG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], TEST_HTG_COIL_CMD_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], TEST_MIX_AIR_DAMPER_COL: [1.1, 0.55, 1.2, 1.3, 0.55, 1.1], # Mixed types - TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55] + TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], } return pd.DataFrame(data) def test_fault_on_mixed_types(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_MIX_AIR_DAMPER_COL)): + with pytest.raises( + TypeError, match=HelperUtils().float_max_check_err(TEST_MIX_AIR_DAMPER_COL) + ): fc14.apply(self.fault_df_on_mixed_types()) + if __name__ == "__main__": pytest.main() diff --git a/open_fdd/tests/ahu/test_ahu_fc15.py b/open_fdd/tests/ahu/test_ahu_fc15.py index 0a036bc..637880e 100644 --- a/open_fdd/tests/ahu/test_ahu_fc15.py +++ b/open_fdd/tests/ahu/test_ahu_fc15.py @@ -1,14 +1,16 @@ import pandas as pd import pytest -from open_fdd.air_handling_unit.faults.fault_condition_fifteen import FaultConditionFifteen +from open_fdd.air_handling_unit.faults.fault_condition_fifteen import ( + FaultConditionFifteen, +) from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils -''' +""" To see print statements in pytest run with: $ py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc15.py -rP -s Temp rise across inactive htg coil in OS2, OS3, & OS4 -''' +""" # Constants TEST_DELTA_SUPPLY_FAN = 2.0 @@ -25,22 +27,23 @@ # Initialize FaultConditionFifteen with a dictionary fault_condition_params = { - 'DELTA_SUPPLY_FAN': TEST_DELTA_SUPPLY_FAN, - 'COIL_TEMP_ENTER_ERR_THRES': TEST_COIL_TEMP_ENTER_ERR_THRES, - 'COIL_TEMP_LEAV_ERR_THRES': TEST_COIL_TEMP_LEAVE_ERR_THRES, - 'AHU_MIN_OA_DPR': TEST_AHU_MIN_OA_DPR, - 'HTG_COIL_ENTER_TEMP_COL': TEST_HTG_COIL_ENTER_TEMP_COL, - 'HTG_COIL_LEAVE_TEMP_COL': TEST_HTG_COIL_LEAVE_TEMP_COL, - 'COOLING_SIG_COL': TEST_CLG_COIL_CMD_COL, - 'HEATING_SIG_COL': TEST_HTG_COIL_CMD_COL, - 'ECONOMIZER_SIG_COL': TEST_MIX_AIR_DAMPER_COL, - 'SUPPLY_VFD_SPEED_COL': TEST_SUPPLY_VFD_SPEED_COL, - 'TROUBLESHOOT_MODE': False, - 'ROLLING_WINDOW_SIZE': ROLLING_WINDOW_SIZE + "DELTA_SUPPLY_FAN": TEST_DELTA_SUPPLY_FAN, + "COIL_TEMP_ENTER_ERR_THRES": TEST_COIL_TEMP_ENTER_ERR_THRES, + "COIL_TEMP_LEAV_ERR_THRES": TEST_COIL_TEMP_LEAVE_ERR_THRES, + "AHU_MIN_OA_DPR": TEST_AHU_MIN_OA_DPR, + "HTG_COIL_ENTER_TEMP_COL": TEST_HTG_COIL_ENTER_TEMP_COL, + "HTG_COIL_LEAVE_TEMP_COL": TEST_HTG_COIL_LEAVE_TEMP_COL, + "COOLING_SIG_COL": TEST_CLG_COIL_CMD_COL, + "HEATING_SIG_COL": TEST_HTG_COIL_CMD_COL, + "ECONOMIZER_SIG_COL": TEST_MIX_AIR_DAMPER_COL, + "SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL, + "TROUBLESHOOT_MODE": False, + "ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE, } fc15 = FaultConditionFifteen(fault_condition_params) + class TestFaultConditionFifteen: def no_fault_df_econ(self) -> pd.DataFrame: @@ -50,7 +53,7 @@ def no_fault_df_econ(self) -> pd.DataFrame: TEST_HTG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], TEST_CLG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], TEST_MIX_AIR_DAMPER_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], - TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55] + TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], } return pd.DataFrame(data) @@ -61,7 +64,7 @@ def no_fault_df_os3(self) -> pd.DataFrame: TEST_HTG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], TEST_CLG_COIL_CMD_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], TEST_MIX_AIR_DAMPER_COL: [0.99, 0.99, 0.99, 0.99, 0.99, 0.99], - TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55] + TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], } return pd.DataFrame(data) @@ -72,7 +75,7 @@ def fault_df_in_econ(self) -> pd.DataFrame: TEST_HTG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], TEST_CLG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], TEST_MIX_AIR_DAMPER_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], - TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55] + TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], } return pd.DataFrame(data) @@ -83,38 +86,39 @@ def fault_df_in_os3(self) -> pd.DataFrame: TEST_HTG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], TEST_CLG_COIL_CMD_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], TEST_MIX_AIR_DAMPER_COL: [0.99, 0.99, 0.99, 0.99, 0.99, 0.99], - TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55] + TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], } return pd.DataFrame(data) def test_no_fault_econ(self): results = fc15.apply(self.no_fault_df_econ()) - actual = results['fc15_flag'].sum() + actual = results["fc15_flag"].sum() expected = 0 message = f"FC15 no_fault_df_econ actual is {actual} and expected is {expected}" assert actual == expected, message def test_no_fault_os3(self): results = fc15.apply(self.no_fault_df_os3()) - actual = results['fc15_flag'].sum() + actual = results["fc15_flag"].sum() expected = 0 message = f"FC15 no_fault_df_os3 actual is {actual} and expected is {expected}" assert actual == expected, message def test_fault_in_econ(self): results = fc15.apply(self.fault_df_in_econ()) - actual = results['fc15_flag'].sum() + actual = results["fc15_flag"].sum() expected = 2 message = f"FC15 fault_df_in_econ actual is {actual} and expected is {expected}" assert actual == expected, message def test_fault_in_os3(self): results = fc15.apply(self.fault_df_in_os3()) - actual = results['fc15_flag'].sum() + actual = results["fc15_flag"].sum() expected = 2 message = f"FC15 fault_df_in_os3 actual is {actual} and expected is {expected}" assert actual == expected, message + class TestFaultOnInt: def fault_df_on_output_int(self) -> pd.DataFrame: @@ -124,15 +128,17 @@ def fault_df_on_output_int(self) -> pd.DataFrame: TEST_HTG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], TEST_CLG_COIL_CMD_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], TEST_MIX_AIR_DAMPER_COL: [55, 55, 55, 55, 55, 55], # Incorrect type - TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55] + TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], } return pd.DataFrame(data) def test_fault_on_int(self): - with pytest.raises(TypeError, - match=HelperUtils().float_int_check_err(TEST_MIX_AIR_DAMPER_COL)): + with pytest.raises( + TypeError, match=HelperUtils().float_int_check_err(TEST_MIX_AIR_DAMPER_COL) + ): fc15.apply(self.fault_df_on_output_int()) + class TestFaultOnFloatGreaterThanOne: def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: @@ -142,15 +148,17 @@ def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: TEST_HTG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], TEST_CLG_COIL_CMD_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], TEST_MIX_AIR_DAMPER_COL: [1.1, 1.2, 1.1, 1.3, 1.1, 1.2], # Values > 1.0 - TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55] + TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], } return pd.DataFrame(data) def test_fault_on_float_greater_than_one(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_MIX_AIR_DAMPER_COL)): + with pytest.raises( + TypeError, match=HelperUtils().float_max_check_err(TEST_MIX_AIR_DAMPER_COL) + ): fc15.apply(self.fault_df_on_output_greater_than_one()) + class TestFaultOnMixedTypes: def fault_df_on_mixed_types(self) -> pd.DataFrame: @@ -160,14 +168,16 @@ def fault_df_on_mixed_types(self) -> pd.DataFrame: TEST_HTG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], TEST_CLG_COIL_CMD_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], TEST_MIX_AIR_DAMPER_COL: [1.1, 0.55, 1.2, 1.3, 0.55, 1.1], # Mixed types - TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55] + TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55], } return pd.DataFrame(data) def test_fault_on_mixed_types(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_MIX_AIR_DAMPER_COL)): + with pytest.raises( + TypeError, match=HelperUtils().float_max_check_err(TEST_MIX_AIR_DAMPER_COL) + ): fc15.apply(self.fault_df_on_mixed_types()) + if __name__ == "__main__": pytest.main() diff --git a/open_fdd/tests/ahu/test_ahu_fc2.py b/open_fdd/tests/ahu/test_ahu_fc2.py index 32ac96a..5729dbc 100644 --- a/open_fdd/tests/ahu/test_ahu_fc2.py +++ b/open_fdd/tests/ahu/test_ahu_fc2.py @@ -3,12 +3,12 @@ from open_fdd.air_handling_unit.faults.fault_condition_two import FaultConditionTwo from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils -''' +""" To see print statements in pytest run with: $ py -3.12 -m pytest tests/ahu/test_ahu_fc2.py -rP -s Mix air temp lower than out temp -''' +""" TEST_OUTDOOR_DEGF_ERR_THRES = 5.0 TEST_MIX_DEGF_ERR_THRES = 2.0 @@ -21,19 +21,20 @@ # Initialize FaultConditionTwo with a dictionary fault_condition_params = { - 'MIX_DEGF_ERR_THRES': TEST_MIX_DEGF_ERR_THRES, - 'RETURN_DEGF_ERR_THRES': TEST_RETURN_DEGF_ERR_THRES, - 'OUTDOOR_DEGF_ERR_THRES': TEST_OUTDOOR_DEGF_ERR_THRES, - 'MAT_COL': TEST_MIX_TEMP_COL, - 'RAT_COL': TEST_RETURN_TEMP_COL, - 'OAT_COL': TEST_OUT_TEMP_COL, - 'SUPPLY_VFD_SPEED_COL': TEST_SUPPLY_VFD_SPEED_COL, - 'TROUBLESHOOT_MODE': False, # default value - 'ROLLING_WINDOW_SIZE': TEST_ROLLING_WINDOW_SIZE + "MIX_DEGF_ERR_THRES": TEST_MIX_DEGF_ERR_THRES, + "RETURN_DEGF_ERR_THRES": TEST_RETURN_DEGF_ERR_THRES, + "OUTDOOR_DEGF_ERR_THRES": TEST_OUTDOOR_DEGF_ERR_THRES, + "MAT_COL": TEST_MIX_TEMP_COL, + "RAT_COL": TEST_RETURN_TEMP_COL, + "OAT_COL": TEST_OUT_TEMP_COL, + "SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL, + "TROUBLESHOOT_MODE": False, # default value + "ROLLING_WINDOW_SIZE": TEST_ROLLING_WINDOW_SIZE, } fc2 = FaultConditionTwo(fault_condition_params) + class TestNoFault(object): def no_fault_df(self) -> pd.DataFrame: @@ -47,7 +48,7 @@ def no_fault_df(self) -> pd.DataFrame: def test_no_fault(self): results = fc2.apply(self.no_fault_df()) - actual = results['fc2_flag'].sum() + actual = results["fc2_flag"].sum() expected = 0 message = f"fc2 no_fault_df actual is {actual} and expected is {expected}" print(message) @@ -67,7 +68,7 @@ def fault_df(self) -> pd.DataFrame: def test_fault(self): results = fc2.apply(self.fault_df()) - actual = results['fc2_flag'].sum() + actual = results["fc2_flag"].sum() expected = 2 message = f"fc2 fault_df actual is {actual} and expected is {expected}" print(message) @@ -86,8 +87,10 @@ def fault_df_on_output_int(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_int(self): - with pytest.raises(TypeError, - match=HelperUtils().float_int_check_err(TEST_SUPPLY_VFD_SPEED_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_int_check_err(TEST_SUPPLY_VFD_SPEED_COL), + ): fc2.apply(self.fault_df_on_output_int()) @@ -103,8 +106,10 @@ def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_float_greater_than_one(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL), + ): fc2.apply(self.fault_df_on_output_greater_than_one()) @@ -120,6 +125,8 @@ def fault_df_on_mixed_types(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_mixed_types(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL), + ): fc2.apply(self.fault_df_on_mixed_types()) diff --git a/open_fdd/tests/ahu/test_ahu_fc3.py b/open_fdd/tests/ahu/test_ahu_fc3.py index 772ce0e..d8a285b 100644 --- a/open_fdd/tests/ahu/test_ahu_fc3.py +++ b/open_fdd/tests/ahu/test_ahu_fc3.py @@ -3,12 +3,12 @@ from open_fdd.air_handling_unit.faults.fault_condition_three import FaultConditionThree from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils -''' +""" To see print statements in pytest run with: $ py -3.12 -m pytest tests/ahu/test_ahu_fc3.py -rP -s Mix air temp higher than out temp -''' +""" # Constants TEST_MIX_DEGF_ERR_THRES = 2.0 @@ -22,19 +22,20 @@ # Initialize FaultConditionThree with a dictionary fault_condition_params = { - 'MIX_DEGF_ERR_THRES': TEST_MIX_DEGF_ERR_THRES, - 'RETURN_DEGF_ERR_THRES': TEST_RETURN_DEGF_ERR_THRES, - 'OUTDOOR_DEGF_ERR_THRES': TEST_OUTDOOR_DEGF_ERR_THRES, - 'MAT_COL': TEST_MIX_TEMP_COL, - 'RAT_COL': TEST_RETURN_TEMP_COL, - 'OAT_COL': TEST_OUT_TEMP_COL, - 'SUPPLY_VFD_SPEED_COL': TEST_SUPPLY_VFD_SPEED_COL, - 'TROUBLESHOOT_MODE': False, # default value - 'ROLLING_WINDOW_SIZE': ROLLING_WINDOW_SIZE + "MIX_DEGF_ERR_THRES": TEST_MIX_DEGF_ERR_THRES, + "RETURN_DEGF_ERR_THRES": TEST_RETURN_DEGF_ERR_THRES, + "OUTDOOR_DEGF_ERR_THRES": TEST_OUTDOOR_DEGF_ERR_THRES, + "MAT_COL": TEST_MIX_TEMP_COL, + "RAT_COL": TEST_RETURN_TEMP_COL, + "OAT_COL": TEST_OUT_TEMP_COL, + "SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL, + "TROUBLESHOOT_MODE": False, # default value + "ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE, } fc3 = FaultConditionThree(fault_condition_params) + class TestNoFault: def no_fault_df(self) -> pd.DataFrame: @@ -42,17 +43,18 @@ def no_fault_df(self) -> pd.DataFrame: TEST_MIX_TEMP_COL: [55.0, 56.0, 57.0, 56.0, 55.5, 55.0], TEST_RETURN_TEMP_COL: [70.0, 71.0, 72.0, 70.0, 71.0, 70.0], TEST_OUT_TEMP_COL: [50.0, 51.0, 52.0, 50.0, 51.0, 50.0], - TEST_SUPPLY_VFD_SPEED_COL: [0.8, 0.82, 0.83, 0.8, 0.82, 0.8] + TEST_SUPPLY_VFD_SPEED_COL: [0.8, 0.82, 0.83, 0.8, 0.82, 0.8], } return pd.DataFrame(data) def test_no_fault(self): results = fc3.apply(self.no_fault_df()) - actual = results['fc3_flag'].sum() + actual = results["fc3_flag"].sum() expected = 0 message = f"FC3 no_fault_df actual is {actual} and expected is {expected}" assert actual == expected, message + class TestFault: def fault_df(self) -> pd.DataFrame: @@ -60,17 +62,18 @@ def fault_df(self) -> pd.DataFrame: TEST_MIX_TEMP_COL: [80.0, 81.0, 79.0, 80.5, 82.0, 80.0], TEST_RETURN_TEMP_COL: [70.0, 70.5, 71.0, 70.0, 70.5, 70.0], TEST_OUT_TEMP_COL: [50.0, 51.0, 52.0, 50.5, 51.0, 50.0], - TEST_SUPPLY_VFD_SPEED_COL: [0.9, 0.91, 0.92, 0.9, 0.91, 0.9] + TEST_SUPPLY_VFD_SPEED_COL: [0.9, 0.91, 0.92, 0.9, 0.91, 0.9], } return pd.DataFrame(data) def test_fault(self): results = fc3.apply(self.fault_df()) - actual = results['fc3_flag'].sum() + actual = results["fc3_flag"].sum() expected = 2 message = f"FC3 fault_df actual is {actual} and expected is {expected}" assert actual == expected, message + class TestFaultOnInt: def fault_df_on_output_int(self) -> pd.DataFrame: @@ -83,10 +86,13 @@ def fault_df_on_output_int(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_int(self): - with pytest.raises(TypeError, - match=HelperUtils().float_int_check_err(TEST_SUPPLY_VFD_SPEED_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_int_check_err(TEST_SUPPLY_VFD_SPEED_COL), + ): fc3.apply(self.fault_df_on_output_int()) + class TestFaultOnFloatGreaterThanOne: def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: @@ -99,10 +105,13 @@ def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_float_greater_than_one(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL), + ): fc3.apply(self.fault_df_on_output_greater_than_one()) + class TestFaultOnMixedTypes: def fault_df_on_mixed_types(self) -> pd.DataFrame: @@ -115,6 +124,8 @@ def fault_df_on_mixed_types(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_mixed_types(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL), + ): fc3.apply(self.fault_df_on_mixed_types()) diff --git a/open_fdd/tests/ahu/test_ahu_fc4.py b/open_fdd/tests/ahu/test_ahu_fc4.py index 61627b9..e0177d3 100644 --- a/open_fdd/tests/ahu/test_ahu_fc4.py +++ b/open_fdd/tests/ahu/test_ahu_fc4.py @@ -4,13 +4,13 @@ from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils from datetime import datetime, timezone -''' +""" To see print statements in pytest run with: $ py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc4.py -rP -s Too much hunting in control system OS state changes greater than 7 in an hour -''' +""" # Constants DELTA_OS_MAX = 7 @@ -23,26 +23,30 @@ # Initialize FaultConditionFour with a dictionary fault_condition_params = { - 'DELTA_OS_MAX': DELTA_OS_MAX, - 'AHU_MIN_OA_DPR': AHU_MIN_OA, - 'ECONOMIZER_SIG_COL': TEST_MIX_AIR_DAMPER_COL, - 'HEATING_SIG_COL': TEST_HEATING_COIL_SIG_COL, - 'COOLING_SIG_COL': TEST_COOLING_COIL_SIG_COL, - 'SUPPLY_VFD_SPEED_COL': TEST_SUPPLY_VFD_SPEED_COL, - 'TROUBLESHOOT_MODE': False # default value + "DELTA_OS_MAX": DELTA_OS_MAX, + "AHU_MIN_OA_DPR": AHU_MIN_OA, + "ECONOMIZER_SIG_COL": TEST_MIX_AIR_DAMPER_COL, + "HEATING_SIG_COL": TEST_HEATING_COIL_SIG_COL, + "COOLING_SIG_COL": TEST_COOLING_COIL_SIG_COL, + "SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL, + "TROUBLESHOOT_MODE": False, # default value } fc4 = FaultConditionFour(fault_condition_params) + def generate_timestamp() -> pd.Series: df = pd.DataFrame() date_range = pd.period_range( # make a time stamp starting at top of # the hour with one min intervals start=datetime(2022, 6, 6, 14, 30, 0, 0, tzinfo=timezone.utc), - periods=TEST_DATASET_ROWS, freq='min') - df['Date'] = [x.to_timestamp() for x in date_range] - return df['Date'] + periods=TEST_DATASET_ROWS, + freq="min", + ) + df["Date"] = [x.to_timestamp() for x in date_range] + return df["Date"] + def econ_plus_mech_clg_row() -> dict: data = { @@ -53,6 +57,7 @@ def econ_plus_mech_clg_row() -> dict: } return data + def mech_clg_row() -> dict: data = { TEST_MIX_AIR_DAMPER_COL: 0.0, @@ -62,6 +67,7 @@ def mech_clg_row() -> dict: } return data + def econ_plus_mech_clg_row_int() -> dict: data = { TEST_MIX_AIR_DAMPER_COL: 0.6, @@ -71,6 +77,7 @@ def econ_plus_mech_clg_row_int() -> dict: } return data + def econ_plus_mech_clg_row_float_greater_than_one() -> dict: data = { TEST_MIX_AIR_DAMPER_COL: 0.6, @@ -80,6 +87,7 @@ def econ_plus_mech_clg_row_float_greater_than_one() -> dict: } return data + class TestFault(object): def fault_df(self) -> pd.DataFrame: @@ -101,6 +109,7 @@ def test_fault(self): message = f"FC4 fault_df actual is {actual} and expected is {expected}" assert actual == expected, message + class TestNoFault(object): def no_fault_df(self) -> pd.DataFrame: @@ -117,6 +126,7 @@ def test_no_fault(self): message = f"FC4 no_fault_df actual is {actual} and expected is {expected}" assert actual == expected, message + class TestFaultOnInt(object): def fault_df_on_output_int(self) -> pd.DataFrame: @@ -129,11 +139,16 @@ def fault_df_on_output_int(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_int(self): - fault_df_on_output_int = self.fault_df_on_output_int().set_index(generate_timestamp()) - with pytest.raises(TypeError, - match=HelperUtils().float_int_check_err(TEST_SUPPLY_VFD_SPEED_COL)): + fault_df_on_output_int = self.fault_df_on_output_int().set_index( + generate_timestamp() + ) + with pytest.raises( + TypeError, + match=HelperUtils().float_int_check_err(TEST_SUPPLY_VFD_SPEED_COL), + ): fc4.apply(fault_df_on_output_int) + class TestFaultOnFloatGreaterThanOne(object): def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: @@ -146,29 +161,40 @@ def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_float_greater_than_one(self): - fault_df_on_output_greater_than_one = self.fault_df_on_output_greater_than_one().set_index(generate_timestamp()) - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL)): + fault_df_on_output_greater_than_one = ( + self.fault_df_on_output_greater_than_one().set_index(generate_timestamp()) + ) + with pytest.raises( + TypeError, + match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL), + ): fc4.apply(fault_df_on_output_greater_than_one) + class TestFaultOnMixedTypes(object): def fault_df_on_mixed_types(self) -> pd.DataFrame: data = [] for i in range(TEST_DATASET_ROWS): if i % 2 == 0: - data.append({ - TEST_MIX_AIR_DAMPER_COL: 0.6, - TEST_HEATING_COIL_SIG_COL: 0.0, - TEST_COOLING_COIL_SIG_COL: 0.6, - TEST_SUPPLY_VFD_SPEED_COL: 1.1, - }) + data.append( + { + TEST_MIX_AIR_DAMPER_COL: 0.6, + TEST_HEATING_COIL_SIG_COL: 0.0, + TEST_COOLING_COIL_SIG_COL: 0.6, + TEST_SUPPLY_VFD_SPEED_COL: 1.1, + } + ) else: data.append(mech_clg_row()) return pd.DataFrame(data) def test_fault_on_mixed_types(self): - fault_df_on_mixed_types = self.fault_df_on_mixed_types().set_index(generate_timestamp()) - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL)): + fault_df_on_mixed_types = self.fault_df_on_mixed_types().set_index( + generate_timestamp() + ) + with pytest.raises( + TypeError, + match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL), + ): fc4.apply(fault_df_on_mixed_types) diff --git a/open_fdd/tests/ahu/test_ahu_fc5.py b/open_fdd/tests/ahu/test_ahu_fc5.py index 2ba308b..22ba44b 100644 --- a/open_fdd/tests/ahu/test_ahu_fc5.py +++ b/open_fdd/tests/ahu/test_ahu_fc5.py @@ -3,12 +3,12 @@ from open_fdd.air_handling_unit.faults.fault_condition_five import FaultConditionFive from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils -''' +""" To see print statements in pytest run with: $ py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc5.py -rP -s SAT too low; should be higher than MAT in HTG MODE -''' +""" # Constants TEST_MIX_DEGF_ERR_THRES = 2.0 @@ -22,19 +22,20 @@ # Initialize FaultConditionFive with a dictionary fault_condition_params = { - 'MIX_DEGF_ERR_THRES': TEST_MIX_DEGF_ERR_THRES, - 'SUPPLY_DEGF_ERR_THRES': TEST_SUPPLY_DEGF_ERR_THRES, - 'DELTA_T_SUPPLY_FAN': TEST_DELTA_T_SUPPLY_FAN, - 'MAT_COL': TEST_MIX_TEMP_COL, - 'SAT_COL': TEST_SUPPLY_TEMP_COL, - 'HEATING_SIG_COL': TEST_HEATING_COIL_SIG_COL, - 'SUPPLY_VFD_SPEED_COL': TEST_SUPPLY_VFD_SPEED_COL, - 'TROUBLESHOOT_MODE': False, # default value - 'ROLLING_WINDOW_SIZE': ROLLING_WINDOW_SIZE # rolling sum window size + "MIX_DEGF_ERR_THRES": TEST_MIX_DEGF_ERR_THRES, + "SUPPLY_DEGF_ERR_THRES": TEST_SUPPLY_DEGF_ERR_THRES, + "DELTA_T_SUPPLY_FAN": TEST_DELTA_T_SUPPLY_FAN, + "MAT_COL": TEST_MIX_TEMP_COL, + "SAT_COL": TEST_SUPPLY_TEMP_COL, + "HEATING_SIG_COL": TEST_HEATING_COIL_SIG_COL, + "SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL, + "TROUBLESHOOT_MODE": False, # default value + "ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE, # rolling sum window size } fc5 = FaultConditionFive(fault_condition_params) + class TestNoFaultInHtg(object): def no_fault_df_in_htg(self) -> pd.DataFrame: @@ -48,17 +49,27 @@ def no_fault_df_in_htg(self) -> pd.DataFrame: def test_no_fault_in_htg(self): results = fc5.apply(self.no_fault_df_in_htg()) - actual = results['fc5_flag'].sum() + actual = results["fc5_flag"].sum() expected = 0 - message = f"FC5 no_fault_df_in_htg actual is {actual} and expected is {expected}" + message = ( + f"FC5 no_fault_df_in_htg actual is {actual} and expected is {expected}" + ) assert actual == expected, message + class TestFaultInHtg(object): def fault_df_in_htg(self) -> pd.DataFrame: data = { TEST_MIX_TEMP_COL: [80.0, 81.0, 79.0, 80.5, 82.0, 80.0], - TEST_SUPPLY_TEMP_COL: [81.0, 81.0, 79.0, 80.5, 82.0, 80.0], #sim not temp rise + TEST_SUPPLY_TEMP_COL: [ + 81.0, + 81.0, + 79.0, + 80.5, + 82.0, + 80.0, + ], # sim not temp rise TEST_HEATING_COIL_SIG_COL: [0.55, 0.56, 0.54, 0.55, 0.57, 0.55], TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.56, 0.54, 0.55, 0.57, 0.55], } @@ -66,11 +77,12 @@ def fault_df_in_htg(self) -> pd.DataFrame: def test_fault_in_htg(self): results = fc5.apply(self.fault_df_in_htg()) - actual = results['fc5_flag'].sum() + actual = results["fc5_flag"].sum() expected = 2 message = f"FC5 fault_df_in_htg actual is {actual} and expected is {expected}" assert actual == expected, message + class TestNoFaultNoHtg(object): def no_fault_df_no_htg(self) -> pd.DataFrame: @@ -84,11 +96,14 @@ def no_fault_df_no_htg(self) -> pd.DataFrame: def test_no_fault_no_htg(self): results = fc5.apply(self.no_fault_df_no_htg()) - actual = results['fc5_flag'].sum() + actual = results["fc5_flag"].sum() expected = 0 - message = f"FC5 no_fault_df_no_htg actual is {actual} and expected is {expected}" + message = ( + f"FC5 no_fault_df_no_htg actual is {actual} and expected is {expected}" + ) assert actual == expected, message + class TestFaultNoHtg(object): def fault_df_no_htg(self) -> pd.DataFrame: @@ -102,11 +117,12 @@ def fault_df_no_htg(self) -> pd.DataFrame: def test_fault_no_htg(self): results = fc5.apply(self.fault_df_no_htg()) - actual = results['fc5_flag'].sum() + actual = results["fc5_flag"].sum() expected = 0 message = f"FC5 fault_df_no_htg actual is {actual} and expected is {expected}" assert actual == expected, message + class TestFaultOnInt(object): def fault_df_on_output_int(self) -> pd.DataFrame: @@ -119,10 +135,13 @@ def fault_df_on_output_int(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_int(self): - with pytest.raises(TypeError, - match=HelperUtils().float_int_check_err(TEST_SUPPLY_VFD_SPEED_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_int_check_err(TEST_SUPPLY_VFD_SPEED_COL), + ): fc5.apply(self.fault_df_on_output_int()) + class TestFaultOnFloatGreaterThanOne(object): def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: @@ -135,10 +154,13 @@ def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_float_greater_than_one(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL), + ): fc5.apply(self.fault_df_on_output_greater_than_one()) + class TestFaultOnMixedTypes(object): def fault_df_on_mixed_types(self) -> pd.DataFrame: @@ -151,6 +173,8 @@ def fault_df_on_mixed_types(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_mixed_types(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL), + ): fc5.apply(self.fault_df_on_mixed_types()) diff --git a/open_fdd/tests/ahu/test_ahu_fc6.py b/open_fdd/tests/ahu/test_ahu_fc6.py index 14d7873..16f0e25 100644 --- a/open_fdd/tests/ahu/test_ahu_fc6.py +++ b/open_fdd/tests/ahu/test_ahu_fc6.py @@ -3,12 +3,12 @@ from open_fdd.air_handling_unit.faults.fault_condition_six import FaultConditionSix from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils -''' +""" To see print statements in pytest run with: $ py -3.12 -m pytest tests/ahu/test_ahu_fc6.py -rP -s OA FRACTION TOO LOW OR TOO HIGH; SHOULD BE EQUAL TO %OAmin -''' +""" # Constants TEST_AIRFLOW_ERR_THRES = 0.3 @@ -29,26 +29,27 @@ # Initialize FaultConditionSix with a dictionary fault_condition_params = { - 'AIRFLOW_ERR_THRES': TEST_AIRFLOW_ERR_THRES, - 'AHU_MIN_OA_CFM_DESIGN': TEST_AHU_MIN_CFM_DESIGN, - 'OUTDOOR_DEGF_ERR_THRES': TEST_OAT_DEGF_ERR_THRES, - 'RETURN_DEGF_ERR_THRES': TEST_RAT_DEGF_ERR_THRES, - 'OAT_RAT_DELTA_MIN': TEST_DELTA_TEMP_MIN, - 'AHU_MIN_OA_DPR': TEST_AHU_MIN_OA_DPR, - 'SUPPLY_FAN_AIR_VOLUME_COL': TEST_VAV_TOTAL_AIR_FLOW_COL, - 'MAT_COL': TEST_MIX_TEMP_COL, - 'OAT_COL': TEST_OUT_TEMP_COL, - 'RAT_COL': TEST_RETURN_TEMP_COL, - 'SUPPLY_VFD_SPEED_COL': TEST_SUPPLY_VFD_SPEED_COL, - 'ECONOMIZER_SIG_COL': TEST_MIX_AIR_DAMPER_COL, - 'HEATING_SIG_COL': TEST_HEATING_COIL_SIG_COL, - 'COOLING_SIG_COL': TEST_COOLING_COIL_SIG_COL, - 'TROUBLESHOOT_MODE': False, # default value - 'ROLLING_WINDOW_SIZE': ROLLING_WINDOW_SIZE # rolling sum window size + "AIRFLOW_ERR_THRES": TEST_AIRFLOW_ERR_THRES, + "AHU_MIN_OA_CFM_DESIGN": TEST_AHU_MIN_CFM_DESIGN, + "OUTDOOR_DEGF_ERR_THRES": TEST_OAT_DEGF_ERR_THRES, + "RETURN_DEGF_ERR_THRES": TEST_RAT_DEGF_ERR_THRES, + "OAT_RAT_DELTA_MIN": TEST_DELTA_TEMP_MIN, + "AHU_MIN_OA_DPR": TEST_AHU_MIN_OA_DPR, + "SUPPLY_FAN_AIR_VOLUME_COL": TEST_VAV_TOTAL_AIR_FLOW_COL, + "MAT_COL": TEST_MIX_TEMP_COL, + "OAT_COL": TEST_OUT_TEMP_COL, + "RAT_COL": TEST_RETURN_TEMP_COL, + "SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL, + "ECONOMIZER_SIG_COL": TEST_MIX_AIR_DAMPER_COL, + "HEATING_SIG_COL": TEST_HEATING_COIL_SIG_COL, + "COOLING_SIG_COL": TEST_COOLING_COIL_SIG_COL, + "TROUBLESHOOT_MODE": False, # default value + "ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE, # rolling sum window size } fc6 = FaultConditionSix(fault_condition_params) + class TestNoFaultNoHtg(object): def no_fault_df_no_htg(self) -> pd.DataFrame: @@ -66,11 +67,14 @@ def no_fault_df_no_htg(self) -> pd.DataFrame: def test_no_fault_no_htg(self): results = fc6.apply(self.no_fault_df_no_htg()) - actual = results['fc6_flag'].sum() + actual = results["fc6_flag"].sum() expected = 0 - message = f"FC6 no_fault_df_no_htg actual is {actual} and expected is {expected}" + message = ( + f"FC6 no_fault_df_no_htg actual is {actual} and expected is {expected}" + ) assert actual == expected, message + class TestFaultInHtg(object): def fault_df_in_htg(self) -> pd.DataFrame: @@ -88,11 +92,12 @@ def fault_df_in_htg(self) -> pd.DataFrame: def test_fault_in_htg(self): results = fc6.apply(self.fault_df_in_htg()) - actual = results['fc6_flag'].sum() + actual = results["fc6_flag"].sum() expected = 2 message = f"FC6 fault_df_in_htg actual is {actual} and expected is {expected}" assert actual == expected, message + class TestNoFaultInEconMode(object): def no_fault_df_in_econ(self) -> pd.DataFrame: @@ -110,11 +115,14 @@ def no_fault_df_in_econ(self) -> pd.DataFrame: def test_no_fault_in_econ_mode(self): results = fc6.apply(self.no_fault_df_in_econ()) - actual = results['fc6_flag'].sum() + actual = results["fc6_flag"].sum() expected = 0 - message = f"FC6 no_fault_df_in_econ actual is {actual} and expected is {expected}" + message = ( + f"FC6 no_fault_df_in_econ actual is {actual} and expected is {expected}" + ) assert actual == expected, message + class TestNoFaultInEconPlusMechClg(object): def no_fault_df_in_econ_plus_mech(self) -> pd.DataFrame: @@ -132,11 +140,12 @@ def no_fault_df_in_econ_plus_mech(self) -> pd.DataFrame: def test_no_fault_in_econ_plus_mech_mode(self): results = fc6.apply(self.no_fault_df_in_econ_plus_mech()) - actual = results['fc6_flag'].sum() + actual = results["fc6_flag"].sum() expected = 0 message = f"FC6 no_fault_df_in_econ_plus_mech actual is {actual} and expected is {expected}" assert actual == expected, message + class TestFaultInMechClg(object): def fault_df_in_mech_clg(self) -> pd.DataFrame: @@ -154,11 +163,14 @@ def fault_df_in_mech_clg(self) -> pd.DataFrame: def test_fault_in_mech_mode(self): results = fc6.apply(self.fault_df_in_mech_clg()) - actual = results['fc6_flag'].sum() + actual = results["fc6_flag"].sum() expected = 2 - message = f"FC6 fault_df_in_mech_clg actual is {actual} and expected is {expected}" + message = ( + f"FC6 fault_df_in_mech_clg actual is {actual} and expected is {expected}" + ) assert actual == expected, message + class TestNoFaultInMechClg(object): def no_fault_df_in_mech_clg(self) -> pd.DataFrame: @@ -176,11 +188,14 @@ def no_fault_df_in_mech_clg(self) -> pd.DataFrame: def test_no_fault_in_mech_mode(self): results = fc6.apply(self.no_fault_df_in_mech_clg()) - actual = results['fc6_flag'].sum() + actual = results["fc6_flag"].sum() expected = 0 - message = f"FC6 no_fault_df_in_mech_clg actual is {actual} and expected is {expected}" + message = ( + f"FC6 no_fault_df_in_mech_clg actual is {actual} and expected is {expected}" + ) assert actual == expected, message + class TestFaultOnInt(object): def fault_df_on_output_int(self) -> pd.DataFrame: @@ -197,10 +212,13 @@ def fault_df_on_output_int(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_int(self): - with pytest.raises(TypeError, - match=HelperUtils().float_int_check_err(TEST_SUPPLY_VFD_SPEED_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_int_check_err(TEST_SUPPLY_VFD_SPEED_COL), + ): fc6.apply(self.fault_df_on_output_int()) + class TestFaultOnFloatGreaterThanOne(object): def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: @@ -217,9 +235,12 @@ def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_float_greater_than_one(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL), + ): fc6.apply(self.fault_df_on_output_greater_than_one()) + if __name__ == "__main__": pytest.main() diff --git a/open_fdd/tests/ahu/test_ahu_fc7.py b/open_fdd/tests/ahu/test_ahu_fc7.py index b3b37d7..b40e94c 100644 --- a/open_fdd/tests/ahu/test_ahu_fc7.py +++ b/open_fdd/tests/ahu/test_ahu_fc7.py @@ -3,12 +3,12 @@ from open_fdd.air_handling_unit.faults.fault_condition_seven import FaultConditionSeven from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils -''' +""" To see print statements in pytest run with: $ py -3.12 -m pytest tests/ahu/test_ahu_fc7.py -rP -s Supply air temperature too low in full heating. -''' +""" # Constants TEST_SUPPLY_DEGF_ERR_THRES = 2.0 @@ -20,17 +20,18 @@ # Initialize FaultConditionSeven with a dictionary fault_condition_params = { - 'SUPPLY_DEGF_ERR_THRES': TEST_SUPPLY_DEGF_ERR_THRES, - 'SAT_COL': TEST_SAT_COL, - 'SAT_SETPOINT_COL': TEST_SAT_SETPOINT_COL, - 'HEATING_SIG_COL': TEST_HEATING_SIG_COL, - 'SUPPLY_VFD_SPEED_COL': TEST_SUPPLY_VFD_SPEED_COL, - 'TROUBLESHOOT_MODE': False, # default value - 'ROLLING_WINDOW_SIZE': ROLLING_WINDOW_SIZE # rolling sum window size + "SUPPLY_DEGF_ERR_THRES": TEST_SUPPLY_DEGF_ERR_THRES, + "SAT_COL": TEST_SAT_COL, + "SAT_SETPOINT_COL": TEST_SAT_SETPOINT_COL, + "HEATING_SIG_COL": TEST_HEATING_SIG_COL, + "SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL, + "TROUBLESHOOT_MODE": False, # default value + "ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE, # rolling sum window size } fc7 = FaultConditionSeven(fault_condition_params) + class TestFaultConditionSeven: def fault_df(self) -> pd.DataFrame: @@ -53,17 +54,18 @@ def no_fault_df(self) -> pd.DataFrame: def test_fault_condition_seven(self): results = fc7.apply(self.fault_df()) - actual = results['fc7_flag'].sum() + actual = results["fc7_flag"].sum() expected = 2 message = f"FC7 fault_df actual is {actual} and expected is {expected}" assert actual == expected, message def test_no_fault_condition_seven(self): results = fc7.apply(self.no_fault_df()) - actual = results['fc7_flag'].sum() + actual = results["fc7_flag"].sum() expected = 0 message = f"FC7 no_fault_df actual is {actual} and expected is {expected}" assert actual == expected, message + if __name__ == "__main__": pytest.main() diff --git a/open_fdd/tests/ahu/test_ahu_fc8.py b/open_fdd/tests/ahu/test_ahu_fc8.py index 615796f..17cceb0 100644 --- a/open_fdd/tests/ahu/test_ahu_fc8.py +++ b/open_fdd/tests/ahu/test_ahu_fc8.py @@ -3,12 +3,12 @@ from open_fdd.air_handling_unit.faults.fault_condition_eight import FaultConditionEight from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils -''' +""" To see print statements in pytest run with: $ py -3.12 -m pytest tests/ahu/test_ahu_fc8.py -rP -s Supply air temperature approx equal mix temp in full econ. -''' +""" # Constants TEST_DELTA_T_SUPPLY_FAN = 2.0 @@ -23,20 +23,21 @@ # Initialize FaultConditionEight with a dictionary fault_condition_params = { - 'DELTA_T_SUPPLY_FAN': TEST_DELTA_T_SUPPLY_FAN, - 'MIX_DEGF_ERR_THRES': TEST_MIX_DEGF_ERR_THRES, - 'SUPPLY_DEGF_ERR_THRES': TEST_SUPPLY_DEGF_ERR_THRES, - 'AHU_MIN_OA_DPR': TEST_AHU_MIN_OA_DPR, - 'MAT_COL': TEST_MAT_COL, - 'SAT_COL': TEST_SAT_COL, - 'ECONOMIZER_SIG_COL': TEST_ECONOMIZER_SIG_COL, - 'COOLING_SIG_COL': TEST_COOLING_SIG_COL, - 'TROUBLESHOOT_MODE': False, - 'ROLLING_WINDOW_SIZE': ROLLING_WINDOW_SIZE + "DELTA_T_SUPPLY_FAN": TEST_DELTA_T_SUPPLY_FAN, + "MIX_DEGF_ERR_THRES": TEST_MIX_DEGF_ERR_THRES, + "SUPPLY_DEGF_ERR_THRES": TEST_SUPPLY_DEGF_ERR_THRES, + "AHU_MIN_OA_DPR": TEST_AHU_MIN_OA_DPR, + "MAT_COL": TEST_MAT_COL, + "SAT_COL": TEST_SAT_COL, + "ECONOMIZER_SIG_COL": TEST_ECONOMIZER_SIG_COL, + "COOLING_SIG_COL": TEST_COOLING_SIG_COL, + "TROUBLESHOOT_MODE": False, + "ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE, } fc8 = FaultConditionEight(fault_condition_params) + class TestFaultConditionEight: def fault_df(self) -> pd.DataFrame: @@ -59,18 +60,19 @@ def no_fault_df(self) -> pd.DataFrame: def test_fault_condition_eight(self): results = fc8.apply(self.fault_df()) - actual = results['fc8_flag'].sum() + actual = results["fc8_flag"].sum() expected = 2 message = f"FC8 fault_df actual is {actual} and expected is {expected}" assert actual == expected, message def test_no_fault_condition_eight(self): results = fc8.apply(self.no_fault_df()) - actual = results['fc8_flag'].sum() + actual = results["fc8_flag"].sum() expected = 0 message = f"FC8 no_fault_df actual is {actual} and expected is {expected}" assert actual == expected, message + class TestFaultOnInt: def fault_df_on_output_int(self) -> pd.DataFrame: @@ -83,10 +85,12 @@ def fault_df_on_output_int(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_int(self): - with pytest.raises(TypeError, - match=HelperUtils().float_int_check_err(TEST_COOLING_SIG_COL)): + with pytest.raises( + TypeError, match=HelperUtils().float_int_check_err(TEST_COOLING_SIG_COL) + ): fc8.apply(self.fault_df_on_output_int()) + class TestFaultOnFloatGreaterThanOne: def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: @@ -99,10 +103,12 @@ def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_float_greater_than_one(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_COOLING_SIG_COL)): + with pytest.raises( + TypeError, match=HelperUtils().float_max_check_err(TEST_COOLING_SIG_COL) + ): fc8.apply(self.fault_df_on_output_greater_than_one()) + class TestFaultOnMixedTypes: def fault_df_on_mixed_types(self) -> pd.DataFrame: @@ -115,9 +121,11 @@ def fault_df_on_mixed_types(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_mixed_types(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_COOLING_SIG_COL)): + with pytest.raises( + TypeError, match=HelperUtils().float_max_check_err(TEST_COOLING_SIG_COL) + ): fc8.apply(self.fault_df_on_mixed_types()) + if __name__ == "__main__": pytest.main() diff --git a/open_fdd/tests/ahu/test_ahu_fc9.py b/open_fdd/tests/ahu/test_ahu_fc9.py index bf086d8..9050cee 100644 --- a/open_fdd/tests/ahu/test_ahu_fc9.py +++ b/open_fdd/tests/ahu/test_ahu_fc9.py @@ -3,12 +3,12 @@ from open_fdd.air_handling_unit.faults.fault_condition_nine import FaultConditionNine from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils -''' +""" To see print statements in pytest run with: $ py -3.12 -m pytest tests/ahu/test_ahu_fc9.py -rP -s OAT too high in economizer mode without mechanical clg -''' +""" # Constants TEST_DELTA_SUPPLY_FAN = 2.0 @@ -23,20 +23,21 @@ # Initialize FaultConditionNine with a dictionary fault_condition_params = { - 'DELTA_T_SUPPLY_FAN': TEST_DELTA_SUPPLY_FAN, - 'OUTDOOR_DEGF_ERR_THRES': TEST_OAT_DEGF_ERR_THRES, - 'SUPPLY_DEGF_ERR_THRES': TEST_SUPPLY_DEGF_ERR_THRES, - 'AHU_MIN_OA_DPR': TEST_AHU_MIN_OA_DPR, - 'SAT_SETPOINT_COL': TEST_SAT_SP_COL, - 'OAT_COL': TEST_OAT_COL, - 'COOLING_SIG_COL': TEST_COOLING_COIL_SIG_COL, - 'ECONOMIZER_SIG_COL': TEST_MIX_AIR_DAMPER_COL, - 'TROUBLESHOOT_MODE': False, - 'ROLLING_WINDOW_SIZE': ROLLING_WINDOW_SIZE + "DELTA_T_SUPPLY_FAN": TEST_DELTA_SUPPLY_FAN, + "OUTDOOR_DEGF_ERR_THRES": TEST_OAT_DEGF_ERR_THRES, + "SUPPLY_DEGF_ERR_THRES": TEST_SUPPLY_DEGF_ERR_THRES, + "AHU_MIN_OA_DPR": TEST_AHU_MIN_OA_DPR, + "SAT_SETPOINT_COL": TEST_SAT_SP_COL, + "OAT_COL": TEST_OAT_COL, + "COOLING_SIG_COL": TEST_COOLING_COIL_SIG_COL, + "ECONOMIZER_SIG_COL": TEST_MIX_AIR_DAMPER_COL, + "TROUBLESHOOT_MODE": False, + "ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE, } fc9 = FaultConditionNine(fault_condition_params) + class TestFaultConditionNine: def no_fault_df_no_econ(self) -> pd.DataFrame: @@ -59,18 +60,21 @@ def fault_df_in_econ(self) -> pd.DataFrame: def test_no_fault_no_econ(self): results = fc9.apply(self.no_fault_df_no_econ()) - actual = results['fc9_flag'].sum() + actual = results["fc9_flag"].sum() expected = 0 - message = f"FC9 no_fault_df_no_econ actual is {actual} and expected is {expected}" + message = ( + f"FC9 no_fault_df_no_econ actual is {actual} and expected is {expected}" + ) assert actual == expected, message def test_fault_in_econ(self): results = fc9.apply(self.fault_df_in_econ()) - actual = results['fc9_flag'].sum() + actual = results["fc9_flag"].sum() expected = 2 message = f"FC9 fault_df_in_econ actual is {actual} and expected is {expected}" assert actual == expected, message + class TestFaultOnInt: def fault_df_on_output_int(self) -> pd.DataFrame: @@ -83,10 +87,13 @@ def fault_df_on_output_int(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_int(self): - with pytest.raises(TypeError, - match=HelperUtils().float_int_check_err(TEST_COOLING_COIL_SIG_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_int_check_err(TEST_COOLING_COIL_SIG_COL), + ): fc9.apply(self.fault_df_on_output_int()) + class TestFaultOnFloatGreaterThanOne: def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: @@ -99,10 +106,13 @@ def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_float_greater_than_one(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_COOLING_COIL_SIG_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_max_check_err(TEST_COOLING_COIL_SIG_COL), + ): fc9.apply(self.fault_df_on_output_greater_than_one()) + class TestFaultOnMixedTypes: def fault_df_on_mixed_types(self) -> pd.DataFrame: @@ -115,9 +125,12 @@ def fault_df_on_mixed_types(self) -> pd.DataFrame: return pd.DataFrame(data) def test_fault_on_mixed_types(self): - with pytest.raises(TypeError, - match=HelperUtils().float_max_check_err(TEST_COOLING_COIL_SIG_COL)): + with pytest.raises( + TypeError, + match=HelperUtils().float_max_check_err(TEST_COOLING_COIL_SIG_COL), + ): fc9.apply(self.fault_df_on_mixed_types()) + if __name__ == "__main__": pytest.main() diff --git a/setup.py b/setup.py index 937d942..8b6a9d0 100644 --- a/setup.py +++ b/setup.py @@ -1,19 +1,21 @@ from setuptools import setup, find_packages + def read_long_description(file_path): - with open(file_path, encoding='utf-8') as f: + with open(file_path, encoding="utf-8") as f: return f.read() + setup( name="open_fdd", version="0.1.0", author="Ben Bartling", author_email="ben.bartling@gmail.com", description="A package for fault detection and diagnosis in HVAC systems", - long_description=read_long_description('README.md'), - long_description_content_type='text/markdown', + long_description=read_long_description("README.md"), + long_description_content_type="text/markdown", url="https://github.com/bbartling/open-fdd", - packages=find_packages(include=['open_fdd', 'open_fdd.*']), + packages=find_packages(include=["open_fdd", "open_fdd.*"]), install_requires=[ "pandas", "matplotlib", @@ -24,6 +26,5 @@ def read_long_description(file_path): "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], - python_requires='>=3.6', - + python_requires=">=3.6", )