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
@@ -1,1487 +1,30 @@
1
- import pandas as pd
2
- import numpy as np
3
- from open_fdd.core.base_fault import BaseFaultCondition
4
- from open_fdd.core.mixins import FaultConditionMixin
5
- from open_fdd.air_handling_unit.faults.helper_utils import SharedUtils
6
- import operator
7
- import sys
8
- from open_fdd.core.exceptions import MissingColumnError, InvalidParameterError
9
-
10
-
11
- class FaultConditionOne(BaseFaultCondition, FaultConditionMixin):
12
- """Class provides the definitions for Fault Condition 1.
13
- AHU low duct static pressure fan fault.
14
-
15
- py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc1.py -rP -s
16
- """
17
-
18
- def _init_specific_attributes(self, dict_):
19
- # Initialize specific attributes
20
- self.duct_static_col = dict_.get("DUCT_STATIC_COL", None)
21
- self.supply_vfd_speed_col = dict_.get("SUPPLY_VFD_SPEED_COL", None)
22
- self.duct_static_setpoint_col = dict_.get("DUCT_STATIC_SETPOINT_COL", None)
23
- self.duct_static_inches_err_thres = dict_.get(
24
- "DUCT_STATIC_INCHES_ERR_THRES", None
25
- )
26
- self.vfd_speed_percent_max = dict_.get("VFD_SPEED_PERCENT_MAX", None)
27
- self.vfd_speed_percent_err_thres = dict_.get(
28
- "VFD_SPEED_PERCENT_ERR_THRES", None
29
- )
30
-
31
- # Set required columns
32
- self.required_columns = [
33
- self.duct_static_col,
34
- self.supply_vfd_speed_col,
35
- self.duct_static_setpoint_col,
36
- ]
37
-
38
- # Set documentation strings
39
- self.equation_string = "fc1_flag = 1 if (DP < DPSP - εDP) and (VFDSPD >= VFDSPD_max - εVFDSPD) for N consecutive values else 0 \n"
40
- self.description_string = (
41
- "Fault Condition 1: Duct static too low at fan at full speed \n"
42
- )
43
- self.required_column_description = "Required inputs are the duct static pressure, setpoint, and supply fan VFD speed \n"
44
- self.error_string = "One or more required columns are missing or None \n"
45
-
46
- @FaultConditionMixin._handle_errors
47
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
48
- self._apply_common_checks(df)
49
- self._apply_analog_checks(df, [self.supply_vfd_speed_col])
50
-
51
- # Convert VFD speed from percentage to fraction if needed
52
- if (df[self.supply_vfd_speed_col] > 1.0).any():
53
- df[self.supply_vfd_speed_col] = df[self.supply_vfd_speed_col] / 100.0
54
-
55
- # Convert thresholds from percentage to fraction
56
- vfd_speed_max = self.vfd_speed_percent_max / 100.0
57
- vfd_speed_err_thres = self.vfd_speed_percent_err_thres / 100.0
58
-
59
- # Specific checks
60
- static_check = (
61
- df[self.duct_static_col]
62
- < df[self.duct_static_setpoint_col] - self.duct_static_inches_err_thres
63
- )
64
- fan_check = df[self.supply_vfd_speed_col] >= vfd_speed_max - vfd_speed_err_thres
65
- combined_check = static_check & fan_check
66
-
67
- self._set_fault_flag(df, combined_check, "fc1_flag")
68
- return df
69
-
70
-
71
- class FaultConditionTwo(BaseFaultCondition, FaultConditionMixin):
72
- """Class provides the definitions for Fault Condition 2.
73
- Mix temperature too low; should be between outside and return air.
74
- """
75
-
76
- def _init_specific_attributes(self, dict_):
77
- # Initialize specific attributes
78
- self.mat_col = dict_.get("MAT_COL", None)
79
- self.rat_col = dict_.get("RAT_COL", None)
80
- self.oat_col = dict_.get("OAT_COL", None)
81
- self.supply_vfd_speed_col = dict_.get("SUPPLY_VFD_SPEED_COL", None)
82
- self.mix_degf_err_thres = dict_.get("MIX_DEGF_ERR_THRES", None)
83
- self.outdoor_degf_err_thres = dict_.get("OUTDOOR_DEGF_ERR_THRES", None)
84
- self.return_degf_err_thres = dict_.get("RETURN_DEGF_ERR_THRES", None)
85
-
86
- # Set required columns
87
- self.required_columns = [
88
- self.mat_col,
89
- self.rat_col,
90
- self.oat_col,
91
- self.supply_vfd_speed_col,
92
- ]
93
-
94
- # Set documentation strings
95
- self.equation_string = "fc2_flag = 1 if (MAT - εMAT < min(RAT - εRAT, OAT - εOAT)) and (VFDSPD > 0) for N consecutive values else 0 \n"
96
- self.description_string = "Fault Condition 2: Mix temperature too low; should be between outside and return air \n"
97
- self.required_column_description = "Required inputs are the mixed air temperature, return air temperature, outside air temperature, and supply fan VFD speed \n"
98
- self.error_string = "One or more required columns are missing or None \n"
99
-
100
- @FaultConditionMixin._handle_errors
101
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
102
- self._apply_common_checks(df)
103
- self._apply_analog_checks(df, [self.supply_vfd_speed_col])
104
-
105
- # Specific checks
106
- mat_check = df[self.mat_col] - self.mix_degf_err_thres
107
- temp_min_check = np.minimum(
108
- df[self.rat_col] - self.return_degf_err_thres,
109
- df[self.oat_col] - self.outdoor_degf_err_thres,
110
- )
111
- combined_check = (mat_check < temp_min_check) & (
112
- df[self.supply_vfd_speed_col] > 0.01
113
- )
114
-
115
- self._set_fault_flag(df, combined_check, "fc2_flag")
116
- return df
117
-
118
-
119
- class FaultConditionThree(BaseFaultCondition, FaultConditionMixin):
120
- """Class provides the definitions for Fault Condition 3.
121
- Mix temperature too high; should be between outside and return air.
122
- """
123
-
124
- def _init_specific_attributes(self, dict_):
125
- # Initialize specific attributes
126
- self.mat_col = dict_.get("MAT_COL", None)
127
- self.rat_col = dict_.get("RAT_COL", None)
128
- self.oat_col = dict_.get("OAT_COL", None)
129
- self.supply_vfd_speed_col = dict_.get("SUPPLY_VFD_SPEED_COL", None)
130
- self.mix_degf_err_thres = dict_.get("MIX_DEGF_ERR_THRES", None)
131
- self.outdoor_degf_err_thres = dict_.get("OUTDOOR_DEGF_ERR_THRES", None)
132
- self.return_degf_err_thres = dict_.get("RETURN_DEGF_ERR_THRES", None)
133
-
134
- # Set required columns
135
- self.required_columns = [
136
- self.mat_col,
137
- self.rat_col,
138
- self.oat_col,
139
- self.supply_vfd_speed_col,
140
- ]
141
-
142
- # Set documentation strings
143
- self.equation_string = "fc3_flag = 1 if (MAT - εMAT > max(RAT + εRAT, OAT + εOAT)) and (VFDSPD > 0) for N consecutive values else 0 \n"
144
- self.description_string = "Fault Condition 3: Mix temperature too high; should be between outside and return air \n"
145
- self.required_column_description = "Required inputs are the mixed air temperature, return air temperature, outside air temperature, and supply fan VFD speed \n"
146
- self.error_string = "One or more required columns are missing or None \n"
147
-
148
- @FaultConditionMixin._handle_errors
149
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
150
- self._apply_common_checks(df)
151
- self._apply_analog_checks(df, [self.supply_vfd_speed_col])
152
-
153
- # Specific checks
154
- mat_check = df[self.mat_col] - self.mix_degf_err_thres
155
- temp_max_check = np.maximum(
156
- df[self.rat_col] + self.return_degf_err_thres,
157
- df[self.oat_col] + self.outdoor_degf_err_thres,
158
- )
159
- combined_check = (mat_check > temp_max_check) & (
160
- df[self.supply_vfd_speed_col] > 0.01
161
- )
162
-
163
- self._set_fault_flag(df, combined_check, "fc3_flag")
164
- return df
165
-
166
-
167
- class FaultConditionFour(BaseFaultCondition, FaultConditionMixin):
168
- """Class provides the definitions for Fault Condition 4.
169
-
170
- This fault flags excessive operating states on the AHU
171
- if it's hunting between heating, econ, econ+mech, and
172
- a mech clg modes. The code counts how many operating
173
- changes in an hour and will throw a fault if there is
174
- excessive OS changes to flag control sys hunting.
175
-
176
- py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc4.py -rP -s
177
- """
178
-
179
- def _init_specific_attributes(self, dict_):
180
- # Threshold parameters
181
- self.delta_os_max = dict_.get("DELTA_OS_MAX", None)
182
- self.ahu_min_oa_dpr = dict_.get("AHU_MIN_OA_DPR", None)
183
-
184
- # Validate that delta_os_max can be either a float or an integer
185
- if not isinstance(self.delta_os_max, (int)):
186
- raise InvalidParameterError(
187
- f"The parameter 'delta_os_max' should be an integer data type, but got {type(self.delta_os_max).__name__}."
188
- )
189
-
190
- # Validate that ahu_min_oa_dpr is a float
191
- if not isinstance(self.ahu_min_oa_dpr, float):
192
- raise InvalidParameterError(
193
- f"The parameter 'ahu_min_oa_dpr' should be a float, but got {type(self.ahu_min_oa_dpr).__name__}."
194
- )
195
-
196
- # Other attributes
197
- self.economizer_sig_col = dict_.get("ECONOMIZER_SIG_COL", None)
198
- self.heating_sig_col = dict_.get("HEATING_SIG_COL", None)
199
- self.cooling_sig_col = dict_.get("COOLING_SIG_COL", None)
200
- self.supply_vfd_speed_col = dict_.get("SUPPLY_VFD_SPEED_COL", None)
201
-
202
- # Set documentation strings
203
- self.equation_string = (
204
- "fc4_flag = 1 if excessive mode changes (> δOS_max) occur "
205
- "within an hour across heating, econ, econ+mech, mech clg, and min OA modes \n"
206
- )
207
- self.description_string = "Fault Condition 4: Excessive AHU operating state changes detected (hunting behavior) \n"
208
- self.required_column_description = (
209
- "Required inputs are the economizer signal, supply fan VFD speed, "
210
- "and optionally heating and cooling signals \n"
211
- )
212
- self.error_string = "One or more required columns are missing or None \n"
213
-
214
- # Set required columns, making heating and cooling optional
215
- self.required_columns = [
216
- self.economizer_sig_col,
217
- self.supply_vfd_speed_col,
218
- ]
219
-
220
- # If heating or cooling columns are provided, add them to the required columns
221
- if self.heating_sig_col:
222
- self.required_columns.append(self.heating_sig_col)
223
- if self.cooling_sig_col:
224
- self.required_columns.append(self.cooling_sig_col)
225
-
226
- @FaultConditionMixin._handle_errors
227
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
228
- """Apply the fault condition to the DataFrame."""
229
- # Apply common checks
230
- self._apply_common_checks(df)
231
- # Add analog checks for supply_vfd_speed_col
232
- self._apply_analog_checks(df, [self.supply_vfd_speed_col])
233
-
234
- # Convert VFD speed from percentage to fraction if needed
235
- if (df[self.supply_vfd_speed_col] > 1.0).any():
236
- df[self.supply_vfd_speed_col] = df[self.supply_vfd_speed_col] / 100.0
237
-
238
- # Calculate operating state changes
239
- df["os_change"] = 0
240
- df.loc[df[self.economizer_sig_col] > 0, "os_change"] += 1
241
- df.loc[df[self.supply_vfd_speed_col] > self.ahu_min_oa_dpr, "os_change"] += 1
242
- if self.heating_sig_col:
243
- df.loc[df[self.heating_sig_col] > 0, "os_change"] += 1
244
- if self.cooling_sig_col:
245
- df.loc[df[self.cooling_sig_col] > 0, "os_change"] += 1
246
-
247
- # Calculate changes in operating state
248
- df["os_change_diff"] = df["os_change"].diff().abs()
249
- df["os_change_diff"] = df["os_change_diff"].fillna(0)
250
-
251
- # Calculate rolling sum of changes
252
- df["os_change_sum"] = df["os_change_diff"].rolling(window=60).sum()
253
-
254
- # Set fault flag
255
- df["fc4_flag"] = (df["os_change_sum"] > self.delta_os_max).astype(int)
256
-
257
- return df
258
-
259
-
260
- class FaultConditionFive(BaseFaultCondition, FaultConditionMixin):
261
- """Class provides the definitions for Fault Condition 5.
262
- SAT too low; should be higher than MAT in HTG MODE
263
- --Broken heating valve or other mechanical issue
264
- related to heat valve not working as designed
265
- """
266
-
267
- def _init_specific_attributes(self, dict_):
268
- # Threshold parameters
269
- self.mix_degf_err_thres = dict_.get("MIX_DEGF_ERR_THRES", None)
270
- self.supply_degf_err_thres = dict_.get("SUPPLY_DEGF_ERR_THRES", None)
271
- self.delta_t_supply_fan = dict_.get("DELTA_T_SUPPLY_FAN", None)
272
-
273
- # Validate that threshold parameters are floats
274
- for param, value in [
275
- ("mix_degf_err_thres", self.mix_degf_err_thres),
276
- ("supply_degf_err_thres", self.supply_degf_err_thres),
277
- ("delta_t_supply_fan", self.delta_t_supply_fan),
278
- ]:
279
- if not isinstance(value, float):
280
- raise InvalidParameterError(
281
- f"The parameter '{param}' should be a float, but got {type(value).__name__}."
282
- )
283
-
284
- # Other attributes
285
- self.mat_col = dict_.get("MAT_COL", None)
286
- self.sat_col = dict_.get("SAT_COL", None)
287
- self.heating_sig_col = dict_.get("HEATING_SIG_COL", None)
288
- self.supply_vfd_speed_col = dict_.get("SUPPLY_VFD_SPEED_COL", None)
289
-
290
- # Set documentation strings
291
- self.equation_string = (
292
- "fc5_flag = 1 if (SAT + εSAT <= MAT - εMAT + ΔT_supply_fan) and "
293
- "(heating signal > 0) and (VFDSPD > 0) for N consecutive values else 0 \n"
294
- )
295
- self.description_string = (
296
- "Fault Condition 5: SAT too low; should be higher than MAT in HTG MODE, "
297
- "potential broken heating valve or mechanical issue \n"
298
- )
299
- self.required_column_description = (
300
- "Required inputs are the mixed air temperature, supply air temperature, "
301
- "heating signal, and supply fan VFD speed \n"
302
- )
303
- self.error_string = "One or more required columns are missing or None \n"
304
-
305
- # Set required columns specific to this fault condition
306
- self.required_columns = [
307
- self.mat_col,
308
- self.sat_col,
309
- self.heating_sig_col,
310
- self.supply_vfd_speed_col,
311
- ]
312
-
313
- @FaultConditionMixin._handle_errors
314
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
315
- """Apply the fault condition to the DataFrame."""
316
- # Apply common checks
317
- self._apply_common_checks(df)
318
-
319
- # Check analog outputs [data with units of %] are floats only
320
- columns_to_check = [self.supply_vfd_speed_col, self.heating_sig_col]
321
- self._apply_analog_checks(df, columns_to_check)
322
-
323
- # Perform checks
324
- sat_check = df[self.sat_col] + self.supply_degf_err_thres
325
- mat_check = df[self.mat_col] - self.mix_degf_err_thres + self.delta_t_supply_fan
326
-
327
- combined_check = (
328
- (sat_check <= mat_check)
329
- & (df[self.heating_sig_col] > 0.01)
330
- & (df[self.supply_vfd_speed_col] > 0.01)
331
- )
332
-
333
- # Set fault flag
334
- self._set_fault_flag(df, combined_check, "fc5_flag")
335
-
336
- return df
337
-
338
-
339
- class FaultConditionSix(BaseFaultCondition, FaultConditionMixin):
340
- """Class provides the definitions for Fault Condition 6.
341
-
342
- This fault related to knowing the design air flow for
343
- ventilation AHU_MIN_CFM_DESIGN which comes from the
344
- design mech engineered records where then the fault
345
- tries to calculate that based on totalized measured
346
- AHU air flow and outside air fraction calc from
347
- AHU temp sensors. The fault could flag issues where
348
- flow stations are either not in calibration, temp
349
- sensors used in the OA frac calc, or possibly the AHU
350
- not bringing in design air flow when not operating in
351
- economizer free cooling modes.
352
-
353
- py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc6.py -rP -s
354
- """
355
-
356
- def _init_specific_attributes(self, dict_):
357
- # Threshold parameters
358
- self.airflow_err_thres = dict_.get("AIRFLOW_ERR_THRES", None)
359
- self.ahu_min_oa_cfm_design = dict_.get("AHU_MIN_OA_CFM_DESIGN", None)
360
- self.outdoor_degf_err_thres = dict_.get("OUTDOOR_DEGF_ERR_THRES", None)
361
- self.return_degf_err_thres = dict_.get("RETURN_DEGF_ERR_THRES", None)
362
- self.oat_rat_delta_min = dict_.get("OAT_RAT_DELTA_MIN", None)
363
- self.ahu_min_oa_dpr = dict_.get("AHU_MIN_OA_DPR", None)
364
-
365
- if not isinstance(self.ahu_min_oa_cfm_design, (float, int)):
366
- raise InvalidParameterError(
367
- f"The parameter 'ahu_min_oa_cfm_design' should be an integer data type, but got {type(self.ahu_min_oa_cfm_design).__name__}."
368
- )
369
-
370
- # Validate that threshold parameters are floats
371
- for param, value in [
372
- ("airflow_err_thres", self.airflow_err_thres),
373
- ("outdoor_degf_err_thres", self.outdoor_degf_err_thres),
374
- ("return_degf_err_thres", self.return_degf_err_thres),
375
- ("oat_rat_delta_min", self.oat_rat_delta_min),
376
- ("ahu_min_oa_dpr", self.ahu_min_oa_dpr),
377
- ]:
378
- if not isinstance(value, float):
379
- raise InvalidParameterError(
380
- f"The parameter '{param}' should be a float, but got {type(value).__name__}."
381
- )
382
-
383
- # Other attributes
384
- self.supply_fan_air_volume_col = dict_.get("SUPPLY_FAN_AIR_VOLUME_COL", None)
385
- self.mat_col = dict_.get("MAT_COL", None)
386
- self.oat_col = dict_.get("OAT_COL", None)
387
- self.rat_col = dict_.get("RAT_COL", None)
388
- self.supply_vfd_speed_col = dict_.get("SUPPLY_VFD_SPEED_COL", None)
389
- self.economizer_sig_col = dict_.get("ECONOMIZER_SIG_COL", None)
390
- self.heating_sig_col = dict_.get("HEATING_SIG_COL", None)
391
- self.cooling_sig_col = dict_.get("COOLING_SIG_COL", None)
392
-
393
- # Set documentation strings
394
- self.equation_string = (
395
- "fc6_flag = 1 if |OA_frac_calc - OA_min| > airflow_err_thres "
396
- "in non-economizer modes, considering htg and mech clg OS \n"
397
- )
398
- self.description_string = (
399
- "Fault Condition 6: Issues detected with OA fraction calculation or AHU "
400
- "not maintaining design air flow in non-economizer conditions \n"
401
- )
402
- self.required_column_description = (
403
- "Required inputs are the supply fan air volume, mixed air temperature, "
404
- "outside air temperature, return air temperature, and VFD speed. "
405
- "Optional inputs include economizer signal, heating signal, and cooling signal \n"
406
- )
407
- self.error_string = "One or more required columns are missing or None \n"
408
-
409
- # Set required columns specific to this fault condition
410
- self.required_columns = [
411
- self.supply_fan_air_volume_col,
412
- self.mat_col,
413
- self.oat_col,
414
- self.rat_col,
415
- self.supply_vfd_speed_col,
416
- self.economizer_sig_col,
417
- self.heating_sig_col,
418
- self.cooling_sig_col,
419
- ]
420
-
421
- @FaultConditionMixin._handle_errors
422
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
423
- """Apply the fault condition to the DataFrame."""
424
- # Apply common checks
425
- self._apply_common_checks(df)
426
-
427
- # Check for zeros in the columns that could lead to division by zero errors
428
- cols_to_check = [self.rat_col, self.oat_col, self.supply_fan_air_volume_col]
429
- if df[cols_to_check].eq(0).any().any():
430
- print(f"Warning: Zero values found in columns: {cols_to_check}")
431
- print("This may cause division by zero errors.")
432
- sys.stdout.flush()
433
-
434
- # Check analog outputs [data with units of %] are floats only
435
- columns_to_check = [
436
- self.supply_vfd_speed_col,
437
- self.economizer_sig_col,
438
- self.heating_sig_col,
439
- self.cooling_sig_col,
440
- ]
441
- self._apply_analog_checks(df, columns_to_check)
442
-
443
- # Calculate intermediate values
444
- rat_minus_oat = abs(df[self.rat_col] - df[self.oat_col])
445
- percent_oa_calc = (df[self.mat_col] - df[self.rat_col]) / (
446
- df[self.oat_col] - df[self.rat_col]
447
- )
448
-
449
- # Replace negative values in percent_oa_calc with zero using vectorized operation
450
- percent_oa_calc = percent_oa_calc.clip(lower=0)
451
-
452
- perc_OAmin = self.ahu_min_oa_cfm_design / df[self.supply_fan_air_volume_col]
453
- percent_oa_calc_minus_perc_OAmin = abs(percent_oa_calc - perc_OAmin)
454
-
455
- # Combined checks for OS 1 and OS 4 modes
456
- os1_htg_mode_check = (
457
- (rat_minus_oat >= self.oat_rat_delta_min)
458
- & (percent_oa_calc_minus_perc_OAmin > self.airflow_err_thres)
459
- & (df[self.heating_sig_col] > 0.0)
460
- & (df[self.supply_vfd_speed_col] > 0.0)
461
- )
462
-
463
- os4_clg_mode_check = (
464
- (rat_minus_oat >= self.oat_rat_delta_min)
465
- & (percent_oa_calc_minus_perc_OAmin > self.airflow_err_thres)
466
- & (df[self.heating_sig_col] == 0.0)
467
- & (df[self.cooling_sig_col] > 0.0)
468
- & (df[self.supply_vfd_speed_col] > 0.0)
469
- & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr)
470
- )
471
-
472
- combined_check = os1_htg_mode_check | os4_clg_mode_check
473
-
474
- # Set fault flag
475
- self._set_fault_flag(df, combined_check, "fc6_flag")
476
-
477
- return df
478
-
479
-
480
- class FaultConditionSeven(BaseFaultCondition, FaultConditionMixin):
481
- """Class provides the definitions for Fault Condition 7.
482
- Very similar to FC 13 but uses heating valve.
483
- Supply air temperature too low in full heating.
484
-
485
- py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc7.py -rP -s
486
- """
487
-
488
- def _init_specific_attributes(self, dict_):
489
- # Threshold parameters
490
- self.supply_degf_err_thres = dict_.get("SUPPLY_DEGF_ERR_THRES", None)
491
-
492
- # Validate that threshold parameters are floats
493
- if not isinstance(self.supply_degf_err_thres, float):
494
- raise InvalidParameterError(
495
- f"The parameter 'supply_degf_err_thres' should be a float, but got {type(self.supply_degf_err_thres).__name__}."
496
- )
497
-
498
- # Other attributes
499
- self.sat_col = dict_.get("SAT_COL", None)
500
- self.sat_setpoint_col = dict_.get("SAT_SETPOINT_COL", None)
501
- self.heating_sig_col = dict_.get("HEATING_SIG_COL", None)
502
- self.supply_vfd_speed_col = dict_.get("SUPPLY_VFD_SPEED_COL", None)
503
-
504
- # Set documentation strings
505
- self.equation_string = (
506
- "fc7_flag = 1 if SAT < (SATSP - εSAT) in full heating mode "
507
- "and VFD speed > 0 for N consecutive values else 0 \n"
508
- )
509
- self.description_string = (
510
- "Fault Condition 7: Supply air temperature too low in full heating mode "
511
- "with heating valve fully open \n"
512
- )
513
- self.required_column_description = (
514
- "Required inputs are the supply air temperature, supply air temperature setpoint, "
515
- "heating signal, and supply fan VFD speed \n"
516
- )
517
- self.error_string = "One or more required columns are missing or None \n"
518
-
519
- # Set required columns specific to this fault condition
520
- self.required_columns = [
521
- self.sat_col,
522
- self.sat_setpoint_col,
523
- self.heating_sig_col,
524
- self.supply_vfd_speed_col,
525
- ]
526
-
527
- @FaultConditionMixin._handle_errors
528
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
529
- """Apply the fault condition to the DataFrame."""
530
- # Apply common checks
531
- self._apply_common_checks(df)
532
-
533
- # Check analog outputs [data with units of %] are floats only
534
- columns_to_check = [self.supply_vfd_speed_col, self.heating_sig_col]
535
- self._apply_analog_checks(df, columns_to_check)
536
-
537
- # Perform checks
538
- sat_check = df[self.sat_setpoint_col] - self.supply_degf_err_thres
539
-
540
- combined_check = (
541
- (df[self.sat_col] < sat_check)
542
- & (df[self.heating_sig_col] > 0.9)
543
- & (df[self.supply_vfd_speed_col] > 0)
544
- )
545
-
546
- # Set fault flag
547
- self._set_fault_flag(df, combined_check, "fc7_flag")
548
-
549
- return df
550
-
551
-
552
- class FaultConditionEight(BaseFaultCondition, FaultConditionMixin):
553
- """Class provides the definitions for Fault Condition 8.
554
- Supply air temperature and mix air temperature should
555
- be approx equal in economizer mode.
556
-
557
- py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc8.py -rP -s
558
- """
559
-
560
- def _init_specific_attributes(self, dict_):
561
- # Threshold parameters
562
- self.delta_t_supply_fan = dict_.get("DELTA_T_SUPPLY_FAN", None)
563
- self.mix_degf_err_thres = dict_.get("MIX_DEGF_ERR_THRES", None)
564
- self.supply_degf_err_thres = dict_.get("SUPPLY_DEGF_ERR_THRES", None)
565
- self.ahu_min_oa_dpr = dict_.get("AHU_MIN_OA_DPR", None)
566
-
567
- # Validate that threshold parameters are floats
568
- for param, value in [
569
- ("delta_t_supply_fan", self.delta_t_supply_fan),
570
- ("mix_degf_err_thres", self.mix_degf_err_thres),
571
- ("supply_degf_err_thres", self.supply_degf_err_thres),
572
- ("ahu_min_oa_dpr", self.ahu_min_oa_dpr),
573
- ]:
574
- if not isinstance(value, float):
575
- raise InvalidParameterError(
576
- f"The parameter '{param}' should be a float, but got {type(value).__name__}."
577
- )
578
-
579
- # Other attributes
580
- self.mat_col = dict_.get("MAT_COL", None)
581
- self.sat_col = dict_.get("SAT_COL", None)
582
- self.economizer_sig_col = dict_.get("ECONOMIZER_SIG_COL", None)
583
- self.cooling_sig_col = dict_.get("COOLING_SIG_COL", None)
584
-
585
- # Set documentation strings
586
- self.equation_string = (
587
- "fc8_flag = 1 if |SAT - MAT - ΔT_fan| > √(εSAT² + εMAT²) "
588
- "in economizer mode for N consecutive values else 0 \n"
589
- )
590
- self.description_string = (
591
- "Fault Condition 8: Supply air temperature and mixed air temperature should "
592
- "be approximately equal in economizer mode \n"
593
- )
594
- self.required_column_description = (
595
- "Required inputs are the mixed air temperature, supply air temperature, "
596
- "economizer signal, and cooling signal \n"
597
- )
598
- self.error_string = "One or more required columns are missing or None \n"
599
-
600
- self.set_attributes(dict_)
601
-
602
- # Set required columns specific to this fault condition
603
- self.required_columns = [
604
- self.mat_col,
605
- self.sat_col,
606
- self.economizer_sig_col,
607
- self.cooling_sig_col,
608
- ]
609
-
610
- # Check if any of the required columns are None
611
- if any(col is None for col in self.required_columns):
612
- raise MissingColumnError(
613
- f"{self.error_string}"
614
- f"{self.equation_string}"
615
- f"{self.description_string}"
616
- f"{self.required_column_description}"
617
- f"{self.required_columns}"
618
- )
619
-
620
- # Ensure all required columns are strings
621
- self.required_columns = [str(col) for col in self.required_columns]
622
-
623
- self.mapped_columns = (
624
- f"Your config dictionary is mapped as: {', '.join(self.required_columns)}"
625
- )
626
-
627
- def get_required_columns(self) -> str:
628
- """Returns a string representation of the required columns."""
629
- return (
630
- f"{self.equation_string}"
631
- f"{self.description_string}"
632
- f"{self.required_column_description}"
633
- f"{self.mapped_columns}"
634
- )
635
-
636
- @FaultConditionMixin._handle_errors
637
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
638
- """Apply the fault condition to the DataFrame."""
639
- # Apply common checks
640
- self._apply_common_checks(df)
641
-
642
- # Check analog outputs [data with units of %] are floats only
643
- columns_to_check = [
644
- self.economizer_sig_col,
645
- self.cooling_sig_col,
646
- ]
647
- self._apply_analog_checks(df, columns_to_check)
648
-
649
- # Perform checks
650
- sat_fan_mat = abs(df[self.sat_col] - self.delta_t_supply_fan - df[self.mat_col])
651
- sat_mat_sqrted = np.sqrt(
652
- self.supply_degf_err_thres**2 + self.mix_degf_err_thres**2
653
- )
654
-
655
- combined_check = (
656
- (sat_fan_mat > sat_mat_sqrted)
657
- # Verify AHU is running in OS 3 cooling mode with minimum OA
658
- & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
659
- & (df[self.cooling_sig_col] < 0.1)
660
- )
661
-
662
- # Rolling sum to count consecutive trues
663
- rolling_sum = combined_check.rolling(window=self.rolling_window_size).sum()
664
-
665
- # Set flag to 1 if rolling sum equals the window size
666
- df["fc8_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
667
-
668
- return df
669
-
670
-
671
- class FaultConditionNine(BaseFaultCondition, FaultConditionMixin):
672
- """Class provides the definitions for Fault Condition 9.
673
- Outside air temperature too high in free cooling without
674
- additional mechanical cooling in economizer mode.
675
-
676
- py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc9.py -rP -s
677
- """
678
-
679
- def _init_specific_attributes(self, dict_):
680
- # Threshold parameters
681
- self.delta_t_supply_fan = dict_.get("DELTA_T_SUPPLY_FAN", None)
682
- self.outdoor_degf_err_thres = dict_.get("OUTDOOR_DEGF_ERR_THRES", None)
683
- self.supply_degf_err_thres = dict_.get("SUPPLY_DEGF_ERR_THRES", None)
684
- self.ahu_min_oa_dpr = dict_.get("AHU_MIN_OA_DPR", None)
685
-
686
- # Validate that threshold parameters are floats
687
- for param, value in [
688
- ("delta_t_supply_fan", self.delta_t_supply_fan),
689
- ("outdoor_degf_err_thres", self.outdoor_degf_err_thres),
690
- ("supply_degf_err_thres", self.supply_degf_err_thres),
691
- ("ahu_min_oa_dpr", self.ahu_min_oa_dpr),
692
- ]:
693
- if not isinstance(value, float):
694
- raise InvalidParameterError(
695
- f"The parameter '{param}' should be a float, but got {type(value).__name__}."
696
- )
697
-
698
- # Other attributes
699
- self.sat_setpoint_col = dict_.get("SAT_SETPOINT_COL", None)
700
- self.oat_col = dict_.get("OAT_COL", None)
701
- self.cooling_sig_col = dict_.get("COOLING_SIG_COL", None)
702
- self.economizer_sig_col = dict_.get("ECONOMIZER_SIG_COL", None)
703
-
704
- # Set documentation strings
705
- self.equation_string = (
706
- "fc9_flag = 1 if OAT > (SATSP - ΔT_fan + εSAT) "
707
- "in free cooling mode for N consecutive values else 0 \n"
708
- )
709
- self.description_string = (
710
- "Fault Condition 9: Outside air temperature too high in free cooling mode "
711
- "without additional mechanical cooling in economizer mode \n"
712
- )
713
- self.required_column_description = (
714
- "Required inputs are the supply air temperature setpoint, outside air temperature, "
715
- "cooling signal, and economizer signal \n"
716
- )
717
- self.error_string = "One or more required columns are missing or None \n"
718
-
719
- # Set required columns specific to this fault condition
720
- self.required_columns = [
721
- self.sat_setpoint_col,
722
- self.oat_col,
723
- self.cooling_sig_col,
724
- self.economizer_sig_col,
725
- ]
726
-
727
- @FaultConditionMixin._handle_errors
728
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
729
- """Apply the fault condition to the DataFrame."""
730
- # Apply common checks
731
- self._apply_common_checks(df)
732
-
733
- # Check analog outputs [data with units of %] are floats only
734
- columns_to_check = [
735
- self.economizer_sig_col,
736
- self.cooling_sig_col,
737
- ]
738
- self._apply_analog_checks(df, columns_to_check)
739
-
740
- # Perform calculations
741
- oat_minus_oaterror = df[self.oat_col] - self.outdoor_degf_err_thres
742
- satsp_delta_saterr = (
743
- df[self.sat_setpoint_col]
744
- - self.delta_t_supply_fan
745
- + self.supply_degf_err_thres
746
- )
747
-
748
- combined_check = (
749
- (oat_minus_oaterror > satsp_delta_saterr)
750
- # verify AHU is in OS2 only free cooling mode
751
- & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
752
- & (df[self.cooling_sig_col] < 0.1)
753
- )
754
-
755
- # Set fault flag
756
- self._set_fault_flag(df, combined_check, "fc9_flag")
757
-
758
- return df
759
-
760
-
761
- class FaultConditionTen(BaseFaultCondition, FaultConditionMixin):
762
- """Class provides the definitions for Fault Condition 10.
763
- Outdoor air temperature and mix air temperature should
764
- be approx equal in economizer plus mech cooling mode.
765
-
766
- py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc10.py -rP -s
767
- """
768
-
769
- def _init_specific_attributes(self, dict_):
770
- # Threshold parameters
771
- self.outdoor_degf_err_thres = dict_.get("OUTDOOR_DEGF_ERR_THRES", None)
772
- self.mix_degf_err_thres = dict_.get("MIX_DEGF_ERR_THRES", None)
773
-
774
- # Validate that threshold parameters are floats
775
- for param, value in [
776
- ("outdoor_degf_err_thres", self.outdoor_degf_err_thres),
777
- ("mix_degf_err_thres", self.mix_degf_err_thres),
778
- ]:
779
- if not isinstance(value, float):
780
- raise InvalidParameterError(
781
- f"The parameter '{param}' should be a float, but got {type(value).__name__}."
782
- )
783
-
784
- # Other attributes
785
- self.oat_col = dict_.get("OAT_COL", None)
786
- self.mat_col = dict_.get("MAT_COL", None)
787
- self.cooling_sig_col = dict_.get("COOLING_SIG_COL", None)
788
- self.economizer_sig_col = dict_.get("ECONOMIZER_SIG_COL", None)
789
-
790
- # Set documentation strings
791
- self.equation_string = (
792
- "fc10_flag = 1 if |OAT - MAT| > √(εOAT² + εMAT²) in "
793
- "economizer + mech cooling mode for N consecutive values else 0 \n"
794
- )
795
- self.description_string = (
796
- "Fault Condition 10: Outdoor air temperature and mixed air temperature "
797
- "should be approximately equal in economizer plus mechanical cooling mode \n"
798
- )
799
- self.required_column_description = (
800
- "Required inputs are the outside air temperature, mixed air temperature, "
801
- "cooling signal, and economizer signal \n"
802
- )
803
- self.error_string = "One or more required columns are missing or None \n"
804
-
805
- # Set required columns specific to this fault condition
806
- self.required_columns = [
807
- self.oat_col,
808
- self.mat_col,
809
- self.cooling_sig_col,
810
- self.economizer_sig_col,
811
- ]
812
-
813
- @FaultConditionMixin._handle_errors
814
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
815
- """Apply the fault condition to the DataFrame."""
816
- # Apply common checks
817
- self._apply_common_checks(df)
818
-
819
- # Check analog outputs [data with units of %] are floats only
820
- columns_to_check = [
821
- self.economizer_sig_col,
822
- self.cooling_sig_col,
823
- ]
824
- self._apply_analog_checks(df, columns_to_check)
825
-
826
- # Perform calculations
827
- abs_mat_minus_oat = abs(df[self.mat_col] - df[self.oat_col])
828
- mat_oat_sqrted = np.sqrt(
829
- self.mix_degf_err_thres**2 + self.outdoor_degf_err_thres**2
830
- )
831
-
832
- combined_check = (
833
- (abs_mat_minus_oat > mat_oat_sqrted)
834
- # Verify AHU is running in OS 3 cooling mode with minimum OA
835
- & (df[self.cooling_sig_col] > 0.01)
836
- & (df[self.economizer_sig_col] > 0.9)
837
- )
838
-
839
- # Set fault flag
840
- self._set_fault_flag(df, combined_check, "fc10_flag")
841
-
842
- return df
843
-
844
-
845
- class FaultConditionEleven(BaseFaultCondition, FaultConditionMixin):
846
- """Class provides the definitions for Fault Condition 11.
847
- Outdoor air temperature and mix air temperature should
848
- be approx equal in economizer mode.
849
-
850
- py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc11.py -rP -s
851
- """
852
-
853
- def _init_specific_attributes(self, dict_):
854
- # Threshold parameters
855
- self.outdoor_degf_err_thres = dict_.get("OUTDOOR_DEGF_ERR_THRES", None)
856
- self.mix_degf_err_thres = dict_.get("MIX_DEGF_ERR_THRES", None)
857
-
858
- # Validate that threshold parameters are floats
859
- for param, value in [
860
- ("outdoor_degf_err_thres", self.outdoor_degf_err_thres),
861
- ("mix_degf_err_thres", self.mix_degf_err_thres),
862
- ]:
863
- if not isinstance(value, float):
864
- raise InvalidParameterError(
865
- f"The parameter '{param}' should be a float, but got {type(value).__name__}."
866
- )
867
-
868
- # Other attributes
869
- self.oat_col = dict_.get("OAT_COL", None)
870
- self.mat_col = dict_.get("MAT_COL", None)
871
- self.economizer_sig_col = dict_.get("ECONOMIZER_SIG_COL", None)
872
-
873
- # Set documentation strings
874
- self.equation_string = (
875
- "fc11_flag = 1 if |OAT - MAT| > √(εOAT² + εMAT²) in "
876
- "economizer mode for N consecutive values else 0 \n"
877
- )
878
- self.description_string = (
879
- "Fault Condition 11: Outdoor air temperature and mixed air temperature "
880
- "should be approximately equal in economizer mode \n"
881
- )
882
- self.required_column_description = (
883
- "Required inputs are the outside air temperature, mixed air temperature, "
884
- "and economizer signal \n"
885
- )
886
- self.error_string = "One or more required columns are missing or None \n"
887
-
888
- # Set required columns specific to this fault condition
889
- self.required_columns = [
890
- self.oat_col,
891
- self.mat_col,
892
- self.economizer_sig_col,
893
- ]
894
-
895
- @FaultConditionMixin._handle_errors
896
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
897
- """Apply the fault condition to the DataFrame."""
898
- # Apply common checks
899
- self._apply_common_checks(df)
900
-
901
- # Check analog outputs [data with units of %] are floats only
902
- columns_to_check = [self.economizer_sig_col]
903
- self._apply_analog_checks(df, columns_to_check)
904
-
905
- # Perform calculations
906
- abs_mat_minus_oat = abs(df[self.mat_col] - df[self.oat_col])
907
- mat_oat_sqrted = np.sqrt(
908
- self.mix_degf_err_thres**2 + self.outdoor_degf_err_thres**2
909
- )
910
-
911
- combined_check = (
912
- (abs_mat_minus_oat > mat_oat_sqrted)
913
- # Verify AHU is running in economizer mode
914
- & (df[self.economizer_sig_col] > 0.9)
915
- )
916
-
917
- # Set fault flag
918
- self._set_fault_flag(df, combined_check, "fc11_flag")
919
-
920
- return df
921
-
922
-
923
- class FaultConditionTwelve(BaseFaultCondition, FaultConditionMixin):
924
- """Class provides the definitions for Fault Condition 12.
925
- Supply air temperature too high; should be less than mixed air temperature
926
- in OS3 (economizer + mechanical cooling) and OS4 (mechanical cooling only) modes.
927
-
928
- py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc12.py -rP -s
929
- """
930
-
931
- def _init_specific_attributes(self, dict_):
932
- # Threshold parameters
933
- self.delta_t_supply_fan = dict_.get("DELTA_T_SUPPLY_FAN", None)
934
- self.mix_degf_err_thres = dict_.get("MIX_DEGF_ERR_THRES", None)
935
- self.supply_degf_err_thres = dict_.get("SUPPLY_DEGF_ERR_THRES", None)
936
- self.outdoor_degf_err_thres = dict_.get("OUTDOOR_DEGF_ERR_THRES", None)
937
- self.ahu_min_oa_dpr = dict_.get("AHU_MIN_OA_DPR", None)
938
-
939
- # Validate that threshold parameters are floats
940
- for param, value in [
941
- ("delta_t_supply_fan", self.delta_t_supply_fan),
942
- ("mix_degf_err_thres", self.mix_degf_err_thres),
943
- ("supply_degf_err_thres", self.supply_degf_err_thres),
944
- ("outdoor_degf_err_thres", self.outdoor_degf_err_thres),
945
- ("ahu_min_oa_dpr", self.ahu_min_oa_dpr),
946
- ]:
947
- if not isinstance(value, float):
948
- raise InvalidParameterError(
949
- f"The parameter '{param}' should be a float, but got {type(value).__name__}."
950
- )
951
-
952
- # Other attributes
953
- self.sat_col = dict_.get("SAT_COL", None)
954
- self.mat_col = dict_.get("MAT_COL", None)
955
- self.oat_col = dict_.get("OAT_COL", None)
956
- self.cooling_sig_col = dict_.get("COOLING_SIG_COL", None)
957
- self.economizer_sig_col = dict_.get("ECONOMIZER_SIG_COL", None)
958
-
959
- # Set documentation strings
960
- self.equation_string = (
961
- "fc12_flag = 1 if (SAT > MAT + εSAT) and "
962
- "((CLG > 0 and ECO > 0.9) or (CLG > 0.9 and ECO = MIN_OA)) "
963
- "for N consecutive values else 0 \n"
964
- )
965
- self.description_string = (
966
- "Fault Condition 12: Supply air temperature too high; should be less than "
967
- "mixed air temperature in OS3 (economizer + mechanical cooling) and "
968
- "OS4 (mechanical cooling only) modes \n"
969
- )
970
- self.required_column_description = (
971
- "Required inputs are the supply air temperature, mixed air temperature, "
972
- "outside air temperature, cooling signal, and economizer signal \n"
973
- )
974
- self.error_string = "One or more required columns are missing or None \n"
975
-
976
- # Set required columns specific to this fault condition
977
- self.required_columns = [
978
- self.sat_col,
979
- self.mat_col,
980
- self.oat_col,
981
- self.cooling_sig_col,
982
- self.economizer_sig_col,
983
- ]
984
-
985
- @FaultConditionMixin._handle_errors
986
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
987
- """Apply the fault condition to the DataFrame."""
988
- # Apply common checks
989
- self._apply_common_checks(df)
990
-
991
- # Check analog outputs [data with units of %] are floats only
992
- columns_to_check = [
993
- self.economizer_sig_col,
994
- self.cooling_sig_col,
995
- ]
996
- self._apply_analog_checks(df, columns_to_check)
997
-
998
- # Calculate the threshold for SAT vs MAT comparison
999
- sat_mat_threshold = np.sqrt(
1000
- self.supply_degf_err_thres**2 + self.mix_degf_err_thres**2
1001
- )
1002
-
1003
- # Check if SAT is too high compared to MAT (accounting for supply fan heat)
1004
- sat_too_high = df[self.sat_col] > (
1005
- df[self.mat_col] + sat_mat_threshold + self.delta_t_supply_fan
1006
- )
1007
-
1008
- # Check operating modes:
1009
- # OS3: Economizer + mechanical cooling (ECO > 0.9 and CLG > 0)
1010
- os3_mode = (df[self.economizer_sig_col] > 0.9) & (df[self.cooling_sig_col] > 0)
1011
-
1012
- # OS4: Mechanical cooling only (ECO = MIN_OA and CLG > 0.9)
1013
- os4_mode = (df[self.economizer_sig_col] <= self.ahu_min_oa_dpr) & (
1014
- df[self.cooling_sig_col] > 0.9
1015
- )
1016
-
1017
- # Combine conditions:
1018
- # Fault occurs when SAT is too high in either OS3 or OS4 mode
1019
- combined_check = sat_too_high & (os3_mode | os4_mode)
1020
-
1021
- # Set fault flag
1022
- self._set_fault_flag(df, combined_check, "fc12_flag")
1023
-
1024
- return df
1025
-
1026
-
1027
- class FaultConditionThirteen(BaseFaultCondition, FaultConditionMixin):
1028
- """Class provides the definitions for Fault Condition 13.
1029
- Supply air temperature too high in full cooling mode.
1030
- This fault checks if SAT is too high compared to SAT setpoint
1031
- in OS3 (economizer + mechanical cooling) and OS4 (mechanical cooling only) modes.
1032
-
1033
- py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc13.py -rP -s
1034
- """
1035
-
1036
- def _init_specific_attributes(self, dict_):
1037
- # Threshold parameters
1038
- self.supply_degf_err_thres = dict_.get("SUPPLY_DEGF_ERR_THRES", None)
1039
- self.mix_degf_err_thres = dict_.get("MIX_DEGF_ERR_THRES", None)
1040
- self.outdoor_degf_err_thres = dict_.get("OUTDOOR_DEGF_ERR_THRES", None)
1041
- self.ahu_min_oa_dpr = dict_.get("AHU_MIN_OA_DPR", None)
1042
-
1043
- # Validate that threshold parameters are floats
1044
- for param, value in [
1045
- ("supply_degf_err_thres", self.supply_degf_err_thres),
1046
- ("mix_degf_err_thres", self.mix_degf_err_thres),
1047
- ("outdoor_degf_err_thres", self.outdoor_degf_err_thres),
1048
- ("ahu_min_oa_dpr", self.ahu_min_oa_dpr),
1049
- ]:
1050
- if not isinstance(value, float):
1051
- raise InvalidParameterError(
1052
- f"The parameter '{param}' should be a float, but got {type(value).__name__}."
1053
- )
1054
-
1055
- # Other attributes
1056
- self.sat_col = dict_.get("SAT_COL", None)
1057
- self.sat_sp_col = dict_.get("SAT_SP_COL", None)
1058
- self.cooling_sig_col = dict_.get("COOLING_SIG_COL", None)
1059
- self.economizer_sig_col = dict_.get("ECONOMIZER_SIG_COL", None)
1060
-
1061
- # Set documentation strings
1062
- self.equation_string = (
1063
- "fc13_flag = 1 if (SAT > SATSP + εSAT) and "
1064
- "((CLG > 0.9 and ECO > 0.9) or (CLG > 0.9 and ECO = MIN_OA)) "
1065
- "for N consecutive values else 0 \n"
1066
- )
1067
- self.description_string = (
1068
- "Fault Condition 13: Supply air temperature too high in full cooling mode "
1069
- "in OS3 (economizer + mechanical cooling) and OS4 (mechanical cooling only) modes \n"
1070
- )
1071
- self.required_column_description = (
1072
- "Required inputs are the supply air temperature, supply air temperature setpoint, "
1073
- "cooling signal, and economizer signal \n"
1074
- )
1075
- self.error_string = "One or more required columns are missing or None \n"
1076
-
1077
- # Set required columns specific to this fault condition
1078
- self.required_columns = [
1079
- self.sat_col,
1080
- self.sat_sp_col,
1081
- self.cooling_sig_col,
1082
- self.economizer_sig_col,
1083
- ]
1084
-
1085
- @FaultConditionMixin._handle_errors
1086
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
1087
- """Apply the fault condition to the DataFrame."""
1088
- # Apply common checks
1089
- self._apply_common_checks(df)
1090
-
1091
- # Check analog outputs [data with units of %] are floats only
1092
- columns_to_check = [
1093
- self.economizer_sig_col,
1094
- self.cooling_sig_col,
1095
- ]
1096
- self._apply_analog_checks(df, columns_to_check)
1097
-
1098
- # Check if SAT is too high compared to setpoint
1099
- sat_too_high = df[self.sat_col] > (
1100
- df[self.sat_sp_col] + self.supply_degf_err_thres
1101
- )
1102
-
1103
- # Check operating modes:
1104
- # OS3: Economizer + full mechanical cooling (ECO > 0.9 and CLG > 0.9)
1105
- os3_mode = (df[self.economizer_sig_col] > 0.9) & (
1106
- df[self.cooling_sig_col] > 0.9
1107
- )
1108
-
1109
- # OS4: Full mechanical cooling only (ECO = MIN_OA and CLG > 0.9)
1110
- os4_mode = (df[self.economizer_sig_col] <= self.ahu_min_oa_dpr) & (
1111
- df[self.cooling_sig_col] > 0.9
1112
- )
1113
-
1114
- # Combine conditions:
1115
- # Fault occurs when SAT is too high in either OS3 or OS4 mode with full cooling
1116
- combined_check = sat_too_high & (os3_mode | os4_mode)
1117
-
1118
- # Set fault flag
1119
- self._set_fault_flag(df, combined_check, "fc13_flag")
1120
-
1121
- return df
1122
-
1123
-
1124
- class FaultConditionFourteen(BaseFaultCondition, FaultConditionMixin):
1125
- """Class provides the definitions for Fault Condition 14.
1126
- Temperature drop across inactive cooling coil in OS1 (heating) and OS2 (economizer) modes.
1127
- This fault checks if there is an unexpected temperature drop across the cooling coil
1128
- when it should be inactive.
1129
-
1130
- py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc14.py -rP -s
1131
- """
1132
-
1133
- def _init_specific_attributes(self, dict_):
1134
- # Threshold parameters
1135
- self.delta_t_supply_fan = dict_.get("DELTA_T_SUPPLY_FAN", None)
1136
- self.coil_temp_enter_err_thres = dict_.get("COIL_TEMP_ENTER_ERR_THRES", None)
1137
- self.coil_temp_leave_err_thres = dict_.get("COIL_TEMP_LEAV_ERR_THRES", None)
1138
- self.ahu_min_oa_dpr = dict_.get("AHU_MIN_OA_DPR", None)
1139
-
1140
- # Validate that threshold parameters are floats
1141
- for param, value in [
1142
- ("delta_t_supply_fan", self.delta_t_supply_fan),
1143
- ("coil_temp_enter_err_thres", self.coil_temp_enter_err_thres),
1144
- ("coil_temp_leave_err_thres", self.coil_temp_leave_err_thres),
1145
- ("ahu_min_oa_dpr", self.ahu_min_oa_dpr),
1146
- ]:
1147
- if not isinstance(value, float):
1148
- raise InvalidParameterError(
1149
- f"The parameter '{param}' should be a float, but got {type(value).__name__}."
1150
- )
1151
-
1152
- # Other attributes
1153
- self.clg_coil_enter_temp_col = dict_.get("CLG_COIL_ENTER_TEMP_COL", None)
1154
- self.clg_coil_leave_temp_col = dict_.get("CLG_COIL_LEAVE_TEMP_COL", None)
1155
- self.cooling_sig_col = dict_.get("COOLING_SIG_COL", None)
1156
- self.heating_sig_col = dict_.get("HEATING_SIG_COL", None)
1157
- self.economizer_sig_col = dict_.get("ECONOMIZER_SIG_COL", None)
1158
- self.supply_vfd_speed_col = dict_.get("SUPPLY_VFD_SPEED_COL", None)
1159
-
1160
- # Set documentation strings
1161
- self.equation_string = (
1162
- "fc14_flag = 1 if (CLG_LEAVE < CLG_ENTER - √(εENTER² + εLEAVE²)) "
1163
- "in OS1 (heating) or OS2 (economizer) modes for N consecutive values else 0 \n"
1164
- )
1165
- self.description_string = (
1166
- "Fault Condition 14: Temperature drop across inactive cooling coil "
1167
- "in OS1 (heating) and OS2 (economizer) modes \n"
1168
- )
1169
- self.required_column_description = (
1170
- "Required inputs are the cooling coil entering and leaving air temperatures, "
1171
- "cooling signal, heating signal, economizer signal, and supply fan VFD speed \n"
1172
- )
1173
- self.error_string = "One or more required columns are missing or None \n"
1174
-
1175
- # Set required columns specific to this fault condition
1176
- self.required_columns = [
1177
- self.clg_coil_enter_temp_col,
1178
- self.clg_coil_leave_temp_col,
1179
- self.cooling_sig_col,
1180
- self.heating_sig_col,
1181
- self.economizer_sig_col,
1182
- self.supply_vfd_speed_col,
1183
- ]
1184
-
1185
- @FaultConditionMixin._handle_errors
1186
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
1187
- """Apply the fault condition to the DataFrame."""
1188
- # Apply common checks
1189
- self._apply_common_checks(df)
1190
-
1191
- # Check analog outputs [data with units of %] are floats only
1192
- columns_to_check = [
1193
- self.cooling_sig_col,
1194
- self.heating_sig_col,
1195
- self.economizer_sig_col,
1196
- self.supply_vfd_speed_col,
1197
- ]
1198
- self._apply_analog_checks(df, columns_to_check)
1199
-
1200
- # Calculate the threshold for temperature drop
1201
- temp_drop_threshold = np.sqrt(
1202
- self.coil_temp_enter_err_thres**2 + self.coil_temp_leave_err_thres**2
1203
- )
1204
-
1205
- # Check if there's a significant temperature drop across the cooling coil
1206
- temp_drop = df[self.clg_coil_enter_temp_col] - df[self.clg_coil_leave_temp_col]
1207
- significant_temp_drop = temp_drop > temp_drop_threshold
1208
-
1209
- # Check operating modes:
1210
- # OS1: Heating mode (HTG > 0, CLG = 0, ECO = MIN_OA)
1211
- os1_mode = (
1212
- (df[self.heating_sig_col] > 0.0)
1213
- & (df[self.cooling_sig_col] == 0.0)
1214
- & (df[self.economizer_sig_col] <= self.ahu_min_oa_dpr)
1215
- )
1216
-
1217
- # OS2: Economizer mode (HTG = 0, CLG = 0, ECO > MIN_OA)
1218
- os2_mode = (
1219
- (df[self.heating_sig_col] == 0.0)
1220
- & (df[self.cooling_sig_col] == 0.0)
1221
- & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
1222
- )
1223
-
1224
- # Combine conditions:
1225
- # Fault occurs when there's a significant temperature drop across an inactive cooling coil
1226
- # in either OS1 (heating) or OS2 (economizer) mode
1227
- combined_check = significant_temp_drop & (os1_mode | os2_mode)
1228
-
1229
- # Set fault flag
1230
- self._set_fault_flag(df, combined_check, "fc14_flag")
1231
-
1232
- return df
1233
-
1234
-
1235
- class FaultConditionFifteen(BaseFaultCondition, FaultConditionMixin):
1236
- """Class provides the definitions for Fault Condition 15.
1237
- Temperature rise across inactive heating coil in OS2 (economizer),
1238
- OS3 (economizer + mechanical cooling), and OS4 (mechanical cooling only) modes.
1239
- This fault checks if there is an unexpected temperature rise across the heating coil
1240
- when it should be inactive.
1241
-
1242
- py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc15.py -rP -s
1243
- """
1244
-
1245
- def _init_specific_attributes(self, dict_):
1246
- # Threshold parameters
1247
- self.delta_t_supply_fan = dict_.get("DELTA_SUPPLY_FAN", None)
1248
- self.coil_temp_enter_err_thres = dict_.get("COIL_TEMP_ENTER_ERR_THRES", None)
1249
- self.coil_temp_leave_err_thres = dict_.get("COIL_TEMP_LEAV_ERR_THRES", None)
1250
- self.ahu_min_oa_dpr = dict_.get("AHU_MIN_OA_DPR", None)
1251
-
1252
- # Validate that threshold parameters are floats
1253
- for param, value in [
1254
- ("delta_t_supply_fan", self.delta_t_supply_fan),
1255
- ("coil_temp_enter_err_thres", self.coil_temp_enter_err_thres),
1256
- ("coil_temp_leave_err_thres", self.coil_temp_leave_err_thres),
1257
- ("ahu_min_oa_dpr", self.ahu_min_oa_dpr),
1258
- ]:
1259
- if not isinstance(value, float):
1260
- raise InvalidParameterError(
1261
- f"The parameter '{param}' should be a float, but got {type(value).__name__}."
1262
- )
1263
-
1264
- # Other attributes
1265
- self.htg_coil_enter_temp_col = dict_.get("HTG_COIL_ENTER_TEMP_COL", None)
1266
- self.htg_coil_leave_temp_col = dict_.get("HTG_COIL_LEAVE_TEMP_COL", None)
1267
- self.cooling_sig_col = dict_.get("COOLING_SIG_COL", None)
1268
- self.heating_sig_col = dict_.get("HEATING_SIG_COL", None)
1269
- self.economizer_sig_col = dict_.get("ECONOMIZER_SIG_COL", None)
1270
- self.supply_vfd_speed_col = dict_.get("SUPPLY_VFD_SPEED_COL", None)
1271
-
1272
- # Set documentation strings
1273
- self.equation_string = (
1274
- "fc15_flag = 1 if (HTG_LEAVE > HTG_ENTER + √(εENTER² + εLEAVE²) + ΔTfan) "
1275
- "in OS2 (economizer), OS3 (economizer + mechanical cooling), or "
1276
- "OS4 (mechanical cooling only) modes for N consecutive values else 0 \n"
1277
- )
1278
- self.description_string = (
1279
- "Fault Condition 15: Temperature rise across inactive heating coil "
1280
- "in OS2 (economizer), OS3 (economizer + mechanical cooling), and "
1281
- "OS4 (mechanical cooling only) modes \n"
1282
- )
1283
- self.required_column_description = (
1284
- "Required inputs are the heating coil entering and leaving air temperatures, "
1285
- "cooling signal, heating signal, economizer signal, and supply fan VFD speed \n"
1286
- )
1287
- self.error_string = "One or more required columns are missing or None \n"
1288
-
1289
- # Set required columns specific to this fault condition
1290
- self.required_columns = [
1291
- self.htg_coil_enter_temp_col,
1292
- self.htg_coil_leave_temp_col,
1293
- self.cooling_sig_col,
1294
- self.heating_sig_col,
1295
- self.economizer_sig_col,
1296
- self.supply_vfd_speed_col,
1297
- ]
1298
-
1299
- @FaultConditionMixin._handle_errors
1300
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
1301
- """Apply the fault condition to the DataFrame."""
1302
- # Apply common checks
1303
- self._apply_common_checks(df)
1304
-
1305
- # Check analog outputs [data with units of %] are floats only
1306
- columns_to_check = [
1307
- self.cooling_sig_col,
1308
- self.heating_sig_col,
1309
- self.economizer_sig_col,
1310
- self.supply_vfd_speed_col,
1311
- ]
1312
- self._apply_analog_checks(df, columns_to_check)
1313
-
1314
- # Calculate the threshold for temperature rise, including supply fan heat
1315
- temp_rise_threshold = (
1316
- np.sqrt(
1317
- self.coil_temp_enter_err_thres**2 + self.coil_temp_leave_err_thres**2
1318
- )
1319
- + self.delta_t_supply_fan
1320
- )
1321
-
1322
- # Check if there's a significant temperature rise across the heating coil
1323
- temp_rise = df[self.htg_coil_leave_temp_col] - df[self.htg_coil_enter_temp_col]
1324
- significant_temp_rise = temp_rise > temp_rise_threshold
1325
-
1326
- # Check operating modes:
1327
- # OS2: Economizer mode (HTG = 0, CLG = 0, ECO > MIN_OA)
1328
- os2_mode = (
1329
- (df[self.heating_sig_col] == 0.0)
1330
- & (df[self.cooling_sig_col] == 0.0)
1331
- & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
1332
- )
1333
-
1334
- # OS3: Economizer + mechanical cooling (HTG = 0, CLG > 0, ECO > 0.9)
1335
- os3_mode = (
1336
- (df[self.heating_sig_col] == 0.0)
1337
- & (df[self.cooling_sig_col] > 0.0)
1338
- & (df[self.economizer_sig_col] > 0.9)
1339
- )
1340
-
1341
- # OS4: Mechanical cooling only (HTG = 0, CLG > 0, ECO = MIN_OA)
1342
- os4_mode = (
1343
- (df[self.heating_sig_col] == 0.0)
1344
- & (df[self.cooling_sig_col] > 0.0)
1345
- & (df[self.economizer_sig_col] <= self.ahu_min_oa_dpr)
1346
- )
1347
-
1348
- # Combine conditions:
1349
- # Fault occurs when there's a significant temperature rise across an inactive heating coil
1350
- # in OS2 (economizer), OS3 (economizer + mechanical cooling), or OS4 (mechanical cooling only) mode
1351
- combined_check = significant_temp_rise & (os2_mode | os3_mode | os4_mode)
1352
-
1353
- # Set fault flag
1354
- self._set_fault_flag(df, combined_check, "fc15_flag")
1355
-
1356
- return df
1357
-
1358
-
1359
- class FaultConditionSixteen(BaseFaultCondition, FaultConditionMixin):
1360
- """Class provides the definitions for Fault Condition 16.
1361
- ERV effectiveness should be within specified thresholds based on OAT.
1362
- This fault checks if the ERV (Energy Recovery Ventilator) is operating
1363
- within expected efficiency ranges in both heating and cooling modes.
1364
-
1365
- py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc16.py -rP -s
1366
- """
1367
-
1368
- def _init_specific_attributes(self, dict_):
1369
- # Threshold parameters
1370
- self.erv_efficiency_min_heating = dict_.get("ERV_EFFICIENCY_MIN_HEATING", None)
1371
- self.erv_efficiency_max_heating = dict_.get("ERV_EFFICIENCY_MAX_HEATING", None)
1372
- self.erv_efficiency_min_cooling = dict_.get("ERV_EFFICIENCY_MIN_COOLING", None)
1373
- self.erv_efficiency_max_cooling = dict_.get("ERV_EFFICIENCY_MAX_COOLING", None)
1374
- self.oat_low_threshold = dict_.get("OAT_LOW_THRESHOLD", None)
1375
- self.oat_high_threshold = dict_.get("OAT_HIGH_THRESHOLD", None)
1376
- self.oat_rat_delta_min = dict_.get("OAT_RAT_DELTA_MIN", None)
1377
-
1378
- # Validate that threshold parameters are floats
1379
- for param, value in [
1380
- ("erv_efficiency_min_heating", self.erv_efficiency_min_heating),
1381
- ("erv_efficiency_max_heating", self.erv_efficiency_max_heating),
1382
- ("erv_efficiency_min_cooling", self.erv_efficiency_min_cooling),
1383
- ("erv_efficiency_max_cooling", self.erv_efficiency_max_cooling),
1384
- ("oat_low_threshold", self.oat_low_threshold),
1385
- ("oat_high_threshold", self.oat_high_threshold),
1386
- ("oat_rat_delta_min", self.oat_rat_delta_min),
1387
- ]:
1388
- if not isinstance(value, float):
1389
- raise InvalidParameterError(
1390
- f"The parameter '{param}' should be a float, but got {type(value).__name__}."
1391
- )
1392
-
1393
- # Validate that efficiency values are between 0.0 and 1.0
1394
- for param, value in [
1395
- ("ERV_EFFICIENCY_MIN_HEATING", self.erv_efficiency_min_heating),
1396
- ("ERV_EFFICIENCY_MAX_HEATING", self.erv_efficiency_max_heating),
1397
- ("ERV_EFFICIENCY_MIN_COOLING", self.erv_efficiency_min_cooling),
1398
- ("ERV_EFFICIENCY_MAX_COOLING", self.erv_efficiency_max_cooling),
1399
- ]:
1400
- if not 0.0 <= value <= 1.0:
1401
- raise InvalidParameterError(
1402
- f"The parameter '{param}' should be a float between 0.0 and 1.0, but got {value}."
1403
- )
1404
-
1405
- # Other attributes
1406
- self.erv_oat_enter_col = dict_.get("ERV_OAT_ENTER_COL", None)
1407
- self.erv_oat_leaving_col = dict_.get("ERV_OAT_LEAVING_COL", None)
1408
- self.erv_eat_enter_col = dict_.get("ERV_EAT_ENTER_COL", None)
1409
- self.erv_eat_leaving_col = dict_.get("ERV_EAT_LEAVING_COL", None)
1410
- self.supply_vfd_speed_col = dict_.get("SUPPLY_VFD_SPEED_COL", None)
1411
-
1412
- # Set documentation strings
1413
- self.equation_string = (
1414
- "fc16_flag = 1 if ERV effectiveness is outside expected range "
1415
- "(heating: εmin_htg ≤ ε ≤ εmax_htg, cooling: εmin_clg ≤ ε ≤ εmax_clg) "
1416
- "for N consecutive values else 0 \n"
1417
- )
1418
- self.description_string = (
1419
- "Fault Condition 16: ERV effectiveness should be within specified "
1420
- "thresholds based on OAT \n"
1421
- )
1422
- self.required_column_description = (
1423
- "Required inputs are the ERV outdoor air entering and leaving temperatures, "
1424
- "ERV exhaust air entering and leaving temperatures, and supply fan VFD speed \n"
1425
- )
1426
- self.error_string = "One or more required columns are missing or None \n"
1427
-
1428
- # Set required columns specific to this fault condition
1429
- self.required_columns = [
1430
- self.erv_oat_enter_col,
1431
- self.erv_oat_leaving_col,
1432
- self.erv_eat_enter_col,
1433
- self.erv_eat_leaving_col,
1434
- self.supply_vfd_speed_col,
1435
- ]
1436
-
1437
- @FaultConditionMixin._handle_errors
1438
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
1439
- """Apply the fault condition to the DataFrame."""
1440
- # Apply common checks
1441
- self._apply_common_checks(df)
1442
-
1443
- # Check analog outputs [data with units of %] are floats only
1444
- columns_to_check = [self.supply_vfd_speed_col]
1445
- self._apply_analog_checks(df, columns_to_check)
1446
-
1447
- # Calculate temperature differences
1448
- oat_rat_delta = abs(df[self.erv_oat_enter_col] - df[self.erv_eat_enter_col])
1449
-
1450
- # Calculate ERV effectiveness
1451
- # ε = (T_leaving - T_entering) / (T_exhaust - T_entering)
1452
- erv_effectiveness = (
1453
- df[self.erv_oat_leaving_col] - df[self.erv_oat_enter_col]
1454
- ) / (df[self.erv_eat_enter_col] - df[self.erv_oat_enter_col])
1455
-
1456
- # Determine operating mode based on OAT
1457
- heating_mode = df[self.erv_oat_enter_col] < self.oat_low_threshold
1458
- cooling_mode = df[self.erv_oat_enter_col] > self.oat_high_threshold
1459
-
1460
- # Check effectiveness against thresholds
1461
- low_effectiveness_htg = heating_mode & (
1462
- erv_effectiveness < self.erv_efficiency_min_heating
1463
- )
1464
- high_effectiveness_htg = heating_mode & (
1465
- erv_effectiveness > self.erv_efficiency_max_heating
1466
- )
1467
- low_effectiveness_clg = cooling_mode & (
1468
- erv_effectiveness < self.erv_efficiency_min_cooling
1469
- )
1470
- high_effectiveness_clg = cooling_mode & (
1471
- erv_effectiveness > self.erv_efficiency_max_cooling
1472
- )
1473
-
1474
- # Combine conditions:
1475
- # Fault occurs when ERV effectiveness is outside expected range
1476
- # and there's sufficient temperature difference between OAT and RAT
1477
- combined_check = (oat_rat_delta >= self.oat_rat_delta_min) & (
1478
- low_effectiveness_htg
1479
- | high_effectiveness_htg
1480
- | low_effectiveness_clg
1481
- | high_effectiveness_clg
1482
- )
1483
-
1484
- # Set fault flag
1485
- self._set_fault_flag(df, combined_check, "fc16_flag")
1486
-
1487
- return df
1
+ """Base module for all fault conditions in the air handling unit (AHU) module."""
2
+
3
+ from open_fdd.air_handling_unit.faults.fault_condition_eight import FaultConditionEight
4
+ from open_fdd.air_handling_unit.faults.fault_condition_eleven import (
5
+ FaultConditionEleven,
6
+ )
7
+ from open_fdd.air_handling_unit.faults.fault_condition_fifteen import (
8
+ FaultConditionFifteen,
9
+ )
10
+ from open_fdd.air_handling_unit.faults.fault_condition_five import FaultConditionFive
11
+ from open_fdd.air_handling_unit.faults.fault_condition_four import FaultConditionFour
12
+ from open_fdd.air_handling_unit.faults.fault_condition_fourteen import (
13
+ FaultConditionFourteen,
14
+ )
15
+ from open_fdd.air_handling_unit.faults.fault_condition_nine import FaultConditionNine
16
+ from open_fdd.air_handling_unit.faults.fault_condition_one import FaultConditionOne
17
+ from open_fdd.air_handling_unit.faults.fault_condition_seven import FaultConditionSeven
18
+ from open_fdd.air_handling_unit.faults.fault_condition_six import FaultConditionSix
19
+ from open_fdd.air_handling_unit.faults.fault_condition_sixteen import (
20
+ FaultConditionSixteen,
21
+ )
22
+ from open_fdd.air_handling_unit.faults.fault_condition_ten import FaultConditionTen
23
+ from open_fdd.air_handling_unit.faults.fault_condition_thirteen import (
24
+ FaultConditionThirteen,
25
+ )
26
+ from open_fdd.air_handling_unit.faults.fault_condition_three import FaultConditionThree
27
+ from open_fdd.air_handling_unit.faults.fault_condition_twelve import (
28
+ FaultConditionTwelve,
29
+ )
30
+ from open_fdd.air_handling_unit.faults.fault_condition_two import FaultConditionTwo