open-fdd 0.1.5__py3-none-any.whl → 0.1.6__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 +301 -301
- open_fdd/air_handling_unit/reports/__init__.py +94 -0
- open_fdd/air_handling_unit/reports/fault_report.py +1 -0
- open_fdd/tests/ahu/test_ahu_fc16.py +190 -0
- {open_fdd-0.1.5.dist-info → open_fdd-0.1.6.dist-info}/METADATA +4 -3
- open_fdd-0.1.6.dist-info/RECORD +31 -0
- {open_fdd-0.1.5.dist-info → open_fdd-0.1.6.dist-info}/WHEEL +1 -1
- open_fdd/air_handling_unit/faults/fault_condition_eight.py +0 -127
- open_fdd/air_handling_unit/faults/fault_condition_eleven.py +0 -126
- open_fdd/air_handling_unit/faults/fault_condition_fifteen.py +0 -152
- open_fdd/air_handling_unit/faults/fault_condition_five.py +0 -123
- open_fdd/air_handling_unit/faults/fault_condition_four.py +0 -168
- open_fdd/air_handling_unit/faults/fault_condition_fourteen.py +0 -143
- open_fdd/air_handling_unit/faults/fault_condition_nine.py +0 -128
- open_fdd/air_handling_unit/faults/fault_condition_one.py +0 -112
- open_fdd/air_handling_unit/faults/fault_condition_seven.py +0 -114
- open_fdd/air_handling_unit/faults/fault_condition_six.py +0 -181
- open_fdd/air_handling_unit/faults/fault_condition_ten.py +0 -123
- open_fdd/air_handling_unit/faults/fault_condition_thirteen.py +0 -127
- open_fdd/air_handling_unit/faults/fault_condition_three.py +0 -113
- open_fdd/air_handling_unit/faults/fault_condition_twelve.py +0 -132
- open_fdd/air_handling_unit/faults/fault_condition_two.py +0 -113
- open_fdd/air_handling_unit/images/ahu1_fc1_2024-06_1.jpg +0 -0
- open_fdd/air_handling_unit/images/ahu1_fc1_2024-06_2.jpg +0 -0
- open_fdd/air_handling_unit/images/example1.jpg +0 -0
- open_fdd/air_handling_unit/images/example2.jpg +0 -0
- open_fdd/air_handling_unit/images/fc10_definition.png +0 -0
- open_fdd/air_handling_unit/images/fc11_definition.png +0 -0
- open_fdd/air_handling_unit/images/fc12_definition.png +0 -0
- open_fdd/air_handling_unit/images/fc13_definition.png +0 -0
- open_fdd/air_handling_unit/images/fc1_definition.png +0 -0
- open_fdd/air_handling_unit/images/fc1_report_screenshot_all.png +0 -0
- open_fdd/air_handling_unit/images/fc2_definition.png +0 -0
- open_fdd/air_handling_unit/images/fc3_definition.png +0 -0
- open_fdd/air_handling_unit/images/fc4_definition.png +0 -0
- open_fdd/air_handling_unit/images/fc5_definition.png +0 -0
- open_fdd/air_handling_unit/images/fc6_definition.png +0 -0
- open_fdd/air_handling_unit/images/fc7_definition.png +0 -0
- open_fdd/air_handling_unit/images/fc8_definition.png +0 -0
- open_fdd/air_handling_unit/images/fc9_definition.png +0 -0
- open_fdd/air_handling_unit/images/latex_generator.py +0 -175
- open_fdd/air_handling_unit/images/params.docx +0 -0
- open_fdd/air_handling_unit/images/params.pdf +0 -0
- open_fdd/air_handling_unit/images/plot_for_repo.png +0 -0
- open_fdd/air_handling_unit/reports/base_report.py +0 -47
- open_fdd/air_handling_unit/reports/report_fc1.py +0 -115
- open_fdd/air_handling_unit/reports/report_fc10.py +0 -126
- open_fdd/air_handling_unit/reports/report_fc11.py +0 -128
- open_fdd/air_handling_unit/reports/report_fc12.py +0 -126
- open_fdd/air_handling_unit/reports/report_fc13.py +0 -126
- open_fdd/air_handling_unit/reports/report_fc14.py +0 -124
- open_fdd/air_handling_unit/reports/report_fc15.py +0 -124
- open_fdd/air_handling_unit/reports/report_fc2.py +0 -119
- open_fdd/air_handling_unit/reports/report_fc3.py +0 -119
- open_fdd/air_handling_unit/reports/report_fc4.py +0 -148
- open_fdd/air_handling_unit/reports/report_fc5.py +0 -132
- open_fdd/air_handling_unit/reports/report_fc6.py +0 -156
- open_fdd/air_handling_unit/reports/report_fc7.py +0 -126
- open_fdd/air_handling_unit/reports/report_fc8.py +0 -118
- open_fdd/air_handling_unit/reports/report_fc9.py +0 -120
- open_fdd-0.1.5.dist-info/RECORD +0 -83
- {open_fdd-0.1.5.dist-info → open_fdd-0.1.6.dist-info}/LICENSE +0 -0
- {open_fdd-0.1.5.dist-info → open_fdd-0.1.6.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,9 @@
|
|
1
1
|
import matplotlib.pyplot as plt
|
2
2
|
from open_fdd.air_handling_unit.reports.fault_report import BaseFaultReport
|
3
|
+
from open_fdd.air_handling_unit.faults import FaultConditionSixteen
|
3
4
|
import pandas as pd
|
5
|
+
import numpy as np
|
6
|
+
import sys
|
4
7
|
|
5
8
|
|
6
9
|
class FaultCodeOneReport(BaseFaultReport):
|
@@ -892,3 +895,94 @@ class FaultCodeFifteenReport(BaseFaultReport):
|
|
892
895
|
),
|
893
896
|
}
|
894
897
|
return summary
|
898
|
+
|
899
|
+
|
900
|
+
class FaultCodeSixteenReport(BaseFaultReport):
|
901
|
+
def __init__(self, config):
|
902
|
+
super().__init__(config, "fc16_flag")
|
903
|
+
|
904
|
+
self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"]
|
905
|
+
self.erv_oat_enter_col = config["ERV_OAT_ENTER_COL"]
|
906
|
+
self.erv_oat_leaving_col = config["ERV_OAT_LEAVING_COL"]
|
907
|
+
self.erv_eat_enter_col = config["ERV_EAT_ENTER_COL"]
|
908
|
+
self.erv_eat_leaving_col = config["ERV_EAT_LEAVING_COL"]
|
909
|
+
|
910
|
+
# Instantiate FaultConditionSixteen to access its methods
|
911
|
+
self.fc16 = FaultConditionSixteen(config)
|
912
|
+
|
913
|
+
def create_plot(self, df: pd.DataFrame):
|
914
|
+
# Calculate the efficiency before plotting using FaultConditionSixteen method
|
915
|
+
df = self.fc16.calculate_erv_efficiency(df)
|
916
|
+
|
917
|
+
print("=" * 50)
|
918
|
+
print("Info: ERV calculated efficiency ")
|
919
|
+
print("summary statistics ")
|
920
|
+
print(df["erv_efficiency_oa"].describe())
|
921
|
+
print("=" * 50)
|
922
|
+
|
923
|
+
sys.stdout.flush()
|
924
|
+
|
925
|
+
# Create the plot with four subplots
|
926
|
+
fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, 1, figsize=(25, 10))
|
927
|
+
fig.suptitle("Fault Conditions 16 Plot")
|
928
|
+
|
929
|
+
# Plot ERV Outdoor Air Side Temps
|
930
|
+
ax1.plot(df.index, df[self.erv_oat_enter_col], label="Enter", color="blue")
|
931
|
+
ax1.plot(df.index, df[self.erv_oat_leaving_col], label="Leaving", color="green")
|
932
|
+
ax1.legend(loc="best")
|
933
|
+
ax1.set_ylabel("ERV Outdoor Air Side Temps °F")
|
934
|
+
|
935
|
+
# Plot ERV Exhaust Air Side Temps
|
936
|
+
ax2.plot(df.index, df[self.erv_eat_enter_col], label="Enter", color="red")
|
937
|
+
ax2.plot(
|
938
|
+
df.index, df[self.erv_eat_leaving_col], label="Leaving", color="purple"
|
939
|
+
)
|
940
|
+
ax2.legend(loc="best")
|
941
|
+
ax2.set_ylabel("ERV Exhaust Air Side Temps °F")
|
942
|
+
|
943
|
+
# Plot ERV Efficiency
|
944
|
+
ax3.plot(
|
945
|
+
df.index, df["erv_efficiency_oa"], label="ERV Efficiency OA", color="b"
|
946
|
+
)
|
947
|
+
ax3.legend(loc="best")
|
948
|
+
ax3.set_ylabel("ERV Efficiency OA")
|
949
|
+
|
950
|
+
# Plot Fault Flags
|
951
|
+
ax4.plot(df.index, df[self.fault_col], label="Fault", color="k")
|
952
|
+
ax4.set_xlabel("Date")
|
953
|
+
ax4.set_ylabel("Fault Flags")
|
954
|
+
ax4.legend(loc="best")
|
955
|
+
|
956
|
+
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
|
957
|
+
plt.show()
|
958
|
+
plt.close()
|
959
|
+
|
960
|
+
def summarize_fault_times(self, df: pd.DataFrame) -> dict:
|
961
|
+
delta = df.index.to_series().diff()
|
962
|
+
summary = {
|
963
|
+
"total_days": round(delta.sum() / pd.Timedelta(days=1), 2),
|
964
|
+
"total_hours": round(delta.sum() / pd.Timedelta(hours=1)),
|
965
|
+
"hours_fc16_mode": round(
|
966
|
+
(delta * df[self.fault_col]).sum() / pd.Timedelta(hours=1)
|
967
|
+
),
|
968
|
+
"percent_true": round(df[self.fault_col].mean() * 100, 2),
|
969
|
+
"percent_false": round((100 - df[self.fault_col].mean() * 100), 2),
|
970
|
+
"flag_true_erv_oat_enter_temp": round(
|
971
|
+
df[self.erv_oat_enter_col].where(df[self.fault_col] == 1).mean(), 2
|
972
|
+
),
|
973
|
+
"flag_true_erv_oat_leave_temp": round(
|
974
|
+
df[self.erv_oat_leaving_col].where(df[self.fault_col] == 1).mean(), 2
|
975
|
+
),
|
976
|
+
"flag_true_erv_eat_enter_temp": round(
|
977
|
+
df[self.erv_eat_enter_col].where(df[self.fault_col] == 1).mean(), 2
|
978
|
+
),
|
979
|
+
"flag_true_erv_eat_leave_temp": round(
|
980
|
+
df[self.erv_eat_leaving_col].where(df[self.fault_col] == 1).mean(), 2
|
981
|
+
),
|
982
|
+
"hours_motor_runtime": round(
|
983
|
+
(delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum()
|
984
|
+
/ pd.Timedelta(hours=1),
|
985
|
+
2,
|
986
|
+
),
|
987
|
+
}
|
988
|
+
return summary
|
@@ -0,0 +1,190 @@
|
|
1
|
+
import pandas as pd
|
2
|
+
import pytest
|
3
|
+
from open_fdd.air_handling_unit.faults import FaultConditionSixteen
|
4
|
+
from open_fdd.air_handling_unit.faults.fault_condition import (
|
5
|
+
InvalidParameterError,
|
6
|
+
MissingColumnError,
|
7
|
+
)
|
8
|
+
|
9
|
+
"""
|
10
|
+
To see print statements in pytest run with:
|
11
|
+
$ py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc16.py -rP -s
|
12
|
+
|
13
|
+
ERV effectiveness should be within specified thresholds based on OAT.
|
14
|
+
"""
|
15
|
+
|
16
|
+
|
17
|
+
# Constants
|
18
|
+
TEST_ERV_EFFICIENCY_MIN_HEATING = 0.65
|
19
|
+
TEST_ERV_EFFICIENCY_MAX_HEATING = 0.8
|
20
|
+
TEST_ERV_EFFICIENCY_MIN_COOLING = 0.45
|
21
|
+
TEST_ERV_EFFICIENCY_MAX_COOLING = 0.6
|
22
|
+
TEST_OAT_LOW_THRESHOLD = 32.0
|
23
|
+
TEST_OAT_HIGH_THRESHOLD = 80.0
|
24
|
+
TEST_ERV_DEGF_ERR_THRES = 2.0
|
25
|
+
TEST_ERV_OAT_ENTER_COL = "erv_oat_enter"
|
26
|
+
TEST_ERV_OAT_LEAVING_COL = "erv_oat_leaving"
|
27
|
+
TEST_ERV_EAT_ENTER_COL = "erv_eat_enter"
|
28
|
+
TEST_ERV_EAT_LEAVING_COL = "erv_eat_leaving"
|
29
|
+
TEST_SUPPLY_VFD_SPEED_COL = "supply_vfd_speed"
|
30
|
+
ROLLING_WINDOW_SIZE = 1
|
31
|
+
|
32
|
+
# Initialize FaultConditionSixteen with a dictionary
|
33
|
+
fault_condition_params = {
|
34
|
+
"ERV_EFFICIENCY_MIN_HEATING": TEST_ERV_EFFICIENCY_MIN_HEATING,
|
35
|
+
"ERV_EFFICIENCY_MAX_HEATING": TEST_ERV_EFFICIENCY_MAX_HEATING,
|
36
|
+
"ERV_EFFICIENCY_MIN_COOLING": TEST_ERV_EFFICIENCY_MIN_COOLING,
|
37
|
+
"ERV_EFFICIENCY_MAX_COOLING": TEST_ERV_EFFICIENCY_MAX_COOLING,
|
38
|
+
"OAT_LOW_THRESHOLD": TEST_OAT_LOW_THRESHOLD,
|
39
|
+
"OAT_HIGH_THRESHOLD": TEST_OAT_HIGH_THRESHOLD,
|
40
|
+
"ERV_DEGF_ERR_THRES": TEST_ERV_DEGF_ERR_THRES,
|
41
|
+
"ERV_OAT_ENTER_COL": TEST_ERV_OAT_ENTER_COL,
|
42
|
+
"ERV_OAT_LEAVING_COL": TEST_ERV_OAT_LEAVING_COL,
|
43
|
+
"ERV_EAT_ENTER_COL": TEST_ERV_EAT_ENTER_COL,
|
44
|
+
"ERV_EAT_LEAVING_COL": TEST_ERV_EAT_LEAVING_COL,
|
45
|
+
"SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL,
|
46
|
+
"TROUBLESHOOT_MODE": False,
|
47
|
+
"ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE,
|
48
|
+
}
|
49
|
+
|
50
|
+
fc16 = FaultConditionSixteen(fault_condition_params)
|
51
|
+
|
52
|
+
|
53
|
+
class TestFaultConditionSixteen:
|
54
|
+
|
55
|
+
def no_fault_htg_df(self) -> pd.DataFrame:
|
56
|
+
data = {
|
57
|
+
TEST_ERV_OAT_ENTER_COL: [10, 10, 10, 10, 10, 10],
|
58
|
+
TEST_ERV_OAT_LEAVING_COL: [50.0, 50.5, 50.8, 50.6, 50.2, 50.4],
|
59
|
+
TEST_ERV_EAT_ENTER_COL: [70, 70, 70, 70, 70, 70],
|
60
|
+
TEST_ERV_EAT_LEAVING_COL: [60, 60.5, 60.2, 60.4, 60.1, 60.3],
|
61
|
+
TEST_SUPPLY_VFD_SPEED_COL: [0.5, 0.6, 0.5, 0.7, 0.5, 0.6],
|
62
|
+
}
|
63
|
+
return pd.DataFrame(data)
|
64
|
+
|
65
|
+
def fault_htg_df_low_eff(self) -> pd.DataFrame:
|
66
|
+
data = {
|
67
|
+
TEST_ERV_OAT_ENTER_COL: [10, 10, 10, 10, 10, 10],
|
68
|
+
TEST_ERV_OAT_LEAVING_COL: [20.0, 20.5, 20.8, 20.6, 20.2, 20.4],
|
69
|
+
TEST_ERV_EAT_ENTER_COL: [70, 70, 70, 70, 70, 70],
|
70
|
+
TEST_ERV_EAT_LEAVING_COL: [60, 60.5, 60.2, 60.4, 60.1, 60.3],
|
71
|
+
TEST_SUPPLY_VFD_SPEED_COL: [0.5, 0.6, 0.5, 0.7, 0.5, 0.6],
|
72
|
+
}
|
73
|
+
return pd.DataFrame(data)
|
74
|
+
|
75
|
+
def fault_htg_df_high_eff(self) -> pd.DataFrame:
|
76
|
+
data = {
|
77
|
+
TEST_ERV_OAT_ENTER_COL: [10, 10, 10, 10, 10, 10],
|
78
|
+
TEST_ERV_OAT_LEAVING_COL: [90.0, 90.5, 90.8, 90.6, 90.2, 90.4],
|
79
|
+
TEST_ERV_EAT_ENTER_COL: [70, 70, 70, 70, 70, 70],
|
80
|
+
TEST_ERV_EAT_LEAVING_COL: [60, 60.5, 60.2, 60.4, 60.1, 60.3],
|
81
|
+
TEST_SUPPLY_VFD_SPEED_COL: [0.5, 0.6, 0.5, 0.7, 0.5, 0.6],
|
82
|
+
}
|
83
|
+
return pd.DataFrame(data)
|
84
|
+
|
85
|
+
def test_no_fault_htg(self):
|
86
|
+
results = fc16.apply(self.no_fault_htg_df())
|
87
|
+
actual = results["fc16_flag"].sum()
|
88
|
+
expected = 0
|
89
|
+
message = f"FC16 no_fault_htg actual is {actual} and expected is {expected}"
|
90
|
+
assert actual == expected, message
|
91
|
+
|
92
|
+
def test_fault_htg_low_eff(self):
|
93
|
+
results = fc16.apply(self.fault_htg_df_low_eff())
|
94
|
+
actual = results["fc16_flag"].sum()
|
95
|
+
expected = 6
|
96
|
+
message = (
|
97
|
+
f"FC16 fault_htg_low_eff actual is {actual} and expected is {expected}"
|
98
|
+
)
|
99
|
+
assert actual == expected, message
|
100
|
+
|
101
|
+
def test_fault_htg_high_eff(self):
|
102
|
+
results = fc16.apply(self.fault_htg_df_high_eff())
|
103
|
+
actual = results["fc16_flag"].sum()
|
104
|
+
expected = 6
|
105
|
+
message = (
|
106
|
+
f"FC16 fault_htg_high_eff actual is {actual} and expected is {expected}"
|
107
|
+
)
|
108
|
+
assert actual == expected, message
|
109
|
+
|
110
|
+
|
111
|
+
class TestFaultOnInvalidParams:
|
112
|
+
|
113
|
+
def test_invalid_param_type(self):
|
114
|
+
"""Test that InvalidParameterError is raised for non-float parameters."""
|
115
|
+
with pytest.raises(InvalidParameterError) as excinfo:
|
116
|
+
FaultConditionSixteen(
|
117
|
+
{
|
118
|
+
"ERV_EFFICIENCY_MIN_HEATING": "0.65", # Invalid, should be float
|
119
|
+
"ERV_EFFICIENCY_MAX_HEATING": 0.8,
|
120
|
+
"ERV_EFFICIENCY_MIN_COOLING": 0.45,
|
121
|
+
"ERV_EFFICIENCY_MAX_COOLING": 0.6,
|
122
|
+
"OAT_LOW_THRESHOLD": 32.0,
|
123
|
+
"OAT_HIGH_THRESHOLD": 80.0,
|
124
|
+
"ERV_DEGF_ERR_THRES": 2.0,
|
125
|
+
"ERV_OAT_ENTER_COL": TEST_ERV_OAT_ENTER_COL,
|
126
|
+
"ERV_OAT_LEAVING_COL": TEST_ERV_OAT_LEAVING_COL,
|
127
|
+
"ERV_EAT_ENTER_COL": TEST_ERV_EAT_ENTER_COL,
|
128
|
+
"ERV_EAT_LEAVING_COL": TEST_ERV_EAT_LEAVING_COL,
|
129
|
+
"SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL,
|
130
|
+
}
|
131
|
+
)
|
132
|
+
assert "should be a float" in str(excinfo.value)
|
133
|
+
|
134
|
+
def test_invalid_efficiency_value(self):
|
135
|
+
"""Test that InvalidParameterError is raised if efficiency values are out of 0.0 - 1.0 range."""
|
136
|
+
with pytest.raises(InvalidParameterError) as excinfo:
|
137
|
+
FaultConditionSixteen(
|
138
|
+
{
|
139
|
+
"ERV_EFFICIENCY_MIN_HEATING": 75.0, # Invalid, should be between 0.0 and 1.0
|
140
|
+
"ERV_EFFICIENCY_MAX_HEATING": 0.8,
|
141
|
+
"ERV_EFFICIENCY_MIN_COOLING": 0.45,
|
142
|
+
"ERV_EFFICIENCY_MAX_COOLING": 0.6,
|
143
|
+
"OAT_LOW_THRESHOLD": 32.0,
|
144
|
+
"OAT_HIGH_THRESHOLD": 80.0,
|
145
|
+
"ERV_DEGF_ERR_THRES": 2.0,
|
146
|
+
"ERV_OAT_ENTER_COL": TEST_ERV_OAT_ENTER_COL,
|
147
|
+
"ERV_OAT_LEAVING_COL": TEST_ERV_OAT_LEAVING_COL,
|
148
|
+
"ERV_EAT_ENTER_COL": TEST_ERV_EAT_ENTER_COL,
|
149
|
+
"ERV_EAT_LEAVING_COL": TEST_ERV_EAT_LEAVING_COL,
|
150
|
+
"SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL,
|
151
|
+
}
|
152
|
+
)
|
153
|
+
assert "should be a float between 0.0 and 1.0" in str(excinfo.value)
|
154
|
+
|
155
|
+
|
156
|
+
class TestFaultOnMissingColumns:
|
157
|
+
|
158
|
+
def test_missing_column(self):
|
159
|
+
"""Test that MissingColumnError is raised if any required column is None or missing."""
|
160
|
+
with pytest.raises(MissingColumnError) as excinfo:
|
161
|
+
FaultConditionSixteen(
|
162
|
+
{
|
163
|
+
"ERV_EFFICIENCY_MIN_HEATING": 0.65,
|
164
|
+
"ERV_EFFICIENCY_MAX_HEATING": 0.8,
|
165
|
+
"ERV_EFFICIENCY_MIN_COOLING": 0.45,
|
166
|
+
"ERV_EFFICIENCY_MAX_COOLING": 0.6,
|
167
|
+
"OAT_LOW_THRESHOLD": 32.0,
|
168
|
+
"OAT_HIGH_THRESHOLD": 80.0,
|
169
|
+
"ERV_DEGF_ERR_THRES": 2.0,
|
170
|
+
"ERV_OAT_ENTER_COL": TEST_ERV_OAT_ENTER_COL,
|
171
|
+
"ERV_OAT_LEAVING_COL": None, # Missing column
|
172
|
+
"ERV_EAT_ENTER_COL": TEST_ERV_EAT_ENTER_COL,
|
173
|
+
"ERV_EAT_LEAVING_COL": TEST_ERV_EAT_LEAVING_COL,
|
174
|
+
"SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL,
|
175
|
+
}
|
176
|
+
).apply(
|
177
|
+
pd.DataFrame(
|
178
|
+
{
|
179
|
+
TEST_ERV_OAT_ENTER_COL: [10, 10, 10],
|
180
|
+
TEST_ERV_EAT_ENTER_COL: [70, 70, 70],
|
181
|
+
TEST_ERV_EAT_LEAVING_COL: [60, 60, 60],
|
182
|
+
TEST_SUPPLY_VFD_SPEED_COL: [0.5, 0.5, 0.5],
|
183
|
+
}
|
184
|
+
)
|
185
|
+
)
|
186
|
+
assert "One or more required columns are missing or None" in str(excinfo.value)
|
187
|
+
|
188
|
+
|
189
|
+
if __name__ == "__main__":
|
190
|
+
pytest.main()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: open_fdd
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.6
|
4
4
|
Summary: A package for fault detection and diagnosis in HVAC systems
|
5
5
|
Home-page: https://github.com/bbartling/open-fdd
|
6
6
|
Author: Ben Bartling
|
@@ -47,10 +47,10 @@ The following are key objectives to enhance this project into a fully interactiv
|
|
47
47
|
|
48
48
|
### In Progress
|
49
49
|
- [ ] Create IPython notebook tutorials showcasing AHU FDD examples, incorporating BRICK metadata integration.
|
50
|
-
- [ ]
|
50
|
+
- [ ] Extend the project to include `central_plant` fault conditions, IPython reports, and example applications for boiler and chiller systems.
|
51
|
+
|
51
52
|
|
52
53
|
### Upcoming
|
53
|
-
- [ ] Extend the project to include `central_plant` fault conditions, IPython reports, and example applications.
|
54
54
|
- [ ] Design `energy_efficiency` fault detection modules, including IPython reports and examples focused on optimizing energy consumption.
|
55
55
|
- [ ] Develop `metering` fault conditions, along with IPython reports and examples, potentially modeling utility metering data.
|
56
56
|
- [ ] Implement SQL integration examples for reading data from a time series database, writing back to SQL, and visualizing faults in Grafana.
|
@@ -58,6 +58,7 @@ The following are key objectives to enhance this project into a fully interactiv
|
|
58
58
|
### Future Considerations
|
59
59
|
Explore additional features and enhancements as the project evolves.
|
60
60
|
- [ ] Explore additional features and enhancements as the project evolves.
|
61
|
+
- [ ] Develop a comprehensive guide on a github.io website (or other?) for defining fault parameters, including error thresholds and other critical settings.
|
61
62
|
|
62
63
|
|
63
64
|
## Contribute
|
@@ -0,0 +1,31 @@
|
|
1
|
+
open_fdd/__init__.py,sha256=iGj8QTOZJUTE4nNnBiCHXEXsOdV6YvKcGiLrnOusJCg,1411
|
2
|
+
open_fdd/air_handling_unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
open_fdd/air_handling_unit/faults/__init__.py,sha256=NBdGNlp0MTUPNB-43zgnYs3iYqFhEeNa3q_VgDPGAoo,91603
|
4
|
+
open_fdd/air_handling_unit/faults/fault_condition.py,sha256=UN2_k2AZWMDoO4oPAnJW3fvhkHlMkLXZikzU2lXAOuQ,2364
|
5
|
+
open_fdd/air_handling_unit/faults/helper_utils.py,sha256=-Bd1mMMLnFmpNPe7eC23kMJxPtazsIyz5P7W6Yqlu4w,12580
|
6
|
+
open_fdd/air_handling_unit/faults/shared_utils.py,sha256=Kp8ZUBhtrh-jU2Q_bbXTpnbVVUAyacp41tDMknY50i4,2252
|
7
|
+
open_fdd/air_handling_unit/reports/__init__.py,sha256=iXIqEaaO_8yRnlVUuYMcc_L0xQf3iqSYi3ykIzeGBf8,40124
|
8
|
+
open_fdd/air_handling_unit/reports/fault_report.py,sha256=QxYLJzoLTwf1N0nls2XMmhHJvBSgGCBNT0KxA59QG4Y,1442
|
9
|
+
open_fdd/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
+
open_fdd/tests/ahu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
+
open_fdd/tests/ahu/test_ahu_fc1.py,sha256=ojpdYGZtuIYAKnZ4W9KhxQuoyXnGEI5N7braQXh3kAw,5437
|
12
|
+
open_fdd/tests/ahu/test_ahu_fc10.py,sha256=niYL7fi6OlgP0wnF8hNh9A07PLzHiRZkyRkrA1zoL2s,4577
|
13
|
+
open_fdd/tests/ahu/test_ahu_fc11.py,sha256=mdXlGiEMPkPfshf3NN_nJavL74e4HCmkJQMu86aZc6Q,4723
|
14
|
+
open_fdd/tests/ahu/test_ahu_fc12.py,sha256=5T-XcM6xm9KHrc121uPGC9JWLCYehrAYk0KcbmGgYjw,5848
|
15
|
+
open_fdd/tests/ahu/test_ahu_fc13.py,sha256=vJlSy4e2WV9hx02P0SiJ75I1DWL2lZ0p7-AWlw97pks,5725
|
16
|
+
open_fdd/tests/ahu/test_ahu_fc14.py,sha256=MU0LKqIuoQ_dJ0Kij8_A0YyimCMvUwL6IlMwpQhDbqI,8052
|
17
|
+
open_fdd/tests/ahu/test_ahu_fc15.py,sha256=SIolJ9vnJwnpKfRW2ALugYWySuIiZ9_repWP9fdb5H4,7661
|
18
|
+
open_fdd/tests/ahu/test_ahu_fc16.py,sha256=3xhNfN2pKon-2_jyCUSh_JEKSv9jYGiTxRjLOL7uvVI,8052
|
19
|
+
open_fdd/tests/ahu/test_ahu_fc2.py,sha256=CjmO_WUyaSHs17ifCCew3GBJ43nYG55uGL0vHDZpAq8,4736
|
20
|
+
open_fdd/tests/ahu/test_ahu_fc3.py,sha256=NB6pOXDS-R4P0LNoRN8ItAqhhLnGnGuAHZha32Qw-hE,4658
|
21
|
+
open_fdd/tests/ahu/test_ahu_fc4.py,sha256=vV8jEnFuNGLfhCoTVz29RsIcoDpDOMWg722G0aBEXaE,6304
|
22
|
+
open_fdd/tests/ahu/test_ahu_fc5.py,sha256=TpSQTBIF591Q3mVaBeJ6HyqPWhVHmD_gSZNEIFT6yyg,6538
|
23
|
+
open_fdd/tests/ahu/test_ahu_fc6.py,sha256=66dwv0EBU_ujZK-J9Ki5a3fnXlk17nOwmtKDiQOHdbM,10351
|
24
|
+
open_fdd/tests/ahu/test_ahu_fc7.py,sha256=sABbw2m7WlAXbsqfDD323vfEfg606ThI0QzQyB-OjFo,2469
|
25
|
+
open_fdd/tests/ahu/test_ahu_fc8.py,sha256=UZy6BP2PgV1FROUPqMORTx8YnT5ZvqVDhut_Ar81494,4663
|
26
|
+
open_fdd/tests/ahu/test_ahu_fc9.py,sha256=b-eIzhNzjZUjVNsP0JAHkOgZu-BtDuPeNnblVVm-jU8,4796
|
27
|
+
open_fdd-0.1.6.dist-info/LICENSE,sha256=eghao_GGx_0gB2Sll3x2vV29knONEzUQKrkaXpX1F7w,1087
|
28
|
+
open_fdd-0.1.6.dist-info/METADATA,sha256=IER_eG7nwqbDIoDA2a9LyFgh20pOeBYhAGHlhT1PFLo,6958
|
29
|
+
open_fdd-0.1.6.dist-info/WHEEL,sha256=UvcQYKBHoFqaQd6LKyqHw9fxEolWLQnlzP0h_LgJAfI,91
|
30
|
+
open_fdd-0.1.6.dist-info/top_level.txt,sha256=Q7sB6UB2d8Ch1v_xIsTiNegmgcCXPkwkrxK3ug6VEOs,9
|
31
|
+
open_fdd-0.1.6.dist-info/RECORD,,
|
@@ -1,127 +0,0 @@
|
|
1
|
-
import pandas as pd
|
2
|
-
import numpy as np
|
3
|
-
from open_fdd.air_handling_unit.faults.fault_condition import (
|
4
|
-
FaultCondition,
|
5
|
-
MissingColumnError,
|
6
|
-
)
|
7
|
-
import sys
|
8
|
-
|
9
|
-
|
10
|
-
class FaultConditionEight(FaultCondition):
|
11
|
-
"""Class provides the definitions for Fault Condition 8.
|
12
|
-
Supply air temperature and mix air temperature should
|
13
|
-
be approx equal in economizer mode.
|
14
|
-
"""
|
15
|
-
|
16
|
-
def __init__(self, dict_):
|
17
|
-
super().__init__()
|
18
|
-
self.delta_t_supply_fan = float
|
19
|
-
self.mix_degf_err_thres = float
|
20
|
-
self.supply_degf_err_thres = float
|
21
|
-
self.ahu_min_oa_dpr = float
|
22
|
-
self.mat_col = str
|
23
|
-
self.sat_col = str
|
24
|
-
self.economizer_sig_col = str
|
25
|
-
self.cooling_sig_col = str
|
26
|
-
self.troubleshoot_mode = bool # default should be False
|
27
|
-
self.rolling_window_size = int
|
28
|
-
|
29
|
-
self.equation_string = (
|
30
|
-
"fc8_flag = 1 if |SAT - MAT - ΔT_fan| > √(εSAT² + εMAT²) "
|
31
|
-
"in economizer mode for N consecutive values else 0 \n"
|
32
|
-
)
|
33
|
-
self.description_string = (
|
34
|
-
"Fault Condition 8: Supply air temperature and mixed air temperature should "
|
35
|
-
"be approximately equal in economizer mode \n"
|
36
|
-
)
|
37
|
-
self.required_column_description = (
|
38
|
-
"Required inputs are the mixed air temperature, supply air temperature, "
|
39
|
-
"economizer signal, and cooling signal \n"
|
40
|
-
)
|
41
|
-
self.error_string = f"One or more required columns are missing or None \n"
|
42
|
-
|
43
|
-
self.set_attributes(dict_)
|
44
|
-
|
45
|
-
# Set required columns specific to this fault condition
|
46
|
-
self.required_columns = [
|
47
|
-
self.mat_col,
|
48
|
-
self.sat_col,
|
49
|
-
self.economizer_sig_col,
|
50
|
-
self.cooling_sig_col,
|
51
|
-
]
|
52
|
-
|
53
|
-
# Check if any of the required columns are None
|
54
|
-
if any(col is None for col in self.required_columns):
|
55
|
-
raise MissingColumnError(
|
56
|
-
f"{self.error_string}"
|
57
|
-
f"{self.equation_string}"
|
58
|
-
f"{self.description_string}"
|
59
|
-
f"{self.required_column_description}"
|
60
|
-
f"{self.required_columns}"
|
61
|
-
)
|
62
|
-
|
63
|
-
# Ensure all required columns are strings
|
64
|
-
self.required_columns = [str(col) for col in self.required_columns]
|
65
|
-
|
66
|
-
self.mapped_columns = (
|
67
|
-
f"Your config dictionary is mapped as: {', '.join(self.required_columns)}"
|
68
|
-
)
|
69
|
-
|
70
|
-
def get_required_columns(self) -> str:
|
71
|
-
"""Returns a string representation of the required columns."""
|
72
|
-
return (
|
73
|
-
f"{self.equation_string}"
|
74
|
-
f"{self.description_string}"
|
75
|
-
f"{self.required_column_description}"
|
76
|
-
f"{self.mapped_columns}"
|
77
|
-
)
|
78
|
-
|
79
|
-
def apply(self, df: pd.DataFrame) -> pd.DataFrame:
|
80
|
-
try:
|
81
|
-
# Ensure all required columns are present
|
82
|
-
self.check_required_columns(df)
|
83
|
-
|
84
|
-
if self.troubleshoot_mode:
|
85
|
-
self.troubleshoot_cols(df)
|
86
|
-
|
87
|
-
# Check analog outputs [data with units of %] are floats only
|
88
|
-
columns_to_check = [
|
89
|
-
self.economizer_sig_col,
|
90
|
-
self.cooling_sig_col,
|
91
|
-
]
|
92
|
-
|
93
|
-
self.check_analog_pct(df, columns_to_check)
|
94
|
-
|
95
|
-
df["sat_fan_mat"] = abs(
|
96
|
-
df[self.sat_col] - self.delta_t_supply_fan - df[self.mat_col]
|
97
|
-
)
|
98
|
-
df["sat_mat_sqrted"] = np.sqrt(
|
99
|
-
self.supply_degf_err_thres**2 + self.mix_degf_err_thres**2
|
100
|
-
)
|
101
|
-
|
102
|
-
df["combined_check"] = (
|
103
|
-
(df["sat_fan_mat"] > df["sat_mat_sqrted"])
|
104
|
-
& (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
|
105
|
-
& (df[self.cooling_sig_col] < 0.1)
|
106
|
-
)
|
107
|
-
|
108
|
-
# Rolling sum to count consecutive trues
|
109
|
-
rolling_sum = (
|
110
|
-
df["combined_check"].rolling(window=self.rolling_window_size).sum()
|
111
|
-
)
|
112
|
-
# Set flag to 1 if rolling sum equals the window size
|
113
|
-
df["fc8_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
|
114
|
-
|
115
|
-
if self.troubleshoot_mode:
|
116
|
-
print("Troubleshoot mode enabled - not removing helper columns")
|
117
|
-
sys.stdout.flush()
|
118
|
-
del df["sat_fan_mat"]
|
119
|
-
del df["sat_mat_sqrted"]
|
120
|
-
del df["combined_check"]
|
121
|
-
|
122
|
-
return df
|
123
|
-
|
124
|
-
except MissingColumnError as e:
|
125
|
-
print(f"Error: {e.message}")
|
126
|
-
sys.stdout.flush()
|
127
|
-
raise e
|
@@ -1,126 +0,0 @@
|
|
1
|
-
import pandas as pd
|
2
|
-
from open_fdd.air_handling_unit.faults.fault_condition import (
|
3
|
-
FaultCondition,
|
4
|
-
MissingColumnError,
|
5
|
-
)
|
6
|
-
import sys
|
7
|
-
|
8
|
-
|
9
|
-
class FaultConditionEleven(FaultCondition):
|
10
|
-
"""Class provides the definitions for Fault Condition 11.
|
11
|
-
Outside air temperature too low for 100% outdoor
|
12
|
-
air cooling in economizer cooling mode.
|
13
|
-
Economizer performance fault
|
14
|
-
"""
|
15
|
-
|
16
|
-
def __init__(self, dict_):
|
17
|
-
super().__init__()
|
18
|
-
self.delta_t_supply_fan = float
|
19
|
-
self.outdoor_degf_err_thres = float
|
20
|
-
self.supply_degf_err_thres = float
|
21
|
-
self.sat_setpoint_col = str
|
22
|
-
self.oat_col = str
|
23
|
-
self.cooling_sig_col = str
|
24
|
-
self.economizer_sig_col = str
|
25
|
-
self.troubleshoot_mode = bool # default False
|
26
|
-
self.rolling_window_size = int
|
27
|
-
|
28
|
-
self.equation_string = (
|
29
|
-
"fc11_flag = 1 if OAT < (SATSP - ΔT_fan - εSAT) in "
|
30
|
-
"economizer cooling mode for N consecutive values else 0 \n"
|
31
|
-
)
|
32
|
-
self.description_string = (
|
33
|
-
"Fault Condition 11: Outside air temperature too low for 100% outdoor air cooling "
|
34
|
-
"in economizer cooling mode (Economizer performance fault) \n"
|
35
|
-
)
|
36
|
-
self.required_column_description = (
|
37
|
-
"Required inputs are the supply air temperature setpoint, outside air temperature, "
|
38
|
-
"cooling signal, and economizer signal \n"
|
39
|
-
)
|
40
|
-
self.error_string = f"One or more required columns are missing or None \n"
|
41
|
-
|
42
|
-
self.set_attributes(dict_)
|
43
|
-
|
44
|
-
# Set required columns specific to this fault condition
|
45
|
-
self.required_columns = [
|
46
|
-
self.sat_setpoint_col,
|
47
|
-
self.oat_col,
|
48
|
-
self.cooling_sig_col,
|
49
|
-
self.economizer_sig_col,
|
50
|
-
]
|
51
|
-
|
52
|
-
# Check if any of the required columns are None
|
53
|
-
if any(col is None for col in self.required_columns):
|
54
|
-
raise MissingColumnError(
|
55
|
-
f"{self.error_string}"
|
56
|
-
f"{self.equation_string}"
|
57
|
-
f"{self.description_string}"
|
58
|
-
f"{self.required_column_description}"
|
59
|
-
f"{self.required_columns}"
|
60
|
-
)
|
61
|
-
|
62
|
-
# Ensure all required columns are strings
|
63
|
-
self.required_columns = [str(col) for col in self.required_columns]
|
64
|
-
|
65
|
-
self.mapped_columns = (
|
66
|
-
f"Your config dictionary is mapped as: {', '.join(self.required_columns)}"
|
67
|
-
)
|
68
|
-
|
69
|
-
def get_required_columns(self) -> str:
|
70
|
-
"""Returns a string representation of the required columns."""
|
71
|
-
return (
|
72
|
-
f"{self.equation_string}"
|
73
|
-
f"{self.description_string}"
|
74
|
-
f"{self.required_column_description}"
|
75
|
-
f"{self.mapped_columns}"
|
76
|
-
)
|
77
|
-
|
78
|
-
def apply(self, df: pd.DataFrame) -> pd.DataFrame:
|
79
|
-
try:
|
80
|
-
# Ensure all required columns are present
|
81
|
-
self.check_required_columns(df)
|
82
|
-
|
83
|
-
if self.troubleshoot_mode:
|
84
|
-
self.troubleshoot_cols(df)
|
85
|
-
|
86
|
-
# Check analog outputs [data with units of %] are floats only
|
87
|
-
columns_to_check = [
|
88
|
-
self.economizer_sig_col,
|
89
|
-
self.cooling_sig_col,
|
90
|
-
]
|
91
|
-
self.check_analog_pct(df, columns_to_check)
|
92
|
-
|
93
|
-
df["oat_plus_oaterror"] = df[self.oat_col] + self.outdoor_degf_err_thres
|
94
|
-
df["satsp_delta_saterr"] = (
|
95
|
-
df[self.sat_setpoint_col]
|
96
|
-
- self.delta_t_supply_fan
|
97
|
-
- self.supply_degf_err_thres
|
98
|
-
)
|
99
|
-
|
100
|
-
df["combined_check"] = (
|
101
|
-
(df["oat_plus_oaterror"] < df["satsp_delta_saterr"])
|
102
|
-
# verify ahu is running in OS 3 clg mode in 100 OA
|
103
|
-
& (df[self.cooling_sig_col] > 0.01)
|
104
|
-
& (df[self.economizer_sig_col] > 0.9)
|
105
|
-
)
|
106
|
-
|
107
|
-
# Rolling sum to count consecutive trues
|
108
|
-
rolling_sum = (
|
109
|
-
df["combined_check"].rolling(window=self.rolling_window_size).sum()
|
110
|
-
)
|
111
|
-
# Set flag to 1 if rolling sum equals the window size
|
112
|
-
df["fc11_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
|
113
|
-
|
114
|
-
if self.troubleshoot_mode:
|
115
|
-
print("Troubleshoot mode enabled - not removing helper columns")
|
116
|
-
sys.stdout.flush()
|
117
|
-
del df["oat_plus_oaterror"]
|
118
|
-
del df["satsp_delta_saterr"]
|
119
|
-
del df["combined_check"]
|
120
|
-
|
121
|
-
return df
|
122
|
-
|
123
|
-
except MissingColumnError as e:
|
124
|
-
print(f"Error: {e.message}")
|
125
|
-
sys.stdout.flush()
|
126
|
-
raise e
|