open-fdd 0.1.7__py3-none-any.whl → 0.1.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. open_fdd/air_handling_unit/faults/__init__.py +29 -2283
  2. open_fdd/air_handling_unit/faults/fault_condition_eight.py +135 -0
  3. open_fdd/air_handling_unit/faults/fault_condition_eleven.py +108 -0
  4. open_fdd/air_handling_unit/faults/fault_condition_fifteen.py +189 -0
  5. open_fdd/air_handling_unit/faults/fault_condition_five.py +126 -0
  6. open_fdd/air_handling_unit/faults/fault_condition_four.py +128 -0
  7. open_fdd/air_handling_unit/faults/fault_condition_fourteen.py +177 -0
  8. open_fdd/air_handling_unit/faults/fault_condition_nine.py +140 -0
  9. open_fdd/air_handling_unit/faults/fault_condition_one.py +113 -0
  10. open_fdd/air_handling_unit/faults/fault_condition_seven.py +106 -0
  11. open_fdd/air_handling_unit/faults/fault_condition_six.py +228 -0
  12. open_fdd/air_handling_unit/faults/fault_condition_sixteen.py +196 -0
  13. open_fdd/air_handling_unit/faults/fault_condition_ten.py +119 -0
  14. open_fdd/air_handling_unit/faults/fault_condition_thirteen.py +139 -0
  15. open_fdd/air_handling_unit/faults/fault_condition_three.py +112 -0
  16. open_fdd/air_handling_unit/faults/fault_condition_twelve.py +164 -0
  17. open_fdd/air_handling_unit/faults/fault_condition_two.py +112 -0
  18. open_fdd/air_handling_unit/faults/helper_utils.py +29 -19
  19. open_fdd/air_handling_unit/reports/__init__.py +6 -4
  20. open_fdd/air_handling_unit/reports/fault_report.py +3 -2
  21. open_fdd/chiller_plant/__init__.py +0 -0
  22. open_fdd/chiller_plant/faults/__init__.py +7 -0
  23. open_fdd/chiller_plant/faults/fault_condition_one.py +113 -0
  24. open_fdd/chiller_plant/faults/fault_condition_two.py +100 -0
  25. open_fdd/tests/ahu/test_ahu_fc1.py +2 -2
  26. open_fdd/tests/ahu/test_ahu_fc10.py +1 -0
  27. open_fdd/tests/ahu/test_ahu_fc11.py +24 -33
  28. open_fdd/tests/ahu/test_ahu_fc12.py +56 -18
  29. open_fdd/tests/ahu/test_ahu_fc13.py +15 -8
  30. open_fdd/tests/ahu/test_ahu_fc14.py +11 -3
  31. open_fdd/tests/ahu/test_ahu_fc15.py +15 -4
  32. open_fdd/tests/ahu/test_ahu_fc16.py +15 -6
  33. open_fdd/tests/ahu/test_ahu_fc2.py +1 -0
  34. open_fdd/tests/ahu/test_ahu_fc3.py +1 -0
  35. open_fdd/tests/ahu/test_ahu_fc4.py +3 -1
  36. open_fdd/tests/ahu/test_ahu_fc5.py +1 -0
  37. open_fdd/tests/ahu/test_ahu_fc6.py +1 -0
  38. open_fdd/tests/ahu/test_ahu_fc7.py +1 -0
  39. open_fdd/tests/ahu/test_ahu_fc8.py +1 -0
  40. open_fdd/tests/ahu/test_ahu_fc9.py +1 -0
  41. open_fdd/tests/chiller/__init__.py +0 -0
  42. open_fdd/tests/chiller/test_chiller_fc1.py +122 -0
  43. open_fdd/tests/chiller/test_chiller_fc2.py +95 -0
  44. open_fdd-0.1.9.dist-info/METADATA +137 -0
  45. open_fdd-0.1.9.dist-info/RECORD +52 -0
  46. {open_fdd-0.1.7.dist-info → open_fdd-0.1.9.dist-info}/WHEEL +1 -1
  47. open_fdd/air_handling_unit/faults/fault_condition.py +0 -69
  48. open_fdd/air_handling_unit/faults/shared_utils.py +0 -90
  49. open_fdd-0.1.7.dist-info/METADATA +0 -95
  50. open_fdd-0.1.7.dist-info/RECORD +0 -31
  51. {open_fdd-0.1.7.dist-info → open_fdd-0.1.9.dist-info/licenses}/LICENSE +0 -0
  52. {open_fdd-0.1.7.dist-info → open_fdd-0.1.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,135 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ from open_fdd.core.base_fault import BaseFaultCondition
5
+ from open_fdd.core.components import FaultInputColumn, InstanceAttribute
6
+ from open_fdd.core.exceptions import InvalidParameterError, MissingColumnError
7
+ from open_fdd.core.mixins import FaultConditionMixin
8
+
9
+ INPUT_COLS = [
10
+ FaultInputColumn(
11
+ name="mat_col",
12
+ constant_form="MAT_COL",
13
+ description="Mixed air temperature",
14
+ unit="°F",
15
+ required=True,
16
+ type=float,
17
+ ),
18
+ FaultInputColumn(
19
+ name="sat_col",
20
+ constant_form="SAT_COL",
21
+ description="Supply air temperature",
22
+ unit="°F",
23
+ required=True,
24
+ type=float,
25
+ ),
26
+ FaultInputColumn(
27
+ name="economizer_sig_col",
28
+ constant_form="ECONOMIZER_SIG_COL",
29
+ description="Economizer signal",
30
+ unit="%",
31
+ required=True,
32
+ type=float,
33
+ ),
34
+ FaultInputColumn(
35
+ name="cooling_sig_col",
36
+ constant_form="COOLING_SIG_COL",
37
+ description="Cooling signal",
38
+ unit="%",
39
+ required=True,
40
+ type=float,
41
+ ),
42
+ ]
43
+
44
+ FAULT_PARAMS = [
45
+ InstanceAttribute(
46
+ name="delta_t_supply_fan",
47
+ constant_form="DELTA_T_SUPPLY_FAN",
48
+ description="Temperature rise across supply fan",
49
+ unit="°F",
50
+ type=float,
51
+ ),
52
+ InstanceAttribute(
53
+ name="mix_degf_err_thres",
54
+ constant_form="MIX_DEGF_ERR_THRES",
55
+ description="Mixed air temperature error threshold",
56
+ unit="°F",
57
+ type=float,
58
+ ),
59
+ InstanceAttribute(
60
+ name="supply_degf_err_thres",
61
+ constant_form="SUPPLY_DEGF_ERR_THRES",
62
+ description="Supply air temperature error threshold",
63
+ unit="°F",
64
+ type=float,
65
+ ),
66
+ InstanceAttribute(
67
+ name="ahu_min_oa_dpr",
68
+ constant_form="AHU_MIN_OA_DPR",
69
+ description="Minimum outdoor air damper position",
70
+ unit="fraction",
71
+ type=float,
72
+ ),
73
+ ]
74
+
75
+
76
+ class FaultConditionEight(BaseFaultCondition, FaultConditionMixin):
77
+ """Class provides the definitions for Fault Condition 8.
78
+ Supply air temperature and mix air temperature should
79
+ be approx equal in economizer mode.
80
+
81
+ py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc8.py -rP -s
82
+ """
83
+
84
+ input_columns = INPUT_COLS
85
+ fault_params = FAULT_PARAMS
86
+ equation_string = (
87
+ "fc8_flag = 1 if |SAT - MAT - ΔT_fan| > √(εSAT² + εMAT²) "
88
+ "in economizer mode for N consecutive values else 0 \n"
89
+ )
90
+ description_string = (
91
+ "Fault Condition 8: Supply air temperature and mixed air temperature should "
92
+ "be approximately equal in economizer mode \n"
93
+ )
94
+ error_string = "One or more required columns are missing or None \n"
95
+
96
+ @FaultConditionMixin._handle_errors
97
+ def apply(self, df: pd.DataFrame) -> pd.DataFrame:
98
+ """Apply the fault condition to the DataFrame."""
99
+ # Apply common checks
100
+ self._apply_common_checks(df)
101
+
102
+ # Get column values using accessor methods
103
+ mat_col = self.get_input_column("mat_col")
104
+ sat_col = self.get_input_column("sat_col")
105
+ economizer_sig_col = self.get_input_column("economizer_sig_col")
106
+ cooling_sig_col = self.get_input_column("cooling_sig_col")
107
+
108
+ # Get parameter values using accessor methods
109
+ delta_t_supply_fan = self.get_param("delta_t_supply_fan")
110
+ mix_degf_err_thres = self.get_param("mix_degf_err_thres")
111
+ supply_degf_err_thres = self.get_param("supply_degf_err_thres")
112
+ ahu_min_oa_dpr = self.get_param("ahu_min_oa_dpr")
113
+
114
+ # Check analog outputs [data with units of %] are floats only
115
+ columns_to_check = [
116
+ economizer_sig_col,
117
+ cooling_sig_col,
118
+ ]
119
+ self._apply_analog_checks(df, columns_to_check, check_greater_than_one=True)
120
+
121
+ # Perform checks
122
+ sat_fan_mat = abs(df[sat_col] - delta_t_supply_fan - df[mat_col])
123
+ sat_mat_sqrted = np.sqrt(supply_degf_err_thres**2 + mix_degf_err_thres**2)
124
+
125
+ combined_check = (
126
+ (sat_fan_mat > sat_mat_sqrted)
127
+ # Verify AHU is running in OS 3 cooling mode with minimum OA
128
+ & (df[economizer_sig_col] > ahu_min_oa_dpr)
129
+ & (df[cooling_sig_col] < 0.1)
130
+ )
131
+
132
+ # Set fault flag
133
+ self._set_fault_flag(df, combined_check, "fc8_flag")
134
+
135
+ return df
@@ -0,0 +1,108 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ from open_fdd.core.base_fault import BaseFaultCondition
5
+ from open_fdd.core.components import FaultInputColumn, InstanceAttribute
6
+ from open_fdd.core.exceptions import InvalidParameterError
7
+ from open_fdd.core.mixins import FaultConditionMixin
8
+
9
+ INPUT_COLS = [
10
+ FaultInputColumn(
11
+ name="oat_col",
12
+ constant_form="OAT_COL",
13
+ description="Outside air temperature",
14
+ unit="°F",
15
+ required=True,
16
+ type=float,
17
+ ),
18
+ FaultInputColumn(
19
+ name="mat_col",
20
+ constant_form="MAT_COL",
21
+ description="Mixed air temperature",
22
+ unit="°F",
23
+ required=True,
24
+ type=float,
25
+ ),
26
+ FaultInputColumn(
27
+ name="economizer_sig_col",
28
+ constant_form="ECONOMIZER_SIG_COL",
29
+ description="Economizer signal",
30
+ unit="%",
31
+ required=True,
32
+ type=float,
33
+ ),
34
+ ]
35
+
36
+ FAULT_PARAMS = [
37
+ InstanceAttribute(
38
+ name="outdoor_degf_err_thres",
39
+ constant_form="OUTDOOR_DEGF_ERR_THRES",
40
+ description="Outdoor air temperature error threshold",
41
+ unit="°F",
42
+ type=float,
43
+ range=(0.0, 10.0),
44
+ ),
45
+ InstanceAttribute(
46
+ name="mix_degf_err_thres",
47
+ constant_form="MIX_DEGF_ERR_THRES",
48
+ description="Mixed air temperature error threshold",
49
+ unit="°F",
50
+ type=float,
51
+ range=(0.0, 10.0),
52
+ ),
53
+ ]
54
+
55
+
56
+ class FaultConditionEleven(BaseFaultCondition, FaultConditionMixin):
57
+ """Class provides the definitions for Fault Condition 11.
58
+ Outdoor air temperature and mix air temperature should
59
+ be approx equal in economizer mode.
60
+
61
+ py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc11.py -rP -s
62
+ """
63
+
64
+ input_columns = INPUT_COLS
65
+ fault_params = FAULT_PARAMS
66
+ equation_string = (
67
+ "fc11_flag = 1 if |OAT - MAT| > √(εOAT² + εMAT²) in "
68
+ "economizer mode for N consecutive values else 0 \n"
69
+ )
70
+ description_string = (
71
+ "Fault Condition 11: Outdoor air temperature and mixed air temperature "
72
+ "should be approximately equal in economizer mode \n"
73
+ )
74
+ error_string = "One or more required columns are missing or None \n"
75
+
76
+ @FaultConditionMixin._handle_errors
77
+ def apply(self, df: pd.DataFrame) -> pd.DataFrame:
78
+ """Apply the fault condition to the DataFrame."""
79
+ # Apply common checks
80
+ self._apply_common_checks(df)
81
+
82
+ # Get column values using accessor methods
83
+ mat_col = self.get_input_column("mat_col")
84
+ oat_col = self.get_input_column("oat_col")
85
+ economizer_sig_col = self.get_input_column("economizer_sig_col")
86
+
87
+ # Get parameter values using accessor methods
88
+ mix_degf_err_thres = self.get_param("mix_degf_err_thres")
89
+ outdoor_degf_err_thres = self.get_param("outdoor_degf_err_thres")
90
+
91
+ # Check analog outputs [data with units of %] are floats only
92
+ columns_to_check = [economizer_sig_col]
93
+ self._apply_analog_checks(df, columns_to_check, check_greater_than_one=True)
94
+
95
+ # Perform calculations
96
+ abs_mat_minus_oat = abs(df[mat_col] - df[oat_col])
97
+ mat_oat_sqrted = np.sqrt(mix_degf_err_thres**2 + outdoor_degf_err_thres**2)
98
+
99
+ combined_check = (
100
+ (abs_mat_minus_oat > mat_oat_sqrted)
101
+ # Verify AHU is running in economizer mode
102
+ & (df[economizer_sig_col] > 0.9)
103
+ )
104
+
105
+ # Set fault flag
106
+ self._set_fault_flag(df, combined_check, "fc11_flag")
107
+
108
+ return df
@@ -0,0 +1,189 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ from open_fdd.core.base_fault import BaseFaultCondition
5
+ from open_fdd.core.components import FaultInputColumn, InstanceAttribute
6
+ from open_fdd.core.exceptions import InvalidParameterError
7
+ from open_fdd.core.mixins import FaultConditionMixin
8
+
9
+ INPUT_COLS = [
10
+ FaultInputColumn(
11
+ name="htg_coil_enter_temp_col",
12
+ constant_form="HTG_COIL_ENTER_TEMP_COL",
13
+ description="Heating coil entering air temperature",
14
+ unit="°F",
15
+ required=True,
16
+ type=float,
17
+ ),
18
+ FaultInputColumn(
19
+ name="htg_coil_leave_temp_col",
20
+ constant_form="HTG_COIL_LEAVE_TEMP_COL",
21
+ description="Heating coil leaving air temperature",
22
+ unit="°F",
23
+ required=True,
24
+ type=float,
25
+ ),
26
+ FaultInputColumn(
27
+ name="cooling_sig_col",
28
+ constant_form="COOLING_SIG_COL",
29
+ description="Cooling signal",
30
+ unit="%",
31
+ required=True,
32
+ type=float,
33
+ ),
34
+ FaultInputColumn(
35
+ name="heating_sig_col",
36
+ constant_form="HEATING_SIG_COL",
37
+ description="Heating signal",
38
+ unit="%",
39
+ required=True,
40
+ type=float,
41
+ ),
42
+ FaultInputColumn(
43
+ name="economizer_sig_col",
44
+ constant_form="ECONOMIZER_SIG_COL",
45
+ description="Economizer signal",
46
+ unit="%",
47
+ required=True,
48
+ type=float,
49
+ ),
50
+ FaultInputColumn(
51
+ name="supply_vfd_speed_col",
52
+ constant_form="SUPPLY_VFD_SPEED_COL",
53
+ description="Supply fan VFD speed",
54
+ unit="%",
55
+ required=True,
56
+ type=float,
57
+ ),
58
+ ]
59
+
60
+ FAULT_PARAMS = [
61
+ InstanceAttribute(
62
+ name="delta_t_supply_fan",
63
+ constant_form="DELTA_SUPPLY_FAN",
64
+ description="Temperature rise across supply fan",
65
+ unit="°F",
66
+ type=float,
67
+ range=(0.0, 5.0),
68
+ ),
69
+ InstanceAttribute(
70
+ name="coil_temp_enter_err_thres",
71
+ constant_form="COIL_TEMP_ENTER_ERR_THRES",
72
+ description="Coil entering air temperature error threshold",
73
+ unit="°F",
74
+ type=float,
75
+ range=(0.0, 10.0),
76
+ ),
77
+ InstanceAttribute(
78
+ name="coil_temp_leave_err_thres",
79
+ constant_form="COIL_TEMP_LEAV_ERR_THRES",
80
+ description="Coil leaving air temperature error threshold",
81
+ unit="°F",
82
+ type=float,
83
+ range=(0.0, 10.0),
84
+ ),
85
+ InstanceAttribute(
86
+ name="ahu_min_oa_dpr",
87
+ constant_form="AHU_MIN_OA_DPR",
88
+ description="Minimum outdoor air damper position",
89
+ unit="fraction",
90
+ type=float,
91
+ range=(0.0, 1.0),
92
+ ),
93
+ ]
94
+
95
+
96
+ class FaultConditionFifteen(BaseFaultCondition, FaultConditionMixin):
97
+ """Class provides the definitions for Fault Condition 15.
98
+ Temperature rise across inactive heating coil in OS2 (economizer),
99
+ OS3 (economizer + mechanical cooling), and OS4 (mechanical cooling only) modes.
100
+ This fault checks if there is an unexpected temperature rise across the heating coil
101
+ when it should be inactive.
102
+
103
+ py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc15.py -rP -s
104
+ """
105
+
106
+ input_columns = INPUT_COLS
107
+ fault_params = FAULT_PARAMS
108
+ equation_string = (
109
+ "fc15_flag = 1 if (HTG_LEAVE > HTG_ENTER + √(εENTER² + εLEAVE²) + ΔTfan) "
110
+ "in OS2 (economizer), OS3 (economizer + mechanical cooling), or "
111
+ "OS4 (mechanical cooling only) modes for N consecutive values else 0 \n"
112
+ )
113
+ description_string = (
114
+ "Fault Condition 15: Temperature rise across inactive heating coil "
115
+ "in OS2 (economizer), OS3 (economizer + mechanical cooling), and "
116
+ "OS4 (mechanical cooling only) modes \n"
117
+ )
118
+ error_string = "One or more required columns are missing or None \n"
119
+
120
+ @FaultConditionMixin._handle_errors
121
+ def apply(self, df: pd.DataFrame) -> pd.DataFrame:
122
+ """Apply the fault condition to the DataFrame."""
123
+ # Apply common checks
124
+ self._apply_common_checks(df)
125
+
126
+ # Get column values using accessor methods
127
+ cooling_sig_col = self.get_input_column("cooling_sig_col")
128
+ heating_sig_col = self.get_input_column("heating_sig_col")
129
+ economizer_sig_col = self.get_input_column("economizer_sig_col")
130
+ supply_vfd_speed_col = self.get_input_column("supply_vfd_speed_col")
131
+ htg_coil_enter_temp_col = self.get_input_column("htg_coil_enter_temp_col")
132
+ htg_coil_leave_temp_col = self.get_input_column("htg_coil_leave_temp_col")
133
+
134
+ # Get parameter values using accessor methods
135
+ coil_temp_enter_err_thres = self.get_param("coil_temp_enter_err_thres")
136
+ coil_temp_leave_err_thres = self.get_param("coil_temp_leave_err_thres")
137
+ delta_t_supply_fan = self.get_param("delta_t_supply_fan")
138
+ ahu_min_oa_dpr = self.get_param("ahu_min_oa_dpr")
139
+
140
+ # Check analog outputs [data with units of %] are floats only
141
+ columns_to_check = [
142
+ cooling_sig_col,
143
+ heating_sig_col,
144
+ economizer_sig_col,
145
+ supply_vfd_speed_col,
146
+ ]
147
+ self._apply_analog_checks(df, columns_to_check, check_greater_than_one=True)
148
+
149
+ # Calculate the threshold for temperature rise, including supply fan heat
150
+ temp_rise_threshold = (
151
+ np.sqrt(coil_temp_enter_err_thres**2 + coil_temp_leave_err_thres**2)
152
+ + delta_t_supply_fan
153
+ )
154
+
155
+ # Check if there's a significant temperature rise across the heating coil
156
+ temp_rise = df[htg_coil_leave_temp_col] - df[htg_coil_enter_temp_col]
157
+ significant_temp_rise = temp_rise > temp_rise_threshold
158
+
159
+ # Check operating modes:
160
+ # OS2: Economizer mode (HTG = 0, CLG = 0, ECO > MIN_OA)
161
+ os2_mode = (
162
+ (df[heating_sig_col] == 0.0)
163
+ & (df[cooling_sig_col] == 0.0)
164
+ & (df[economizer_sig_col] > ahu_min_oa_dpr)
165
+ )
166
+
167
+ # OS3: Economizer + mechanical cooling (HTG = 0, CLG > 0, ECO > 0.9)
168
+ os3_mode = (
169
+ (df[heating_sig_col] == 0.0)
170
+ & (df[cooling_sig_col] > 0.0)
171
+ & (df[economizer_sig_col] > 0.9)
172
+ )
173
+
174
+ # OS4: Mechanical cooling only (HTG = 0, CLG > 0, ECO = MIN_OA)
175
+ os4_mode = (
176
+ (df[heating_sig_col] == 0.0)
177
+ & (df[cooling_sig_col] > 0.0)
178
+ & (df[economizer_sig_col] <= ahu_min_oa_dpr)
179
+ )
180
+
181
+ # Combine conditions:
182
+ # Fault occurs when there's a significant temperature rise across an inactive heating coil
183
+ # in OS2 (economizer), OS3 (economizer + mechanical cooling), or OS4 (mechanical cooling only) mode
184
+ combined_check = significant_temp_rise & (os2_mode | os3_mode | os4_mode)
185
+
186
+ # Set fault flag
187
+ self._set_fault_flag(df, combined_check, "fc15_flag")
188
+
189
+ return df
@@ -0,0 +1,126 @@
1
+ import pandas as pd
2
+
3
+ from open_fdd.core.base_fault import BaseFaultCondition
4
+ from open_fdd.core.components import FaultInputColumn, InstanceAttribute
5
+ from open_fdd.core.exceptions import InvalidParameterError
6
+ from open_fdd.core.mixins import FaultConditionMixin
7
+
8
+ INPUT_COLS = [
9
+ FaultInputColumn(
10
+ name="mat_col",
11
+ constant_form="MAT_COL",
12
+ description="Mixed air temperature",
13
+ unit="°F",
14
+ required=True,
15
+ type=float,
16
+ ),
17
+ FaultInputColumn(
18
+ name="sat_col",
19
+ constant_form="SAT_COL",
20
+ description="Supply air temperature",
21
+ unit="°F",
22
+ required=True,
23
+ type=float,
24
+ ),
25
+ FaultInputColumn(
26
+ name="heating_sig_col",
27
+ constant_form="HEATING_SIG_COL",
28
+ description="Heating signal",
29
+ unit="%",
30
+ required=True,
31
+ type=float,
32
+ ),
33
+ FaultInputColumn(
34
+ name="supply_vfd_speed_col",
35
+ constant_form="SUPPLY_VFD_SPEED_COL",
36
+ description="Supply fan VFD speed",
37
+ unit="%",
38
+ required=True,
39
+ type=float,
40
+ ),
41
+ ]
42
+
43
+ FAULT_PARAMS = [
44
+ InstanceAttribute(
45
+ name="mix_degf_err_thres",
46
+ constant_form="MIX_DEGF_ERR_THRES",
47
+ description="Mixed air temperature error threshold",
48
+ unit="°F",
49
+ type=float,
50
+ range=(0.0, 10.0),
51
+ ),
52
+ InstanceAttribute(
53
+ name="supply_degf_err_thres",
54
+ constant_form="SUPPLY_DEGF_ERR_THRES",
55
+ description="Supply air temperature error threshold",
56
+ unit="°F",
57
+ type=float,
58
+ range=(0.0, 10.0),
59
+ ),
60
+ InstanceAttribute(
61
+ name="delta_t_supply_fan",
62
+ constant_form="DELTA_T_SUPPLY_FAN",
63
+ description="Temperature rise across supply fan",
64
+ unit="°F",
65
+ type=float,
66
+ range=(0.0, 5.0),
67
+ ),
68
+ ]
69
+
70
+
71
+ class FaultConditionFive(BaseFaultCondition, FaultConditionMixin):
72
+ """Class provides the definitions for Fault Condition 5.
73
+ SAT too low; should be higher than MAT in HTG MODE
74
+ --Broken heating valve or other mechanical issue
75
+ related to heat valve not working as designed
76
+
77
+ py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc5.py -rP -s
78
+ """
79
+
80
+ input_columns = INPUT_COLS
81
+ fault_params = FAULT_PARAMS
82
+ equation_string = (
83
+ "fc5_flag = 1 if (SAT + εSAT <= MAT - εMAT + ΔT_supply_fan) and "
84
+ "(heating signal > 0) and (VFDSPD > 0) for N consecutive values else 0 \n"
85
+ )
86
+ description_string = (
87
+ "Fault Condition 5: SAT too low; should be higher than MAT in HTG MODE, "
88
+ "potential broken heating valve or mechanical issue \n"
89
+ )
90
+ error_string = "One or more required columns are missing or None \n"
91
+
92
+ @FaultConditionMixin._handle_errors
93
+ def apply(self, df: pd.DataFrame) -> pd.DataFrame:
94
+ """Apply the fault condition to the DataFrame."""
95
+ # Apply common checks
96
+ self._apply_common_checks(df)
97
+
98
+ # Get column values using accessor methods
99
+ supply_vfd_speed_col = self.get_input_column("supply_vfd_speed_col")
100
+ heating_sig_col = self.get_input_column("heating_sig_col")
101
+ sat_col = self.get_input_column("sat_col")
102
+ mat_col = self.get_input_column("mat_col")
103
+
104
+ # Get parameter values using accessor methods
105
+ supply_degf_err_thres = self.get_param("supply_degf_err_thres")
106
+ mix_degf_err_thres = self.get_param("mix_degf_err_thres")
107
+ delta_t_supply_fan = self.get_param("delta_t_supply_fan")
108
+
109
+ # Check analog outputs [data with units of %] are floats only
110
+ columns_to_check = [supply_vfd_speed_col, heating_sig_col]
111
+ self._apply_analog_checks(df, columns_to_check, check_greater_than_one=True)
112
+
113
+ # Perform checks
114
+ sat_check = df[sat_col] + supply_degf_err_thres
115
+ mat_check = df[mat_col] - mix_degf_err_thres + delta_t_supply_fan
116
+
117
+ combined_check = (
118
+ (sat_check <= mat_check)
119
+ & (df[heating_sig_col] > 0.01)
120
+ & (df[supply_vfd_speed_col] > 0.01)
121
+ )
122
+
123
+ # Set fault flag
124
+ self._set_fault_flag(df, combined_check, "fc5_flag")
125
+
126
+ return df
@@ -0,0 +1,128 @@
1
+ import pandas as pd
2
+
3
+ from open_fdd.core.base_fault import BaseFaultCondition
4
+ from open_fdd.core.components import FaultInputColumn, InstanceAttribute
5
+ from open_fdd.core.exceptions import InvalidParameterError, MissingColumnError
6
+ from open_fdd.core.mixins import FaultConditionMixin
7
+
8
+ INPUT_COLS = [
9
+ FaultInputColumn(
10
+ name="economizer_sig_col",
11
+ constant_form="ECONOMIZER_SIG_COL",
12
+ description="Economizer signal",
13
+ unit="%",
14
+ required=True,
15
+ type=float,
16
+ ),
17
+ FaultInputColumn(
18
+ name="heating_sig_col",
19
+ constant_form="HEATING_SIG_COL",
20
+ description="Heating signal",
21
+ unit="%",
22
+ required=False,
23
+ type=float,
24
+ ),
25
+ FaultInputColumn(
26
+ name="cooling_sig_col",
27
+ constant_form="COOLING_SIG_COL",
28
+ description="Cooling signal",
29
+ unit="%",
30
+ required=False,
31
+ type=float,
32
+ ),
33
+ FaultInputColumn(
34
+ name="supply_vfd_speed_col",
35
+ constant_form="SUPPLY_VFD_SPEED_COL",
36
+ description="Supply fan VFD speed",
37
+ unit="%",
38
+ required=True,
39
+ type=float,
40
+ ),
41
+ ]
42
+
43
+ FAULT_PARAMS = [
44
+ InstanceAttribute(
45
+ name="delta_os_max",
46
+ constant_form="DELTA_OS_MAX",
47
+ description="Maximum allowable operating state changes",
48
+ unit="count",
49
+ type=int,
50
+ range=(0, 100),
51
+ ),
52
+ InstanceAttribute(
53
+ name="ahu_min_oa_dpr",
54
+ constant_form="AHU_MIN_OA_DPR",
55
+ description="Minimum outdoor air damper position",
56
+ unit="fraction",
57
+ type=float,
58
+ range=(0.0, 1.0),
59
+ ),
60
+ ]
61
+
62
+
63
+ class FaultConditionFour(BaseFaultCondition, FaultConditionMixin):
64
+ """Class provides the definitions for Fault Condition 4.
65
+
66
+ This fault flags excessive operating states on the AHU
67
+ if it's hunting between heating, econ, econ+mech, and
68
+ a mech clg modes. The code counts how many operating
69
+ changes in an hour and will throw a fault if there is
70
+ excessive OS changes to flag control sys hunting.
71
+
72
+ py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc4.py -rP -s
73
+ """
74
+
75
+ input_columns = INPUT_COLS
76
+ fault_params = FAULT_PARAMS
77
+ equation_string = (
78
+ "fc4_flag = 1 if excessive mode changes (> δOS_max) occur "
79
+ "within an hour across heating, econ, econ+mech, mech clg, and min OA modes \n"
80
+ )
81
+ description_string = "Fault Condition 4: Excessive AHU operating state changes detected (hunting behavior) \n"
82
+ error_string = "One or more required columns are missing or None \n"
83
+
84
+ @FaultConditionMixin._handle_errors
85
+ def apply(self, df: pd.DataFrame) -> pd.DataFrame:
86
+ """Apply the fault condition to the DataFrame."""
87
+ # Apply common checks
88
+ self._apply_common_checks(df)
89
+
90
+ # Get column values using accessor methods
91
+ supply_vfd_speed_col = self.get_input_column("supply_vfd_speed_col")
92
+ economizer_sig_col = self.get_input_column("economizer_sig_col")
93
+ heating_sig_col = self.get_input_column("heating_sig_col")
94
+ cooling_sig_col = self.get_input_column("cooling_sig_col")
95
+
96
+ # Get parameter values using accessor methods
97
+ delta_os_max = self.get_param("delta_os_max")
98
+ ahu_min_oa_dpr = self.get_param("ahu_min_oa_dpr")
99
+
100
+ # Add analog checks for supply_vfd_speed_col
101
+ self._apply_analog_checks(
102
+ df, [supply_vfd_speed_col], check_greater_than_one=True
103
+ )
104
+
105
+ # Convert VFD speed from percentage to fraction if needed
106
+ if (df[supply_vfd_speed_col] > 1.0).any():
107
+ df[supply_vfd_speed_col] = df[supply_vfd_speed_col] / 100.0
108
+
109
+ # Calculate operating state changes
110
+ df["os_change"] = 0
111
+ df.loc[df[economizer_sig_col] > 0, "os_change"] += 1
112
+ df.loc[df[supply_vfd_speed_col] > ahu_min_oa_dpr, "os_change"] += 1
113
+ if heating_sig_col:
114
+ df.loc[df[heating_sig_col] > 0, "os_change"] += 1
115
+ if cooling_sig_col:
116
+ df.loc[df[cooling_sig_col] > 0, "os_change"] += 1
117
+
118
+ # Calculate changes in operating state
119
+ df["os_change_diff"] = df["os_change"].diff().abs()
120
+ df["os_change_diff"] = df["os_change_diff"].fillna(0)
121
+
122
+ # Calculate rolling sum of changes
123
+ df["os_change_sum"] = df["os_change_diff"].rolling(window=60).sum()
124
+
125
+ # Set fault flag
126
+ df["fc4_flag"] = (df["os_change_sum"] > delta_os_max).astype(int)
127
+
128
+ return df