open-fdd 0.1.0__py3-none-any.whl → 0.1.3__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 (49) hide show
  1. open_fdd/air_handling_unit/faults/fault_condition.py +26 -8
  2. open_fdd/air_handling_unit/faults/fault_condition_eight.py +61 -37
  3. open_fdd/air_handling_unit/faults/fault_condition_eleven.py +59 -37
  4. open_fdd/air_handling_unit/faults/fault_condition_fifteen.py +77 -51
  5. open_fdd/air_handling_unit/faults/fault_condition_five.py +60 -41
  6. open_fdd/air_handling_unit/faults/fault_condition_four.py +108 -65
  7. open_fdd/air_handling_unit/faults/fault_condition_fourteen.py +71 -44
  8. open_fdd/air_handling_unit/faults/fault_condition_nine.py +60 -36
  9. open_fdd/air_handling_unit/faults/fault_condition_one.py +58 -37
  10. open_fdd/air_handling_unit/faults/fault_condition_seven.py +55 -32
  11. open_fdd/air_handling_unit/faults/fault_condition_six.py +100 -76
  12. open_fdd/air_handling_unit/faults/fault_condition_ten.py +62 -37
  13. open_fdd/air_handling_unit/faults/fault_condition_thirteen.py +61 -36
  14. open_fdd/air_handling_unit/faults/fault_condition_three.py +58 -33
  15. open_fdd/air_handling_unit/faults/fault_condition_twelve.py +63 -39
  16. open_fdd/air_handling_unit/faults/fault_condition_two.py +58 -36
  17. open_fdd/air_handling_unit/faults/helper_utils.py +294 -64
  18. open_fdd/air_handling_unit/images/ahu1_fc1_2024-06_1.jpg +0 -0
  19. open_fdd/air_handling_unit/images/ahu1_fc1_2024-06_2.jpg +0 -0
  20. open_fdd/air_handling_unit/images/example1.jpg +0 -0
  21. open_fdd/air_handling_unit/images/example2.jpg +0 -0
  22. open_fdd/air_handling_unit/images/fc10_definition.png +0 -0
  23. open_fdd/air_handling_unit/images/fc11_definition.png +0 -0
  24. open_fdd/air_handling_unit/images/fc12_definition.png +0 -0
  25. open_fdd/air_handling_unit/images/fc13_definition.png +0 -0
  26. open_fdd/air_handling_unit/images/fc1_definition.png +0 -0
  27. open_fdd/air_handling_unit/images/fc1_report_screenshot_all.png +0 -0
  28. open_fdd/air_handling_unit/images/fc2_definition.png +0 -0
  29. open_fdd/air_handling_unit/images/fc3_definition.png +0 -0
  30. open_fdd/air_handling_unit/images/fc4_definition.png +0 -0
  31. open_fdd/air_handling_unit/images/fc5_definition.png +0 -0
  32. open_fdd/air_handling_unit/images/fc6_definition.png +0 -0
  33. open_fdd/air_handling_unit/images/fc7_definition.png +0 -0
  34. open_fdd/air_handling_unit/images/fc8_definition.png +0 -0
  35. open_fdd/air_handling_unit/images/fc9_definition.png +0 -0
  36. open_fdd/air_handling_unit/images/latex_generator.py +175 -0
  37. open_fdd/air_handling_unit/images/params.docx +0 -0
  38. open_fdd/air_handling_unit/images/params.pdf +0 -0
  39. open_fdd/air_handling_unit/images/plot_for_repo.png +0 -0
  40. open_fdd/air_handling_unit/reports/base_report.py +47 -0
  41. open_fdd/air_handling_unit/reports/report_fc7.py +3 -1
  42. open_fdd/tests/ahu/test_ahu_fc1.py +17 -0
  43. open_fdd/tests/ahu/test_ahu_fc4.py +127 -199
  44. open_fdd-0.1.3.dist-info/METADATA +87 -0
  45. {open_fdd-0.1.0.dist-info → open_fdd-0.1.3.dist-info}/RECORD +48 -25
  46. open_fdd-0.1.0.dist-info/METADATA +0 -65
  47. {open_fdd-0.1.0.dist-info → open_fdd-0.1.3.dist-info}/LICENSE +0 -0
  48. {open_fdd-0.1.0.dist-info → open_fdd-0.1.3.dist-info}/WHEEL +0 -0
  49. {open_fdd-0.1.0.dist-info → open_fdd-0.1.3.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,8 @@
1
1
  import pandas as pd
2
- from open_fdd.air_handling_unit.faults.fault_condition import FaultCondition
3
- from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils
2
+ from open_fdd.air_handling_unit.faults.fault_condition import (
3
+ FaultCondition,
4
+ MissingColumnError,
5
+ )
4
6
  import sys
5
7
 
6
8
 
@@ -8,14 +10,14 @@ class FaultConditionFour(FaultCondition):
8
10
  """Class provides the definitions for Fault Condition 4.
9
11
 
10
12
  This fault flags excessive operating states on the AHU
11
- if its hunting between heating, econ, econ+mech, and
13
+ if it's hunting between heating, econ, econ+mech, and
12
14
  a mech clg modes. The code counts how many operating
13
15
  changes in an hour and will throw a fault if there is
14
16
  excessive OS changes to flag control sys hunting.
15
-
16
17
  """
17
18
 
18
19
  def __init__(self, dict_):
20
+ super().__init__()
19
21
  self.delta_os_max = float
20
22
  self.ahu_min_oa_dpr = float
21
23
  self.economizer_sig_col = str
@@ -26,68 +28,109 @@ class FaultConditionFour(FaultCondition):
26
28
 
27
29
  self.set_attributes(dict_)
28
30
 
29
- # adds in these boolean columns to the dataframe
30
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
31
- if self.troubleshoot_mode:
32
- self.troubleshoot_cols(df)
33
-
34
- # check analog outputs [data with units of %] are floats only
35
- columns_to_check = [
31
+ # Set required columns, making heating and cooling optional
32
+ self.required_columns = [
36
33
  self.economizer_sig_col,
37
- self.heating_sig_col,
38
- self.cooling_sig_col,
39
34
  self.supply_vfd_speed_col,
40
35
  ]
41
36
 
42
- for col in columns_to_check:
43
- self.check_analog_pct(df, [col])
44
-
45
- print("=" * 50)
46
- print("Warning: The program is in FC4 and resampling the data")
47
- print("to compute AHU OS state changes per hour")
48
- print("to flag any hunting issue")
49
- print("and this usually takes a while to run...")
50
- print("=" * 50)
51
-
52
- sys.stdout.flush()
53
-
54
- # AHU htg only mode based on OA damper @ min oa and only htg pid/vlv modulating
55
- df["heating_mode"] = (
56
- (df[self.heating_sig_col] > 0)
57
- & (df[self.cooling_sig_col] == 0)
58
- & (df[self.supply_vfd_speed_col] > 0)
59
- & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr)
60
- )
61
-
62
- # AHU econ only mode based on OA damper modulating and clg htg = zero
63
- df["econ_only_cooling_mode"] = (
64
- (df[self.heating_sig_col] == 0)
65
- & (df[self.cooling_sig_col] == 0)
66
- & (df[self.supply_vfd_speed_col] > 0)
67
- & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
68
- )
69
-
70
- # AHU econ+mech clg mode based on OA damper modulating for cooling and clg pid/vlv modulating
71
- df["econ_plus_mech_cooling_mode"] = (
72
- (df[self.heating_sig_col] == 0)
73
- & (df[self.cooling_sig_col] > 0)
74
- & (df[self.supply_vfd_speed_col] > 0)
75
- & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
76
- )
77
-
78
- # AHU mech mode based on OA damper @ min OA and clg pid/vlv modulating
79
- df["mech_cooling_only_mode"] = (
80
- (df[self.heating_sig_col] == 0)
81
- & (df[self.cooling_sig_col] > 0)
82
- & (df[self.supply_vfd_speed_col] > 0)
83
- & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr)
84
- )
85
-
86
- # Fill non-finite values with zero or drop them
87
- df = df.fillna(0)
88
-
89
- df = df.astype(int)
90
- df = df.resample("60min").apply(lambda x: (x.eq(1) & x.shift().ne(1)).sum())
91
-
92
- df["fc4_flag"] = df[df.columns].gt(self.delta_os_max).any(axis=1).astype(int)
93
- return df
37
+ # If heating or cooling columns are provided, add them to the required columns
38
+ if self.heating_sig_col:
39
+ self.required_columns.append(self.heating_sig_col)
40
+ if self.cooling_sig_col:
41
+ self.required_columns.append(self.cooling_sig_col)
42
+
43
+ def get_required_columns(self) -> str:
44
+ """Returns a string representation of the required columns."""
45
+ return f"Required columns for FaultConditionFour: {', '.join(self.required_columns)}"
46
+
47
+ def apply(self, df: pd.DataFrame) -> pd.DataFrame:
48
+ try:
49
+ # Ensure all required columns are present
50
+ self.check_required_columns(df)
51
+
52
+ # If the optional columns are not present, create them with all values set to 0.0
53
+ if self.heating_sig_col not in df.columns:
54
+ df[self.heating_sig_col] = 0.0
55
+ if self.cooling_sig_col not in df.columns:
56
+ df[self.cooling_sig_col] = 0.0
57
+
58
+ if self.troubleshoot_mode:
59
+ self.troubleshoot_cols(df)
60
+
61
+ # Check analog outputs [data with units of %] are floats only
62
+ columns_to_check = [
63
+ self.economizer_sig_col,
64
+ self.heating_sig_col,
65
+ self.cooling_sig_col,
66
+ self.supply_vfd_speed_col,
67
+ ]
68
+
69
+ for col in columns_to_check:
70
+ self.check_analog_pct(df, [col])
71
+
72
+ print("=" * 50)
73
+ print("Warning: The program is in FC4 and resampling the data")
74
+ print("to compute AHU OS state changes per hour")
75
+ print("to flag any hunting issue")
76
+ print("and this usually takes a while to run...")
77
+ print("=" * 50)
78
+
79
+ sys.stdout.flush()
80
+
81
+ # AHU htg only mode based on OA damper @ min oa and only htg pid/vlv modulating
82
+ df["heating_mode"] = (
83
+ (df[self.heating_sig_col] > 0)
84
+ & (df[self.cooling_sig_col] == 0)
85
+ & (df[self.supply_vfd_speed_col] > 0)
86
+ & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr)
87
+ )
88
+
89
+ # AHU econ only mode based on OA damper modulating and clg htg = zero
90
+ df["econ_only_cooling_mode"] = (
91
+ (df[self.heating_sig_col] == 0)
92
+ & (df[self.cooling_sig_col] == 0)
93
+ & (df[self.supply_vfd_speed_col] > 0)
94
+ & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
95
+ )
96
+
97
+ # AHU econ+mech clg mode based on OA damper modulating for cooling and clg pid/vlv modulating
98
+ df["econ_plus_mech_cooling_mode"] = (
99
+ (df[self.heating_sig_col] == 0)
100
+ & (df[self.cooling_sig_col] > 0)
101
+ & (df[self.supply_vfd_speed_col] > 0)
102
+ & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
103
+ )
104
+
105
+ # AHU mech mode based on OA damper @ min OA and clg pid/vlv modulating
106
+ df["mech_cooling_only_mode"] = (
107
+ (df[self.heating_sig_col] == 0)
108
+ & (df[self.cooling_sig_col] > 0)
109
+ & (df[self.supply_vfd_speed_col] > 0)
110
+ & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr)
111
+ )
112
+
113
+ # AHU minimum OA mode without heating or cooling (ventilation mode)
114
+ df["min_oa_mode_only"] = (
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
+ # Fill non-finite values with zero or drop them
122
+ df = df.fillna(0)
123
+
124
+ df = df.astype(int)
125
+ df = df.resample("60min").apply(lambda x: (x.eq(1) & x.shift().ne(1)).sum())
126
+
127
+ df["fc4_flag"] = (
128
+ df[df.columns].gt(self.delta_os_max).any(axis=1).astype(int)
129
+ )
130
+
131
+ return df
132
+
133
+ except MissingColumnError as e:
134
+ print(f"Error: {e.message}")
135
+ sys.stdout.flush()
136
+ raise e # Re-raise the exception so it can be caught by pytest
@@ -1,8 +1,10 @@
1
1
  import pandas as pd
2
2
  import numpy as np
3
- from open_fdd.air_handling_unit.faults.fault_condition import FaultCondition
4
- from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils
5
3
  import operator
4
+ from open_fdd.air_handling_unit.faults.fault_condition import (
5
+ FaultCondition,
6
+ MissingColumnError,
7
+ )
6
8
  import sys
7
9
 
8
10
 
@@ -13,6 +15,7 @@ class FaultConditionFourteen(FaultCondition):
13
15
  """
14
16
 
15
17
  def __init__(self, dict_):
18
+ super().__init__()
16
19
  self.delta_t_supply_fan = float
17
20
  self.coil_temp_enter_err_thres = float
18
21
  self.coil_temp_leav_err_thres = float
@@ -28,53 +31,77 @@ class FaultConditionFourteen(FaultCondition):
28
31
 
29
32
  self.set_attributes(dict_)
30
33
 
31
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
32
- if self.troubleshoot_mode:
33
- self.troubleshoot_cols(df)
34
-
35
- # Check analog outputs [data with units of %] are floats only
36
- columns_to_check = [
37
- self.economizer_sig_col,
34
+ # Set required columns specific to this fault condition
35
+ self.required_columns = [
36
+ self.clg_coil_enter_temp_col,
37
+ self.clg_coil_leave_temp_col,
38
38
  self.cooling_sig_col,
39
39
  self.heating_sig_col,
40
+ self.economizer_sig_col,
40
41
  self.supply_vfd_speed_col,
41
42
  ]
42
- self.check_analog_pct(df, columns_to_check)
43
43
 
44
- # Create helper columns
45
- df["clg_delta_temp"] = (
46
- df[self.clg_coil_enter_temp_col] - df[self.clg_coil_leave_temp_col]
47
- )
44
+ def get_required_columns(self) -> str:
45
+ """Returns a string representation of the required columns."""
46
+ return f"Required columns for FaultConditionFourteen: {', '.join(self.required_columns)}"
48
47
 
49
- df["clg_delta_sqrted"] = (
50
- np.sqrt(
51
- self.coil_temp_enter_err_thres**2 + self.coil_temp_leav_err_thres**2
48
+ def apply(self, df: pd.DataFrame) -> pd.DataFrame:
49
+ try:
50
+ # Ensure all required columns are present
51
+ self.check_required_columns(df)
52
+
53
+ if self.troubleshoot_mode:
54
+ self.troubleshoot_cols(df)
55
+
56
+ # Check analog outputs [data with units of %] are floats only
57
+ columns_to_check = [
58
+ self.economizer_sig_col,
59
+ self.cooling_sig_col,
60
+ self.heating_sig_col,
61
+ self.supply_vfd_speed_col,
62
+ ]
63
+ self.check_analog_pct(df, columns_to_check)
64
+
65
+ # Create helper columns
66
+ df["clg_delta_temp"] = (
67
+ df[self.clg_coil_enter_temp_col] - df[self.clg_coil_leave_temp_col]
52
68
  )
53
- + self.delta_t_supply_fan
54
- )
55
-
56
- df["combined_check"] = operator.or_(
57
- (df["clg_delta_temp"] >= df["clg_delta_sqrted"])
58
- # verify AHU is in OS2 only free cooling mode
59
- & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
60
- & (df[self.cooling_sig_col] < 0.1), # OR
61
- (df["clg_delta_temp"] >= df["clg_delta_sqrted"])
62
- # verify AHU is running in OS 1 at near full heat
63
- & (df[self.heating_sig_col] > 0.0) & (df[self.supply_vfd_speed_col] > 0.0),
64
- )
65
-
66
- # Rolling sum to count consecutive trues
67
- rolling_sum = (
68
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
69
- )
70
- # Set flag to 1 if rolling sum equals the window size
71
- df["fc14_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
72
-
73
- if self.troubleshoot_mode:
74
- print("Troubleshoot mode enabled - not removing helper columns")
75
- sys.stdout.flush()
76
- del df["clg_delta_temp"]
77
- del df["clg_delta_sqrted"]
78
- del df["combined_check"]
79
69
 
80
- return df
70
+ df["clg_delta_sqrted"] = (
71
+ np.sqrt(
72
+ self.coil_temp_enter_err_thres**2 + self.coil_temp_leav_err_thres**2
73
+ )
74
+ + self.delta_t_supply_fan
75
+ )
76
+
77
+ df["combined_check"] = operator.or_(
78
+ (df["clg_delta_temp"] >= df["clg_delta_sqrted"])
79
+ # verify AHU is in OS2 only free cooling mode
80
+ & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
81
+ & (df[self.cooling_sig_col] < 0.1), # OR
82
+ (df["clg_delta_temp"] >= df["clg_delta_sqrted"])
83
+ # verify AHU is running in OS 1 at near full heat
84
+ & (df[self.heating_sig_col] > 0.0)
85
+ & (df[self.supply_vfd_speed_col] > 0.0),
86
+ )
87
+
88
+ # Rolling sum to count consecutive trues
89
+ rolling_sum = (
90
+ df["combined_check"].rolling(window=self.rolling_window_size).sum()
91
+ )
92
+ # Set flag to 1 if rolling sum equals the window size
93
+ df["fc14_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
94
+
95
+ if self.troubleshoot_mode:
96
+ print("Troubleshoot mode enabled - not removing helper columns")
97
+ sys.stdout.flush()
98
+ del df["clg_delta_temp"]
99
+ del df["clg_delta_sqrted"]
100
+ del df["combined_check"]
101
+
102
+ return df
103
+
104
+ except MissingColumnError as e:
105
+ print(f"Error: {e.message}")
106
+ sys.stdout.flush()
107
+ raise e # Re-raise the exception so it can be caught by pytest
@@ -1,7 +1,9 @@
1
1
  import pandas as pd
2
2
  import numpy as np
3
- from open_fdd.air_handling_unit.faults.fault_condition import FaultCondition
4
- from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils
3
+ from open_fdd.air_handling_unit.faults.fault_condition import (
4
+ FaultCondition,
5
+ MissingColumnError,
6
+ )
5
7
  import sys
6
8
 
7
9
 
@@ -12,6 +14,7 @@ class FaultConditionNine(FaultCondition):
12
14
  """
13
15
 
14
16
  def __init__(self, dict_):
17
+ super().__init__()
15
18
  self.delta_t_supply_fan = float
16
19
  self.outdoor_degf_err_thres = float
17
20
  self.supply_degf_err_thres = float
@@ -25,44 +28,65 @@ class FaultConditionNine(FaultCondition):
25
28
 
26
29
  self.set_attributes(dict_)
27
30
 
28
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
29
- if self.troubleshoot_mode:
30
- self.troubleshoot_cols(df)
31
-
32
- # Check analog outputs [data with units of %] are floats only
33
- columns_to_check = [
34
- self.economizer_sig_col,
31
+ # Set required columns specific to this fault condition
32
+ self.required_columns = [
33
+ self.sat_setpoint_col,
34
+ self.oat_col,
35
35
  self.cooling_sig_col,
36
+ self.economizer_sig_col,
36
37
  ]
37
- self.check_analog_pct(df, columns_to_check)
38
38
 
39
- # Create helper columns
40
- df["oat_minus_oaterror"] = df[self.oat_col] - self.outdoor_degf_err_thres
41
- df["satsp_delta_saterr"] = (
42
- df[self.sat_setpoint_col]
43
- - self.delta_t_supply_fan
44
- + self.supply_degf_err_thres
45
- )
39
+ def get_required_columns(self) -> str:
40
+ """Returns a string representation of the required columns."""
41
+ return f"Required columns for FaultConditionNine: {', '.join(self.required_columns)}"
46
42
 
47
- df["combined_check"] = (
48
- (df["oat_minus_oaterror"] > df["satsp_delta_saterr"])
49
- # verify AHU is in OS2 only free cooling mode
50
- & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
51
- & (df[self.cooling_sig_col] < 0.1)
52
- )
43
+ def apply(self, df: pd.DataFrame) -> pd.DataFrame:
44
+ try:
45
+ # Ensure all required columns are present
46
+ self.check_required_columns(df)
53
47
 
54
- # Rolling sum to count consecutive trues
55
- rolling_sum = (
56
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
57
- )
58
- # Set flag to 1 if rolling sum equals the window size
59
- df["fc9_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
48
+ if self.troubleshoot_mode:
49
+ self.troubleshoot_cols(df)
60
50
 
61
- if self.troubleshoot_mode:
62
- print("Troubleshoot mode enabled - not removing helper columns")
63
- sys.stdout.flush()
64
- del df["oat_minus_oaterror"]
65
- del df["satsp_delta_saterr"]
66
- del df["combined_check"]
51
+ # Check analog outputs [data with units of %] are floats only
52
+ columns_to_check = [
53
+ self.economizer_sig_col,
54
+ self.cooling_sig_col,
55
+ ]
56
+ self.check_analog_pct(df, columns_to_check)
67
57
 
68
- return df
58
+ # Create helper columns
59
+ df["oat_minus_oaterror"] = df[self.oat_col] - self.outdoor_degf_err_thres
60
+ df["satsp_delta_saterr"] = (
61
+ df[self.sat_setpoint_col]
62
+ - self.delta_t_supply_fan
63
+ + self.supply_degf_err_thres
64
+ )
65
+
66
+ df["combined_check"] = (
67
+ (df["oat_minus_oaterror"] > df["satsp_delta_saterr"])
68
+ # verify AHU is in OS2 only free cooling mode
69
+ & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
70
+ & (df[self.cooling_sig_col] < 0.1)
71
+ )
72
+
73
+ # Rolling sum to count consecutive trues
74
+ rolling_sum = (
75
+ df["combined_check"].rolling(window=self.rolling_window_size).sum()
76
+ )
77
+ # Set flag to 1 if rolling sum equals the window size
78
+ df["fc9_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
79
+
80
+ if self.troubleshoot_mode:
81
+ print("Troubleshoot mode enabled - not removing helper columns")
82
+ sys.stdout.flush()
83
+ del df["oat_minus_oaterror"]
84
+ del df["satsp_delta_saterr"]
85
+ del df["combined_check"]
86
+
87
+ return df
88
+
89
+ except MissingColumnError as e:
90
+ print(f"Error: {e.message}")
91
+ sys.stdout.flush()
92
+ raise e # Re-raise the exception so it can be caught by pytest
@@ -1,5 +1,8 @@
1
1
  import pandas as pd
2
- from open_fdd.air_handling_unit.faults.fault_condition import FaultCondition
2
+ from open_fdd.air_handling_unit.faults.fault_condition import (
3
+ FaultCondition,
4
+ MissingColumnError,
5
+ )
3
6
  import sys
4
7
 
5
8
 
@@ -9,9 +12,7 @@ class FaultConditionOne(FaultCondition):
9
12
  """
10
13
 
11
14
  def __init__(self, dict_):
12
- """
13
- :param dict_:
14
- """
15
+ super().__init__()
15
16
  self.vfd_speed_percent_err_thres = float
16
17
  self.vfd_speed_percent_max = float
17
18
  self.duct_static_inches_err_thres = float
@@ -23,38 +24,58 @@ class FaultConditionOne(FaultCondition):
23
24
 
24
25
  self.set_attributes(dict_)
25
26
 
27
+ # Set required columns specific to this fault condition manually
28
+ self.required_columns = [
29
+ self.duct_static_col,
30
+ self.supply_vfd_speed_col,
31
+ self.duct_static_setpoint_col,
32
+ ]
33
+
34
+ def get_required_columns(self) -> str:
35
+ """Returns a string representation of the required columns."""
36
+ return f"Required columns for FaultConditionOne: {', '.join(self.required_columns)}"
37
+
26
38
  def apply(self, df: pd.DataFrame) -> pd.DataFrame:
27
- if self.troubleshoot_mode:
28
- self.troubleshoot_cols(df)
29
-
30
- # check analog outputs [data with units of %] are floats only
31
- columns_to_check = [self.supply_vfd_speed_col]
32
- self.check_analog_pct(df, columns_to_check)
33
-
34
- df["static_check_"] = (
35
- df[self.duct_static_col]
36
- < df[self.duct_static_setpoint_col] - self.duct_static_inches_err_thres
37
- )
38
- df["fan_check_"] = (
39
- df[self.supply_vfd_speed_col]
40
- >= self.vfd_speed_percent_max - self.vfd_speed_percent_err_thres
41
- )
42
-
43
- # Combined condition check
44
- df["combined_check"] = df["static_check_"] & df["fan_check_"]
45
-
46
- # Rolling sum to count consecutive trues
47
- rolling_sum = (
48
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
49
- )
50
- # Set flag to 1 if rolling sum equals the window size
51
- df["fc1_flag"] = (rolling_sum == self.rolling_window_size).astype(int)
52
-
53
- if self.troubleshoot_mode:
54
- print("Troubleshoot mode enabled - not removing helper columns")
55
- sys.stdout.flush()
56
- del df["static_check_"]
57
- del df["fan_check_"]
58
- del df["combined_check"]
39
+ try:
40
+ # Ensure all required columns are present
41
+ self.check_required_columns(df)
42
+
43
+ if self.troubleshoot_mode:
44
+ self.troubleshoot_cols(df)
45
+
46
+ # Check analog outputs [data with units of %] are floats only
47
+ columns_to_check = [self.supply_vfd_speed_col]
48
+ self.check_analog_pct(df, columns_to_check)
49
+
50
+ df["static_check_"] = (
51
+ df[self.duct_static_col]
52
+ < df[self.duct_static_setpoint_col] - self.duct_static_inches_err_thres
53
+ )
54
+ df["fan_check_"] = (
55
+ df[self.supply_vfd_speed_col]
56
+ >= self.vfd_speed_percent_max - self.vfd_speed_percent_err_thres
57
+ )
59
58
 
60
- return df
59
+ # Combined condition check
60
+ df["combined_check"] = df["static_check_"] & df["fan_check_"]
61
+
62
+ # Rolling sum to count consecutive trues
63
+ rolling_sum = (
64
+ df["combined_check"].rolling(window=self.rolling_window_size).sum()
65
+ )
66
+ # Set flag to 1 if rolling sum equals the window size
67
+ df["fc1_flag"] = (rolling_sum == self.rolling_window_size).astype(int)
68
+
69
+ if self.troubleshoot_mode:
70
+ print("Troubleshoot mode enabled - not removing helper columns")
71
+ sys.stdout.flush()
72
+ del df["static_check_"]
73
+ del df["fan_check_"]
74
+ del df["combined_check"]
75
+
76
+ return df
77
+
78
+ except MissingColumnError as e:
79
+ print(f"Error: {e.message}")
80
+ sys.stdout.flush()
81
+ raise e # Re-raise the exception so it can be caught by pytest