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.
Files changed (59) hide show
  1. open_fdd/__init__.py +39 -0
  2. open_fdd/air_handling_unit/__init__.py +0 -0
  3. open_fdd/air_handling_unit/faults/__init__.py +0 -0
  4. open_fdd/air_handling_unit/faults/fault_condition.py +49 -0
  5. open_fdd/air_handling_unit/faults/fault_condition_eight.py +67 -0
  6. open_fdd/air_handling_unit/faults/fault_condition_eleven.py +68 -0
  7. open_fdd/air_handling_unit/faults/fault_condition_fifteen.py +90 -0
  8. open_fdd/air_handling_unit/faults/fault_condition_five.py +68 -0
  9. open_fdd/air_handling_unit/faults/fault_condition_four.py +93 -0
  10. open_fdd/air_handling_unit/faults/fault_condition_fourteen.py +80 -0
  11. open_fdd/air_handling_unit/faults/fault_condition_nine.py +68 -0
  12. open_fdd/air_handling_unit/faults/fault_condition_one.py +60 -0
  13. open_fdd/air_handling_unit/faults/fault_condition_seven.py +55 -0
  14. open_fdd/air_handling_unit/faults/fault_condition_six.py +120 -0
  15. open_fdd/air_handling_unit/faults/fault_condition_ten.py +62 -0
  16. open_fdd/air_handling_unit/faults/fault_condition_thirteen.py +66 -0
  17. open_fdd/air_handling_unit/faults/fault_condition_three.py +58 -0
  18. open_fdd/air_handling_unit/faults/fault_condition_twelve.py +71 -0
  19. open_fdd/air_handling_unit/faults/fault_condition_two.py +61 -0
  20. open_fdd/air_handling_unit/faults/helper_utils.py +191 -0
  21. open_fdd/air_handling_unit/faults/shared_utils.py +75 -0
  22. open_fdd/air_handling_unit/reports/__init__.py +0 -0
  23. open_fdd/air_handling_unit/reports/report_fc1.py +115 -0
  24. open_fdd/air_handling_unit/reports/report_fc10.py +126 -0
  25. open_fdd/air_handling_unit/reports/report_fc11.py +128 -0
  26. open_fdd/air_handling_unit/reports/report_fc12.py +126 -0
  27. open_fdd/air_handling_unit/reports/report_fc13.py +126 -0
  28. open_fdd/air_handling_unit/reports/report_fc14.py +124 -0
  29. open_fdd/air_handling_unit/reports/report_fc15.py +124 -0
  30. open_fdd/air_handling_unit/reports/report_fc2.py +119 -0
  31. open_fdd/air_handling_unit/reports/report_fc3.py +119 -0
  32. open_fdd/air_handling_unit/reports/report_fc4.py +148 -0
  33. open_fdd/air_handling_unit/reports/report_fc5.py +132 -0
  34. open_fdd/air_handling_unit/reports/report_fc6.py +156 -0
  35. open_fdd/air_handling_unit/reports/report_fc7.py +124 -0
  36. open_fdd/air_handling_unit/reports/report_fc8.py +118 -0
  37. open_fdd/air_handling_unit/reports/report_fc9.py +120 -0
  38. open_fdd/tests/__init__.py +0 -0
  39. open_fdd/tests/ahu/__init__.py +0 -0
  40. open_fdd/tests/ahu/test_ahu_fc1.py +159 -0
  41. open_fdd/tests/ahu/test_ahu_fc10.py +132 -0
  42. open_fdd/tests/ahu/test_ahu_fc11.py +136 -0
  43. open_fdd/tests/ahu/test_ahu_fc12.py +167 -0
  44. open_fdd/tests/ahu/test_ahu_fc13.py +163 -0
  45. open_fdd/tests/ahu/test_ahu_fc14.py +197 -0
  46. open_fdd/tests/ahu/test_ahu_fc15.py +183 -0
  47. open_fdd/tests/ahu/test_ahu_fc2.py +132 -0
  48. open_fdd/tests/ahu/test_ahu_fc3.py +131 -0
  49. open_fdd/tests/ahu/test_ahu_fc4.py +200 -0
  50. open_fdd/tests/ahu/test_ahu_fc5.py +180 -0
  51. open_fdd/tests/ahu/test_ahu_fc6.py +246 -0
  52. open_fdd/tests/ahu/test_ahu_fc7.py +71 -0
  53. open_fdd/tests/ahu/test_ahu_fc8.py +131 -0
  54. open_fdd/tests/ahu/test_ahu_fc9.py +136 -0
  55. open_fdd-0.1.0.dist-info/LICENSE +21 -0
  56. open_fdd-0.1.0.dist-info/METADATA +65 -0
  57. open_fdd-0.1.0.dist-info/RECORD +59 -0
  58. open_fdd-0.1.0.dist-info/WHEEL +5 -0
  59. 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)