open-fdd 0.1.0__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/__init__.py +39 -0
- open_fdd/air_handling_unit/__init__.py +0 -0
- open_fdd/air_handling_unit/faults/__init__.py +0 -0
- open_fdd/air_handling_unit/faults/fault_condition.py +49 -0
- open_fdd/air_handling_unit/faults/fault_condition_eight.py +67 -0
- open_fdd/air_handling_unit/faults/fault_condition_eleven.py +68 -0
- open_fdd/air_handling_unit/faults/fault_condition_fifteen.py +90 -0
- open_fdd/air_handling_unit/faults/fault_condition_five.py +68 -0
- open_fdd/air_handling_unit/faults/fault_condition_four.py +93 -0
- open_fdd/air_handling_unit/faults/fault_condition_fourteen.py +80 -0
- open_fdd/air_handling_unit/faults/fault_condition_nine.py +68 -0
- open_fdd/air_handling_unit/faults/fault_condition_one.py +60 -0
- open_fdd/air_handling_unit/faults/fault_condition_seven.py +55 -0
- open_fdd/air_handling_unit/faults/fault_condition_six.py +120 -0
- open_fdd/air_handling_unit/faults/fault_condition_ten.py +62 -0
- open_fdd/air_handling_unit/faults/fault_condition_thirteen.py +66 -0
- open_fdd/air_handling_unit/faults/fault_condition_three.py +58 -0
- open_fdd/air_handling_unit/faults/fault_condition_twelve.py +71 -0
- open_fdd/air_handling_unit/faults/fault_condition_two.py +61 -0
- open_fdd/air_handling_unit/faults/helper_utils.py +191 -0
- open_fdd/air_handling_unit/faults/shared_utils.py +75 -0
- open_fdd/air_handling_unit/reports/__init__.py +0 -0
- open_fdd/air_handling_unit/reports/report_fc1.py +115 -0
- open_fdd/air_handling_unit/reports/report_fc10.py +126 -0
- open_fdd/air_handling_unit/reports/report_fc11.py +128 -0
- open_fdd/air_handling_unit/reports/report_fc12.py +126 -0
- open_fdd/air_handling_unit/reports/report_fc13.py +126 -0
- open_fdd/air_handling_unit/reports/report_fc14.py +124 -0
- open_fdd/air_handling_unit/reports/report_fc15.py +124 -0
- open_fdd/air_handling_unit/reports/report_fc2.py +119 -0
- open_fdd/air_handling_unit/reports/report_fc3.py +119 -0
- open_fdd/air_handling_unit/reports/report_fc4.py +148 -0
- open_fdd/air_handling_unit/reports/report_fc5.py +132 -0
- open_fdd/air_handling_unit/reports/report_fc6.py +156 -0
- open_fdd/air_handling_unit/reports/report_fc7.py +124 -0
- open_fdd/air_handling_unit/reports/report_fc8.py +118 -0
- open_fdd/air_handling_unit/reports/report_fc9.py +120 -0
- open_fdd/tests/__init__.py +0 -0
- open_fdd/tests/ahu/__init__.py +0 -0
- open_fdd/tests/ahu/test_ahu_fc1.py +159 -0
- open_fdd/tests/ahu/test_ahu_fc10.py +132 -0
- open_fdd/tests/ahu/test_ahu_fc11.py +136 -0
- open_fdd/tests/ahu/test_ahu_fc12.py +167 -0
- open_fdd/tests/ahu/test_ahu_fc13.py +163 -0
- open_fdd/tests/ahu/test_ahu_fc14.py +197 -0
- open_fdd/tests/ahu/test_ahu_fc15.py +183 -0
- open_fdd/tests/ahu/test_ahu_fc2.py +132 -0
- open_fdd/tests/ahu/test_ahu_fc3.py +131 -0
- open_fdd/tests/ahu/test_ahu_fc4.py +200 -0
- open_fdd/tests/ahu/test_ahu_fc5.py +180 -0
- open_fdd/tests/ahu/test_ahu_fc6.py +246 -0
- open_fdd/tests/ahu/test_ahu_fc7.py +71 -0
- open_fdd/tests/ahu/test_ahu_fc8.py +131 -0
- open_fdd/tests/ahu/test_ahu_fc9.py +136 -0
- open_fdd-0.1.0.dist-info/LICENSE +21 -0
- open_fdd-0.1.0.dist-info/METADATA +65 -0
- open_fdd-0.1.0.dist-info/RECORD +59 -0
- open_fdd-0.1.0.dist-info/WHEEL +5 -0
- open_fdd-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,183 @@
|
|
1
|
+
import pandas as pd
|
2
|
+
import pytest
|
3
|
+
from open_fdd.air_handling_unit.faults.fault_condition_fifteen import (
|
4
|
+
FaultConditionFifteen,
|
5
|
+
)
|
6
|
+
from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils
|
7
|
+
|
8
|
+
"""
|
9
|
+
To see print statements in pytest run with:
|
10
|
+
$ py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc15.py -rP -s
|
11
|
+
|
12
|
+
Temp rise across inactive htg coil in OS2, OS3, & OS4
|
13
|
+
"""
|
14
|
+
|
15
|
+
# Constants
|
16
|
+
TEST_DELTA_SUPPLY_FAN = 2.0
|
17
|
+
TEST_COIL_TEMP_ENTER_ERR_THRES = 1.0
|
18
|
+
TEST_COIL_TEMP_LEAVE_ERR_THRES = 1.0
|
19
|
+
TEST_AHU_MIN_OA_DPR = 0.2
|
20
|
+
TEST_HTG_COIL_ENTER_TEMP_COL = "htg_enter_air_temp"
|
21
|
+
TEST_HTG_COIL_LEAVE_TEMP_COL = "htg_leave_air_temp"
|
22
|
+
TEST_CLG_COIL_CMD_COL = "cooling_sig_col"
|
23
|
+
TEST_HTG_COIL_CMD_COL = "heating_sig_col"
|
24
|
+
TEST_MIX_AIR_DAMPER_COL = "economizer_sig_col"
|
25
|
+
TEST_SUPPLY_VFD_SPEED_COL = "supply_vfd_speed"
|
26
|
+
ROLLING_WINDOW_SIZE = 5
|
27
|
+
|
28
|
+
# Initialize FaultConditionFifteen with a dictionary
|
29
|
+
fault_condition_params = {
|
30
|
+
"DELTA_SUPPLY_FAN": TEST_DELTA_SUPPLY_FAN,
|
31
|
+
"COIL_TEMP_ENTER_ERR_THRES": TEST_COIL_TEMP_ENTER_ERR_THRES,
|
32
|
+
"COIL_TEMP_LEAV_ERR_THRES": TEST_COIL_TEMP_LEAVE_ERR_THRES,
|
33
|
+
"AHU_MIN_OA_DPR": TEST_AHU_MIN_OA_DPR,
|
34
|
+
"HTG_COIL_ENTER_TEMP_COL": TEST_HTG_COIL_ENTER_TEMP_COL,
|
35
|
+
"HTG_COIL_LEAVE_TEMP_COL": TEST_HTG_COIL_LEAVE_TEMP_COL,
|
36
|
+
"COOLING_SIG_COL": TEST_CLG_COIL_CMD_COL,
|
37
|
+
"HEATING_SIG_COL": TEST_HTG_COIL_CMD_COL,
|
38
|
+
"ECONOMIZER_SIG_COL": TEST_MIX_AIR_DAMPER_COL,
|
39
|
+
"SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL,
|
40
|
+
"TROUBLESHOOT_MODE": False,
|
41
|
+
"ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE,
|
42
|
+
}
|
43
|
+
|
44
|
+
fc15 = FaultConditionFifteen(fault_condition_params)
|
45
|
+
|
46
|
+
|
47
|
+
class TestFaultConditionFifteen:
|
48
|
+
|
49
|
+
def no_fault_df_econ(self) -> pd.DataFrame:
|
50
|
+
data = {
|
51
|
+
TEST_HTG_COIL_ENTER_TEMP_COL: [55, 55, 55, 55, 55, 55],
|
52
|
+
TEST_HTG_COIL_LEAVE_TEMP_COL: [56.5, 56.5, 56.5, 56.5, 56.5, 56.5],
|
53
|
+
TEST_HTG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
54
|
+
TEST_CLG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
55
|
+
TEST_MIX_AIR_DAMPER_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55],
|
56
|
+
TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55],
|
57
|
+
}
|
58
|
+
return pd.DataFrame(data)
|
59
|
+
|
60
|
+
def no_fault_df_os3(self) -> pd.DataFrame:
|
61
|
+
data = {
|
62
|
+
TEST_HTG_COIL_ENTER_TEMP_COL: [55, 55, 55, 55, 55, 55],
|
63
|
+
TEST_HTG_COIL_LEAVE_TEMP_COL: [56.5, 56.5, 56.5, 56.5, 56.5, 56.5],
|
64
|
+
TEST_HTG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
65
|
+
TEST_CLG_COIL_CMD_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55],
|
66
|
+
TEST_MIX_AIR_DAMPER_COL: [0.99, 0.99, 0.99, 0.99, 0.99, 0.99],
|
67
|
+
TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55],
|
68
|
+
}
|
69
|
+
return pd.DataFrame(data)
|
70
|
+
|
71
|
+
def fault_df_in_econ(self) -> pd.DataFrame:
|
72
|
+
data = {
|
73
|
+
TEST_HTG_COIL_ENTER_TEMP_COL: [136.5, 136.5, 136.5, 136.5, 136.5, 136.5],
|
74
|
+
TEST_HTG_COIL_LEAVE_TEMP_COL: [140, 140, 140, 140, 140, 140],
|
75
|
+
TEST_HTG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
76
|
+
TEST_CLG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
77
|
+
TEST_MIX_AIR_DAMPER_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55],
|
78
|
+
TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55],
|
79
|
+
}
|
80
|
+
return pd.DataFrame(data)
|
81
|
+
|
82
|
+
def fault_df_in_os3(self) -> pd.DataFrame:
|
83
|
+
data = {
|
84
|
+
TEST_HTG_COIL_ENTER_TEMP_COL: [136.5, 136.5, 136.5, 136.5, 136.5, 136.5],
|
85
|
+
TEST_HTG_COIL_LEAVE_TEMP_COL: [140, 140, 140, 140, 140, 140],
|
86
|
+
TEST_HTG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
87
|
+
TEST_CLG_COIL_CMD_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55],
|
88
|
+
TEST_MIX_AIR_DAMPER_COL: [0.99, 0.99, 0.99, 0.99, 0.99, 0.99],
|
89
|
+
TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55],
|
90
|
+
}
|
91
|
+
return pd.DataFrame(data)
|
92
|
+
|
93
|
+
def test_no_fault_econ(self):
|
94
|
+
results = fc15.apply(self.no_fault_df_econ())
|
95
|
+
actual = results["fc15_flag"].sum()
|
96
|
+
expected = 0
|
97
|
+
message = f"FC15 no_fault_df_econ actual is {actual} and expected is {expected}"
|
98
|
+
assert actual == expected, message
|
99
|
+
|
100
|
+
def test_no_fault_os3(self):
|
101
|
+
results = fc15.apply(self.no_fault_df_os3())
|
102
|
+
actual = results["fc15_flag"].sum()
|
103
|
+
expected = 0
|
104
|
+
message = f"FC15 no_fault_df_os3 actual is {actual} and expected is {expected}"
|
105
|
+
assert actual == expected, message
|
106
|
+
|
107
|
+
def test_fault_in_econ(self):
|
108
|
+
results = fc15.apply(self.fault_df_in_econ())
|
109
|
+
actual = results["fc15_flag"].sum()
|
110
|
+
expected = 2
|
111
|
+
message = f"FC15 fault_df_in_econ actual is {actual} and expected is {expected}"
|
112
|
+
assert actual == expected, message
|
113
|
+
|
114
|
+
def test_fault_in_os3(self):
|
115
|
+
results = fc15.apply(self.fault_df_in_os3())
|
116
|
+
actual = results["fc15_flag"].sum()
|
117
|
+
expected = 2
|
118
|
+
message = f"FC15 fault_df_in_os3 actual is {actual} and expected is {expected}"
|
119
|
+
assert actual == expected, message
|
120
|
+
|
121
|
+
|
122
|
+
class TestFaultOnInt:
|
123
|
+
|
124
|
+
def fault_df_on_output_int(self) -> pd.DataFrame:
|
125
|
+
data = {
|
126
|
+
TEST_HTG_COIL_ENTER_TEMP_COL: [50.5, 50.5, 50.5, 50.5, 50.5, 50.5],
|
127
|
+
TEST_HTG_COIL_LEAVE_TEMP_COL: [55, 55, 55, 55, 55, 55],
|
128
|
+
TEST_HTG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
129
|
+
TEST_CLG_COIL_CMD_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55],
|
130
|
+
TEST_MIX_AIR_DAMPER_COL: [55, 55, 55, 55, 55, 55], # Incorrect type
|
131
|
+
TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55],
|
132
|
+
}
|
133
|
+
return pd.DataFrame(data)
|
134
|
+
|
135
|
+
def test_fault_on_int(self):
|
136
|
+
with pytest.raises(
|
137
|
+
TypeError, match=HelperUtils().float_int_check_err(TEST_MIX_AIR_DAMPER_COL)
|
138
|
+
):
|
139
|
+
fc15.apply(self.fault_df_on_output_int())
|
140
|
+
|
141
|
+
|
142
|
+
class TestFaultOnFloatGreaterThanOne:
|
143
|
+
|
144
|
+
def fault_df_on_output_greater_than_one(self) -> pd.DataFrame:
|
145
|
+
data = {
|
146
|
+
TEST_HTG_COIL_ENTER_TEMP_COL: [50.5, 50.5, 50.5, 50.5, 50.5, 50.5],
|
147
|
+
TEST_HTG_COIL_LEAVE_TEMP_COL: [55, 55, 55, 55, 55, 55],
|
148
|
+
TEST_HTG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
149
|
+
TEST_CLG_COIL_CMD_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55],
|
150
|
+
TEST_MIX_AIR_DAMPER_COL: [1.1, 1.2, 1.1, 1.3, 1.1, 1.2], # Values > 1.0
|
151
|
+
TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55],
|
152
|
+
}
|
153
|
+
return pd.DataFrame(data)
|
154
|
+
|
155
|
+
def test_fault_on_float_greater_than_one(self):
|
156
|
+
with pytest.raises(
|
157
|
+
TypeError, match=HelperUtils().float_max_check_err(TEST_MIX_AIR_DAMPER_COL)
|
158
|
+
):
|
159
|
+
fc15.apply(self.fault_df_on_output_greater_than_one())
|
160
|
+
|
161
|
+
|
162
|
+
class TestFaultOnMixedTypes:
|
163
|
+
|
164
|
+
def fault_df_on_mixed_types(self) -> pd.DataFrame:
|
165
|
+
data = {
|
166
|
+
TEST_HTG_COIL_ENTER_TEMP_COL: [50.5, 50.5, 50.5, 50.5, 50.5, 50.5],
|
167
|
+
TEST_HTG_COIL_LEAVE_TEMP_COL: [55, 55, 55, 55, 55, 55],
|
168
|
+
TEST_HTG_COIL_CMD_COL: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
169
|
+
TEST_CLG_COIL_CMD_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55],
|
170
|
+
TEST_MIX_AIR_DAMPER_COL: [1.1, 0.55, 1.2, 1.3, 0.55, 1.1], # Mixed types
|
171
|
+
TEST_SUPPLY_VFD_SPEED_COL: [0.55, 0.55, 0.55, 0.55, 0.55, 0.55],
|
172
|
+
}
|
173
|
+
return pd.DataFrame(data)
|
174
|
+
|
175
|
+
def test_fault_on_mixed_types(self):
|
176
|
+
with pytest.raises(
|
177
|
+
TypeError, match=HelperUtils().float_max_check_err(TEST_MIX_AIR_DAMPER_COL)
|
178
|
+
):
|
179
|
+
fc15.apply(self.fault_df_on_mixed_types())
|
180
|
+
|
181
|
+
|
182
|
+
if __name__ == "__main__":
|
183
|
+
pytest.main()
|
@@ -0,0 +1,132 @@
|
|
1
|
+
import pandas as pd
|
2
|
+
import pytest
|
3
|
+
from open_fdd.air_handling_unit.faults.fault_condition_two import FaultConditionTwo
|
4
|
+
from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils
|
5
|
+
|
6
|
+
"""
|
7
|
+
To see print statements in pytest run with:
|
8
|
+
$ py -3.12 -m pytest tests/ahu/test_ahu_fc2.py -rP -s
|
9
|
+
|
10
|
+
Mix air temp lower than out temp
|
11
|
+
"""
|
12
|
+
|
13
|
+
TEST_OUTDOOR_DEGF_ERR_THRES = 5.0
|
14
|
+
TEST_MIX_DEGF_ERR_THRES = 2.0
|
15
|
+
TEST_RETURN_DEGF_ERR_THRES = 2.0
|
16
|
+
TEST_MIX_TEMP_COL = "mix_air_temp"
|
17
|
+
TEST_RETURN_TEMP_COL = "return_air_temp"
|
18
|
+
TEST_OUT_TEMP_COL = "out_air_temp"
|
19
|
+
TEST_SUPPLY_VFD_SPEED_COL = "supply_vfd_speed"
|
20
|
+
TEST_ROLLING_WINDOW_SIZE = 5
|
21
|
+
|
22
|
+
# Initialize FaultConditionTwo with a dictionary
|
23
|
+
fault_condition_params = {
|
24
|
+
"MIX_DEGF_ERR_THRES": TEST_MIX_DEGF_ERR_THRES,
|
25
|
+
"RETURN_DEGF_ERR_THRES": TEST_RETURN_DEGF_ERR_THRES,
|
26
|
+
"OUTDOOR_DEGF_ERR_THRES": TEST_OUTDOOR_DEGF_ERR_THRES,
|
27
|
+
"MAT_COL": TEST_MIX_TEMP_COL,
|
28
|
+
"RAT_COL": TEST_RETURN_TEMP_COL,
|
29
|
+
"OAT_COL": TEST_OUT_TEMP_COL,
|
30
|
+
"SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL,
|
31
|
+
"TROUBLESHOOT_MODE": False, # default value
|
32
|
+
"ROLLING_WINDOW_SIZE": TEST_ROLLING_WINDOW_SIZE,
|
33
|
+
}
|
34
|
+
|
35
|
+
fc2 = FaultConditionTwo(fault_condition_params)
|
36
|
+
|
37
|
+
|
38
|
+
class TestNoFault(object):
|
39
|
+
|
40
|
+
def no_fault_df(self) -> pd.DataFrame:
|
41
|
+
data = {
|
42
|
+
TEST_MIX_TEMP_COL: [60.0, 62.0, 64.0, 61.0, 63.0, 60.0],
|
43
|
+
TEST_RETURN_TEMP_COL: [72.0, 73.0, 74.0, 72.0, 73.0, 72.0],
|
44
|
+
TEST_OUT_TEMP_COL: [45.0, 46.0, 47.0, 45.0, 46.0, 45.0],
|
45
|
+
TEST_SUPPLY_VFD_SPEED_COL: [0.8, 0.82, 0.85, 0.8, 0.83, 0.8],
|
46
|
+
}
|
47
|
+
return pd.DataFrame(data)
|
48
|
+
|
49
|
+
def test_no_fault(self):
|
50
|
+
results = fc2.apply(self.no_fault_df())
|
51
|
+
actual = results["fc2_flag"].sum()
|
52
|
+
expected = 0
|
53
|
+
message = f"fc2 no_fault_df actual is {actual} and expected is {expected}"
|
54
|
+
print(message)
|
55
|
+
assert actual == expected, message
|
56
|
+
|
57
|
+
|
58
|
+
class TestFault(object):
|
59
|
+
|
60
|
+
def fault_df(self) -> pd.DataFrame:
|
61
|
+
data = {
|
62
|
+
TEST_MIX_TEMP_COL: [45.0, 46.0, 45.0, 46.0, 45.0, 45.0],
|
63
|
+
TEST_RETURN_TEMP_COL: [72.0, 72.5, 73.0, 72.0, 72.5, 72.0],
|
64
|
+
TEST_OUT_TEMP_COL: [60.0, 60.5, 61.0, 60.0, 60.5, 60.0],
|
65
|
+
TEST_SUPPLY_VFD_SPEED_COL: [0.8, 0.82, 0.81, 0.8, 0.82, 0.8],
|
66
|
+
}
|
67
|
+
return pd.DataFrame(data)
|
68
|
+
|
69
|
+
def test_fault(self):
|
70
|
+
results = fc2.apply(self.fault_df())
|
71
|
+
actual = results["fc2_flag"].sum()
|
72
|
+
expected = 2
|
73
|
+
message = f"fc2 fault_df actual is {actual} and expected is {expected}"
|
74
|
+
print(message)
|
75
|
+
assert actual == expected, message
|
76
|
+
|
77
|
+
|
78
|
+
class TestFaultOnInt(object):
|
79
|
+
|
80
|
+
def fault_df_on_output_int(self) -> pd.DataFrame:
|
81
|
+
data = {
|
82
|
+
TEST_MIX_TEMP_COL: [45.0, 46.0, 45.0, 46.0, 45.0, 45.0],
|
83
|
+
TEST_RETURN_TEMP_COL: [72.0, 72.5, 73.0, 72.0, 72.5, 72.0],
|
84
|
+
TEST_OUT_TEMP_COL: [60.0, 60.5, 61.0, 60.0, 60.5, 60.0],
|
85
|
+
TEST_SUPPLY_VFD_SPEED_COL: [88, 88, 88, 88, 88, 88],
|
86
|
+
}
|
87
|
+
return pd.DataFrame(data)
|
88
|
+
|
89
|
+
def test_fault_on_int(self):
|
90
|
+
with pytest.raises(
|
91
|
+
TypeError,
|
92
|
+
match=HelperUtils().float_int_check_err(TEST_SUPPLY_VFD_SPEED_COL),
|
93
|
+
):
|
94
|
+
fc2.apply(self.fault_df_on_output_int())
|
95
|
+
|
96
|
+
|
97
|
+
class TestFaultOnFloatGreaterThanOne(object):
|
98
|
+
|
99
|
+
def fault_df_on_output_greater_than_one(self) -> pd.DataFrame:
|
100
|
+
data = {
|
101
|
+
TEST_MIX_TEMP_COL: [45.0, 46.0, 45.0, 46.0, 45.0, 45.0],
|
102
|
+
TEST_RETURN_TEMP_COL: [72.0, 72.5, 73.0, 72.0, 72.5, 72.0],
|
103
|
+
TEST_OUT_TEMP_COL: [60.0, 60.5, 61.0, 60.0, 60.5, 60.0],
|
104
|
+
TEST_SUPPLY_VFD_SPEED_COL: [1.1, 1.2, 1.3, 1.1, 1.2, 1.1],
|
105
|
+
}
|
106
|
+
return pd.DataFrame(data)
|
107
|
+
|
108
|
+
def test_fault_on_float_greater_than_one(self):
|
109
|
+
with pytest.raises(
|
110
|
+
TypeError,
|
111
|
+
match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL),
|
112
|
+
):
|
113
|
+
fc2.apply(self.fault_df_on_output_greater_than_one())
|
114
|
+
|
115
|
+
|
116
|
+
class TestFaultOnMixedTypes(object):
|
117
|
+
|
118
|
+
def fault_df_on_mixed_types(self) -> pd.DataFrame:
|
119
|
+
data = {
|
120
|
+
TEST_MIX_TEMP_COL: [45.0, 46.0, 45.0, 46.0, 45.0, 45.0],
|
121
|
+
TEST_RETURN_TEMP_COL: [72.0, 72.5, 73.0, 72.0, 72.5, 72.0],
|
122
|
+
TEST_OUT_TEMP_COL: [60.0, 60.5, 61.0, 60.0, 60.5, 60.0],
|
123
|
+
TEST_SUPPLY_VFD_SPEED_COL: [1.1, 0.82, 1.3, 1.1, 0.82, 1.1],
|
124
|
+
}
|
125
|
+
return pd.DataFrame(data)
|
126
|
+
|
127
|
+
def test_fault_on_mixed_types(self):
|
128
|
+
with pytest.raises(
|
129
|
+
TypeError,
|
130
|
+
match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL),
|
131
|
+
):
|
132
|
+
fc2.apply(self.fault_df_on_mixed_types())
|
@@ -0,0 +1,131 @@
|
|
1
|
+
import pandas as pd
|
2
|
+
import pytest
|
3
|
+
from open_fdd.air_handling_unit.faults.fault_condition_three import FaultConditionThree
|
4
|
+
from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils
|
5
|
+
|
6
|
+
"""
|
7
|
+
To see print statements in pytest run with:
|
8
|
+
$ py -3.12 -m pytest tests/ahu/test_ahu_fc3.py -rP -s
|
9
|
+
|
10
|
+
Mix air temp higher than out temp
|
11
|
+
"""
|
12
|
+
|
13
|
+
# Constants
|
14
|
+
TEST_MIX_DEGF_ERR_THRES = 2.0
|
15
|
+
TEST_RETURN_DEGF_ERR_THRES = 2.0
|
16
|
+
TEST_OUTDOOR_DEGF_ERR_THRES = 5.0
|
17
|
+
TEST_MIX_TEMP_COL = "mix_air_temp"
|
18
|
+
TEST_RETURN_TEMP_COL = "return_air_temp"
|
19
|
+
TEST_OUT_TEMP_COL = "out_air_temp"
|
20
|
+
TEST_SUPPLY_VFD_SPEED_COL = "supply_vfd_speed"
|
21
|
+
ROLLING_WINDOW_SIZE = 5
|
22
|
+
|
23
|
+
# Initialize FaultConditionThree with a dictionary
|
24
|
+
fault_condition_params = {
|
25
|
+
"MIX_DEGF_ERR_THRES": TEST_MIX_DEGF_ERR_THRES,
|
26
|
+
"RETURN_DEGF_ERR_THRES": TEST_RETURN_DEGF_ERR_THRES,
|
27
|
+
"OUTDOOR_DEGF_ERR_THRES": TEST_OUTDOOR_DEGF_ERR_THRES,
|
28
|
+
"MAT_COL": TEST_MIX_TEMP_COL,
|
29
|
+
"RAT_COL": TEST_RETURN_TEMP_COL,
|
30
|
+
"OAT_COL": TEST_OUT_TEMP_COL,
|
31
|
+
"SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL,
|
32
|
+
"TROUBLESHOOT_MODE": False, # default value
|
33
|
+
"ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE,
|
34
|
+
}
|
35
|
+
|
36
|
+
fc3 = FaultConditionThree(fault_condition_params)
|
37
|
+
|
38
|
+
|
39
|
+
class TestNoFault:
|
40
|
+
|
41
|
+
def no_fault_df(self) -> pd.DataFrame:
|
42
|
+
data = {
|
43
|
+
TEST_MIX_TEMP_COL: [55.0, 56.0, 57.0, 56.0, 55.5, 55.0],
|
44
|
+
TEST_RETURN_TEMP_COL: [70.0, 71.0, 72.0, 70.0, 71.0, 70.0],
|
45
|
+
TEST_OUT_TEMP_COL: [50.0, 51.0, 52.0, 50.0, 51.0, 50.0],
|
46
|
+
TEST_SUPPLY_VFD_SPEED_COL: [0.8, 0.82, 0.83, 0.8, 0.82, 0.8],
|
47
|
+
}
|
48
|
+
return pd.DataFrame(data)
|
49
|
+
|
50
|
+
def test_no_fault(self):
|
51
|
+
results = fc3.apply(self.no_fault_df())
|
52
|
+
actual = results["fc3_flag"].sum()
|
53
|
+
expected = 0
|
54
|
+
message = f"FC3 no_fault_df actual is {actual} and expected is {expected}"
|
55
|
+
assert actual == expected, message
|
56
|
+
|
57
|
+
|
58
|
+
class TestFault:
|
59
|
+
|
60
|
+
def fault_df(self) -> pd.DataFrame:
|
61
|
+
data = {
|
62
|
+
TEST_MIX_TEMP_COL: [80.0, 81.0, 79.0, 80.5, 82.0, 80.0],
|
63
|
+
TEST_RETURN_TEMP_COL: [70.0, 70.5, 71.0, 70.0, 70.5, 70.0],
|
64
|
+
TEST_OUT_TEMP_COL: [50.0, 51.0, 52.0, 50.5, 51.0, 50.0],
|
65
|
+
TEST_SUPPLY_VFD_SPEED_COL: [0.9, 0.91, 0.92, 0.9, 0.91, 0.9],
|
66
|
+
}
|
67
|
+
return pd.DataFrame(data)
|
68
|
+
|
69
|
+
def test_fault(self):
|
70
|
+
results = fc3.apply(self.fault_df())
|
71
|
+
actual = results["fc3_flag"].sum()
|
72
|
+
expected = 2
|
73
|
+
message = f"FC3 fault_df actual is {actual} and expected is {expected}"
|
74
|
+
assert actual == expected, message
|
75
|
+
|
76
|
+
|
77
|
+
class TestFaultOnInt:
|
78
|
+
|
79
|
+
def fault_df_on_output_int(self) -> pd.DataFrame:
|
80
|
+
data = {
|
81
|
+
TEST_MIX_TEMP_COL: [80.0, 81.0, 79.0, 80.5, 82.0, 80.0],
|
82
|
+
TEST_RETURN_TEMP_COL: [70.0, 70.5, 71.0, 70.0, 70.5, 70.0],
|
83
|
+
TEST_OUT_TEMP_COL: [50.0, 51.0, 52.0, 50.5, 51.0, 50.0],
|
84
|
+
TEST_SUPPLY_VFD_SPEED_COL: [99, 99, 99, 99, 99, 99],
|
85
|
+
}
|
86
|
+
return pd.DataFrame(data)
|
87
|
+
|
88
|
+
def test_fault_on_int(self):
|
89
|
+
with pytest.raises(
|
90
|
+
TypeError,
|
91
|
+
match=HelperUtils().float_int_check_err(TEST_SUPPLY_VFD_SPEED_COL),
|
92
|
+
):
|
93
|
+
fc3.apply(self.fault_df_on_output_int())
|
94
|
+
|
95
|
+
|
96
|
+
class TestFaultOnFloatGreaterThanOne:
|
97
|
+
|
98
|
+
def fault_df_on_output_greater_than_one(self) -> pd.DataFrame:
|
99
|
+
data = {
|
100
|
+
TEST_MIX_TEMP_COL: [80.0, 81.0, 79.0, 80.5, 82.0, 80.0],
|
101
|
+
TEST_RETURN_TEMP_COL: [70.0, 70.5, 71.0, 70.0, 70.5, 70.0],
|
102
|
+
TEST_OUT_TEMP_COL: [50.0, 51.0, 52.0, 50.5, 51.0, 50.0],
|
103
|
+
TEST_SUPPLY_VFD_SPEED_COL: [1.1, 1.2, 1.3, 1.1, 1.2, 1.1],
|
104
|
+
}
|
105
|
+
return pd.DataFrame(data)
|
106
|
+
|
107
|
+
def test_fault_on_float_greater_than_one(self):
|
108
|
+
with pytest.raises(
|
109
|
+
TypeError,
|
110
|
+
match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL),
|
111
|
+
):
|
112
|
+
fc3.apply(self.fault_df_on_output_greater_than_one())
|
113
|
+
|
114
|
+
|
115
|
+
class TestFaultOnMixedTypes:
|
116
|
+
|
117
|
+
def fault_df_on_mixed_types(self) -> pd.DataFrame:
|
118
|
+
data = {
|
119
|
+
TEST_MIX_TEMP_COL: [80.0, 81.0, 79.0, 80.5, 82.0, 80.0],
|
120
|
+
TEST_RETURN_TEMP_COL: [70.0, 70.5, 71.0, 70.0, 70.5, 70.0],
|
121
|
+
TEST_OUT_TEMP_COL: [50.0, 51.0, 52.0, 50.5, 51.0, 50.0],
|
122
|
+
TEST_SUPPLY_VFD_SPEED_COL: [1.1, 0.91, 1.3, 1.1, 0.92, 1.1],
|
123
|
+
}
|
124
|
+
return pd.DataFrame(data)
|
125
|
+
|
126
|
+
def test_fault_on_mixed_types(self):
|
127
|
+
with pytest.raises(
|
128
|
+
TypeError,
|
129
|
+
match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL),
|
130
|
+
):
|
131
|
+
fc3.apply(self.fault_df_on_mixed_types())
|
@@ -0,0 +1,200 @@
|
|
1
|
+
import pandas as pd
|
2
|
+
import pytest
|
3
|
+
from open_fdd.air_handling_unit.faults.fault_condition_four import FaultConditionFour
|
4
|
+
from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils
|
5
|
+
from datetime import datetime, timezone
|
6
|
+
|
7
|
+
"""
|
8
|
+
To see print statements in pytest run with:
|
9
|
+
$ py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc4.py -rP -s
|
10
|
+
|
11
|
+
Too much hunting in control system
|
12
|
+
OS state changes greater than 7 in an hour
|
13
|
+
"""
|
14
|
+
|
15
|
+
# Constants
|
16
|
+
DELTA_OS_MAX = 7
|
17
|
+
AHU_MIN_OA = 0.20
|
18
|
+
TEST_MIX_AIR_DAMPER_COL = "economizer_sig_col"
|
19
|
+
TEST_HEATING_COIL_SIG_COL = "heating_sig_col"
|
20
|
+
TEST_COOLING_COIL_SIG_COL = "cooling_sig_col"
|
21
|
+
TEST_SUPPLY_VFD_SPEED_COL = "fan_vfd_speed_col"
|
22
|
+
TEST_DATASET_ROWS = 60
|
23
|
+
|
24
|
+
# Initialize FaultConditionFour with a dictionary
|
25
|
+
fault_condition_params = {
|
26
|
+
"DELTA_OS_MAX": DELTA_OS_MAX,
|
27
|
+
"AHU_MIN_OA_DPR": AHU_MIN_OA,
|
28
|
+
"ECONOMIZER_SIG_COL": TEST_MIX_AIR_DAMPER_COL,
|
29
|
+
"HEATING_SIG_COL": TEST_HEATING_COIL_SIG_COL,
|
30
|
+
"COOLING_SIG_COL": TEST_COOLING_COIL_SIG_COL,
|
31
|
+
"SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL,
|
32
|
+
"TROUBLESHOOT_MODE": False, # default value
|
33
|
+
}
|
34
|
+
|
35
|
+
fc4 = FaultConditionFour(fault_condition_params)
|
36
|
+
|
37
|
+
|
38
|
+
def generate_timestamp() -> pd.Series:
|
39
|
+
df = pd.DataFrame()
|
40
|
+
date_range = pd.period_range(
|
41
|
+
# make a time stamp starting at top of
|
42
|
+
# the hour with one min intervals
|
43
|
+
start=datetime(2022, 6, 6, 14, 30, 0, 0, tzinfo=timezone.utc),
|
44
|
+
periods=TEST_DATASET_ROWS,
|
45
|
+
freq="min",
|
46
|
+
)
|
47
|
+
df["Date"] = [x.to_timestamp() for x in date_range]
|
48
|
+
return df["Date"]
|
49
|
+
|
50
|
+
|
51
|
+
def econ_plus_mech_clg_row() -> dict:
|
52
|
+
data = {
|
53
|
+
TEST_MIX_AIR_DAMPER_COL: 0.6,
|
54
|
+
TEST_HEATING_COIL_SIG_COL: 0.0,
|
55
|
+
TEST_COOLING_COIL_SIG_COL: 0.6,
|
56
|
+
TEST_SUPPLY_VFD_SPEED_COL: 0.8,
|
57
|
+
}
|
58
|
+
return data
|
59
|
+
|
60
|
+
|
61
|
+
def mech_clg_row() -> dict:
|
62
|
+
data = {
|
63
|
+
TEST_MIX_AIR_DAMPER_COL: 0.0,
|
64
|
+
TEST_HEATING_COIL_SIG_COL: 0.0,
|
65
|
+
TEST_COOLING_COIL_SIG_COL: 0.6,
|
66
|
+
TEST_SUPPLY_VFD_SPEED_COL: 0.8,
|
67
|
+
}
|
68
|
+
return data
|
69
|
+
|
70
|
+
|
71
|
+
def econ_plus_mech_clg_row_int() -> dict:
|
72
|
+
data = {
|
73
|
+
TEST_MIX_AIR_DAMPER_COL: 0.6,
|
74
|
+
TEST_HEATING_COIL_SIG_COL: 0.0,
|
75
|
+
TEST_COOLING_COIL_SIG_COL: 0.6,
|
76
|
+
TEST_SUPPLY_VFD_SPEED_COL: 88,
|
77
|
+
}
|
78
|
+
return data
|
79
|
+
|
80
|
+
|
81
|
+
def econ_plus_mech_clg_row_float_greater_than_one() -> dict:
|
82
|
+
data = {
|
83
|
+
TEST_MIX_AIR_DAMPER_COL: 0.6,
|
84
|
+
TEST_HEATING_COIL_SIG_COL: 0.0,
|
85
|
+
TEST_COOLING_COIL_SIG_COL: 0.6,
|
86
|
+
TEST_SUPPLY_VFD_SPEED_COL: 88.8,
|
87
|
+
}
|
88
|
+
return data
|
89
|
+
|
90
|
+
|
91
|
+
class TestFault(object):
|
92
|
+
|
93
|
+
def fault_df(self) -> pd.DataFrame:
|
94
|
+
data = []
|
95
|
+
counter = 0
|
96
|
+
for i in range(TEST_DATASET_ROWS):
|
97
|
+
if i % 2 == 0 and counter < 11:
|
98
|
+
data.append(econ_plus_mech_clg_row())
|
99
|
+
counter += 1 # only simulate 10 OS changes
|
100
|
+
else:
|
101
|
+
data.append(mech_clg_row())
|
102
|
+
return pd.DataFrame(data)
|
103
|
+
|
104
|
+
def test_fault(self):
|
105
|
+
fault_df = self.fault_df().set_index(generate_timestamp())
|
106
|
+
results = fc4.apply(fault_df)
|
107
|
+
actual = results["fc4_flag"].sum()
|
108
|
+
expected = 1
|
109
|
+
message = f"FC4 fault_df actual is {actual} and expected is {expected}"
|
110
|
+
assert actual == expected, message
|
111
|
+
|
112
|
+
|
113
|
+
class TestNoFault(object):
|
114
|
+
|
115
|
+
def no_fault_df(self) -> pd.DataFrame:
|
116
|
+
data = []
|
117
|
+
for i in range(TEST_DATASET_ROWS):
|
118
|
+
data.append(mech_clg_row())
|
119
|
+
return pd.DataFrame(data)
|
120
|
+
|
121
|
+
def test_no_fault(self):
|
122
|
+
no_fault_df = self.no_fault_df().set_index(generate_timestamp())
|
123
|
+
results = fc4.apply(no_fault_df)
|
124
|
+
actual = results["fc4_flag"].sum()
|
125
|
+
expected = 0
|
126
|
+
message = f"FC4 no_fault_df actual is {actual} and expected is {expected}"
|
127
|
+
assert actual == expected, message
|
128
|
+
|
129
|
+
|
130
|
+
class TestFaultOnInt(object):
|
131
|
+
|
132
|
+
def fault_df_on_output_int(self) -> pd.DataFrame:
|
133
|
+
data = []
|
134
|
+
for i in range(TEST_DATASET_ROWS):
|
135
|
+
if i % 2 == 0:
|
136
|
+
data.append(econ_plus_mech_clg_row_int())
|
137
|
+
else:
|
138
|
+
data.append(mech_clg_row())
|
139
|
+
return pd.DataFrame(data)
|
140
|
+
|
141
|
+
def test_fault_on_int(self):
|
142
|
+
fault_df_on_output_int = self.fault_df_on_output_int().set_index(
|
143
|
+
generate_timestamp()
|
144
|
+
)
|
145
|
+
with pytest.raises(
|
146
|
+
TypeError,
|
147
|
+
match=HelperUtils().float_int_check_err(TEST_SUPPLY_VFD_SPEED_COL),
|
148
|
+
):
|
149
|
+
fc4.apply(fault_df_on_output_int)
|
150
|
+
|
151
|
+
|
152
|
+
class TestFaultOnFloatGreaterThanOne(object):
|
153
|
+
|
154
|
+
def fault_df_on_output_greater_than_one(self) -> pd.DataFrame:
|
155
|
+
data = []
|
156
|
+
for i in range(TEST_DATASET_ROWS):
|
157
|
+
if i % 2 == 0:
|
158
|
+
data.append(econ_plus_mech_clg_row_float_greater_than_one())
|
159
|
+
else:
|
160
|
+
data.append(mech_clg_row())
|
161
|
+
return pd.DataFrame(data)
|
162
|
+
|
163
|
+
def test_fault_on_float_greater_than_one(self):
|
164
|
+
fault_df_on_output_greater_than_one = (
|
165
|
+
self.fault_df_on_output_greater_than_one().set_index(generate_timestamp())
|
166
|
+
)
|
167
|
+
with pytest.raises(
|
168
|
+
TypeError,
|
169
|
+
match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL),
|
170
|
+
):
|
171
|
+
fc4.apply(fault_df_on_output_greater_than_one)
|
172
|
+
|
173
|
+
|
174
|
+
class TestFaultOnMixedTypes(object):
|
175
|
+
|
176
|
+
def fault_df_on_mixed_types(self) -> pd.DataFrame:
|
177
|
+
data = []
|
178
|
+
for i in range(TEST_DATASET_ROWS):
|
179
|
+
if i % 2 == 0:
|
180
|
+
data.append(
|
181
|
+
{
|
182
|
+
TEST_MIX_AIR_DAMPER_COL: 0.6,
|
183
|
+
TEST_HEATING_COIL_SIG_COL: 0.0,
|
184
|
+
TEST_COOLING_COIL_SIG_COL: 0.6,
|
185
|
+
TEST_SUPPLY_VFD_SPEED_COL: 1.1,
|
186
|
+
}
|
187
|
+
)
|
188
|
+
else:
|
189
|
+
data.append(mech_clg_row())
|
190
|
+
return pd.DataFrame(data)
|
191
|
+
|
192
|
+
def test_fault_on_mixed_types(self):
|
193
|
+
fault_df_on_mixed_types = self.fault_df_on_mixed_types().set_index(
|
194
|
+
generate_timestamp()
|
195
|
+
)
|
196
|
+
with pytest.raises(
|
197
|
+
TypeError,
|
198
|
+
match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL),
|
199
|
+
):
|
200
|
+
fc4.apply(fault_df_on_mixed_types)
|