open-fdd 0.1.3__py3-none-any.whl → 0.1.4__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 (38) hide show
  1. open_fdd/air_handling_unit/faults/__init__.py +2253 -0
  2. open_fdd/air_handling_unit/faults/fault_condition.py +12 -10
  3. open_fdd/air_handling_unit/faults/fault_condition_eight.py +38 -2
  4. open_fdd/air_handling_unit/faults/fault_condition_eleven.py +38 -2
  5. open_fdd/air_handling_unit/faults/fault_condition_fifteen.py +38 -2
  6. open_fdd/air_handling_unit/faults/fault_condition_five.py +38 -2
  7. open_fdd/air_handling_unit/faults/fault_condition_four.py +34 -2
  8. open_fdd/air_handling_unit/faults/fault_condition_fourteen.py +38 -2
  9. open_fdd/air_handling_unit/faults/fault_condition_nine.py +38 -2
  10. open_fdd/air_handling_unit/faults/fault_condition_one.py +37 -6
  11. open_fdd/air_handling_unit/faults/fault_condition_seven.py +38 -2
  12. open_fdd/air_handling_unit/faults/fault_condition_six.py +39 -2
  13. open_fdd/air_handling_unit/faults/fault_condition_ten.py +39 -3
  14. open_fdd/air_handling_unit/faults/fault_condition_thirteen.py +38 -2
  15. open_fdd/air_handling_unit/faults/fault_condition_three.py +32 -2
  16. open_fdd/air_handling_unit/faults/fault_condition_twelve.py +40 -3
  17. open_fdd/air_handling_unit/faults/fault_condition_two.py +32 -2
  18. open_fdd/air_handling_unit/faults/helper_utils.py +1 -29
  19. open_fdd/tests/ahu/test_ahu_fc1.py +1 -1
  20. open_fdd/tests/ahu/test_ahu_fc10.py +1 -1
  21. open_fdd/tests/ahu/test_ahu_fc11.py +1 -1
  22. open_fdd/tests/ahu/test_ahu_fc12.py +1 -1
  23. open_fdd/tests/ahu/test_ahu_fc13.py +1 -1
  24. open_fdd/tests/ahu/test_ahu_fc14.py +1 -1
  25. open_fdd/tests/ahu/test_ahu_fc15.py +1 -1
  26. open_fdd/tests/ahu/test_ahu_fc2.py +1 -1
  27. open_fdd/tests/ahu/test_ahu_fc3.py +1 -1
  28. open_fdd/tests/ahu/test_ahu_fc4.py +199 -127
  29. open_fdd/tests/ahu/test_ahu_fc5.py +1 -1
  30. open_fdd/tests/ahu/test_ahu_fc6.py +2 -2
  31. open_fdd/tests/ahu/test_ahu_fc7.py +1 -1
  32. open_fdd/tests/ahu/test_ahu_fc8.py +1 -1
  33. open_fdd/tests/ahu/test_ahu_fc9.py +1 -1
  34. {open_fdd-0.1.3.dist-info → open_fdd-0.1.4.dist-info}/METADATA +2 -3
  35. {open_fdd-0.1.3.dist-info → open_fdd-0.1.4.dist-info}/RECORD +38 -38
  36. {open_fdd-0.1.3.dist-info → open_fdd-0.1.4.dist-info}/LICENSE +0 -0
  37. {open_fdd-0.1.3.dist-info → open_fdd-0.1.4.dist-info}/WHEEL +0 -0
  38. {open_fdd-0.1.3.dist-info → open_fdd-0.1.4.dist-info}/top_level.txt +0 -0
@@ -3,6 +3,8 @@ import pandas.api.types as pdtypes
3
3
  from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils
4
4
  import sys
5
5
 
6
+ """see __init__.py for fault classes"""
7
+
6
8
 
7
9
  class MissingColumnError(Exception):
8
10
  """Custom exception raised when a required column is missing or None."""
@@ -12,6 +14,14 @@ class MissingColumnError(Exception):
12
14
  super().__init__(self.message)
13
15
 
14
16
 
17
+ class InvalidParameterError(Exception):
18
+ """Custom exception raised when a parameter is not valid (e.g., not a float)."""
19
+
20
+ def __init__(self, message):
21
+ self.message = message
22
+ super().__init__(self.message)
23
+
24
+
15
25
  class FaultCondition:
16
26
  """Parent class for Fault Conditions. Methods are inherited to all children."""
17
27
 
@@ -36,11 +46,7 @@ class FaultCondition:
36
46
  raise MissingColumnError(f"Missing required columns: {missing_columns}")
37
47
 
38
48
  def troubleshoot_cols(self, df):
39
- """print troubleshoot columns mapping
40
-
41
- :param df:
42
- :return:
43
- """
49
+ """Print troubleshoot columns mapping."""
44
50
  print("Troubleshoot mode enabled - not removing helper columns")
45
51
  for col in df.columns:
46
52
  print(
@@ -54,11 +60,7 @@ class FaultCondition:
54
60
  sys.stdout.flush()
55
61
 
56
62
  def check_analog_pct(self, df, columns):
57
- """check analog outputs [data with units of %] are floats only
58
-
59
- :param columns:
60
- :return:
61
- """
63
+ """Check analog outputs [data with units of %] are floats only."""
62
64
  helper = HelperUtils()
63
65
  for col in columns:
64
66
  if not pdtypes.is_float_dtype(df[col]):
@@ -26,6 +26,20 @@ class FaultConditionEight(FaultCondition):
26
26
  self.troubleshoot_mode = bool # default should be False
27
27
  self.rolling_window_size = int
28
28
 
29
+ self.equation_string = (
30
+ "fc8_flag = 1 if |SAT - MAT - ΔT_fan| > √(εSAT² + εMAT²) "
31
+ "in economizer mode for N consecutive values else 0 \n"
32
+ )
33
+ self.description_string = (
34
+ "Fault Condition 8: Supply air temperature and mixed air temperature should "
35
+ "be approximately equal in economizer mode \n"
36
+ )
37
+ self.required_column_description = (
38
+ "Required inputs are the mixed air temperature, supply air temperature, "
39
+ "economizer signal, and cooling signal \n"
40
+ )
41
+ self.error_string = f"One or more required columns are missing or None \n"
42
+
29
43
  self.set_attributes(dict_)
30
44
 
31
45
  # Set required columns specific to this fault condition
@@ -36,9 +50,31 @@ class FaultConditionEight(FaultCondition):
36
50
  self.cooling_sig_col,
37
51
  ]
38
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
+
39
70
  def get_required_columns(self) -> str:
40
71
  """Returns a string representation of the required columns."""
41
- return f"Required columns for FaultConditionEight: {', '.join(self.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
+ )
42
78
 
43
79
  def apply(self, df: pd.DataFrame) -> pd.DataFrame:
44
80
  try:
@@ -88,4 +124,4 @@ class FaultConditionEight(FaultCondition):
88
124
  except MissingColumnError as e:
89
125
  print(f"Error: {e.message}")
90
126
  sys.stdout.flush()
91
- raise e # Re-raise the exception so it can be caught by pytest
127
+ raise e
@@ -25,6 +25,20 @@ class FaultConditionEleven(FaultCondition):
25
25
  self.troubleshoot_mode = bool # default False
26
26
  self.rolling_window_size = int
27
27
 
28
+ self.equation_string = (
29
+ "fc11_flag = 1 if OAT < (SATSP - ΔT_fan - εSAT) in "
30
+ "economizer cooling mode for N consecutive values else 0 \n"
31
+ )
32
+ self.description_string = (
33
+ "Fault Condition 11: Outside air temperature too low for 100% outdoor air cooling "
34
+ "in economizer cooling mode (Economizer performance fault) \n"
35
+ )
36
+ self.required_column_description = (
37
+ "Required inputs are the supply air temperature setpoint, outside air temperature, "
38
+ "cooling signal, and economizer signal \n"
39
+ )
40
+ self.error_string = f"One or more required columns are missing or None \n"
41
+
28
42
  self.set_attributes(dict_)
29
43
 
30
44
  # Set required columns specific to this fault condition
@@ -35,9 +49,31 @@ class FaultConditionEleven(FaultCondition):
35
49
  self.economizer_sig_col,
36
50
  ]
37
51
 
52
+ # Check if any of the required columns are None
53
+ if any(col is None for col in self.required_columns):
54
+ raise MissingColumnError(
55
+ f"{self.error_string}"
56
+ f"{self.equation_string}"
57
+ f"{self.description_string}"
58
+ f"{self.required_column_description}"
59
+ f"{self.required_columns}"
60
+ )
61
+
62
+ # Ensure all required columns are strings
63
+ self.required_columns = [str(col) for col in self.required_columns]
64
+
65
+ self.mapped_columns = (
66
+ f"Your config dictionary is mapped as: {', '.join(self.required_columns)}"
67
+ )
68
+
38
69
  def get_required_columns(self) -> str:
39
70
  """Returns a string representation of the required columns."""
40
- return f"Required columns for FaultConditionEleven: {', '.join(self.required_columns)}"
71
+ return (
72
+ f"{self.equation_string}"
73
+ f"{self.description_string}"
74
+ f"{self.required_column_description}"
75
+ f"{self.mapped_columns}"
76
+ )
41
77
 
42
78
  def apply(self, df: pd.DataFrame) -> pd.DataFrame:
43
79
  try:
@@ -87,4 +123,4 @@ class FaultConditionEleven(FaultCondition):
87
123
  except MissingColumnError as e:
88
124
  print(f"Error: {e.message}")
89
125
  sys.stdout.flush()
90
- raise e # Re-raise the exception so it can be caught by pytest
126
+ raise e
@@ -28,6 +28,20 @@ class FaultConditionFifteen(FaultCondition):
28
28
  self.troubleshoot_mode = bool # default to False
29
29
  self.rolling_window_size = int
30
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
+
31
45
  self.set_attributes(dict_)
32
46
 
33
47
  # Set required columns specific to this fault condition
@@ -40,9 +54,31 @@ class FaultConditionFifteen(FaultCondition):
40
54
  self.supply_vfd_speed_col,
41
55
  ]
42
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
+
43
74
  def get_required_columns(self) -> str:
44
75
  """Returns a string representation of the required columns."""
45
- return f"Required columns for FaultConditionFifteen: {', '.join(self.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
+ )
46
82
 
47
83
  def apply(self, df: pd.DataFrame) -> pd.DataFrame:
48
84
  try:
@@ -113,4 +149,4 @@ class FaultConditionFifteen(FaultCondition):
113
149
  except MissingColumnError as e:
114
150
  print(f"Error: {e.message}")
115
151
  sys.stdout.flush()
116
- raise e # Re-raise the exception so it can be caught by pytest
152
+ raise e
@@ -26,6 +26,20 @@ class FaultConditionFive(FaultCondition):
26
26
  self.troubleshoot_mode = bool # default to False
27
27
  self.rolling_window_size = int
28
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
+
29
43
  self.set_attributes(dict_)
30
44
 
31
45
  # Set required columns specific to this fault condition
@@ -36,9 +50,31 @@ class FaultConditionFive(FaultCondition):
36
50
  self.supply_vfd_speed_col,
37
51
  ]
38
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
+
39
70
  def get_required_columns(self) -> str:
40
71
  """Returns a string representation of the required columns."""
41
- return f"Required columns for FaultConditionFive: {', '.join(self.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
+ )
42
78
 
43
79
  def apply(self, df: pd.DataFrame) -> pd.DataFrame:
44
80
  try:
@@ -84,4 +120,4 @@ class FaultConditionFive(FaultCondition):
84
120
  except MissingColumnError as e:
85
121
  print(f"Error: {e.message}")
86
122
  sys.stdout.flush()
87
- raise e # Re-raise the exception so it can be caught by pytest
123
+ raise e
@@ -26,6 +26,17 @@ class FaultConditionFour(FaultCondition):
26
26
  self.supply_vfd_speed_col = str
27
27
  self.troubleshoot_mode = bool # default to False
28
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
+
29
40
  self.set_attributes(dict_)
30
41
 
31
42
  # Set required columns, making heating and cooling optional
@@ -40,9 +51,30 @@ class FaultConditionFour(FaultCondition):
40
51
  if self.cooling_sig_col:
41
52
  self.required_columns.append(self.cooling_sig_col)
42
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
+
43
70
  def get_required_columns(self) -> str:
44
71
  """Returns a string representation of the required columns."""
45
- return f"Required columns for FaultConditionFour: {', '.join(self.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
+ )
46
78
 
47
79
  def apply(self, df: pd.DataFrame) -> pd.DataFrame:
48
80
  try:
@@ -133,4 +165,4 @@ class FaultConditionFour(FaultCondition):
133
165
  except MissingColumnError as e:
134
166
  print(f"Error: {e.message}")
135
167
  sys.stdout.flush()
136
- raise e # Re-raise the exception so it can be caught by pytest
168
+ raise e
@@ -29,6 +29,20 @@ class FaultConditionFourteen(FaultCondition):
29
29
  self.troubleshoot_mode = bool # default to False
30
30
  self.rolling_window_size = int
31
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
+
32
46
  self.set_attributes(dict_)
33
47
 
34
48
  # Set required columns specific to this fault condition
@@ -41,9 +55,31 @@ class FaultConditionFourteen(FaultCondition):
41
55
  self.supply_vfd_speed_col,
42
56
  ]
43
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
+
44
75
  def get_required_columns(self) -> str:
45
76
  """Returns a string representation of the required columns."""
46
- return f"Required columns for FaultConditionFourteen: {', '.join(self.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
+ )
47
83
 
48
84
  def apply(self, df: pd.DataFrame) -> pd.DataFrame:
49
85
  try:
@@ -104,4 +140,4 @@ class FaultConditionFourteen(FaultCondition):
104
140
  except MissingColumnError as e:
105
141
  print(f"Error: {e.message}")
106
142
  sys.stdout.flush()
107
- raise e # Re-raise the exception so it can be caught by pytest
143
+ raise e
@@ -26,6 +26,20 @@ class FaultConditionNine(FaultCondition):
26
26
  self.troubleshoot_mode = bool # default should be False
27
27
  self.rolling_window_size = int
28
28
 
29
+ self.equation_string = (
30
+ "fc9_flag = 1 if OAT > (SATSP - ΔT_fan + εSAT) "
31
+ "in free cooling mode for N consecutive values else 0 \n"
32
+ )
33
+ self.description_string = (
34
+ "Fault Condition 9: Outside air temperature too high in free cooling mode "
35
+ "without additional mechanical cooling in economizer mode \n"
36
+ )
37
+ self.required_column_description = (
38
+ "Required inputs are the supply air temperature setpoint, outside air temperature, "
39
+ "cooling signal, and economizer signal \n"
40
+ )
41
+ self.error_string = f"One or more required columns are missing or None \n"
42
+
29
43
  self.set_attributes(dict_)
30
44
 
31
45
  # Set required columns specific to this fault condition
@@ -36,9 +50,31 @@ class FaultConditionNine(FaultCondition):
36
50
  self.economizer_sig_col,
37
51
  ]
38
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
+
39
70
  def get_required_columns(self) -> str:
40
71
  """Returns a string representation of the required columns."""
41
- return f"Required columns for FaultConditionNine: {', '.join(self.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
+ )
42
78
 
43
79
  def apply(self, df: pd.DataFrame) -> pd.DataFrame:
44
80
  try:
@@ -89,4 +125,4 @@ class FaultConditionNine(FaultCondition):
89
125
  except MissingColumnError as e:
90
126
  print(f"Error: {e.message}")
91
127
  sys.stdout.flush()
92
- raise e # Re-raise the exception so it can be caught by pytest
128
+ raise e
@@ -22,6 +22,13 @@ class FaultConditionOne(FaultCondition):
22
22
  self.troubleshoot_mode = bool # default should be False
23
23
  self.rolling_window_size = int
24
24
 
25
+ self.equation_string = "fc1_flag = 1 if (DSP < DPSP - εDSP) and (VFDSPD >= VFDSPD_max - εVFDSPD) for N consecutive values else 0 \n"
26
+ self.description_string = (
27
+ "Fault Condition 1: Duct static too low at fan at full speed \n"
28
+ )
29
+ self.required_column_description = "Required inputs are the duct static pressure, setpoint, and supply fan VFD speed \n"
30
+ self.error_string = f"One or more required columns are missing or None \n"
31
+
25
32
  self.set_attributes(dict_)
26
33
 
27
34
  # Set required columns specific to this fault condition manually
@@ -31,9 +38,31 @@ class FaultConditionOne(FaultCondition):
31
38
  self.duct_static_setpoint_col,
32
39
  ]
33
40
 
41
+ # Check if any of the required columns are None
42
+ if any(col is None for col in self.required_columns):
43
+ raise MissingColumnError(
44
+ f"{self.error_string}"
45
+ f"{self.equation_string}"
46
+ f"{self.description_string}"
47
+ f"{self.required_column_description}"
48
+ f"{self.required_columns}"
49
+ )
50
+
51
+ # Ensure all required columns are strings
52
+ self.required_columns = [str(col) for col in self.required_columns]
53
+
54
+ self.mapped_columns = (
55
+ f"Your config dictionary is mapped as: {', '.join(self.required_columns)}"
56
+ )
57
+
34
58
  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)}"
59
+ """called from IPython to print out"""
60
+ return (
61
+ f"{self.equation_string}"
62
+ f"{self.description_string}"
63
+ f"{self.required_column_description}"
64
+ f"{self.mapped_columns}"
65
+ )
37
66
 
38
67
  def apply(self, df: pd.DataFrame) -> pd.DataFrame:
39
68
  try:
@@ -69,13 +98,15 @@ class FaultConditionOne(FaultCondition):
69
98
  if self.troubleshoot_mode:
70
99
  print("Troubleshoot mode enabled - not removing helper columns")
71
100
  sys.stdout.flush()
72
- del df["static_check_"]
73
- del df["fan_check_"]
74
- del df["combined_check"]
101
+
102
+ # Optionally remove temporary columns
103
+ df.drop(
104
+ columns=["static_check_", "fan_check_", "combined_check"], inplace=True
105
+ )
75
106
 
76
107
  return df
77
108
 
78
109
  except MissingColumnError as e:
79
110
  print(f"Error: {e.message}")
80
111
  sys.stdout.flush()
81
- raise e # Re-raise the exception so it can be caught by pytest
112
+ raise e
@@ -22,6 +22,20 @@ class FaultConditionSeven(FaultCondition):
22
22
  self.troubleshoot_mode = bool # default to False
23
23
  self.rolling_window_size = int
24
24
 
25
+ self.equation_string = (
26
+ "fc7_flag = 1 if SAT < (SATSP - εSAT) in full heating mode "
27
+ "and VFD speed > 0 for N consecutive values else 0 \n"
28
+ )
29
+ self.description_string = (
30
+ "Fault Condition 7: Supply air temperature too low in full heating mode "
31
+ "with heating valve fully open \n"
32
+ )
33
+ self.required_column_description = (
34
+ "Required inputs are the supply air temperature, supply air temperature setpoint, "
35
+ "heating signal, and supply fan VFD speed \n"
36
+ )
37
+ self.error_string = f"One or more required columns are missing or None \n"
38
+
25
39
  self.set_attributes(dict_)
26
40
 
27
41
  # Set required columns specific to this fault condition
@@ -32,9 +46,31 @@ class FaultConditionSeven(FaultCondition):
32
46
  self.supply_vfd_speed_col,
33
47
  ]
34
48
 
49
+ # Check if any of the required columns are None
50
+ if any(col is None for col in self.required_columns):
51
+ raise MissingColumnError(
52
+ f"{self.error_string}"
53
+ f"{self.equation_string}"
54
+ f"{self.description_string}"
55
+ f"{self.required_column_description}"
56
+ f"{self.required_columns}"
57
+ )
58
+
59
+ # Ensure all required columns are strings
60
+ self.required_columns = [str(col) for col in self.required_columns]
61
+
62
+ self.mapped_columns = (
63
+ f"Your config dictionary is mapped as: {', '.join(self.required_columns)}"
64
+ )
65
+
35
66
  def get_required_columns(self) -> str:
36
67
  """Returns a string representation of the required columns."""
37
- return f"Required columns for FaultConditionSeven: {', '.join(self.required_columns)}"
68
+ return (
69
+ f"{self.equation_string}"
70
+ f"{self.description_string}"
71
+ f"{self.required_column_description}"
72
+ f"{self.mapped_columns}"
73
+ )
38
74
 
39
75
  def apply(self, df: pd.DataFrame) -> pd.DataFrame:
40
76
  try:
@@ -75,4 +111,4 @@ class FaultConditionSeven(FaultCondition):
75
111
  except MissingColumnError as e:
76
112
  print(f"Error: {e.message}")
77
113
  sys.stdout.flush()
78
- raise e # Re-raise the exception so it can be caught by pytest
114
+ raise e
@@ -41,6 +41,21 @@ class FaultConditionSix(FaultCondition):
41
41
  self.troubleshoot_mode = bool # default should be False
42
42
  self.rolling_window_size = int
43
43
 
44
+ self.equation_string = (
45
+ "fc6_flag = 1 if |OA_frac_calc - OA_min| > airflow_err_thres "
46
+ "in non-economizer modes, considering htg and mech clg OS \n"
47
+ )
48
+ self.description_string = (
49
+ "Fault Condition 6: Issues detected with OA fraction calculation or AHU "
50
+ "not maintaining design air flow in non-economizer conditions \n"
51
+ )
52
+ self.required_column_description = (
53
+ "Required inputs are the supply fan air volume, mixed air temperature, "
54
+ "outside air temperature, return air temperature, and VFD speed. "
55
+ "Optional inputs include economizer signal, heating signal, and cooling signal \n"
56
+ )
57
+ self.error_string = f"One or more required columns are missing or None \n"
58
+
44
59
  self.set_attributes(dict_)
45
60
 
46
61
  # Set required columns specific to this fault condition
@@ -55,9 +70,31 @@ class FaultConditionSix(FaultCondition):
55
70
  self.cooling_sig_col,
56
71
  ]
57
72
 
73
+ # Check if any of the required columns are None
74
+ if any(col is None for col in self.required_columns):
75
+ raise MissingColumnError(
76
+ f"{self.error_string}"
77
+ f"{self.equation_string}"
78
+ f"{self.description_string}"
79
+ f"{self.required_column_description}"
80
+ f"{self.required_columns}"
81
+ )
82
+
83
+ # Ensure all required columns are strings
84
+ self.required_columns = [str(col) for col in self.required_columns]
85
+
86
+ self.mapped_columns = (
87
+ f"Your config dictionary is mapped as: {', '.join(self.required_columns)}"
88
+ )
89
+
58
90
  def get_required_columns(self) -> str:
59
91
  """Returns a string representation of the required columns."""
60
- return f"Required columns for FaultConditionSix: {', '.join(self.required_columns)}"
92
+ return (
93
+ f"{self.equation_string}"
94
+ f"{self.description_string}"
95
+ f"{self.required_column_description}"
96
+ f"{self.mapped_columns}"
97
+ )
61
98
 
62
99
  def apply(self, df: pd.DataFrame) -> pd.DataFrame:
63
100
  try:
@@ -141,4 +178,4 @@ class FaultConditionSix(FaultCondition):
141
178
  except MissingColumnError as e:
142
179
  print(f"Error: {e.message}")
143
180
  sys.stdout.flush()
144
- raise e # Re-raise the exception so it can be caught by pytest
181
+ raise e