open-fdd 0.1.1__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 (48) 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.1.dist-info → open_fdd-0.1.3.dist-info}/METADATA +35 -5
  45. {open_fdd-0.1.1.dist-info → open_fdd-0.1.3.dist-info}/RECORD +48 -25
  46. {open_fdd-0.1.1.dist-info → open_fdd-0.1.3.dist-info}/LICENSE +0 -0
  47. {open_fdd-0.1.1.dist-info → open_fdd-0.1.3.dist-info}/WHEEL +0 -0
  48. {open_fdd-0.1.1.dist-info → open_fdd-0.1.3.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,8 @@
1
1
  import pandas as pd
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
2
+ from open_fdd.air_handling_unit.faults.fault_condition import (
3
+ FaultCondition,
4
+ MissingColumnError,
5
+ )
5
6
  import sys
6
7
 
7
8
 
@@ -12,6 +13,7 @@ class FaultConditionSeven(FaultCondition):
12
13
  """
13
14
 
14
15
  def __init__(self, dict_):
16
+ super().__init__()
15
17
  self.supply_degf_err_thres = float
16
18
  self.sat_col = str
17
19
  self.sat_setpoint_col = str
@@ -22,34 +24,55 @@ class FaultConditionSeven(FaultCondition):
22
24
 
23
25
  self.set_attributes(dict_)
24
26
 
27
+ # Set required columns specific to this fault condition
28
+ self.required_columns = [
29
+ self.sat_col,
30
+ self.sat_setpoint_col,
31
+ self.heating_sig_col,
32
+ self.supply_vfd_speed_col,
33
+ ]
34
+
35
+ def get_required_columns(self) -> str:
36
+ """Returns a string representation of the required columns."""
37
+ return f"Required columns for FaultConditionSeven: {', '.join(self.required_columns)}"
38
+
25
39
  def apply(self, df: pd.DataFrame) -> pd.DataFrame:
26
- if self.troubleshoot_mode:
27
- self.troubleshoot_cols(df)
28
-
29
- # Check analog outputs [data with units of %] are floats only
30
- columns_to_check = [self.supply_vfd_speed_col, self.heating_sig_col]
31
- self.check_analog_pct(df, columns_to_check)
32
-
33
- # Fault condition-specific checks / flags
34
- df["sat_check"] = df[self.sat_setpoint_col] - self.supply_degf_err_thres
35
-
36
- df["combined_check"] = (
37
- (df[self.sat_col] < df["sat_check"])
38
- & (df[self.heating_sig_col] > 0.9)
39
- & (df[self.supply_vfd_speed_col] > 0)
40
- )
41
-
42
- # Rolling sum to count consecutive trues
43
- rolling_sum = (
44
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
45
- )
46
- # Set flag to 1 if rolling sum equals the window size
47
- df["fc7_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
48
-
49
- if self.troubleshoot_mode:
50
- print("Troubleshoot mode enabled - not removing helper columns")
51
- sys.stdout.flush()
52
- del df["sat_check"]
53
- del df["combined_check"]
40
+ try:
41
+ # Ensure all required columns are present
42
+ self.check_required_columns(df)
43
+
44
+ if self.troubleshoot_mode:
45
+ self.troubleshoot_cols(df)
46
+
47
+ # Check analog outputs [data with units of %] are floats only
48
+ columns_to_check = [self.supply_vfd_speed_col, self.heating_sig_col]
49
+ self.check_analog_pct(df, columns_to_check)
50
+
51
+ # Fault condition-specific checks / flags
52
+ df["sat_check"] = df[self.sat_setpoint_col] - self.supply_degf_err_thres
54
53
 
55
- return df
54
+ df["combined_check"] = (
55
+ (df[self.sat_col] < df["sat_check"])
56
+ & (df[self.heating_sig_col] > 0.9)
57
+ & (df[self.supply_vfd_speed_col] > 0)
58
+ )
59
+
60
+ # Rolling sum to count consecutive trues
61
+ rolling_sum = (
62
+ df["combined_check"].rolling(window=self.rolling_window_size).sum()
63
+ )
64
+ # Set flag to 1 if rolling sum equals the window size
65
+ df["fc7_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
66
+
67
+ if self.troubleshoot_mode:
68
+ print("Troubleshoot mode enabled - not removing helper columns")
69
+ sys.stdout.flush()
70
+ del df["sat_check"]
71
+ del df["combined_check"]
72
+
73
+ return df
74
+
75
+ except MissingColumnError as e:
76
+ print(f"Error: {e.message}")
77
+ sys.stdout.flush()
78
+ raise e # Re-raise the exception so it can be caught by pytest
@@ -1,33 +1,29 @@
1
1
  import pandas as pd
2
- import pandas.api.types as pdtypes
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
2
  import operator
3
+ from open_fdd.air_handling_unit.faults.fault_condition import (
4
+ FaultCondition,
5
+ MissingColumnError,
6
+ )
6
7
  import sys
7
8
 
8
9
 
9
10
  class FaultConditionSix(FaultCondition):
10
11
  """Class provides the definitions for Fault Condition 6.
11
12
 
12
- This fault related to knowing the design air flow for
13
- ventilation AHU_MIN_CFM_DESIGN which comes from the
14
- design mech engineered records where then the fault
15
- tries to calculate that based on totalized measured
16
- AHU air flow and outside air fraction calc from
17
- AHU temp sensors. The fault could flag issues where
18
- flow stations are either not in calibration, temp
19
- sensors used in the OA frac calc, or possibly the AHU
20
- not bringing in design air flow when not operating in
21
- economizer free cooling modes. Troubleshoot by TAB tech
22
- verifying flow sensor and temp sensor precisions from
23
- 3rd party sensing tools.
24
-
25
- this fault is confusing if you want to play around
26
- in py code sandbox try this:
27
- https://gist.github.com/bbartling/e0fb8427b1e0d148a06e3f09121ed5dc#file-fc6-py
13
+ This fault related to knowing the design air flow for
14
+ ventilation AHU_MIN_CFM_DESIGN which comes from the
15
+ design mech engineered records where then the fault
16
+ tries to calculate that based on totalized measured
17
+ AHU air flow and outside air fraction calc from
18
+ AHU temp sensors. The fault could flag issues where
19
+ flow stations are either not in calibration, temp
20
+ sensors used in the OA frac calc, or possibly the AHU
21
+ not bringing in design air flow when not operating in
22
+ economizer free cooling modes.
28
23
  """
29
24
 
30
25
  def __init__(self, dict_):
26
+ super().__init__()
31
27
  self.airflow_err_thres = float
32
28
  self.ahu_min_oa_cfm_design = float
33
29
  self.outdoor_degf_err_thres = float
@@ -47,74 +43,102 @@ class FaultConditionSix(FaultCondition):
47
43
 
48
44
  self.set_attributes(dict_)
49
45
 
50
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
51
- if self.troubleshoot_mode:
52
- self.troubleshoot_cols(df)
53
-
54
- # check analog outputs [data with units of %] are floats only
55
- columns_to_check = [
46
+ # Set required columns specific to this fault condition
47
+ self.required_columns = [
48
+ self.supply_fan_air_volume_col,
49
+ self.mat_col,
50
+ self.oat_col,
51
+ self.rat_col,
56
52
  self.supply_vfd_speed_col,
57
53
  self.economizer_sig_col,
58
54
  self.heating_sig_col,
59
55
  self.cooling_sig_col,
60
56
  ]
61
57
 
62
- for col in columns_to_check:
63
- self.check_analog_pct(df, [col])
58
+ def get_required_columns(self) -> str:
59
+ """Returns a string representation of the required columns."""
60
+ return f"Required columns for FaultConditionSix: {', '.join(self.required_columns)}"
64
61
 
65
- # create helper columns
66
- df["rat_minus_oat"] = abs(df[self.rat_col] - df[self.oat_col])
67
- df["percent_oa_calc"] = (df[self.mat_col] - df[self.rat_col]) / (
68
- df[self.oat_col] - df[self.rat_col]
69
- )
62
+ def apply(self, df: pd.DataFrame) -> pd.DataFrame:
63
+ try:
64
+ # Ensure all required columns are present
65
+ self.check_required_columns(df)
66
+
67
+ if self.troubleshoot_mode:
68
+ self.troubleshoot_cols(df)
69
+
70
+ # Check analog outputs [data with units of %] are floats only
71
+ columns_to_check = [
72
+ self.supply_vfd_speed_col,
73
+ self.economizer_sig_col,
74
+ self.heating_sig_col,
75
+ self.cooling_sig_col,
76
+ ]
77
+
78
+ for col in columns_to_check:
79
+ self.check_analog_pct(df, [col])
80
+
81
+ # Create helper columns
82
+ df["rat_minus_oat"] = abs(df[self.rat_col] - df[self.oat_col])
83
+ df["percent_oa_calc"] = (df[self.mat_col] - df[self.rat_col]) / (
84
+ df[self.oat_col] - df[self.rat_col]
85
+ )
70
86
 
71
- # weed out any negative values
72
- df["percent_oa_calc"] = df["percent_oa_calc"].apply(lambda x: x if x > 0 else 0)
87
+ # Weed out any negative values
88
+ df["percent_oa_calc"] = df["percent_oa_calc"].apply(
89
+ lambda x: x if x > 0 else 0
90
+ )
73
91
 
74
- df["perc_OAmin"] = (
75
- self.ahu_min_oa_cfm_design / df[self.supply_fan_air_volume_col]
76
- )
92
+ df["perc_OAmin"] = (
93
+ self.ahu_min_oa_cfm_design / df[self.supply_fan_air_volume_col]
94
+ )
77
95
 
78
- df["percent_oa_calc_minus_perc_OAmin"] = abs(
79
- df["percent_oa_calc"] - df["perc_OAmin"]
80
- )
96
+ df["percent_oa_calc_minus_perc_OAmin"] = abs(
97
+ df["percent_oa_calc"] - df["perc_OAmin"]
98
+ )
81
99
 
82
- df["combined_check"] = operator.or_(
83
- # OS 1 htg mode
84
- (
85
- (df["rat_minus_oat"] >= self.oat_rat_delta_min)
86
- & (df["percent_oa_calc_minus_perc_OAmin"] > self.airflow_err_thres)
100
+ df["combined_check"] = operator.or_(
101
+ # OS 1 htg mode
102
+ (
103
+ (df["rat_minus_oat"] >= self.oat_rat_delta_min)
104
+ & (df["percent_oa_calc_minus_perc_OAmin"] > self.airflow_err_thres)
105
+ )
106
+ # Verify AHU is running in OS 1 htg mode in min OA
107
+ & (
108
+ (df[self.heating_sig_col] > 0.0)
109
+ & (df[self.supply_vfd_speed_col] > 0.0)
110
+ ), # OR
111
+ # OS 4 mech clg mode
112
+ (
113
+ (df["rat_minus_oat"] >= self.oat_rat_delta_min)
114
+ & (df["percent_oa_calc_minus_perc_OAmin"] > self.airflow_err_thres)
115
+ )
116
+ # Verify AHU is running in OS 4 clg mode in min OA
117
+ & (df[self.heating_sig_col] == 0.0)
118
+ & (df[self.cooling_sig_col] > 0.0)
119
+ & (df[self.supply_vfd_speed_col] > 0.0)
120
+ & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr),
87
121
  )
88
- # verify ahu is running in OS 1 htg mode in min OA
89
- & (
90
- (df[self.heating_sig_col] > 0.0) & (df[self.supply_vfd_speed_col] > 0.0)
91
- ), # OR
92
- # OS 4 mech clg mode
93
- (
94
- (df["rat_minus_oat"] >= self.oat_rat_delta_min)
95
- & (df["percent_oa_calc_minus_perc_OAmin"] > self.airflow_err_thres)
122
+
123
+ # Rolling sum to count consecutive trues
124
+ rolling_sum = (
125
+ df["combined_check"].rolling(window=self.rolling_window_size).sum()
96
126
  )
97
- # verify ahu is running in OS 4 clg mode in min OA
98
- & (df[self.heating_sig_col] == 0.0)
99
- & (df[self.cooling_sig_col] > 0.0)
100
- & (df[self.supply_vfd_speed_col] > 0.0)
101
- & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr),
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["fc6_flag"] = (rolling_sum == self.rolling_window_size).astype(int)
110
-
111
- if self.troubleshoot_mode:
112
- print("Troubleshoot mode enabled - not removing helper columns")
127
+ # Set flag to 1 if rolling sum equals the window size
128
+ df["fc6_flag"] = (rolling_sum == self.rolling_window_size).astype(int)
129
+
130
+ if self.troubleshoot_mode:
131
+ print("Troubleshoot mode enabled - not removing helper columns")
132
+ sys.stdout.flush()
133
+ del df["rat_minus_oat"]
134
+ del df["percent_oa_calc"]
135
+ del df["perc_OAmin"]
136
+ del df["percent_oa_calc_minus_perc_OAmin"]
137
+ del df["combined_check"]
138
+
139
+ return df
140
+
141
+ except MissingColumnError as e:
142
+ print(f"Error: {e.message}")
113
143
  sys.stdout.flush()
114
- del df["rat_minus_oat"]
115
- del df["percent_oa_calc"]
116
- del df["perc_OAmin"]
117
- del df["percent_oa_calc_minus_perc_OAmin"]
118
- del df["combined_check"]
119
-
120
- return df
144
+ 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 FaultConditionTen(FaultCondition):
12
14
  """
13
15
 
14
16
  def __init__(self, dict_):
17
+ super().__init__()
15
18
  self.outdoor_degf_err_thres = float
16
19
  self.mix_degf_err_thres = float
17
20
  self.oat_col = str
@@ -23,40 +26,62 @@ class FaultConditionTen(FaultCondition):
23
26
 
24
27
  self.set_attributes(dict_)
25
28
 
26
- 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 = [
32
- self.economizer_sig_col,
29
+ # Set required columns specific to this fault condition
30
+ self.required_columns = [
31
+ self.oat_col,
32
+ self.mat_col,
33
33
  self.cooling_sig_col,
34
+ self.economizer_sig_col,
34
35
  ]
35
- self.check_analog_pct(df, columns_to_check)
36
-
37
- df["abs_mat_minus_oat"] = abs(df[self.mat_col] - df[self.oat_col])
38
- df["mat_oat_sqrted"] = np.sqrt(
39
- self.mix_degf_err_thres**2 + self.outdoor_degf_err_thres**2
40
- )
41
-
42
- df["combined_check"] = (
43
- (df["abs_mat_minus_oat"] > df["mat_oat_sqrted"])
44
- # verify ahu is running in OS 3 clg mode in min OA
45
- & (df[self.cooling_sig_col] > 0.01)
46
- & (df[self.economizer_sig_col] > 0.9)
47
- )
48
-
49
- # Rolling sum to count consecutive trues
50
- rolling_sum = (
51
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
52
- )
53
- # Set flag to 1 if rolling sum equals the window size
54
- df["fc10_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
55
-
56
- if self.troubleshoot_mode:
57
- print("Troubleshoot mode enabled - not removing helper columns")
58
- del df["abs_mat_minus_oat"]
59
- del df["mat_oat_sqrted"]
60
- del df["combined_check"]
61
-
62
- return df
36
+
37
+ def get_required_columns(self) -> str:
38
+ """Returns a string representation of the required columns."""
39
+ return f"Required columns for FaultConditionTen: {', '.join(self.required_columns)}"
40
+
41
+ def apply(self, df: pd.DataFrame) -> pd.DataFrame:
42
+ try:
43
+ # Ensure all required columns are present
44
+ self.check_required_columns(df)
45
+
46
+ if self.troubleshoot_mode:
47
+ self.troubleshoot_cols(df)
48
+
49
+ # Check analog outputs [data with units of %] are floats only
50
+ columns_to_check = [
51
+ self.economizer_sig_col,
52
+ self.cooling_sig_col,
53
+ ]
54
+ self.check_analog_pct(df, columns_to_check)
55
+
56
+ df["abs_mat_minus_oat"] = abs(df[self.mat_col] - df[self.oat_col])
57
+ df["mat_oat_sqrted"] = np.sqrt(
58
+ self.mix_degf_err_thres**2 + self.outdoor_degf_err_thres**2
59
+ )
60
+
61
+ df["combined_check"] = (
62
+ (df["abs_mat_minus_oat"] > df["mat_oat_sqrted"])
63
+ # verify ahu is running in OS 3 clg mode in min OA
64
+ & (df[self.cooling_sig_col] > 0.01)
65
+ & (df[self.economizer_sig_col] > 0.9)
66
+ )
67
+
68
+ # Rolling sum to count consecutive trues
69
+ rolling_sum = (
70
+ df["combined_check"].rolling(window=self.rolling_window_size).sum()
71
+ )
72
+ # Set flag to 1 if rolling sum equals the window size
73
+ df["fc10_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
74
+
75
+ if self.troubleshoot_mode:
76
+ print("Troubleshoot mode enabled - not removing helper columns")
77
+ sys.stdout.flush()
78
+ del df["abs_mat_minus_oat"]
79
+ del df["mat_oat_sqrted"]
80
+ del df["combined_check"]
81
+
82
+ return df
83
+
84
+ except MissingColumnError as e:
85
+ print(f"Error: {e.message}")
86
+ sys.stdout.flush()
87
+ raise e # Re-raise the exception so it can be caught by pytest
@@ -1,8 +1,9 @@
1
1
  import pandas as pd
2
- import numpy as np
3
2
  import operator
4
- from open_fdd.air_handling_unit.faults.fault_condition import FaultCondition
5
- 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
+ )
6
7
  import sys
7
8
 
8
9
 
@@ -13,6 +14,7 @@ class FaultConditionThirteen(FaultCondition):
13
14
  """
14
15
 
15
16
  def __init__(self, dict_):
17
+ super().__init__()
16
18
  self.supply_degf_err_thres = float
17
19
  self.ahu_min_oa_dpr = float
18
20
  self.sat_col = str
@@ -24,43 +26,66 @@ class FaultConditionThirteen(FaultCondition):
24
26
 
25
27
  self.set_attributes(dict_)
26
28
 
27
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
28
- if self.troubleshoot_mode:
29
- self.troubleshoot_cols(df)
30
-
31
- # Check analog outputs [data with units of %] are floats only
32
- columns_to_check = [
33
- self.economizer_sig_col,
29
+ # Set required columns specific to this fault condition
30
+ self.required_columns = [
31
+ self.sat_col,
32
+ self.sat_setpoint_col,
34
33
  self.cooling_sig_col,
34
+ self.economizer_sig_col,
35
35
  ]
36
- self.check_analog_pct(df, columns_to_check)
37
36
 
38
- # Create helper columns
39
- df["sat_greater_than_sp_calc"] = (
40
- df[self.sat_col] > df[self.sat_setpoint_col] + self.supply_degf_err_thres
41
- )
37
+ def get_required_columns(self) -> str:
38
+ """Returns a string representation of the required columns."""
39
+ return f"Required columns for FaultConditionThirteen: {', '.join(self.required_columns)}"
42
40
 
43
- df["combined_check"] = operator.or_(
44
- ((df["sat_greater_than_sp_calc"]))
45
- # OS4 AHU state clg @ min OA
46
- & (df[self.cooling_sig_col] > 0.01)
47
- & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr), # OR
48
- ((df["sat_greater_than_sp_calc"]))
49
- # verify ahu is running in OS 3 clg mode in 100 OA
50
- & (df[self.cooling_sig_col] > 0.01) & (df[self.economizer_sig_col] > 0.9),
51
- )
41
+ def apply(self, df: pd.DataFrame) -> pd.DataFrame:
42
+ try:
43
+ # Ensure all required columns are present
44
+ self.check_required_columns(df)
52
45
 
53
- # Rolling sum to count consecutive trues
54
- rolling_sum = (
55
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
56
- )
57
- # Set flag to 1 if rolling sum equals the window size
58
- df["fc13_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
46
+ if self.troubleshoot_mode:
47
+ self.troubleshoot_cols(df)
59
48
 
60
- if self.troubleshoot_mode:
61
- print("Troubleshoot mode enabled - not removing helper columns")
62
- sys.stdout.flush()
63
- del df["sat_greater_than_sp_calc"]
64
- del df["combined_check"]
49
+ # Check analog outputs [data with units of %] are floats only
50
+ columns_to_check = [
51
+ self.economizer_sig_col,
52
+ self.cooling_sig_col,
53
+ ]
54
+ self.check_analog_pct(df, columns_to_check)
65
55
 
66
- return df
56
+ # Create helper columns
57
+ df["sat_greater_than_sp_calc"] = (
58
+ df[self.sat_col]
59
+ > df[self.sat_setpoint_col] + self.supply_degf_err_thres
60
+ )
61
+
62
+ df["combined_check"] = operator.or_(
63
+ ((df["sat_greater_than_sp_calc"]))
64
+ # OS4 AHU state clg @ min OA
65
+ & (df[self.cooling_sig_col] > 0.01)
66
+ & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr), # OR
67
+ ((df["sat_greater_than_sp_calc"]))
68
+ # verify ahu is running in OS 3 clg mode in 100 OA
69
+ & (df[self.cooling_sig_col] > 0.01)
70
+ & (df[self.economizer_sig_col] > 0.9),
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["fc13_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["sat_greater_than_sp_calc"]
84
+ del df["combined_check"]
85
+
86
+ return df
87
+
88
+ except MissingColumnError as e:
89
+ print(f"Error: {e.message}")
90
+ sys.stdout.flush()
91
+ raise e # Re-raise the exception so it can be caught by pytest
@@ -1,6 +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
3
+ from open_fdd.air_handling_unit.faults.fault_condition import (
4
+ FaultCondition,
5
+ MissingColumnError,
6
+ )
4
7
  import sys
5
8
 
6
9
 
@@ -10,6 +13,7 @@ class FaultConditionThree(FaultCondition):
10
13
  """
11
14
 
12
15
  def __init__(self, dict_):
16
+ super().__init__()
13
17
  self.mix_degf_err_thres = float
14
18
  self.return_degf_err_thres = float
15
19
  self.outdoor_degf_err_thres = float
@@ -22,37 +26,58 @@ class FaultConditionThree(FaultCondition):
22
26
 
23
27
  self.set_attributes(dict_)
24
28
 
29
+ # Set required columns specific to this fault condition
30
+ self.required_columns = [
31
+ self.mat_col,
32
+ self.rat_col,
33
+ self.oat_col,
34
+ self.supply_vfd_speed_col,
35
+ ]
36
+
37
+ def get_required_columns(self) -> str:
38
+ """Returns a string representation of the required columns."""
39
+ return f"Required columns for FaultConditionThree: {', '.join(self.required_columns)}"
40
+
25
41
  def apply(self, df: pd.DataFrame) -> pd.DataFrame:
26
- if self.troubleshoot_mode:
27
- self.troubleshoot_cols(df)
28
-
29
- # Check analog outputs [data with units of %] are floats only
30
- columns_to_check = [self.supply_vfd_speed_col]
31
- self.check_analog_pct(df, columns_to_check)
32
-
33
- # Fault condition-specific checks / flags
34
- df["mat_check"] = df[self.mat_col] - self.mix_degf_err_thres
35
- df["temp_min_check"] = np.maximum(
36
- df[self.rat_col] + self.return_degf_err_thres,
37
- df[self.oat_col] + self.outdoor_degf_err_thres,
38
- )
39
-
40
- df["combined_check"] = (df["mat_check"] > df["temp_min_check"]) & (
41
- df[self.supply_vfd_speed_col] > 0.01
42
- )
43
-
44
- # Rolling sum to count consecutive trues
45
- rolling_sum = (
46
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
47
- )
48
- # Set flag to 1 if rolling sum equals the window size
49
- df["fc3_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
50
-
51
- if self.troubleshoot_mode:
52
- print("Troubleshoot mode enabled - not removing helper columns")
53
- sys.stdout.flush()
54
- del df["mat_check"]
55
- del df["temp_min_check"]
56
- del df["combined_check"]
42
+ try:
43
+ # Ensure all required columns are present
44
+ self.check_required_columns(df)
45
+
46
+ if self.troubleshoot_mode:
47
+ self.troubleshoot_cols(df)
48
+
49
+ # Check analog outputs [data with units of %] are floats only
50
+ columns_to_check = [self.supply_vfd_speed_col]
51
+ self.check_analog_pct(df, columns_to_check)
52
+
53
+ # Fault condition-specific checks / flags
54
+ df["mat_check"] = df[self.mat_col] - self.mix_degf_err_thres
55
+ df["temp_min_check"] = np.maximum(
56
+ df[self.rat_col] + self.return_degf_err_thres,
57
+ df[self.oat_col] + self.outdoor_degf_err_thres,
58
+ )
57
59
 
58
- return df
60
+ df["combined_check"] = (df["mat_check"] > df["temp_min_check"]) & (
61
+ df[self.supply_vfd_speed_col] > 0.01
62
+ )
63
+
64
+ # Rolling sum to count consecutive trues
65
+ rolling_sum = (
66
+ df["combined_check"].rolling(window=self.rolling_window_size).sum()
67
+ )
68
+ # Set flag to 1 if rolling sum equals the window size
69
+ df["fc3_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
70
+
71
+ if self.troubleshoot_mode:
72
+ print("Troubleshoot mode enabled - not removing helper columns")
73
+ sys.stdout.flush()
74
+ del df["mat_check"]
75
+ del df["temp_min_check"]
76
+ del df["combined_check"]
77
+
78
+ return df
79
+
80
+ except MissingColumnError as e:
81
+ print(f"Error: {e.message}")
82
+ sys.stdout.flush()
83
+ raise e # Re-raise the exception so it can be caught by pytest