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