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.
- open_fdd/air_handling_unit/faults/__init__.py +30 -1487
- open_fdd/air_handling_unit/faults/fault_condition_eight.py +135 -0
- open_fdd/air_handling_unit/faults/fault_condition_eleven.py +108 -0
- open_fdd/air_handling_unit/faults/fault_condition_fifteen.py +189 -0
- open_fdd/air_handling_unit/faults/fault_condition_five.py +126 -0
- open_fdd/air_handling_unit/faults/fault_condition_four.py +128 -0
- open_fdd/air_handling_unit/faults/fault_condition_fourteen.py +177 -0
- open_fdd/air_handling_unit/faults/fault_condition_nine.py +140 -0
- open_fdd/air_handling_unit/faults/fault_condition_one.py +113 -0
- open_fdd/air_handling_unit/faults/fault_condition_seven.py +106 -0
- open_fdd/air_handling_unit/faults/fault_condition_six.py +228 -0
- open_fdd/air_handling_unit/faults/fault_condition_sixteen.py +196 -0
- open_fdd/air_handling_unit/faults/fault_condition_ten.py +119 -0
- open_fdd/air_handling_unit/faults/fault_condition_thirteen.py +139 -0
- open_fdd/air_handling_unit/faults/fault_condition_three.py +112 -0
- open_fdd/air_handling_unit/faults/fault_condition_twelve.py +164 -0
- open_fdd/air_handling_unit/faults/fault_condition_two.py +112 -0
- open_fdd/air_handling_unit/faults/helper_utils.py +29 -19
- open_fdd/air_handling_unit/reports/__init__.py +6 -4
- open_fdd/air_handling_unit/reports/fault_report.py +3 -2
- open_fdd/chiller_plant/faults/__init__.py +6 -2279
- open_fdd/chiller_plant/faults/fault_condition_one.py +113 -0
- open_fdd/chiller_plant/faults/fault_condition_two.py +100 -0
- open_fdd/tests/ahu/test_ahu_fc1.py +2 -2
- open_fdd/tests/ahu/test_ahu_fc10.py +1 -0
- open_fdd/tests/ahu/test_ahu_fc11.py +3 -4
- open_fdd/tests/ahu/test_ahu_fc12.py +3 -4
- open_fdd/tests/ahu/test_ahu_fc13.py +3 -4
- open_fdd/tests/ahu/test_ahu_fc14.py +3 -4
- open_fdd/tests/ahu/test_ahu_fc15.py +3 -4
- open_fdd/tests/ahu/test_ahu_fc16.py +4 -3
- open_fdd/tests/ahu/test_ahu_fc2.py +1 -0
- open_fdd/tests/ahu/test_ahu_fc3.py +1 -0
- open_fdd/tests/ahu/test_ahu_fc4.py +3 -1
- open_fdd/tests/ahu/test_ahu_fc5.py +1 -0
- open_fdd/tests/ahu/test_ahu_fc6.py +1 -0
- open_fdd/tests/ahu/test_ahu_fc7.py +1 -0
- open_fdd/tests/ahu/test_ahu_fc8.py +1 -0
- open_fdd/tests/ahu/test_ahu_fc9.py +1 -0
- open_fdd/tests/chiller/test_chiller_fc1.py +2 -2
- open_fdd/tests/chiller/test_chiller_fc2.py +2 -2
- {open_fdd-0.1.8.dist-info → open_fdd-0.1.9.dist-info}/METADATA +4 -3
- open_fdd-0.1.9.dist-info/RECORD +52 -0
- {open_fdd-0.1.8.dist-info → open_fdd-0.1.9.dist-info}/WHEEL +1 -1
- open_fdd/air_handling_unit/faults/fault_condition.py +0 -69
- open_fdd/air_handling_unit/faults/shared_utils.py +0 -90
- open_fdd-0.1.8.dist-info/RECORD +0 -36
- {open_fdd-0.1.8.dist-info → open_fdd-0.1.9.dist-info/licenses}/LICENSE +0 -0
- {open_fdd-0.1.8.dist-info → open_fdd-0.1.9.dist-info}/top_level.txt +0 -0
@@ -1,1487 +1,30 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
from open_fdd.
|
4
|
-
from open_fdd.
|
5
|
-
|
6
|
-
|
7
|
-
import
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|