open-fdd 0.1.8__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 (49) hide show
  1. open_fdd/air_handling_unit/faults/__init__.py +30 -1487
  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/faults/__init__.py +6 -2279
  22. open_fdd/chiller_plant/faults/fault_condition_one.py +113 -0
  23. open_fdd/chiller_plant/faults/fault_condition_two.py +100 -0
  24. open_fdd/tests/ahu/test_ahu_fc1.py +2 -2
  25. open_fdd/tests/ahu/test_ahu_fc10.py +1 -0
  26. open_fdd/tests/ahu/test_ahu_fc11.py +3 -4
  27. open_fdd/tests/ahu/test_ahu_fc12.py +3 -4
  28. open_fdd/tests/ahu/test_ahu_fc13.py +3 -4
  29. open_fdd/tests/ahu/test_ahu_fc14.py +3 -4
  30. open_fdd/tests/ahu/test_ahu_fc15.py +3 -4
  31. open_fdd/tests/ahu/test_ahu_fc16.py +4 -3
  32. open_fdd/tests/ahu/test_ahu_fc2.py +1 -0
  33. open_fdd/tests/ahu/test_ahu_fc3.py +1 -0
  34. open_fdd/tests/ahu/test_ahu_fc4.py +3 -1
  35. open_fdd/tests/ahu/test_ahu_fc5.py +1 -0
  36. open_fdd/tests/ahu/test_ahu_fc6.py +1 -0
  37. open_fdd/tests/ahu/test_ahu_fc7.py +1 -0
  38. open_fdd/tests/ahu/test_ahu_fc8.py +1 -0
  39. open_fdd/tests/ahu/test_ahu_fc9.py +1 -0
  40. open_fdd/tests/chiller/test_chiller_fc1.py +2 -2
  41. open_fdd/tests/chiller/test_chiller_fc2.py +2 -2
  42. {open_fdd-0.1.8.dist-info → open_fdd-0.1.9.dist-info}/METADATA +4 -3
  43. open_fdd-0.1.9.dist-info/RECORD +52 -0
  44. {open_fdd-0.1.8.dist-info → open_fdd-0.1.9.dist-info}/WHEEL +1 -1
  45. open_fdd/air_handling_unit/faults/fault_condition.py +0 -69
  46. open_fdd/air_handling_unit/faults/shared_utils.py +0 -90
  47. open_fdd-0.1.8.dist-info/RECORD +0 -36
  48. {open_fdd-0.1.8.dist-info → open_fdd-0.1.9.dist-info/licenses}/LICENSE +0 -0
  49. {open_fdd-0.1.8.dist-info → open_fdd-0.1.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,228 @@
1
+ import sys
2
+
3
+ import pandas as pd
4
+
5
+ from open_fdd.core.base_fault import BaseFaultCondition
6
+ from open_fdd.core.components import FaultInputColumn, InstanceAttribute
7
+ from open_fdd.core.exceptions import InvalidParameterError
8
+ from open_fdd.core.mixins import FaultConditionMixin
9
+
10
+ INPUT_COLS = [
11
+ FaultInputColumn(
12
+ name="supply_fan_air_volume_col",
13
+ constant_form="SUPPLY_FAN_AIR_VOLUME_COL",
14
+ description="Supply fan air volume",
15
+ unit="CFM",
16
+ required=True,
17
+ type=float,
18
+ ),
19
+ FaultInputColumn(
20
+ name="mat_col",
21
+ constant_form="MAT_COL",
22
+ description="Mixed air temperature",
23
+ unit="°F",
24
+ required=True,
25
+ type=float,
26
+ ),
27
+ FaultInputColumn(
28
+ name="oat_col",
29
+ constant_form="OAT_COL",
30
+ description="Outside air temperature",
31
+ unit="°F",
32
+ required=True,
33
+ type=float,
34
+ ),
35
+ FaultInputColumn(
36
+ name="rat_col",
37
+ constant_form="RAT_COL",
38
+ description="Return air temperature",
39
+ unit="°F",
40
+ required=True,
41
+ type=float,
42
+ ),
43
+ FaultInputColumn(
44
+ name="supply_vfd_speed_col",
45
+ constant_form="SUPPLY_VFD_SPEED_COL",
46
+ description="Supply fan VFD speed",
47
+ unit="%",
48
+ required=True,
49
+ type=float,
50
+ ),
51
+ FaultInputColumn(
52
+ name="economizer_sig_col",
53
+ constant_form="ECONOMIZER_SIG_COL",
54
+ description="Economizer signal",
55
+ unit="%",
56
+ required=True,
57
+ type=float,
58
+ ),
59
+ FaultInputColumn(
60
+ name="heating_sig_col",
61
+ constant_form="HEATING_SIG_COL",
62
+ description="Heating signal",
63
+ unit="%",
64
+ required=True,
65
+ type=float,
66
+ ),
67
+ FaultInputColumn(
68
+ name="cooling_sig_col",
69
+ constant_form="COOLING_SIG_COL",
70
+ description="Cooling signal",
71
+ unit="%",
72
+ required=True,
73
+ type=float,
74
+ ),
75
+ ]
76
+
77
+ FAULT_PARAMS = [
78
+ InstanceAttribute(
79
+ name="airflow_err_thres",
80
+ constant_form="AIRFLOW_ERR_THRES",
81
+ description="Airflow error threshold",
82
+ unit="fraction",
83
+ type=float,
84
+ range=(0.0, 1.0),
85
+ ),
86
+ InstanceAttribute(
87
+ name="ahu_min_oa_cfm_design",
88
+ constant_form="AHU_MIN_OA_CFM_DESIGN",
89
+ description="AHU minimum outdoor air CFM design",
90
+ unit="CFM",
91
+ type=float,
92
+ range=(0.0, 100000.0),
93
+ ),
94
+ InstanceAttribute(
95
+ name="outdoor_degf_err_thres",
96
+ constant_form="OUTDOOR_DEGF_ERR_THRES",
97
+ description="Outdoor air temperature error threshold",
98
+ unit="°F",
99
+ type=float,
100
+ range=(0.0, 10.0),
101
+ ),
102
+ InstanceAttribute(
103
+ name="return_degf_err_thres",
104
+ constant_form="RETURN_DEGF_ERR_THRES",
105
+ description="Return air temperature error threshold",
106
+ unit="°F",
107
+ type=float,
108
+ range=(0.0, 10.0),
109
+ ),
110
+ InstanceAttribute(
111
+ name="oat_rat_delta_min",
112
+ constant_form="OAT_RAT_DELTA_MIN",
113
+ description="Minimum delta between outdoor and return air temperature",
114
+ unit="°F",
115
+ type=float,
116
+ range=(0.0, 20.0),
117
+ ),
118
+ InstanceAttribute(
119
+ name="ahu_min_oa_dpr",
120
+ constant_form="AHU_MIN_OA_DPR",
121
+ description="Minimum outdoor air damper position",
122
+ unit="fraction",
123
+ type=float,
124
+ range=(0.0, 1.0),
125
+ ),
126
+ ]
127
+
128
+
129
+ class FaultConditionSix(BaseFaultCondition, FaultConditionMixin):
130
+ """Class provides the definitions for Fault Condition 6.
131
+
132
+ This fault related to knowing the design air flow for
133
+ ventilation AHU_MIN_CFM_DESIGN which comes from the
134
+ design mech engineered records where then the fault
135
+ tries to calculate that based on totalized measured
136
+ AHU air flow and outside air fraction calc from
137
+ AHU temp sensors. The fault could flag issues where
138
+ flow stations are either not in calibration, temp
139
+ sensors used in the OA frac calc, or possibly the AHU
140
+ not bringing in design air flow when not operating in
141
+ economizer free cooling modes.
142
+
143
+ py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc6.py -rP -s
144
+ """
145
+
146
+ input_columns = INPUT_COLS
147
+ fault_params = FAULT_PARAMS
148
+ equation_string = (
149
+ "fc6_flag = 1 if |OA_frac_calc - OA_min| > airflow_err_thres "
150
+ "in non-economizer modes, considering htg and mech clg OS \n"
151
+ )
152
+ description_string = (
153
+ "Fault Condition 6: Issues detected with OA fraction calculation or AHU "
154
+ "not maintaining design air flow in non-economizer conditions \n"
155
+ )
156
+ error_string = "One or more required columns are missing or None \n"
157
+
158
+ @FaultConditionMixin._handle_errors
159
+ def apply(self, df: pd.DataFrame) -> pd.DataFrame:
160
+ """Apply the fault condition to the DataFrame."""
161
+ # Apply common checks
162
+ self._apply_common_checks(df)
163
+
164
+ # Get column values using accessor methods
165
+ rat_col = self.get_input_column("rat_col")
166
+ oat_col = self.get_input_column("oat_col")
167
+ mat_col = self.get_input_column("mat_col")
168
+ supply_fan_air_volume_col = self.get_input_column("supply_fan_air_volume_col")
169
+ supply_vfd_speed_col = self.get_input_column("supply_vfd_speed_col")
170
+ economizer_sig_col = self.get_input_column("economizer_sig_col")
171
+ heating_sig_col = self.get_input_column("heating_sig_col")
172
+ cooling_sig_col = self.get_input_column("cooling_sig_col")
173
+
174
+ # Get parameter values using accessor methods
175
+ airflow_err_thres = self.get_param("airflow_err_thres")
176
+ ahu_min_oa_cfm_design = self.get_param("ahu_min_oa_cfm_design")
177
+ oat_rat_delta_min = self.get_param("oat_rat_delta_min")
178
+ ahu_min_oa_dpr = self.get_param("ahu_min_oa_dpr")
179
+
180
+ # Check for zeros in the columns that could lead to division by zero errors
181
+ cols_to_check = [rat_col, oat_col, supply_fan_air_volume_col]
182
+ if df[cols_to_check].eq(0).any().any():
183
+ print(f"Warning: Zero values found in columns: {cols_to_check}")
184
+ print("This may cause division by zero errors.")
185
+ sys.stdout.flush()
186
+
187
+ # Check analog outputs [data with units of %] are floats only
188
+ columns_to_check = [
189
+ supply_vfd_speed_col,
190
+ economizer_sig_col,
191
+ heating_sig_col,
192
+ cooling_sig_col,
193
+ ]
194
+ self._apply_analog_checks(df, columns_to_check, check_greater_than_one=True)
195
+
196
+ # Calculate intermediate values
197
+ rat_minus_oat = abs(df[rat_col] - df[oat_col])
198
+ percent_oa_calc = (df[mat_col] - df[rat_col]) / (df[oat_col] - df[rat_col])
199
+
200
+ # Replace negative values in percent_oa_calc with zero using vectorized operation
201
+ percent_oa_calc = percent_oa_calc.clip(lower=0)
202
+
203
+ perc_OAmin = ahu_min_oa_cfm_design / df[supply_fan_air_volume_col]
204
+ percent_oa_calc_minus_perc_OAmin = abs(percent_oa_calc - perc_OAmin)
205
+
206
+ # Combined checks for OS 1 and OS 4 modes
207
+ os1_htg_mode_check = (
208
+ (rat_minus_oat >= oat_rat_delta_min)
209
+ & (percent_oa_calc_minus_perc_OAmin > airflow_err_thres)
210
+ & (df[heating_sig_col] > 0.0)
211
+ & (df[supply_vfd_speed_col] > 0.0)
212
+ )
213
+
214
+ os4_clg_mode_check = (
215
+ (rat_minus_oat >= oat_rat_delta_min)
216
+ & (percent_oa_calc_minus_perc_OAmin > airflow_err_thres)
217
+ & (df[heating_sig_col] == 0.0)
218
+ & (df[cooling_sig_col] > 0.0)
219
+ & (df[supply_vfd_speed_col] > 0.0)
220
+ & (df[economizer_sig_col] == ahu_min_oa_dpr)
221
+ )
222
+
223
+ combined_check = os1_htg_mode_check | os4_clg_mode_check
224
+
225
+ # Set fault flag
226
+ self._set_fault_flag(df, combined_check, "fc6_flag")
227
+
228
+ return df
@@ -0,0 +1,196 @@
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="erv_oat_enter_col",
12
+ constant_form="ERV_OAT_ENTER_COL",
13
+ description="ERV outdoor air entering temperature",
14
+ unit="°F",
15
+ required=True,
16
+ type=float,
17
+ ),
18
+ FaultInputColumn(
19
+ name="erv_oat_leaving_col",
20
+ constant_form="ERV_OAT_LEAVING_COL",
21
+ description="ERV outdoor air leaving temperature",
22
+ unit="°F",
23
+ required=True,
24
+ type=float,
25
+ ),
26
+ FaultInputColumn(
27
+ name="erv_eat_enter_col",
28
+ constant_form="ERV_EAT_ENTER_COL",
29
+ description="ERV exhaust air entering temperature",
30
+ unit="°F",
31
+ required=True,
32
+ type=float,
33
+ ),
34
+ FaultInputColumn(
35
+ name="erv_eat_leaving_col",
36
+ constant_form="ERV_EAT_LEAVING_COL",
37
+ description="ERV exhaust air leaving temperature",
38
+ unit="°F",
39
+ required=True,
40
+ type=float,
41
+ ),
42
+ FaultInputColumn(
43
+ name="supply_vfd_speed_col",
44
+ constant_form="SUPPLY_VFD_SPEED_COL",
45
+ description="Supply fan VFD speed",
46
+ unit="%",
47
+ required=True,
48
+ type=float,
49
+ ),
50
+ ]
51
+
52
+ FAULT_PARAMS = [
53
+ InstanceAttribute(
54
+ name="erv_efficiency_min_heating",
55
+ constant_form="ERV_EFFICIENCY_MIN_HEATING",
56
+ description="Minimum expected ERV efficiency in heating mode",
57
+ unit="fraction",
58
+ type=float,
59
+ range=(0.0, 1.0),
60
+ ),
61
+ InstanceAttribute(
62
+ name="erv_efficiency_max_heating",
63
+ constant_form="ERV_EFFICIENCY_MAX_HEATING",
64
+ description="Maximum expected ERV efficiency in heating mode",
65
+ unit="fraction",
66
+ type=float,
67
+ range=(0.0, 1.0),
68
+ ),
69
+ InstanceAttribute(
70
+ name="erv_efficiency_min_cooling",
71
+ constant_form="ERV_EFFICIENCY_MIN_COOLING",
72
+ description="Minimum expected ERV efficiency in cooling mode",
73
+ unit="fraction",
74
+ type=float,
75
+ range=(0.0, 1.0),
76
+ ),
77
+ InstanceAttribute(
78
+ name="erv_efficiency_max_cooling",
79
+ constant_form="ERV_EFFICIENCY_MAX_COOLING",
80
+ description="Maximum expected ERV efficiency in cooling mode",
81
+ unit="fraction",
82
+ type=float,
83
+ range=(0.0, 1.0),
84
+ ),
85
+ InstanceAttribute(
86
+ name="oat_low_threshold",
87
+ constant_form="OAT_LOW_THRESHOLD",
88
+ description="OAT threshold for heating mode",
89
+ unit="°F",
90
+ type=float,
91
+ ),
92
+ InstanceAttribute(
93
+ name="oat_high_threshold",
94
+ constant_form="OAT_HIGH_THRESHOLD",
95
+ description="OAT threshold for cooling mode",
96
+ unit="°F",
97
+ type=float,
98
+ ),
99
+ InstanceAttribute(
100
+ name="oat_rat_delta_min",
101
+ constant_form="OAT_RAT_DELTA_MIN",
102
+ description="Minimum required delta between OAT and RAT",
103
+ unit="°F",
104
+ type=float,
105
+ ),
106
+ ]
107
+
108
+
109
+ class FaultConditionSixteen(BaseFaultCondition, FaultConditionMixin):
110
+ """Class provides the definitions for Fault Condition 16.
111
+ ERV effectiveness should be within specified thresholds based on OAT.
112
+ This fault checks if the ERV (Energy Recovery Ventilator) is operating
113
+ within expected efficiency ranges in both heating and cooling modes.
114
+
115
+ py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc16.py -rP -s
116
+ """
117
+
118
+ input_columns = INPUT_COLS
119
+ fault_params = FAULT_PARAMS
120
+ equation_string = (
121
+ "fc16_flag = 1 if ERV effectiveness is outside expected range "
122
+ "(heating: εmin_htg ≤ ε ≤ εmax_htg, cooling: εmin_clg ≤ ε ≤ εmax_clg) "
123
+ "for N consecutive values else 0 \n"
124
+ )
125
+ description_string = (
126
+ "Fault Condition 16: ERV effectiveness should be within specified "
127
+ "thresholds based on OAT \n"
128
+ )
129
+ error_string = "One or more required columns are missing or None \n"
130
+
131
+ @FaultConditionMixin._handle_errors
132
+ def apply(self, df: pd.DataFrame) -> pd.DataFrame:
133
+ """Apply the fault condition to the DataFrame."""
134
+ # Apply common checks
135
+ self._apply_common_checks(df)
136
+
137
+ # Get column values using accessor methods
138
+ supply_vfd_speed_col = self.get_input_column("supply_vfd_speed_col")
139
+ erv_oat_enter_col = self.get_input_column("erv_oat_enter_col")
140
+ erv_oat_leaving_col = self.get_input_column("erv_oat_leaving_col")
141
+ erv_eat_enter_col = self.get_input_column("erv_eat_enter_col")
142
+
143
+ # Get parameter values using accessor methods
144
+ erv_efficiency_min_heating = self.get_param("erv_efficiency_min_heating")
145
+ erv_efficiency_max_heating = self.get_param("erv_efficiency_max_heating")
146
+ erv_efficiency_min_cooling = self.get_param("erv_efficiency_min_cooling")
147
+ erv_efficiency_max_cooling = self.get_param("erv_efficiency_max_cooling")
148
+ oat_low_threshold = self.get_param("oat_low_threshold")
149
+ oat_high_threshold = self.get_param("oat_high_threshold")
150
+ oat_rat_delta_min = self.get_param("oat_rat_delta_min")
151
+
152
+ # Check analog outputs [data with units of %] are floats only
153
+ columns_to_check = [supply_vfd_speed_col]
154
+ self._apply_analog_checks(df, columns_to_check, check_greater_than_one=True)
155
+
156
+ # Calculate temperature differences
157
+ oat_rat_delta = abs(df[erv_oat_enter_col] - df[erv_eat_enter_col])
158
+
159
+ # Calculate ERV effectiveness
160
+ # ε = (T_leaving - T_entering) / (T_exhaust - T_entering)
161
+ erv_effectiveness = (df[erv_oat_leaving_col] - df[erv_oat_enter_col]) / (
162
+ df[erv_eat_enter_col] - df[erv_oat_enter_col]
163
+ )
164
+
165
+ # Determine operating mode based on OAT
166
+ heating_mode = df[erv_oat_enter_col] < oat_low_threshold
167
+ cooling_mode = df[erv_oat_enter_col] > oat_high_threshold
168
+
169
+ # Check effectiveness against thresholds
170
+ low_effectiveness_htg = heating_mode & (
171
+ erv_effectiveness < erv_efficiency_min_heating
172
+ )
173
+ high_effectiveness_htg = heating_mode & (
174
+ erv_effectiveness > erv_efficiency_max_heating
175
+ )
176
+ low_effectiveness_clg = cooling_mode & (
177
+ erv_effectiveness < erv_efficiency_min_cooling
178
+ )
179
+ high_effectiveness_clg = cooling_mode & (
180
+ erv_effectiveness > erv_efficiency_max_cooling
181
+ )
182
+
183
+ # Combine conditions:
184
+ # Fault occurs when ERV effectiveness is outside expected range
185
+ # and there's sufficient temperature difference between OAT and RAT
186
+ combined_check = (oat_rat_delta >= oat_rat_delta_min) & (
187
+ low_effectiveness_htg
188
+ | high_effectiveness_htg
189
+ | low_effectiveness_clg
190
+ | high_effectiveness_clg
191
+ )
192
+
193
+ # Set fault flag
194
+ self._set_fault_flag(df, combined_check, "fc16_flag")
195
+
196
+ return df
@@ -0,0 +1,119 @@
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="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="economizer_sig_col",
36
+ constant_form="ECONOMIZER_SIG_COL",
37
+ description="Economizer signal",
38
+ unit="%",
39
+ required=True,
40
+ type=float,
41
+ ),
42
+ ]
43
+
44
+ FAULT_PARAMS = [
45
+ InstanceAttribute(
46
+ name="outdoor_degf_err_thres",
47
+ constant_form="OUTDOOR_DEGF_ERR_THRES",
48
+ description="Outdoor air temperature error threshold",
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
+ ]
60
+
61
+
62
+ class FaultConditionTen(BaseFaultCondition, FaultConditionMixin):
63
+ """Class provides the definitions for Fault Condition 10.
64
+ Outdoor air temperature and mix air temperature should
65
+ be approx equal in economizer plus mech cooling mode.
66
+
67
+ py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc10.py -rP -s
68
+ """
69
+
70
+ input_columns = INPUT_COLS
71
+ fault_params = FAULT_PARAMS
72
+ equation_string = (
73
+ "fc10_flag = 1 if |OAT - MAT| > √(εOAT² + εMAT²) in "
74
+ "economizer + mech cooling mode for N consecutive values else 0 \n"
75
+ )
76
+ description_string = (
77
+ "Fault Condition 10: Outdoor air temperature and mixed air temperature "
78
+ "should be approximately equal in economizer plus mechanical cooling mode \n"
79
+ )
80
+ error_string = "One or more required columns are missing or None \n"
81
+
82
+ @FaultConditionMixin._handle_errors
83
+ def apply(self, df: pd.DataFrame) -> pd.DataFrame:
84
+ """Apply the fault condition to the DataFrame."""
85
+ # Apply common checks
86
+ self._apply_common_checks(df)
87
+
88
+ # Get column values using accessor methods
89
+ oat_col = self.get_input_column("oat_col")
90
+ mat_col = self.get_input_column("mat_col")
91
+ cooling_sig_col = self.get_input_column("cooling_sig_col")
92
+ economizer_sig_col = self.get_input_column("economizer_sig_col")
93
+
94
+ # Get parameter values using accessor methods
95
+ outdoor_degf_err_thres = self.get_param("outdoor_degf_err_thres")
96
+ mix_degf_err_thres = self.get_param("mix_degf_err_thres")
97
+
98
+ # Check analog outputs [data with units of %] are floats only
99
+ columns_to_check = [
100
+ economizer_sig_col,
101
+ cooling_sig_col,
102
+ ]
103
+ self._apply_analog_checks(df, columns_to_check, check_greater_than_one=True)
104
+
105
+ # Perform calculations
106
+ abs_mat_minus_oat = abs(df[mat_col] - df[oat_col])
107
+ mat_oat_sqrted = np.sqrt(mix_degf_err_thres**2 + outdoor_degf_err_thres**2)
108
+
109
+ combined_check = (
110
+ (abs_mat_minus_oat > mat_oat_sqrted)
111
+ # Verify AHU is running in OS 3 cooling mode with minimum OA
112
+ & (df[cooling_sig_col] > 0.01)
113
+ & (df[economizer_sig_col] > 0.9)
114
+ )
115
+
116
+ # Set fault flag
117
+ self._set_fault_flag(df, combined_check, "fc10_flag")
118
+
119
+ return df
@@ -0,0 +1,139 @@
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="sat_col",
11
+ constant_form="SAT_COL",
12
+ description="Supply air temperature",
13
+ unit="°F",
14
+ required=True,
15
+ type=float,
16
+ ),
17
+ FaultInputColumn(
18
+ name="sat_sp_col",
19
+ constant_form="SAT_SP_COL",
20
+ description="Supply air temperature setpoint",
21
+ unit="°F",
22
+ required=True,
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=True,
31
+ type=float,
32
+ ),
33
+ FaultInputColumn(
34
+ name="economizer_sig_col",
35
+ constant_form="ECONOMIZER_SIG_COL",
36
+ description="Economizer signal",
37
+ unit="%",
38
+ required=True,
39
+ type=float,
40
+ ),
41
+ ]
42
+
43
+ FAULT_PARAMS = [
44
+ InstanceAttribute(
45
+ name="supply_degf_err_thres",
46
+ constant_form="SUPPLY_DEGF_ERR_THRES",
47
+ description="Supply air temperature error threshold",
48
+ unit="°F",
49
+ type=float,
50
+ ),
51
+ InstanceAttribute(
52
+ name="mix_degf_err_thres",
53
+ constant_form="MIX_DEGF_ERR_THRES",
54
+ description="Mixed air temperature error threshold",
55
+ unit="°F",
56
+ type=float,
57
+ ),
58
+ InstanceAttribute(
59
+ name="outdoor_degf_err_thres",
60
+ constant_form="OUTDOOR_DEGF_ERR_THRES",
61
+ description="Outdoor air temperature error threshold",
62
+ unit="°F",
63
+ type=float,
64
+ ),
65
+ InstanceAttribute(
66
+ name="ahu_min_oa_dpr",
67
+ constant_form="AHU_MIN_OA_DPR",
68
+ description="Minimum outdoor air damper position",
69
+ unit="fraction",
70
+ type=float,
71
+ ),
72
+ ]
73
+
74
+
75
+ class FaultConditionThirteen(BaseFaultCondition, FaultConditionMixin):
76
+ """Class provides the definitions for Fault Condition 13.
77
+ Supply air temperature too high in full cooling mode.
78
+ This fault checks if SAT is too high compared to SAT setpoint
79
+ in OS3 (economizer + mechanical cooling) and OS4 (mechanical cooling only) modes.
80
+
81
+ py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc13.py -rP -s
82
+ """
83
+
84
+ input_columns = INPUT_COLS
85
+ fault_params = FAULT_PARAMS
86
+ equation_string = (
87
+ "fc13_flag = 1 if (SAT > SATSP + εSAT) and "
88
+ "((CLG > 0.9 and ECO > 0.9) or (CLG > 0.9 and ECO = MIN_OA)) "
89
+ "for N consecutive values else 0 \n"
90
+ )
91
+ description_string = (
92
+ "Fault Condition 13: Supply air temperature too high in full cooling mode "
93
+ "in OS3 (economizer + mechanical cooling) and OS4 (mechanical cooling only) modes \n"
94
+ )
95
+ error_string = "One or more required columns are missing or None \n"
96
+
97
+ @FaultConditionMixin._handle_errors
98
+ def apply(self, df: pd.DataFrame) -> pd.DataFrame:
99
+ """Apply the fault condition to the DataFrame."""
100
+ # Apply common checks
101
+ self._apply_common_checks(df)
102
+
103
+ # Get column values using accessor methods
104
+ sat_col = self.get_input_column("sat_col")
105
+ sat_sp_col = self.get_input_column("sat_sp_col")
106
+ cooling_sig_col = self.get_input_column("cooling_sig_col")
107
+ economizer_sig_col = self.get_input_column("economizer_sig_col")
108
+
109
+ # Get parameter values using accessor methods
110
+ supply_degf_err_thres = self.get_param("supply_degf_err_thres")
111
+ ahu_min_oa_dpr = self.get_param("ahu_min_oa_dpr")
112
+
113
+ # Check analog outputs [data with units of %] are floats only
114
+ columns_to_check = [
115
+ economizer_sig_col,
116
+ cooling_sig_col,
117
+ ]
118
+ self._apply_analog_checks(df, columns_to_check, check_greater_than_one=True)
119
+
120
+ # Check if SAT is too high compared to setpoint
121
+ sat_too_high = df[sat_col] > (df[sat_sp_col] + supply_degf_err_thres)
122
+
123
+ # Check operating modes:
124
+ # OS3: Economizer + full mechanical cooling (ECO > 0.9 and CLG > 0.9)
125
+ os3_mode = (df[economizer_sig_col] > 0.9) & (df[cooling_sig_col] > 0.9)
126
+
127
+ # OS4: Full mechanical cooling only (ECO = MIN_OA and CLG > 0.9)
128
+ os4_mode = (df[economizer_sig_col] <= ahu_min_oa_dpr) & (
129
+ df[cooling_sig_col] > 0.9
130
+ )
131
+
132
+ # Combine conditions:
133
+ # Fault occurs when SAT is too high in either OS3 or OS4 mode with full cooling
134
+ combined_check = sat_too_high & (os3_mode | os4_mode)
135
+
136
+ # Set fault flag
137
+ self._set_fault_flag(df, combined_check, "fc13_flag")
138
+
139
+ return df