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.
Files changed (63) hide show
  1. open_fdd/air_handling_unit/faults/__init__.py +301 -301
  2. open_fdd/air_handling_unit/reports/__init__.py +94 -0
  3. open_fdd/air_handling_unit/reports/fault_report.py +1 -0
  4. open_fdd/tests/ahu/test_ahu_fc16.py +190 -0
  5. {open_fdd-0.1.5.dist-info → open_fdd-0.1.6.dist-info}/METADATA +4 -3
  6. open_fdd-0.1.6.dist-info/RECORD +31 -0
  7. {open_fdd-0.1.5.dist-info → open_fdd-0.1.6.dist-info}/WHEEL +1 -1
  8. open_fdd/air_handling_unit/faults/fault_condition_eight.py +0 -127
  9. open_fdd/air_handling_unit/faults/fault_condition_eleven.py +0 -126
  10. open_fdd/air_handling_unit/faults/fault_condition_fifteen.py +0 -152
  11. open_fdd/air_handling_unit/faults/fault_condition_five.py +0 -123
  12. open_fdd/air_handling_unit/faults/fault_condition_four.py +0 -168
  13. open_fdd/air_handling_unit/faults/fault_condition_fourteen.py +0 -143
  14. open_fdd/air_handling_unit/faults/fault_condition_nine.py +0 -128
  15. open_fdd/air_handling_unit/faults/fault_condition_one.py +0 -112
  16. open_fdd/air_handling_unit/faults/fault_condition_seven.py +0 -114
  17. open_fdd/air_handling_unit/faults/fault_condition_six.py +0 -181
  18. open_fdd/air_handling_unit/faults/fault_condition_ten.py +0 -123
  19. open_fdd/air_handling_unit/faults/fault_condition_thirteen.py +0 -127
  20. open_fdd/air_handling_unit/faults/fault_condition_three.py +0 -113
  21. open_fdd/air_handling_unit/faults/fault_condition_twelve.py +0 -132
  22. open_fdd/air_handling_unit/faults/fault_condition_two.py +0 -113
  23. open_fdd/air_handling_unit/images/ahu1_fc1_2024-06_1.jpg +0 -0
  24. open_fdd/air_handling_unit/images/ahu1_fc1_2024-06_2.jpg +0 -0
  25. open_fdd/air_handling_unit/images/example1.jpg +0 -0
  26. open_fdd/air_handling_unit/images/example2.jpg +0 -0
  27. open_fdd/air_handling_unit/images/fc10_definition.png +0 -0
  28. open_fdd/air_handling_unit/images/fc11_definition.png +0 -0
  29. open_fdd/air_handling_unit/images/fc12_definition.png +0 -0
  30. open_fdd/air_handling_unit/images/fc13_definition.png +0 -0
  31. open_fdd/air_handling_unit/images/fc1_definition.png +0 -0
  32. open_fdd/air_handling_unit/images/fc1_report_screenshot_all.png +0 -0
  33. open_fdd/air_handling_unit/images/fc2_definition.png +0 -0
  34. open_fdd/air_handling_unit/images/fc3_definition.png +0 -0
  35. open_fdd/air_handling_unit/images/fc4_definition.png +0 -0
  36. open_fdd/air_handling_unit/images/fc5_definition.png +0 -0
  37. open_fdd/air_handling_unit/images/fc6_definition.png +0 -0
  38. open_fdd/air_handling_unit/images/fc7_definition.png +0 -0
  39. open_fdd/air_handling_unit/images/fc8_definition.png +0 -0
  40. open_fdd/air_handling_unit/images/fc9_definition.png +0 -0
  41. open_fdd/air_handling_unit/images/latex_generator.py +0 -175
  42. open_fdd/air_handling_unit/images/params.docx +0 -0
  43. open_fdd/air_handling_unit/images/params.pdf +0 -0
  44. open_fdd/air_handling_unit/images/plot_for_repo.png +0 -0
  45. open_fdd/air_handling_unit/reports/base_report.py +0 -47
  46. open_fdd/air_handling_unit/reports/report_fc1.py +0 -115
  47. open_fdd/air_handling_unit/reports/report_fc10.py +0 -126
  48. open_fdd/air_handling_unit/reports/report_fc11.py +0 -128
  49. open_fdd/air_handling_unit/reports/report_fc12.py +0 -126
  50. open_fdd/air_handling_unit/reports/report_fc13.py +0 -126
  51. open_fdd/air_handling_unit/reports/report_fc14.py +0 -124
  52. open_fdd/air_handling_unit/reports/report_fc15.py +0 -124
  53. open_fdd/air_handling_unit/reports/report_fc2.py +0 -119
  54. open_fdd/air_handling_unit/reports/report_fc3.py +0 -119
  55. open_fdd/air_handling_unit/reports/report_fc4.py +0 -148
  56. open_fdd/air_handling_unit/reports/report_fc5.py +0 -132
  57. open_fdd/air_handling_unit/reports/report_fc6.py +0 -156
  58. open_fdd/air_handling_unit/reports/report_fc7.py +0 -126
  59. open_fdd/air_handling_unit/reports/report_fc8.py +0 -118
  60. open_fdd/air_handling_unit/reports/report_fc9.py +0 -120
  61. open_fdd-0.1.5.dist-info/RECORD +0 -83
  62. {open_fdd-0.1.5.dist-info → open_fdd-0.1.6.dist-info}/LICENSE +0 -0
  63. {open_fdd-0.1.5.dist-info → open_fdd-0.1.6.dist-info}/top_level.txt +0 -0
@@ -1,152 +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 FaultConditionFifteen(FaultCondition):
11
- """Class provides the definitions for Fault Condition 15.
12
- Temperature rise across inactive heating coil.
13
- Requires coil leaving temp sensor.
14
- """
15
-
16
- def __init__(self, dict_):
17
- super().__init__()
18
- self.delta_supply_fan = float
19
- self.coil_temp_enter_err_thres = float
20
- self.coil_temp_leav_err_thres = float
21
- self.htg_coil_enter_temp_col = str
22
- self.htg_coil_leave_temp_col = str
23
- self.ahu_min_oa_dpr = float
24
- self.cooling_sig_col = str
25
- self.heating_sig_col = str
26
- self.economizer_sig_col = str
27
- self.supply_vfd_speed_col = str
28
- self.troubleshoot_mode = bool # default to False
29
- self.rolling_window_size = int
30
-
31
- self.equation_string = (
32
- "fc15_flag = 1 if ΔT_coil >= √(εcoil_enter² + εcoil_leave²) + ΔT_fan "
33
- "in inactive heating coil mode for N consecutive values else 0 \n"
34
- )
35
- self.description_string = (
36
- "Fault Condition 15: Temperature rise across inactive heating coil "
37
- "detected, requiring coil leaving temperature sensor \n"
38
- )
39
- self.required_column_description = (
40
- "Required inputs are the heating coil entering temperature, heating coil leaving temperature, "
41
- "cooling signal, heating signal, economizer signal, and supply fan VFD speed \n"
42
- )
43
- self.error_string = f"One or more required columns are missing or None \n"
44
-
45
- self.set_attributes(dict_)
46
-
47
- # Set required columns specific to this fault condition
48
- self.required_columns = [
49
- self.htg_coil_enter_temp_col,
50
- self.htg_coil_leave_temp_col,
51
- self.cooling_sig_col,
52
- self.heating_sig_col,
53
- self.economizer_sig_col,
54
- self.supply_vfd_speed_col,
55
- ]
56
-
57
- # Check if any of the required columns are None
58
- if any(col is None for col in self.required_columns):
59
- raise MissingColumnError(
60
- f"{self.error_string}"
61
- f"{self.equation_string}"
62
- f"{self.description_string}"
63
- f"{self.required_column_description}"
64
- f"{self.required_columns}"
65
- )
66
-
67
- # Ensure all required columns are strings
68
- self.required_columns = [str(col) for col in self.required_columns]
69
-
70
- self.mapped_columns = (
71
- f"Your config dictionary is mapped as: {', '.join(self.required_columns)}"
72
- )
73
-
74
- def get_required_columns(self) -> str:
75
- """Returns a string representation of the required columns."""
76
- return (
77
- f"{self.equation_string}"
78
- f"{self.description_string}"
79
- f"{self.required_column_description}"
80
- f"{self.mapped_columns}"
81
- )
82
-
83
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
84
- try:
85
- # Ensure all required columns are present
86
- self.check_required_columns(df)
87
-
88
- if self.troubleshoot_mode:
89
- self.troubleshoot_cols(df)
90
-
91
- # Check analog outputs [data with units of %] are floats only
92
- columns_to_check = [
93
- self.economizer_sig_col,
94
- self.cooling_sig_col,
95
- self.heating_sig_col,
96
- self.supply_vfd_speed_col,
97
- ]
98
- self.check_analog_pct(df, columns_to_check)
99
-
100
- # Create helper columns
101
- df["htg_delta_temp"] = (
102
- df[self.htg_coil_leave_temp_col] - df[self.htg_coil_enter_temp_col]
103
- )
104
-
105
- df["htg_delta_sqrted"] = (
106
- np.sqrt(
107
- self.coil_temp_enter_err_thres**2 + self.coil_temp_leav_err_thres**2
108
- )
109
- + self.delta_supply_fan
110
- )
111
-
112
- df["combined_check"] = (
113
- (
114
- (df["htg_delta_temp"] >= df["htg_delta_sqrted"])
115
- # verify AHU is in OS2 only free cooling mode
116
- & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
117
- & (df[self.cooling_sig_col] < 0.1)
118
- )
119
- | (
120
- (df["htg_delta_temp"] >= df["htg_delta_sqrted"])
121
- # OS4 AHU state clg @ min OA
122
- & (df[self.cooling_sig_col] > 0.01)
123
- & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr)
124
- )
125
- | (
126
- (df["htg_delta_temp"] >= df["htg_delta_sqrted"])
127
- # verify AHU is running in OS 3 clg mode in 100 OA
128
- & (df[self.cooling_sig_col] > 0.01)
129
- & (df[self.economizer_sig_col] > 0.9)
130
- )
131
- )
132
-
133
- # Rolling sum to count consecutive trues
134
- rolling_sum = (
135
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
136
- )
137
- # Set flag to 1 if rolling sum equals the window size
138
- df["fc15_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
139
-
140
- if self.troubleshoot_mode:
141
- print("Troubleshoot mode enabled - not removing helper columns")
142
- sys.stdout.flush()
143
- del df["htg_delta_temp"]
144
- del df["htg_delta_sqrted"]
145
- del df["combined_check"]
146
-
147
- return df
148
-
149
- except MissingColumnError as e:
150
- print(f"Error: {e.message}")
151
- sys.stdout.flush()
152
- raise e
@@ -1,123 +0,0 @@
1
- import pandas as pd
2
- import pandas.api.types as pdtypes
3
- from open_fdd.air_handling_unit.faults.fault_condition import (
4
- FaultCondition,
5
- MissingColumnError,
6
- )
7
- import sys
8
-
9
-
10
- class FaultConditionFive(FaultCondition):
11
- """Class provides the definitions for Fault Condition 5.
12
- SAT too low; should be higher than MAT in HTG MODE
13
- --Broken heating valve or other mechanical issue
14
- related to heat valve not working as designed
15
- """
16
-
17
- def __init__(self, dict_):
18
- super().__init__()
19
- self.mix_degf_err_thres = float
20
- self.supply_degf_err_thres = float
21
- self.delta_t_supply_fan = float
22
- self.mat_col = str
23
- self.sat_col = str
24
- self.heating_sig_col = str
25
- self.supply_vfd_speed_col = str
26
- self.troubleshoot_mode = bool # default to False
27
- self.rolling_window_size = int
28
-
29
- self.equation_string = (
30
- "fc5_flag = 1 if (SAT + εSAT <= MAT - εMAT + ΔT_supply_fan) and "
31
- "(heating signal > 0) and (VFDSPD > 0) for N consecutive values else 0 \n"
32
- )
33
- self.description_string = (
34
- "Fault Condition 5: SAT too low; should be higher than MAT in HTG MODE, "
35
- "potential broken heating valve or mechanical issue \n"
36
- )
37
- self.required_column_description = (
38
- "Required inputs are the mixed air temperature, supply air temperature, "
39
- "heating signal, and supply fan VFD speed \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.heating_sig_col,
50
- self.supply_vfd_speed_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 = [self.supply_vfd_speed_col, self.heating_sig_col]
89
-
90
- for col in columns_to_check:
91
- self.check_analog_pct(df, [col])
92
-
93
- df["sat_check"] = df[self.sat_col] + self.supply_degf_err_thres
94
- df["mat_check"] = (
95
- df[self.mat_col] - self.mix_degf_err_thres + self.delta_t_supply_fan
96
- )
97
-
98
- df["combined_check"] = (
99
- (df["sat_check"] <= df["mat_check"])
100
- & (df[self.heating_sig_col] > 0.01)
101
- & (df[self.supply_vfd_speed_col] > 0.01)
102
- )
103
-
104
- # Rolling sum to count consecutive trues
105
- rolling_sum = (
106
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
107
- )
108
- # Set flag to 1 if rolling sum equals the window size
109
- df["fc5_flag"] = (rolling_sum == self.rolling_window_size).astype(int)
110
-
111
- if self.troubleshoot_mode:
112
- print("Troubleshoot mode enabled - not removing helper columns")
113
- sys.stdout.flush()
114
- del df["mat_check"]
115
- del df["sat_check"]
116
- del df["combined_check"]
117
-
118
- return df
119
-
120
- except MissingColumnError as e:
121
- print(f"Error: {e.message}")
122
- sys.stdout.flush()
123
- raise e
@@ -1,168 +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 FaultConditionFour(FaultCondition):
10
- """Class provides the definitions for Fault Condition 4.
11
-
12
- This fault flags excessive operating states on the AHU
13
- if it's hunting between heating, econ, econ+mech, and
14
- a mech clg modes. The code counts how many operating
15
- changes in an hour and will throw a fault if there is
16
- excessive OS changes to flag control sys hunting.
17
- """
18
-
19
- def __init__(self, dict_):
20
- super().__init__()
21
- self.delta_os_max = float
22
- self.ahu_min_oa_dpr = float
23
- self.economizer_sig_col = str
24
- self.heating_sig_col = str
25
- self.cooling_sig_col = str
26
- self.supply_vfd_speed_col = str
27
- self.troubleshoot_mode = bool # default to False
28
-
29
- self.equation_string = (
30
- "fc4_flag = 1 if excessive mode changes (> δOS_max) occur "
31
- "within an hour across heating, econ, econ+mech, mech clg, and min OA modes \n"
32
- )
33
- self.description_string = "Fault Condition 4: Excessive AHU operating state changes detected (hunting behavior) \n"
34
- self.required_column_description = (
35
- "Required inputs are the economizer signal, supply fan VFD speed, "
36
- "and optionally heating and cooling signals \n"
37
- )
38
- self.error_string = f"One or more required columns are missing or None \n"
39
-
40
- self.set_attributes(dict_)
41
-
42
- # Set required columns, making heating and cooling optional
43
- self.required_columns = [
44
- self.economizer_sig_col,
45
- self.supply_vfd_speed_col,
46
- ]
47
-
48
- # If heating or cooling columns are provided, add them to the required columns
49
- if self.heating_sig_col:
50
- self.required_columns.append(self.heating_sig_col)
51
- if self.cooling_sig_col:
52
- self.required_columns.append(self.cooling_sig_col)
53
-
54
- # Check if any of the required columns are None
55
- if any(col is None for col in self.required_columns):
56
- raise MissingColumnError(
57
- f"{self.error_string}"
58
- f"{self.equation_string}"
59
- f"{self.description_string}"
60
- f"{self.required_column_description}"
61
- f"{self.required_columns}"
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 the optional columns are not present, create them with all values set to 0.0
85
- if self.heating_sig_col not in df.columns:
86
- df[self.heating_sig_col] = 0.0
87
- if self.cooling_sig_col not in df.columns:
88
- df[self.cooling_sig_col] = 0.0
89
-
90
- if self.troubleshoot_mode:
91
- self.troubleshoot_cols(df)
92
-
93
- # Check analog outputs [data with units of %] are floats only
94
- columns_to_check = [
95
- self.economizer_sig_col,
96
- self.heating_sig_col,
97
- self.cooling_sig_col,
98
- self.supply_vfd_speed_col,
99
- ]
100
-
101
- for col in columns_to_check:
102
- self.check_analog_pct(df, [col])
103
-
104
- print("=" * 50)
105
- print("Warning: The program is in FC4 and resampling the data")
106
- print("to compute AHU OS state changes per hour")
107
- print("to flag any hunting issue")
108
- print("and this usually takes a while to run...")
109
- print("=" * 50)
110
-
111
- sys.stdout.flush()
112
-
113
- # AHU htg only mode based on OA damper @ min oa and only htg pid/vlv modulating
114
- df["heating_mode"] = (
115
- (df[self.heating_sig_col] > 0)
116
- & (df[self.cooling_sig_col] == 0)
117
- & (df[self.supply_vfd_speed_col] > 0)
118
- & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr)
119
- )
120
-
121
- # AHU econ only mode based on OA damper modulating and clg htg = zero
122
- df["econ_only_cooling_mode"] = (
123
- (df[self.heating_sig_col] == 0)
124
- & (df[self.cooling_sig_col] == 0)
125
- & (df[self.supply_vfd_speed_col] > 0)
126
- & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
127
- )
128
-
129
- # AHU econ+mech clg mode based on OA damper modulating for cooling and clg pid/vlv modulating
130
- df["econ_plus_mech_cooling_mode"] = (
131
- (df[self.heating_sig_col] == 0)
132
- & (df[self.cooling_sig_col] > 0)
133
- & (df[self.supply_vfd_speed_col] > 0)
134
- & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
135
- )
136
-
137
- # AHU mech mode based on OA damper @ min OA and clg pid/vlv modulating
138
- df["mech_cooling_only_mode"] = (
139
- (df[self.heating_sig_col] == 0)
140
- & (df[self.cooling_sig_col] > 0)
141
- & (df[self.supply_vfd_speed_col] > 0)
142
- & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr)
143
- )
144
-
145
- # AHU minimum OA mode without heating or cooling (ventilation mode)
146
- df["min_oa_mode_only"] = (
147
- (df[self.heating_sig_col] == 0)
148
- & (df[self.cooling_sig_col] == 0)
149
- & (df[self.supply_vfd_speed_col] > 0)
150
- & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr)
151
- )
152
-
153
- # Fill non-finite values with zero or drop them
154
- df = df.fillna(0)
155
-
156
- df = df.astype(int)
157
- df = df.resample("60min").apply(lambda x: (x.eq(1) & x.shift().ne(1)).sum())
158
-
159
- df["fc4_flag"] = (
160
- df[df.columns].gt(self.delta_os_max).any(axis=1).astype(int)
161
- )
162
-
163
- return df
164
-
165
- except MissingColumnError as e:
166
- print(f"Error: {e.message}")
167
- sys.stdout.flush()
168
- raise e
@@ -1,143 +0,0 @@
1
- import pandas as pd
2
- import numpy as np
3
- import operator
4
- from open_fdd.air_handling_unit.faults.fault_condition import (
5
- FaultCondition,
6
- MissingColumnError,
7
- )
8
- import sys
9
-
10
-
11
- class FaultConditionFourteen(FaultCondition):
12
- """Class provides the definitions for Fault Condition 14.
13
- Temperature drop across inactive cooling coil.
14
- Requires coil leaving temp sensor.
15
- """
16
-
17
- def __init__(self, dict_):
18
- super().__init__()
19
- self.delta_t_supply_fan = float
20
- self.coil_temp_enter_err_thres = float
21
- self.coil_temp_leav_err_thres = float
22
- self.clg_coil_enter_temp_col = str
23
- self.clg_coil_leave_temp_col = str
24
- self.ahu_min_oa_dpr = float
25
- self.cooling_sig_col = str
26
- self.heating_sig_col = str
27
- self.economizer_sig_col = str
28
- self.supply_vfd_speed_col = str
29
- self.troubleshoot_mode = bool # default to False
30
- self.rolling_window_size = int
31
-
32
- self.equation_string = (
33
- "fc14_flag = 1 if ΔT_coil >= √(εcoil_enter² + εcoil_leave²) + ΔT_fan "
34
- "in inactive cooling coil mode for N consecutive values else 0 \n"
35
- )
36
- self.description_string = (
37
- "Fault Condition 14: Temperature drop across inactive cooling coil "
38
- "detected, requiring coil leaving temperature sensor \n"
39
- )
40
- self.required_column_description = (
41
- "Required inputs are the cooling coil entering temperature, cooling coil leaving temperature, "
42
- "cooling signal, heating signal, economizer signal, and supply fan VFD speed \n"
43
- )
44
- self.error_string = f"One or more required columns are missing or None \n"
45
-
46
- self.set_attributes(dict_)
47
-
48
- # Set required columns specific to this fault condition
49
- self.required_columns = [
50
- self.clg_coil_enter_temp_col,
51
- self.clg_coil_leave_temp_col,
52
- self.cooling_sig_col,
53
- self.heating_sig_col,
54
- self.economizer_sig_col,
55
- self.supply_vfd_speed_col,
56
- ]
57
-
58
- # Check if any of the required columns are None
59
- if any(col is None for col in self.required_columns):
60
- raise MissingColumnError(
61
- f"{self.error_string}"
62
- f"{self.equation_string}"
63
- f"{self.description_string}"
64
- f"{self.required_column_description}"
65
- f"{self.required_columns}"
66
- )
67
-
68
- # Ensure all required columns are strings
69
- self.required_columns = [str(col) for col in self.required_columns]
70
-
71
- self.mapped_columns = (
72
- f"Your config dictionary is mapped as: {', '.join(self.required_columns)}"
73
- )
74
-
75
- def get_required_columns(self) -> str:
76
- """Returns a string representation of the required columns."""
77
- return (
78
- f"{self.equation_string}"
79
- f"{self.description_string}"
80
- f"{self.required_column_description}"
81
- f"{self.mapped_columns}"
82
- )
83
-
84
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
85
- try:
86
- # Ensure all required columns are present
87
- self.check_required_columns(df)
88
-
89
- if self.troubleshoot_mode:
90
- self.troubleshoot_cols(df)
91
-
92
- # Check analog outputs [data with units of %] are floats only
93
- columns_to_check = [
94
- self.economizer_sig_col,
95
- self.cooling_sig_col,
96
- self.heating_sig_col,
97
- self.supply_vfd_speed_col,
98
- ]
99
- self.check_analog_pct(df, columns_to_check)
100
-
101
- # Create helper columns
102
- df["clg_delta_temp"] = (
103
- df[self.clg_coil_enter_temp_col] - df[self.clg_coil_leave_temp_col]
104
- )
105
-
106
- df["clg_delta_sqrted"] = (
107
- np.sqrt(
108
- self.coil_temp_enter_err_thres**2 + self.coil_temp_leav_err_thres**2
109
- )
110
- + self.delta_t_supply_fan
111
- )
112
-
113
- df["combined_check"] = operator.or_(
114
- (df["clg_delta_temp"] >= df["clg_delta_sqrted"])
115
- # verify AHU is in OS2 only free cooling mode
116
- & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
117
- & (df[self.cooling_sig_col] < 0.1), # OR
118
- (df["clg_delta_temp"] >= df["clg_delta_sqrted"])
119
- # verify AHU is running in OS 1 at near full heat
120
- & (df[self.heating_sig_col] > 0.0)
121
- & (df[self.supply_vfd_speed_col] > 0.0),
122
- )
123
-
124
- # Rolling sum to count consecutive trues
125
- rolling_sum = (
126
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
127
- )
128
- # Set flag to 1 if rolling sum equals the window size
129
- df["fc14_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
130
-
131
- if self.troubleshoot_mode:
132
- print("Troubleshoot mode enabled - not removing helper columns")
133
- sys.stdout.flush()
134
- del df["clg_delta_temp"]
135
- del df["clg_delta_sqrted"]
136
- del df["combined_check"]
137
-
138
- return df
139
-
140
- except MissingColumnError as e:
141
- print(f"Error: {e.message}")
142
- sys.stdout.flush()
143
- raise e