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,191 @@
1
+ import sys
2
+ from open_fdd.air_handling_unit.faults.shared_utils import SharedUtils
3
+ import pandas as pd
4
+
5
+
6
+ class HelperUtils:
7
+ def float_int_check_err(self, col):
8
+ return SharedUtils.float_int_check_err(col)
9
+
10
+ def float_max_check_err(self, col):
11
+ return SharedUtils.float_max_check_err(col)
12
+
13
+ def isfloat(self, num):
14
+ return SharedUtils.isfloat(num)
15
+
16
+ def isLessThanOnePointOne(self, num):
17
+ return SharedUtils.isLessThanOnePointOne(num)
18
+
19
+ def convert_to_float(self, df, col):
20
+ return SharedUtils.convert_to_float(df, col)
21
+
22
+ def apply_rolling_average_if_needed(self, df, freq="1min", rolling_window="5min"):
23
+ return SharedUtils.apply_rolling_average_if_needed(df, freq, rolling_window)
24
+
25
+ def process_all_faults(self, df, config_dict):
26
+ # Import fault conditions
27
+ from open_fdd.air_handling_unit.faults.fault_condition_one import (
28
+ FaultConditionOne,
29
+ )
30
+ from open_fdd.air_handling_unit.faults.fault_condition_two import (
31
+ FaultConditionTwo,
32
+ )
33
+ from open_fdd.air_handling_unit.faults.fault_condition_three import (
34
+ FaultConditionThree,
35
+ )
36
+ from open_fdd.air_handling_unit.faults.fault_condition_four import (
37
+ FaultConditionFour,
38
+ )
39
+ from open_fdd.air_handling_unit.faults.fault_condition_five import (
40
+ FaultConditionFive,
41
+ )
42
+ from open_fdd.air_handling_unit.faults.fault_condition_six import (
43
+ FaultConditionSix,
44
+ )
45
+ from open_fdd.air_handling_unit.faults.fault_condition_seven import (
46
+ FaultConditionSeven,
47
+ )
48
+ from open_fdd.air_handling_unit.faults.fault_condition_eight import (
49
+ FaultConditionEight,
50
+ )
51
+ from open_fdd.air_handling_unit.faults.fault_condition_nine import (
52
+ FaultConditionNine,
53
+ )
54
+ from open_fdd.air_handling_unit.faults.fault_condition_ten import (
55
+ FaultConditionTen,
56
+ )
57
+ from open_fdd.air_handling_unit.faults.fault_condition_eleven import (
58
+ FaultConditionEleven,
59
+ )
60
+ from open_fdd.air_handling_unit.faults.fault_condition_twelve import (
61
+ FaultConditionTwelve,
62
+ )
63
+ from open_fdd.air_handling_unit.faults.fault_condition_thirteen import (
64
+ FaultConditionThirteen,
65
+ )
66
+ from open_fdd.air_handling_unit.faults.fault_condition_fourteen import (
67
+ FaultConditionFourteen,
68
+ )
69
+ from open_fdd.air_handling_unit.faults.fault_condition_fifteen import (
70
+ FaultConditionFifteen,
71
+ )
72
+
73
+ fault_counts = {}
74
+
75
+ # Apply rolling average if needed
76
+ df = self.apply_rolling_average_if_needed(df)
77
+
78
+ # Initialize Fault Condition Classes
79
+ fc1 = FaultConditionOne(config_dict)
80
+ fc2 = FaultConditionTwo(config_dict)
81
+ fc3 = FaultConditionThree(config_dict)
82
+ fc4 = FaultConditionFour(config_dict)
83
+ fc5 = FaultConditionFive(config_dict)
84
+ fc7 = FaultConditionSeven(config_dict)
85
+ fc8 = FaultConditionEight(config_dict)
86
+ fc9 = FaultConditionNine(config_dict)
87
+ fc10 = FaultConditionTen(config_dict)
88
+ fc11 = FaultConditionEleven(config_dict)
89
+ fc12 = FaultConditionTwelve(config_dict)
90
+ fc13 = FaultConditionThirteen(config_dict)
91
+
92
+ # Optionally initialize Fault Condition Six
93
+ fc6 = None
94
+ if config_dict.get("SUPPLY_FAN_AIR_VOLUME_COL") is not None:
95
+ fc6 = FaultConditionSix(config_dict)
96
+
97
+ # Optionally initialize Fault Condition Fourteen
98
+ fc14 = None
99
+ if (
100
+ config_dict.get("COOLING_SIG_COL") is not None
101
+ and config_dict.get("CLG_COIL_LEAVE_TEMP_COL") is not None
102
+ ):
103
+ fc14 = FaultConditionFourteen(config_dict)
104
+
105
+ # Optionally initialize Fault Condition Fifteen
106
+ fc15 = None
107
+ if (
108
+ config_dict.get("HTG_COIL_ENTER_TEMP_COL") is not None
109
+ and config_dict.get("HTG_COIL_LEAVE_TEMP_COL") is not None
110
+ ):
111
+ fc15 = FaultConditionFifteen(config_dict)
112
+
113
+ # Apply fault conditions and calculate fault counts
114
+ df_fc1 = fc1.apply(df.copy())
115
+ fault_counts["fc1_fault_sum"] = df_fc1["fc1_flag"].sum()
116
+
117
+ df_fc2 = fc2.apply(df.copy())
118
+ fault_counts["fc2_fault_sum"] = df_fc2["fc2_flag"].sum()
119
+
120
+ df_fc3 = fc3.apply(df.copy())
121
+ fault_counts["fc3_fault_sum"] = df_fc3["fc3_flag"].sum()
122
+
123
+ df_fc4 = fc4.apply(df.copy())
124
+ fault_counts["fc4_fault_sum"] = df_fc4["fc4_flag"].sum()
125
+
126
+ df_fc5 = fc5.apply(df.copy())
127
+ fault_counts["fc5_fault_sum"] = df_fc5["fc5_flag"].sum()
128
+
129
+ if fc6 is not None:
130
+ df_fc6 = fc6.apply(df.copy())
131
+ fault_counts["fc6_fault_sum"] = df_fc6["fc6_flag"].sum()
132
+
133
+ df_fc7 = fc7.apply(df.copy())
134
+ fault_counts["fc7_fault_sum"] = df_fc7["fc7_flag"].sum()
135
+
136
+ df_fc8 = fc8.apply(df.copy())
137
+ fault_counts["fc8_fault_sum"] = df_fc8["fc8_flag"].sum()
138
+
139
+ df_fc9 = fc9.apply(df.copy())
140
+ fault_counts["fc9_fault_sum"] = df_fc9["fc9_flag"].sum()
141
+
142
+ df_fc10 = fc10.apply(df.copy())
143
+ fault_counts["fc10_fault_sum"] = df_fc10["fc10_flag"].sum()
144
+
145
+ df_fc11 = fc11.apply(df.copy())
146
+ fault_counts["fc11_fault_sum"] = df_fc11["fc11_flag"].sum()
147
+
148
+ df_fc12 = fc12.apply(df.copy())
149
+ fault_counts["fc12_fault_sum"] = df_fc12["fc12_flag"].sum()
150
+
151
+ df_fc13 = fc13.apply(df.copy())
152
+ fault_counts["fc13_fault_sum"] = df_fc13["fc13_flag"].sum()
153
+
154
+ if fc14 is not None:
155
+ df_fc14 = fc14.apply(df.copy())
156
+ fault_counts["fc14_fault_sum"] = df_fc14["fc14_flag"].sum()
157
+
158
+ if fc15 is not None:
159
+ df_fc15 = fc15.apply(df.copy())
160
+ fault_counts["fc15_fault_sum"] = df_fc15["fc15_flag"].sum()
161
+
162
+ # Combine fault condition results
163
+ df_combined = df_fc1.copy()
164
+ df_combined["fc2_flag"] = df_fc2["fc2_flag"]
165
+ df_combined["fc3_flag"] = df_fc3["fc3_flag"]
166
+ df_combined["fc4_flag"] = df_fc4["fc4_flag"]
167
+ df_combined["fc5_flag"] = df_fc5["fc5_flag"]
168
+
169
+ if fc6 is not None:
170
+ df_combined["fc6_flag"] = df_fc6["fc6_flag"]
171
+
172
+ df_combined["fc7_flag"] = df_fc7["fc7_flag"]
173
+ df_combined["fc8_flag"] = df_fc8["fc8_flag"]
174
+ df_combined["fc9_flag"] = df_fc9["fc9_flag"]
175
+ df_combined["fc10_flag"] = df_fc10["fc10_flag"]
176
+ df_combined["fc11_flag"] = df_fc11["fc11_flag"]
177
+ df_combined["fc12_flag"] = df_fc12["fc12_flag"]
178
+ df_combined["fc13_flag"] = df_fc13["fc13_flag"]
179
+
180
+ if fc14 is not None:
181
+ df_combined["fc14_flag"] = df_fc14["fc14_flag"]
182
+
183
+ if fc15 is not None:
184
+ df_combined["fc15_flag"] = df_fc15["fc15_flag"]
185
+
186
+ # Save fault counts to CSV
187
+ fault_counts_df = pd.DataFrame(
188
+ list(fault_counts.items()), columns=["Fault Condition", "Count"]
189
+ )
190
+
191
+ return df_combined, df_fc4, fault_counts_df
@@ -0,0 +1,75 @@
1
+ import pandas as pd
2
+ import pandas.api.types as pdtypes
3
+ import sys
4
+
5
+
6
+ class SharedUtils:
7
+ @staticmethod
8
+ def float_int_check_err(col):
9
+ err_str = " column failed with a check that the data is a float"
10
+ return str(col) + err_str
11
+
12
+ @staticmethod
13
+ def float_max_check_err(col):
14
+ err_str = (
15
+ " column failed with a check that the data is a float between 0.0 and 1.0"
16
+ )
17
+ return str(col) + err_str
18
+
19
+ @staticmethod
20
+ def isfloat(num):
21
+ try:
22
+ float(num)
23
+ return True
24
+ except:
25
+ return False
26
+
27
+ @staticmethod
28
+ def isLessThanOnePointOne(num):
29
+ try:
30
+ if num <= 1.0:
31
+ return True
32
+ except:
33
+ return False
34
+
35
+ @staticmethod
36
+ def convert_to_float(df, col):
37
+ if not pdtypes.is_float_dtype(df[col]):
38
+ try:
39
+ df[col] = df[col].astype(float)
40
+ except ValueError:
41
+ raise TypeError(SharedUtils.float_int_check_err(col))
42
+ return df
43
+
44
+ @staticmethod
45
+ def apply_rolling_average_if_needed(df, freq="1min", rolling_window="5min"):
46
+ """Apply rolling average if time difference between consecutive
47
+ timestamps is not greater than the specified frequency.
48
+ """
49
+
50
+ print(
51
+ "Warning: If data has a one minute or less sampling frequency a rolling average will be automatically applied"
52
+ )
53
+ sys.stdout.flush()
54
+
55
+ time_diff = df.index.to_series().diff().iloc[1:]
56
+
57
+ # Calculate median time difference to avoid being affected by outliers
58
+ median_diff = time_diff.median()
59
+
60
+ print(
61
+ f"Warning: Median time difference between consecutive timestamps is {median_diff}."
62
+ )
63
+ sys.stdout.flush()
64
+
65
+ if median_diff > pd.Timedelta(freq):
66
+ print(f"Warning: Skipping any rolling averaging...")
67
+ sys.stdout.flush()
68
+
69
+ else:
70
+ df = df.rolling(rolling_window).mean()
71
+ print(
72
+ f"Warning: A {rolling_window} rolling average has been applied to the data."
73
+ )
74
+ sys.stdout.flush()
75
+ return df
File without changes
@@ -0,0 +1,115 @@
1
+ import matplotlib.pyplot as plt
2
+ import pandas as pd
3
+ import sys
4
+
5
+
6
+ class FaultCodeOneReport:
7
+ def __init__(self, config):
8
+ self.vfd_speed_percent_err_thres = config["VFD_SPEED_PERCENT_ERR_THRES"]
9
+ self.duct_static_col = config["DUCT_STATIC_COL"]
10
+ self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"]
11
+ self.duct_static_setpoint_col = config["DUCT_STATIC_SETPOINT_COL"]
12
+
13
+ def create_plot(self, df: pd.DataFrame, output_col: str = None):
14
+ if output_col is None:
15
+ output_col = "fc1_flag"
16
+
17
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(25, 8))
18
+ fig.suptitle("Fault Conditions 1 Plot")
19
+
20
+ ax1.plot(df.index, df[self.duct_static_col], label="STATIC")
21
+ ax1.legend(loc="best")
22
+ ax1.set_ylabel("Inch WC")
23
+
24
+ ax2.plot(df.index, df[self.supply_vfd_speed_col], color="g", label="FAN")
25
+ ax2.legend(loc="best")
26
+ ax2.set_ylabel("%")
27
+
28
+ ax3.plot(df.index, df[output_col], label="Fault", color="k")
29
+ ax3.set_xlabel("Date")
30
+ ax3.set_ylabel("Fault Flags")
31
+ ax3.legend(loc="best")
32
+
33
+ plt.tight_layout(rect=[0, 0.03, 1, 0.95])
34
+ plt.show()
35
+ plt.close()
36
+
37
+ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dict:
38
+ if output_col is None:
39
+ output_col = "fc1_flag"
40
+
41
+ delta = df.index.to_series().diff()
42
+ summary = {
43
+ "total_days": round(delta.sum() / pd.Timedelta(days=1), 2),
44
+ "total_hours": round(delta.sum() / pd.Timedelta(hours=1)),
45
+ "hours_fc1_mode": round(
46
+ (delta * df[output_col]).sum() / pd.Timedelta(hours=1)
47
+ ),
48
+ "percent_true": round(df[output_col].mean() * 100, 2),
49
+ "percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2),
50
+ "flag_true_duct_static": round(
51
+ df[self.duct_static_col].where(df[output_col] == 1).mean(), 2
52
+ ),
53
+ "flag_true_duct_static_spt": round(
54
+ df[self.duct_static_setpoint_col].where(df[output_col] == 1).mean(), 2
55
+ ),
56
+ "hours_motor_runtime": round(
57
+ (delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum()
58
+ / pd.Timedelta(hours=1),
59
+ 2,
60
+ ),
61
+ }
62
+
63
+ return summary
64
+
65
+ def create_hist_plot(self, df: pd.DataFrame, output_col: str = None):
66
+ if output_col is None:
67
+ output_col = "fc1_flag"
68
+
69
+ df["hour_of_the_day_fc1"] = df.index.hour.where(df[output_col] == 1)
70
+
71
+ fig, ax = plt.subplots(tight_layout=True, figsize=(25, 8))
72
+ ax.hist(df.hour_of_the_day_fc1.dropna())
73
+ ax.set_xlabel("24 Hour Number in Day")
74
+ ax.set_ylabel("Frequency")
75
+ ax.set_title(f"Hour-Of-Day When Fault Flag 1 is TRUE")
76
+ plt.show()
77
+ plt.close()
78
+
79
+ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc1_flag"):
80
+ print("Fault Condition 1: Duct static too low at fan at full speed")
81
+
82
+ self.create_plot(df, output_col)
83
+
84
+ summary = self.summarize_fault_times(df, output_col)
85
+
86
+ for key, value in summary.items():
87
+ formatted_key = key.replace("_", " ")
88
+ print(f"{formatted_key}: {value}")
89
+ sys.stdout.flush()
90
+
91
+ fc_max_faults_found = df[output_col].max()
92
+ print("Fault Flag Count: ", fc_max_faults_found)
93
+ sys.stdout.flush()
94
+
95
+ if fc_max_faults_found != 0:
96
+ self.create_hist_plot(df, output_col)
97
+
98
+ print("Duct Static Mean When In Fault: ", summary["flag_true_duct_static"])
99
+ print(
100
+ "Duct Static Setpoint Mean When In Fault: ",
101
+ summary["flag_true_duct_static_spt"],
102
+ )
103
+
104
+ if summary["percent_true"] > 5.0:
105
+ print(
106
+ "The percent True metric that represents the amount of time for when the fault flag is True is high, indicating potential duct issues. Verify system calibration and investigate possible mechanical problems."
107
+ )
108
+ else:
109
+ print(
110
+ "The percent True metric that represents the amount of time for when the fault flag is True is low, indicating the system is likely functioning correctly."
111
+ )
112
+
113
+ else:
114
+ print("NO FAULTS FOUND - Skipping time-of-day Histogram plot")
115
+ sys.stdout.flush()
@@ -0,0 +1,126 @@
1
+ import matplotlib.pyplot as plt
2
+ import pandas as pd
3
+ import sys
4
+
5
+
6
+ class FaultCodeTenReport:
7
+ """Class provides the definitions for Fault Condition 10 Report."""
8
+
9
+ def __init__(self, config):
10
+ self.oat_col = config["OAT_COL"]
11
+ self.mat_col = config["MAT_COL"]
12
+ self.cooling_sig_col = config["COOLING_SIG_COL"]
13
+ self.economizer_sig_col = config["ECONOMIZER_SIG_COL"]
14
+ self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"]
15
+
16
+ def create_plot(self, df: pd.DataFrame, output_col: str = None):
17
+ if output_col is None:
18
+ output_col = "fc10_flag"
19
+
20
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(25, 8))
21
+ fig.suptitle("Fault Conditions 10 Plot")
22
+
23
+ ax1.plot(df.index, df[self.mat_col], label="MAT")
24
+ ax1.plot(df.index, df[self.oat_col], label="OAT")
25
+ ax1.legend(loc="best")
26
+ ax1.set_ylabel("AHU Temps °F")
27
+
28
+ ax2.plot(df.index, df[self.cooling_sig_col], label="AHU Cool Vlv", color="r")
29
+ ax2.plot(df.index, df[self.economizer_sig_col], label="AHU Dpr Cmd", color="g")
30
+ ax2.legend(loc="best")
31
+ ax2.set_ylabel("%")
32
+
33
+ ax3.plot(df.index, df[output_col], label="Fault", color="k")
34
+ ax3.set_xlabel("Date")
35
+ ax3.set_ylabel("Fault Flags")
36
+ ax3.legend(loc="best")
37
+
38
+ plt.tight_layout(rect=[0, 0.03, 1, 0.95])
39
+ plt.show()
40
+ plt.close()
41
+
42
+ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dict:
43
+ if output_col is None:
44
+ output_col = "fc10_flag"
45
+
46
+ delta = df.index.to_series().diff()
47
+ summary = {
48
+ "total_days": round(delta.sum() / pd.Timedelta(days=1), 2),
49
+ "total_hours": round(delta.sum() / pd.Timedelta(hours=1)),
50
+ "hours_fc10_mode": round(
51
+ (delta * df[output_col]).sum() / pd.Timedelta(hours=1)
52
+ ),
53
+ "percent_true": round(df[output_col].mean() * 100, 2),
54
+ "percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2),
55
+ "flag_true_oat": round(
56
+ df[self.oat_col].where(df[output_col] == 1).mean(), 2
57
+ ),
58
+ "flag_true_mat": round(
59
+ df[self.mat_col].where(df[output_col] == 1).mean(), 2
60
+ ),
61
+ "hours_motor_runtime": round(
62
+ (delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum()
63
+ / pd.Timedelta(hours=1),
64
+ 2,
65
+ ),
66
+ }
67
+
68
+ return summary
69
+
70
+ def create_hist_plot(self, df: pd.DataFrame, output_col: str = None):
71
+ if output_col is None:
72
+ output_col = "fc10_flag"
73
+
74
+ df["hour_of_the_day_fc10"] = df.index.hour.where(df[output_col] == 1)
75
+
76
+ fig, ax = plt.subplots(tight_layout=True, figsize=(25, 8))
77
+ ax.hist(df.hour_of_the_day_fc10.dropna())
78
+ ax.set_xlabel("24 Hour Number in Day")
79
+ ax.set_ylabel("Frequency")
80
+ ax.set_title(f"Hour-Of-Day When Fault Flag 10 is TRUE")
81
+ plt.show()
82
+ plt.close()
83
+
84
+ def display_report_in_ipython(
85
+ self, df: pd.DataFrame, output_col: str = "fc10_flag"
86
+ ):
87
+ print(
88
+ "Fault Condition 10: Outdoor air temperature and mix air temperature should be approximately equal in economizer plus mech cooling mode"
89
+ )
90
+
91
+ self.create_plot(df, output_col)
92
+
93
+ summary = self.summarize_fault_times(df, output_col)
94
+
95
+ for key, value in summary.items():
96
+ formatted_key = key.replace("_", " ")
97
+ print(f"{formatted_key}: {value}")
98
+ sys.stdout.flush()
99
+
100
+ fc_max_faults_found = df[output_col].max()
101
+ print("Fault Flag Count: ", fc_max_faults_found)
102
+ sys.stdout.flush()
103
+
104
+ if fc_max_faults_found != 0:
105
+ self.create_hist_plot(df, output_col)
106
+
107
+ flag_true_oat = round(df[self.oat_col].where(df[output_col] == 1).mean(), 2)
108
+ print("Outside Air Temp Mean When In Fault: ", flag_true_oat)
109
+
110
+ flag_true_mat = round(df[self.mat_col].where(df[output_col] == 1).mean(), 2)
111
+ print("Mix Air Temp Mean When In Fault: ", flag_true_mat)
112
+
113
+ sys.stdout.flush()
114
+
115
+ if summary["percent_true"] > 5.0:
116
+ print(
117
+ "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 mixing air dampers are stuck or broken with the inability for the AHU to go into a proper 100 percent outside air mode. Verify the actual installation of temperature sensors, damper operation, and related equipment to address potential issues."
118
+ )
119
+ else:
120
+ print(
121
+ "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."
122
+ )
123
+
124
+ else:
125
+ print("NO FAULTS FOUND - Skipping time-of-day Histogram plot")
126
+ sys.stdout.flush()
@@ -0,0 +1,128 @@
1
+ import matplotlib.pyplot as plt
2
+ import pandas as pd
3
+ import sys
4
+
5
+
6
+ class FaultCodeElevenReport:
7
+ """Class provides the definitions for Fault Condition 11 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.cooling_sig_col = config["COOLING_SIG_COL"]
13
+ self.economizer_sig_col = config["ECONOMIZER_SIG_COL"]
14
+ self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"]
15
+
16
+ def create_plot(self, df: pd.DataFrame, output_col: str = None):
17
+ if output_col is None:
18
+ output_col = "fc11_flag"
19
+
20
+ fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(25, 8))
21
+ fig.suptitle("Fault Conditions 11 Plot")
22
+
23
+ ax1.plot(df.index, df[self.sat_setpoint_col], label="SATSP")
24
+ ax1.plot(df.index, df[self.oat_col], label="OAT")
25
+ ax1.legend(loc="best")
26
+ ax1.set_ylabel("AHU Temps °F")
27
+
28
+ ax2.plot(df.index, df[self.cooling_sig_col], label="AHU Cool Vlv", color="r")
29
+ ax2.plot(df.index, df[self.economizer_sig_col], label="AHU Dpr Cmd", color="g")
30
+ ax2.legend(loc="best")
31
+ ax2.set_ylabel("%")
32
+
33
+ ax3.plot(df.index, df[output_col], label="Fault", color="k")
34
+ ax3.set_xlabel("Date")
35
+ ax3.set_ylabel("Fault Flags")
36
+ ax3.legend(loc="best")
37
+
38
+ plt.tight_layout(rect=[0, 0.03, 1, 0.95])
39
+ plt.show()
40
+ plt.close()
41
+
42
+ def summarize_fault_times(self, df: pd.DataFrame, output_col: str = None) -> dict:
43
+ if output_col is None:
44
+ output_col = "fc11_flag"
45
+
46
+ delta = df.index.to_series().diff()
47
+ summary = {
48
+ "total_days": round(delta.sum() / pd.Timedelta(days=1), 2),
49
+ "total_hours": round(delta.sum() / pd.Timedelta(hours=1)),
50
+ "hours_fc11_mode": round(
51
+ (delta * df[output_col]).sum() / pd.Timedelta(hours=1)
52
+ ),
53
+ "percent_true": round(df[output_col].mean() * 100, 2),
54
+ "percent_false": round((100 - round(df[output_col].mean() * 100, 2)), 2),
55
+ "flag_true_oat": round(
56
+ df[self.oat_col].where(df[output_col] == 1).mean(), 2
57
+ ),
58
+ "flag_true_sat_sp": round(
59
+ df[self.sat_setpoint_col].where(df[output_col] == 1).mean(), 2
60
+ ),
61
+ "hours_motor_runtime": round(
62
+ (delta * df[self.supply_vfd_speed_col].gt(0.01).astype(int)).sum()
63
+ / pd.Timedelta(hours=1),
64
+ 2,
65
+ ),
66
+ }
67
+
68
+ return summary
69
+
70
+ def create_hist_plot(self, df: pd.DataFrame, output_col: str = None):
71
+ if output_col is None:
72
+ output_col = "fc11_flag"
73
+
74
+ df["hour_of_the_day_fc11"] = df.index.hour.where(df[output_col] == 1)
75
+
76
+ fig, ax = plt.subplots(tight_layout=True, figsize=(25, 8))
77
+ ax.hist(df.hour_of_the_day_fc11.dropna())
78
+ ax.set_xlabel("24 Hour Number in Day")
79
+ ax.set_ylabel("Frequency")
80
+ ax.set_title(f"Hour-Of-Day When Fault Flag 11 is TRUE")
81
+ plt.show()
82
+ plt.close()
83
+
84
+ def display_report_in_ipython(
85
+ self, df: pd.DataFrame, output_col: str = "fc11_flag"
86
+ ):
87
+ print(
88
+ "Fault Condition 11: Outside air temperature too low for 100% outside air cooling in economizer mode"
89
+ )
90
+
91
+ self.create_plot(df, output_col)
92
+
93
+ summary = self.summarize_fault_times(df, output_col)
94
+
95
+ for key, value in summary.items():
96
+ formatted_key = key.replace("_", " ")
97
+ print(f"{formatted_key}: {value}")
98
+ sys.stdout.flush()
99
+
100
+ fc_max_faults_found = df[output_col].max()
101
+ print("Fault Flag Count: ", fc_max_faults_found)
102
+ sys.stdout.flush()
103
+
104
+ if fc_max_faults_found != 0:
105
+ self.create_hist_plot(df, output_col)
106
+
107
+ flag_true_oat = round(df[self.oat_col].where(df[output_col] == 1).mean(), 2)
108
+ print("Outside Air Temp Mean When In Fault: ", flag_true_oat)
109
+
110
+ flag_true_sat_sp = round(
111
+ df[self.sat_setpoint_col].where(df[output_col] == 1).mean(), 2
112
+ )
113
+ print("Supply Air Temp Setpoint Mean When In Fault: ", flag_true_sat_sp)
114
+
115
+ sys.stdout.flush()
116
+
117
+ if summary["percent_true"] > 5.0:
118
+ print(
119
+ "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 coil could be leaking, potentially creating simultaneous heating/cooling scenarios. Visually verify damper operation, and consider tuning the BAS programming for proper AHU operation."
120
+ )
121
+ else:
122
+ print(
123
+ "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."
124
+ )
125
+
126
+ else:
127
+ print("NO FAULTS FOUND - Skipping time-of-day Histogram plot")
128
+ sys.stdout.flush()