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,118 @@
|
|
1
|
+
import matplotlib.pyplot as plt
|
2
|
+
import pandas as pd
|
3
|
+
import sys
|
4
|
+
|
5
|
+
|
6
|
+
class FaultCodeEightReport:
|
7
|
+
"""Class provides the definitions for Fault Condition 8 Report."""
|
8
|
+
|
9
|
+
def __init__(self, config):
|
10
|
+
self.sat_col = config["SAT_COL"]
|
11
|
+
self.mat_col = config["MAT_COL"]
|
12
|
+
self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"]
|
13
|
+
self.economizer_sig_col = config["ECONOMIZER_SIG_COL"]
|
14
|
+
|
15
|
+
def create_plot(self, df: pd.DataFrame, output_col: str = None):
|
16
|
+
if output_col is None:
|
17
|
+
output_col = "fc8_flag"
|
18
|
+
|
19
|
+
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(25, 8))
|
20
|
+
fig.suptitle("Fault Conditions 8 Plot")
|
21
|
+
|
22
|
+
ax1.plot(df.index, df[self.sat_col], label="SAT")
|
23
|
+
ax1.plot(df.index, df[self.mat_col], label="MAT")
|
24
|
+
ax1.legend(loc="best")
|
25
|
+
ax1.set_ylabel("AHU Temps °F")
|
26
|
+
|
27
|
+
ax2.plot(df.index, df[output_col], label="Fault", color="k")
|
28
|
+
ax2.set_xlabel("Date")
|
29
|
+
ax2.set_ylabel("Fault Flags")
|
30
|
+
ax2.legend(loc="best")
|
31
|
+
|
32
|
+
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
|
33
|
+
plt.show()
|
34
|
+
plt.close()
|
35
|
+
|
36
|
+
def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dict:
|
37
|
+
if output_col is None:
|
38
|
+
output_col = "fc8_flag"
|
39
|
+
|
40
|
+
delta = df.index.to_series().diff()
|
41
|
+
summary = {
|
42
|
+
"total_days": round(delta.sum() / pd.Timedelta(days=1), 2),
|
43
|
+
"total_hours": round(delta.sum() / pd.Timedelta(hours=1)),
|
44
|
+
"hours_fc8_mode": round(
|
45
|
+
(delta * df[output_col]).sum() / pd.Timedelta(hours=1)
|
46
|
+
),
|
47
|
+
"percent_true": round(df[output_col].mean() * 100, 2),
|
48
|
+
"percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2),
|
49
|
+
"flag_true_mat": round(
|
50
|
+
df[self.mat_col].where(df[output_col] == 1).mean(), 2
|
51
|
+
),
|
52
|
+
"flag_true_sat": round(
|
53
|
+
df[self.sat_col].where(df[output_col] == 1).mean(), 2
|
54
|
+
),
|
55
|
+
"hours_motor_runtime": round(
|
56
|
+
(delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum()
|
57
|
+
/ pd.Timedelta(hours=1),
|
58
|
+
2,
|
59
|
+
),
|
60
|
+
}
|
61
|
+
|
62
|
+
return summary
|
63
|
+
|
64
|
+
def create_hist_plot(self, df: pd.DataFrame, output_col: str = None):
|
65
|
+
if output_col is None:
|
66
|
+
output_col = "fc8_flag"
|
67
|
+
|
68
|
+
df["hour_of_the_day_fc8"] = df.index.hour.where(df[output_col] == 1)
|
69
|
+
|
70
|
+
fig, ax = plt.subplots(tight_layout=True, figsize=(25, 8))
|
71
|
+
ax.hist(df.hour_of_the_day_fc8.dropna())
|
72
|
+
ax.set_xlabel("24 Hour Number in Day")
|
73
|
+
ax.set_ylabel("Frequency")
|
74
|
+
ax.set_title(f"Hour-Of-Day When Fault Flag 8 is TRUE")
|
75
|
+
plt.show()
|
76
|
+
plt.close()
|
77
|
+
|
78
|
+
def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc8_flag"):
|
79
|
+
print(
|
80
|
+
"Fault Condition 8: Supply air temperature and mix air temperature should be approximately equal in economizer mode"
|
81
|
+
)
|
82
|
+
|
83
|
+
self.create_plot(df, output_col)
|
84
|
+
|
85
|
+
summary = self.summarize_fault_times(df, output_col)
|
86
|
+
|
87
|
+
for key, value in summary.items():
|
88
|
+
formatted_key = key.replace("_", " ")
|
89
|
+
print(f"{formatted_key}: {value}")
|
90
|
+
sys.stdout.flush()
|
91
|
+
|
92
|
+
fc_max_faults_found = df[output_col].max()
|
93
|
+
print("Fault Flag Count: ", fc_max_faults_found)
|
94
|
+
sys.stdout.flush()
|
95
|
+
|
96
|
+
if fc_max_faults_found != 0:
|
97
|
+
self.create_hist_plot(df, output_col)
|
98
|
+
|
99
|
+
flag_true_mat = round(df[self.mat_col].where(df[output_col] == 1).mean(), 2)
|
100
|
+
print("Mix Air Temp Mean When In Fault: ", flag_true_mat)
|
101
|
+
|
102
|
+
flag_true_sat = round(df[self.sat_col].where(df[output_col] == 1).mean(), 2)
|
103
|
+
print("Supply Air Temp Mean When In Fault: ", flag_true_sat)
|
104
|
+
|
105
|
+
sys.stdout.flush()
|
106
|
+
|
107
|
+
if summary["percent_true"] > 5.0:
|
108
|
+
print(
|
109
|
+
"The percent True metric that represents the amount of time for when the fault flag is True is high indicating temperature sensor error or the heating/cooling coils are leaking, potentially creating simultaneous heating/cooling which can be an energy penalty for running the AHU in this fashion. Verify AHU mix/supply temperature sensor calibration in addition to a potential mechanical issue of a leaking valve. A leaking valve can be troubleshot by isolating the valve closed by manual shut-off valves where piping lines enter the AHU coil and then verifying any changes in the AHU discharge air temperature."
|
110
|
+
)
|
111
|
+
else:
|
112
|
+
print(
|
113
|
+
"The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the AHU components are within calibration for this fault equation."
|
114
|
+
)
|
115
|
+
|
116
|
+
else:
|
117
|
+
print("NO FAULTS FOUND - Skipping time-of-day Histogram plot")
|
118
|
+
sys.stdout.flush()
|
@@ -0,0 +1,120 @@
|
|
1
|
+
import matplotlib.pyplot as plt
|
2
|
+
import pandas as pd
|
3
|
+
import sys
|
4
|
+
|
5
|
+
|
6
|
+
class FaultCodeNineReport:
|
7
|
+
"""Class provides the definitions for Fault Condition 9 Report."""
|
8
|
+
|
9
|
+
def __init__(self, config):
|
10
|
+
self.sat_setpoint_col = config["SAT_SETPOINT_COL"]
|
11
|
+
self.oat_col = config["OAT_COL"]
|
12
|
+
self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"]
|
13
|
+
self.economizer_sig_col = config["ECONOMIZER_SIG_COL"]
|
14
|
+
|
15
|
+
def create_plot(self, df: pd.DataFrame, output_col: str = None):
|
16
|
+
if output_col is None:
|
17
|
+
output_col = "fc9_flag"
|
18
|
+
|
19
|
+
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(25, 8))
|
20
|
+
fig.suptitle("Fault Conditions 9 Plot")
|
21
|
+
|
22
|
+
ax1.plot(df.index, df[self.sat_setpoint_col], label="SATSP")
|
23
|
+
ax1.plot(df.index, df[self.oat_col], label="OAT")
|
24
|
+
ax1.legend(loc="best")
|
25
|
+
ax1.set_ylabel("AHU Temps °F")
|
26
|
+
|
27
|
+
ax2.plot(df.index, df[output_col], label="Fault", color="k")
|
28
|
+
ax2.set_xlabel("Date")
|
29
|
+
ax2.set_ylabel("Fault Flags")
|
30
|
+
ax2.legend(loc="best")
|
31
|
+
|
32
|
+
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
|
33
|
+
plt.show()
|
34
|
+
plt.close()
|
35
|
+
|
36
|
+
def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dict:
|
37
|
+
if output_col is None:
|
38
|
+
output_col = "fc9_flag"
|
39
|
+
|
40
|
+
delta = df.index.to_series().diff()
|
41
|
+
summary = {
|
42
|
+
"total_days": round(delta.sum() / pd.Timedelta(days=1), 2),
|
43
|
+
"total_hours": round(delta.sum() / pd.Timedelta(hours=1)),
|
44
|
+
"hours_fc9_mode": round(
|
45
|
+
(delta * df[output_col]).sum() / pd.Timedelta(hours=1)
|
46
|
+
),
|
47
|
+
"percent_true": round(df[output_col].mean() * 100, 2),
|
48
|
+
"percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2),
|
49
|
+
"flag_true_oat": round(
|
50
|
+
df[self.oat_col].where(df[output_col] == 1).mean(), 2
|
51
|
+
),
|
52
|
+
"flag_true_satsp": round(
|
53
|
+
df[self.sat_setpoint_col].where(df[output_col] == 1).mean(), 2
|
54
|
+
),
|
55
|
+
"hours_motor_runtime": round(
|
56
|
+
(delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum()
|
57
|
+
/ pd.Timedelta(hours=1),
|
58
|
+
2,
|
59
|
+
),
|
60
|
+
}
|
61
|
+
|
62
|
+
return summary
|
63
|
+
|
64
|
+
def create_hist_plot(self, df: pd.DataFrame, output_col: str = None):
|
65
|
+
if output_col is None:
|
66
|
+
output_col = "fc9_flag"
|
67
|
+
|
68
|
+
df["hour_of_the_day_fc9"] = df.index.hour.where(df[output_col] == 1)
|
69
|
+
|
70
|
+
fig, ax = plt.subplots(tight_layout=True, figsize=(25, 8))
|
71
|
+
ax.hist(df.hour_of_the_day_fc9.dropna())
|
72
|
+
ax.set_xlabel("24 Hour Number in Day")
|
73
|
+
ax.set_ylabel("Frequency")
|
74
|
+
ax.set_title(f"Hour-Of-Day When Fault Flag 9 is TRUE")
|
75
|
+
plt.show()
|
76
|
+
plt.close()
|
77
|
+
|
78
|
+
def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc9_flag"):
|
79
|
+
print(
|
80
|
+
"Fault Condition 9: Outside air temperature too high in free cooling without additional mechanical cooling in economizer mode"
|
81
|
+
)
|
82
|
+
|
83
|
+
self.create_plot(df, output_col)
|
84
|
+
|
85
|
+
summary = self.summarize_fault_times(df, output_col)
|
86
|
+
|
87
|
+
for key, value in summary.items():
|
88
|
+
formatted_key = key.replace("_", " ")
|
89
|
+
print(f"{formatted_key}: {value}")
|
90
|
+
sys.stdout.flush()
|
91
|
+
|
92
|
+
fc_max_faults_found = df[output_col].max()
|
93
|
+
print("Fault Flag Count: ", fc_max_faults_found)
|
94
|
+
sys.stdout.flush()
|
95
|
+
|
96
|
+
if fc_max_faults_found != 0:
|
97
|
+
self.create_hist_plot(df, output_col)
|
98
|
+
|
99
|
+
flag_true_oat = round(df[self.oat_col].where(df[output_col] == 1).mean(), 2)
|
100
|
+
print("Outside Air Temp Mean When In Fault: ", flag_true_oat)
|
101
|
+
|
102
|
+
flag_true_satsp = round(
|
103
|
+
df[self.sat_setpoint_col].where(df[output_col] == 1).mean(), 2
|
104
|
+
)
|
105
|
+
print("Supply Air Temp Setpoint Mean When In Fault: ", flag_true_satsp)
|
106
|
+
|
107
|
+
sys.stdout.flush()
|
108
|
+
|
109
|
+
if summary["percent_true"] > 5.0:
|
110
|
+
print(
|
111
|
+
"The percent True metric that represents the amount of time for when the fault flag is True is high indicating temperature sensor error or the cooling valve is stuck open or leaking causing overcooling. Trouble shoot a leaking valve by isolating the coil with manual shutoff valves and verify a change in AHU discharge air temperature with the AHU running."
|
112
|
+
)
|
113
|
+
else:
|
114
|
+
print(
|
115
|
+
"The percent True metric that represents the amount of time for when the fault flag is True is low indicating the AHU components are within calibration for this fault equation."
|
116
|
+
)
|
117
|
+
|
118
|
+
else:
|
119
|
+
print("NO FAULTS FOUND - Skipping time-of-day Histogram plot")
|
120
|
+
sys.stdout.flush()
|
File without changes
|
File without changes
|
@@ -0,0 +1,159 @@
|
|
1
|
+
import pandas as pd
|
2
|
+
import pytest
|
3
|
+
from open_fdd.air_handling_unit.faults.fault_condition_one import FaultConditionOne
|
4
|
+
from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils
|
5
|
+
|
6
|
+
# Constants
|
7
|
+
TEST_VFD_ERR_THRESHOLD = 0.05
|
8
|
+
TEST_VFD_SPEED_MAX = 0.7
|
9
|
+
TEST_DUCT_STATIC_ERR_THRESHOLD = 0.1
|
10
|
+
TEST_DUCT_STATIC_COL = "duct_static"
|
11
|
+
TEST_DUCT_STATIC_SETPOINT_COL = "duct_static_setpoint"
|
12
|
+
TEST_SUPPLY_VFD_SPEED_COL = "supply_vfd_speed"
|
13
|
+
ROLLING_WINDOW_SIZE = 5
|
14
|
+
|
15
|
+
# Initialize FaultConditionOne with a dictionary
|
16
|
+
fault_condition_params = {
|
17
|
+
"VFD_SPEED_PERCENT_ERR_THRES": TEST_VFD_ERR_THRESHOLD,
|
18
|
+
"VFD_SPEED_PERCENT_MAX": TEST_VFD_SPEED_MAX,
|
19
|
+
"DUCT_STATIC_INCHES_ERR_THRES": TEST_DUCT_STATIC_ERR_THRESHOLD,
|
20
|
+
"DUCT_STATIC_COL": TEST_DUCT_STATIC_COL,
|
21
|
+
"SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL,
|
22
|
+
"DUCT_STATIC_SETPOINT_COL": TEST_DUCT_STATIC_SETPOINT_COL,
|
23
|
+
"TROUBLESHOOT_MODE": False, # default value
|
24
|
+
"ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE, # rolling sum window size
|
25
|
+
}
|
26
|
+
|
27
|
+
fc1 = FaultConditionOne(fault_condition_params)
|
28
|
+
|
29
|
+
|
30
|
+
class TestNoFault:
|
31
|
+
|
32
|
+
def no_fault_df(self) -> pd.DataFrame:
|
33
|
+
data = {
|
34
|
+
TEST_DUCT_STATIC_COL: [0.99, 0.99, 0.99, 0.99, 0.99, 0.99],
|
35
|
+
TEST_DUCT_STATIC_SETPOINT_COL: [1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
|
36
|
+
TEST_SUPPLY_VFD_SPEED_COL: [0.8, 0.8, 0.8, 0.8, 0.8, 0.8],
|
37
|
+
}
|
38
|
+
return pd.DataFrame(data)
|
39
|
+
|
40
|
+
def test_no_fault(self):
|
41
|
+
results = fc1.apply(self.no_fault_df())
|
42
|
+
actual = results["fc1_flag"].sum()
|
43
|
+
expected = 0
|
44
|
+
message = f"FC1 no_fault_df actual is {actual} and expected is {expected}"
|
45
|
+
assert actual == expected, message
|
46
|
+
|
47
|
+
|
48
|
+
class TestFault:
|
49
|
+
|
50
|
+
def fault_df(self) -> pd.DataFrame:
|
51
|
+
data = {
|
52
|
+
TEST_DUCT_STATIC_COL: [
|
53
|
+
0.7,
|
54
|
+
0.7,
|
55
|
+
0.6,
|
56
|
+
0.7,
|
57
|
+
0.65,
|
58
|
+
0.55,
|
59
|
+
0.99,
|
60
|
+
0.99,
|
61
|
+
0.6,
|
62
|
+
0.7,
|
63
|
+
0.65,
|
64
|
+
0.55,
|
65
|
+
0.6,
|
66
|
+
0.7,
|
67
|
+
0.65,
|
68
|
+
0.55,
|
69
|
+
0.6,
|
70
|
+
],
|
71
|
+
TEST_DUCT_STATIC_SETPOINT_COL: [
|
72
|
+
1.0,
|
73
|
+
1.0,
|
74
|
+
1.0,
|
75
|
+
1.0,
|
76
|
+
1.0,
|
77
|
+
1.0,
|
78
|
+
1.0,
|
79
|
+
1.0,
|
80
|
+
1.0,
|
81
|
+
1.0,
|
82
|
+
1.0,
|
83
|
+
1.0,
|
84
|
+
1.0,
|
85
|
+
1.0,
|
86
|
+
1.0,
|
87
|
+
1.0,
|
88
|
+
1.0,
|
89
|
+
],
|
90
|
+
TEST_SUPPLY_VFD_SPEED_COL: [
|
91
|
+
0.99,
|
92
|
+
0.95,
|
93
|
+
0.96,
|
94
|
+
0.97,
|
95
|
+
0.98,
|
96
|
+
0.98,
|
97
|
+
0.5,
|
98
|
+
0.55,
|
99
|
+
0.96,
|
100
|
+
0.97,
|
101
|
+
0.98,
|
102
|
+
0.98,
|
103
|
+
0.96,
|
104
|
+
0.97,
|
105
|
+
0.98,
|
106
|
+
0.98,
|
107
|
+
0.96,
|
108
|
+
],
|
109
|
+
}
|
110
|
+
return pd.DataFrame(data)
|
111
|
+
|
112
|
+
def test_fault(self):
|
113
|
+
results = fc1.apply(self.fault_df())
|
114
|
+
actual = results["fc1_flag"].sum()
|
115
|
+
|
116
|
+
# accumilated 5 faults need to happen before an "official fault"
|
117
|
+
# in TEST_DUCT_STATIC_COL after the 5 first values there is 3 faults
|
118
|
+
# then artificially adjust fake fan data back to normal and another 5
|
119
|
+
# needs happen per ROLLING_WINDOW_SIZE and then 4 faults after that.
|
120
|
+
# so expected = 3 + 4.
|
121
|
+
expected = 3 + 4
|
122
|
+
message = f"FC1 fault_df actual is {actual} and expected is {expected}"
|
123
|
+
assert actual == expected, message
|
124
|
+
|
125
|
+
|
126
|
+
class TestFaultOnInt:
|
127
|
+
|
128
|
+
def fault_df_on_output_int(self) -> pd.DataFrame:
|
129
|
+
data = {
|
130
|
+
TEST_DUCT_STATIC_COL: [0.8] * 6,
|
131
|
+
TEST_DUCT_STATIC_SETPOINT_COL: [1.0] * 6,
|
132
|
+
TEST_SUPPLY_VFD_SPEED_COL: [99] * 6,
|
133
|
+
}
|
134
|
+
return pd.DataFrame(data)
|
135
|
+
|
136
|
+
def test_fault_on_int(self):
|
137
|
+
with pytest.raises(
|
138
|
+
TypeError,
|
139
|
+
match=HelperUtils().float_int_check_err(TEST_SUPPLY_VFD_SPEED_COL),
|
140
|
+
):
|
141
|
+
fc1.apply(self.fault_df_on_output_int())
|
142
|
+
|
143
|
+
|
144
|
+
class TestFaultOnFloatGreaterThanOne:
|
145
|
+
|
146
|
+
def fault_df_on_output_greater_than_one(self) -> pd.DataFrame:
|
147
|
+
data = {
|
148
|
+
TEST_DUCT_STATIC_COL: [0.8] * 6,
|
149
|
+
TEST_DUCT_STATIC_SETPOINT_COL: [1.0] * 6,
|
150
|
+
TEST_SUPPLY_VFD_SPEED_COL: [99.0] * 6,
|
151
|
+
}
|
152
|
+
return pd.DataFrame(data)
|
153
|
+
|
154
|
+
def test_fault_on_float_greater_than_one(self):
|
155
|
+
with pytest.raises(
|
156
|
+
TypeError,
|
157
|
+
match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL),
|
158
|
+
):
|
159
|
+
fc1.apply(self.fault_df_on_output_greater_than_one())
|
@@ -0,0 +1,132 @@
|
|
1
|
+
import pandas as pd
|
2
|
+
import pytest
|
3
|
+
from open_fdd.air_handling_unit.faults.fault_condition_ten import FaultConditionTen
|
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_fc10.py -rP -s
|
9
|
+
|
10
|
+
SAT and MAT should be approx equal in OS3
|
11
|
+
"""
|
12
|
+
|
13
|
+
# Constants
|
14
|
+
TEST_OAT_DEGF_ERR_THRES = 5.0
|
15
|
+
TEST_MIX_DEGF_ERR_THRES = 2.0
|
16
|
+
TEST_OAT_COL = "out_air_temp"
|
17
|
+
TEST_MAT_COL = "mix_air_temp"
|
18
|
+
TEST_COOLING_COIL_SIG_COL = "cooling_sig_col"
|
19
|
+
TEST_MIX_AIR_DAMPER_COL = "economizer_sig_col"
|
20
|
+
ROLLING_WINDOW_SIZE = 5
|
21
|
+
|
22
|
+
# Initialize FaultConditionTen with a dictionary
|
23
|
+
fault_condition_params = {
|
24
|
+
"OUTDOOR_DEGF_ERR_THRES": TEST_OAT_DEGF_ERR_THRES,
|
25
|
+
"MIX_DEGF_ERR_THRES": TEST_MIX_DEGF_ERR_THRES,
|
26
|
+
"OAT_COL": TEST_OAT_COL,
|
27
|
+
"MAT_COL": TEST_MAT_COL,
|
28
|
+
"COOLING_SIG_COL": TEST_COOLING_COIL_SIG_COL,
|
29
|
+
"ECONOMIZER_SIG_COL": TEST_MIX_AIR_DAMPER_COL,
|
30
|
+
"TROUBLESHOOT_MODE": False,
|
31
|
+
"ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE,
|
32
|
+
}
|
33
|
+
|
34
|
+
fc10 = FaultConditionTen(fault_condition_params)
|
35
|
+
|
36
|
+
|
37
|
+
class TestFaultConditionTen:
|
38
|
+
|
39
|
+
def no_fault_df_no_econ(self) -> pd.DataFrame:
|
40
|
+
data = {
|
41
|
+
TEST_MAT_COL: [55, 55, 55, 55, 55, 55],
|
42
|
+
TEST_OAT_COL: [56, 56, 56, 56, 56, 56],
|
43
|
+
TEST_COOLING_COIL_SIG_COL: [0.11, 0.11, 0.11, 0.11, 0.11, 0.11],
|
44
|
+
TEST_MIX_AIR_DAMPER_COL: [0.99, 0.99, 0.99, 0.99, 0.99, 0.99],
|
45
|
+
}
|
46
|
+
return pd.DataFrame(data)
|
47
|
+
|
48
|
+
def fault_df_in_econ(self) -> pd.DataFrame:
|
49
|
+
data = {
|
50
|
+
TEST_MAT_COL: [55, 55, 55, 55, 55, 55],
|
51
|
+
TEST_OAT_COL: [75, 75, 75, 75, 75, 75],
|
52
|
+
TEST_COOLING_COIL_SIG_COL: [0.11, 0.11, 0.11, 0.11, 0.11, 0.11],
|
53
|
+
TEST_MIX_AIR_DAMPER_COL: [0.99, 0.99, 0.99, 0.99, 0.99, 0.99],
|
54
|
+
}
|
55
|
+
return pd.DataFrame(data)
|
56
|
+
|
57
|
+
def test_no_fault_no_econ(self):
|
58
|
+
results = fc10.apply(self.no_fault_df_no_econ())
|
59
|
+
actual = results["fc10_flag"].sum()
|
60
|
+
expected = 0
|
61
|
+
message = (
|
62
|
+
f"FC10 no_fault_df_no_econ actual is {actual} and expected is {expected}"
|
63
|
+
)
|
64
|
+
assert actual == expected, message
|
65
|
+
|
66
|
+
def test_fault_in_econ(self):
|
67
|
+
results = fc10.apply(self.fault_df_in_econ())
|
68
|
+
actual = results["fc10_flag"].sum()
|
69
|
+
expected = 2
|
70
|
+
message = f"FC10 fault_df_in_econ actual is {actual} and expected is {expected}"
|
71
|
+
assert actual == expected, message
|
72
|
+
|
73
|
+
|
74
|
+
class TestFaultOnInt:
|
75
|
+
|
76
|
+
def fault_df_on_output_int(self) -> pd.DataFrame:
|
77
|
+
data = {
|
78
|
+
TEST_MAT_COL: [55, 55, 55, 55, 55, 55],
|
79
|
+
TEST_OAT_COL: [75, 75, 75, 75, 75, 75],
|
80
|
+
TEST_COOLING_COIL_SIG_COL: [11, 11, 11, 11, 11, 11], # Incorrect type
|
81
|
+
TEST_MIX_AIR_DAMPER_COL: [0.80, 0.80, 0.80, 0.80, 0.80, 0.80],
|
82
|
+
}
|
83
|
+
return pd.DataFrame(data)
|
84
|
+
|
85
|
+
def test_fault_on_int(self):
|
86
|
+
with pytest.raises(
|
87
|
+
TypeError,
|
88
|
+
match=HelperUtils().float_int_check_err(TEST_COOLING_COIL_SIG_COL),
|
89
|
+
):
|
90
|
+
fc10.apply(self.fault_df_on_output_int())
|
91
|
+
|
92
|
+
|
93
|
+
class TestFaultOnFloatGreaterThanOne:
|
94
|
+
|
95
|
+
def fault_df_on_output_greater_than_one(self) -> pd.DataFrame:
|
96
|
+
data = {
|
97
|
+
TEST_MAT_COL: [55, 55, 55, 55, 55, 55],
|
98
|
+
TEST_OAT_COL: [75, 75, 75, 75, 75, 75],
|
99
|
+
TEST_COOLING_COIL_SIG_COL: [1.1, 1.2, 1.1, 1.3, 1.1, 1.2], # Values > 1.0
|
100
|
+
TEST_MIX_AIR_DAMPER_COL: [0.80, 0.80, 0.80, 0.80, 0.80, 0.80],
|
101
|
+
}
|
102
|
+
return pd.DataFrame(data)
|
103
|
+
|
104
|
+
def test_fault_on_float_greater_than_one(self):
|
105
|
+
with pytest.raises(
|
106
|
+
TypeError,
|
107
|
+
match=HelperUtils().float_max_check_err(TEST_COOLING_COIL_SIG_COL),
|
108
|
+
):
|
109
|
+
fc10.apply(self.fault_df_on_output_greater_than_one())
|
110
|
+
|
111
|
+
|
112
|
+
class TestFaultOnMixedTypes:
|
113
|
+
|
114
|
+
def fault_df_on_mixed_types(self) -> pd.DataFrame:
|
115
|
+
data = {
|
116
|
+
TEST_MAT_COL: [55, 55, 55, 55, 55, 55],
|
117
|
+
TEST_OAT_COL: [75, 75, 75, 75, 75, 75],
|
118
|
+
TEST_COOLING_COIL_SIG_COL: [1.1, 0.55, 1.2, 1.3, 0.55, 1.1], # Mixed types
|
119
|
+
TEST_MIX_AIR_DAMPER_COL: [0.80, 0.80, 0.80, 0.80, 0.80, 0.80],
|
120
|
+
}
|
121
|
+
return pd.DataFrame(data)
|
122
|
+
|
123
|
+
def test_fault_on_mixed_types(self):
|
124
|
+
with pytest.raises(
|
125
|
+
TypeError,
|
126
|
+
match=HelperUtils().float_max_check_err(TEST_COOLING_COIL_SIG_COL),
|
127
|
+
):
|
128
|
+
fc10.apply(self.fault_df_on_mixed_types())
|
129
|
+
|
130
|
+
|
131
|
+
if __name__ == "__main__":
|
132
|
+
pytest.main()
|
@@ -0,0 +1,136 @@
|
|
1
|
+
import pandas as pd
|
2
|
+
import pytest
|
3
|
+
from open_fdd.air_handling_unit.faults.fault_condition_eleven import (
|
4
|
+
FaultConditionEleven,
|
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 tests/ahu/test_ahu_fc11.py -rP -s
|
11
|
+
|
12
|
+
OAT Temp too low for 100% cooling in OS3
|
13
|
+
"""
|
14
|
+
|
15
|
+
# Constants
|
16
|
+
TEST_DELTA_SUPPLY_FAN = 2.0
|
17
|
+
TEST_OAT_DEGF_ERR_THRES = 5.0
|
18
|
+
TEST_SUPPLY_DEGF_ERR_THRES = 2.0
|
19
|
+
TEST_SAT_SP_COL = "supply_air_sp_temp"
|
20
|
+
TEST_OAT_COL = "out_air_temp"
|
21
|
+
TEST_COOLING_COIL_SIG_COL = "cooling_sig_col"
|
22
|
+
TEST_MIX_AIR_DAMPER_COL = "economizer_sig_col"
|
23
|
+
ROLLING_WINDOW_SIZE = 5
|
24
|
+
|
25
|
+
# Initialize FaultConditionEleven with a dictionary
|
26
|
+
fault_condition_params = {
|
27
|
+
"DELTA_T_SUPPLY_FAN": TEST_DELTA_SUPPLY_FAN,
|
28
|
+
"OUTDOOR_DEGF_ERR_THRES": TEST_OAT_DEGF_ERR_THRES,
|
29
|
+
"SUPPLY_DEGF_ERR_THRES": TEST_SUPPLY_DEGF_ERR_THRES,
|
30
|
+
"SAT_SETPOINT_COL": TEST_SAT_SP_COL,
|
31
|
+
"OAT_COL": TEST_OAT_COL,
|
32
|
+
"COOLING_SIG_COL": TEST_COOLING_COIL_SIG_COL,
|
33
|
+
"ECONOMIZER_SIG_COL": TEST_MIX_AIR_DAMPER_COL,
|
34
|
+
"TROUBLESHOOT_MODE": False,
|
35
|
+
"ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE,
|
36
|
+
}
|
37
|
+
|
38
|
+
fc11 = FaultConditionEleven(fault_condition_params)
|
39
|
+
|
40
|
+
|
41
|
+
class TestFaultConditionEleven:
|
42
|
+
|
43
|
+
def no_fault_df_no_econ(self) -> pd.DataFrame:
|
44
|
+
data = {
|
45
|
+
TEST_SAT_SP_COL: [55, 55, 55, 55, 55, 55],
|
46
|
+
TEST_OAT_COL: [56, 56, 56, 56, 56, 56],
|
47
|
+
TEST_COOLING_COIL_SIG_COL: [0.11, 0.11, 0.11, 0.11, 0.11, 0.11],
|
48
|
+
TEST_MIX_AIR_DAMPER_COL: [0.99, 0.99, 0.99, 0.99, 0.99, 0.99],
|
49
|
+
}
|
50
|
+
return pd.DataFrame(data)
|
51
|
+
|
52
|
+
def fault_df_in_econ(self) -> pd.DataFrame:
|
53
|
+
data = {
|
54
|
+
TEST_SAT_SP_COL: [55, 55, 55, 55, 55, 55],
|
55
|
+
TEST_OAT_COL: [44, 44, 44, 44, 44, 44],
|
56
|
+
TEST_COOLING_COIL_SIG_COL: [0.11, 0.11, 0.11, 0.11, 0.11, 0.11],
|
57
|
+
TEST_MIX_AIR_DAMPER_COL: [0.99, 0.99, 0.99, 0.99, 0.99, 0.99],
|
58
|
+
}
|
59
|
+
return pd.DataFrame(data)
|
60
|
+
|
61
|
+
def test_no_fault_no_econ(self):
|
62
|
+
results = fc11.apply(self.no_fault_df_no_econ())
|
63
|
+
actual = results["fc11_flag"].sum()
|
64
|
+
expected = 0
|
65
|
+
message = (
|
66
|
+
f"FC11 no_fault_df_no_econ actual is {actual} and expected is {expected}"
|
67
|
+
)
|
68
|
+
assert actual == expected, message
|
69
|
+
|
70
|
+
def test_fault_in_econ(self):
|
71
|
+
results = fc11.apply(self.fault_df_in_econ())
|
72
|
+
actual = results["fc11_flag"].sum()
|
73
|
+
expected = 2
|
74
|
+
message = f"FC11 fault_df_in_econ actual is {actual} and expected is {expected}"
|
75
|
+
assert actual == expected, message
|
76
|
+
|
77
|
+
|
78
|
+
class TestFaultOnInt:
|
79
|
+
|
80
|
+
def fault_df_on_output_int(self) -> pd.DataFrame:
|
81
|
+
data = {
|
82
|
+
TEST_SAT_SP_COL: [55, 55, 55, 55, 55, 55],
|
83
|
+
TEST_OAT_COL: [44, 44, 44, 44, 44, 44],
|
84
|
+
TEST_COOLING_COIL_SIG_COL: [11, 11, 11, 11, 11, 11], # Incorrect type
|
85
|
+
TEST_MIX_AIR_DAMPER_COL: [0.99, 0.99, 0.99, 0.99, 0.99, 0.99],
|
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_COOLING_COIL_SIG_COL),
|
93
|
+
):
|
94
|
+
fc11.apply(self.fault_df_on_output_int())
|
95
|
+
|
96
|
+
|
97
|
+
class TestFaultOnFloatGreaterThanOne:
|
98
|
+
|
99
|
+
def fault_df_on_output_greater_than_one(self) -> pd.DataFrame:
|
100
|
+
data = {
|
101
|
+
TEST_SAT_SP_COL: [55, 55, 55, 55, 55, 55],
|
102
|
+
TEST_OAT_COL: [44, 44, 44, 44, 44, 44],
|
103
|
+
TEST_COOLING_COIL_SIG_COL: [1.1, 1.2, 1.1, 1.3, 1.1, 1.2], # Values > 1.0
|
104
|
+
TEST_MIX_AIR_DAMPER_COL: [0.99, 0.99, 0.99, 0.99, 0.99, 0.99],
|
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_COOLING_COIL_SIG_COL),
|
112
|
+
):
|
113
|
+
fc11.apply(self.fault_df_on_output_greater_than_one())
|
114
|
+
|
115
|
+
|
116
|
+
class TestFaultOnMixedTypes:
|
117
|
+
|
118
|
+
def fault_df_on_mixed_types(self) -> pd.DataFrame:
|
119
|
+
data = {
|
120
|
+
TEST_SAT_SP_COL: [55, 55, 55, 55, 55, 55],
|
121
|
+
TEST_OAT_COL: [44, 44, 44, 44, 44, 44],
|
122
|
+
TEST_COOLING_COIL_SIG_COL: [1.1, 0.55, 1.2, 1.3, 0.55, 1.1], # Mixed types
|
123
|
+
TEST_MIX_AIR_DAMPER_COL: [0.99, 0.99, 0.99, 0.99, 0.99, 0.99],
|
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_COOLING_COIL_SIG_COL),
|
131
|
+
):
|
132
|
+
fc11.apply(self.fault_df_on_mixed_types())
|
133
|
+
|
134
|
+
|
135
|
+
if __name__ == "__main__":
|
136
|
+
pytest.main()
|