open-fdd 0.1.1__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 (63) hide show
  1. open_fdd/air_handling_unit/faults/__init__.py +2253 -0
  2. open_fdd/air_handling_unit/faults/fault_condition.py +38 -18
  3. open_fdd/air_handling_unit/faults/fault_condition_eight.py +91 -31
  4. open_fdd/air_handling_unit/faults/fault_condition_eleven.py +93 -35
  5. open_fdd/air_handling_unit/faults/fault_condition_fifteen.py +111 -49
  6. open_fdd/air_handling_unit/faults/fault_condition_five.py +89 -34
  7. open_fdd/air_handling_unit/faults/fault_condition_four.py +136 -61
  8. open_fdd/air_handling_unit/faults/fault_condition_fourteen.py +103 -40
  9. open_fdd/air_handling_unit/faults/fault_condition_nine.py +95 -35
  10. open_fdd/air_handling_unit/faults/fault_condition_one.py +83 -31
  11. open_fdd/air_handling_unit/faults/fault_condition_seven.py +85 -26
  12. open_fdd/air_handling_unit/faults/fault_condition_six.py +134 -73
  13. open_fdd/air_handling_unit/faults/fault_condition_ten.py +91 -30
  14. open_fdd/air_handling_unit/faults/fault_condition_thirteen.py +95 -34
  15. open_fdd/air_handling_unit/faults/fault_condition_three.py +84 -29
  16. open_fdd/air_handling_unit/faults/fault_condition_twelve.py +98 -37
  17. open_fdd/air_handling_unit/faults/fault_condition_two.py +84 -32
  18. open_fdd/air_handling_unit/faults/helper_utils.py +295 -93
  19. open_fdd/air_handling_unit/images/ahu1_fc1_2024-06_1.jpg +0 -0
  20. open_fdd/air_handling_unit/images/ahu1_fc1_2024-06_2.jpg +0 -0
  21. open_fdd/air_handling_unit/images/example1.jpg +0 -0
  22. open_fdd/air_handling_unit/images/example2.jpg +0 -0
  23. open_fdd/air_handling_unit/images/fc10_definition.png +0 -0
  24. open_fdd/air_handling_unit/images/fc11_definition.png +0 -0
  25. open_fdd/air_handling_unit/images/fc12_definition.png +0 -0
  26. open_fdd/air_handling_unit/images/fc13_definition.png +0 -0
  27. open_fdd/air_handling_unit/images/fc1_definition.png +0 -0
  28. open_fdd/air_handling_unit/images/fc1_report_screenshot_all.png +0 -0
  29. open_fdd/air_handling_unit/images/fc2_definition.png +0 -0
  30. open_fdd/air_handling_unit/images/fc3_definition.png +0 -0
  31. open_fdd/air_handling_unit/images/fc4_definition.png +0 -0
  32. open_fdd/air_handling_unit/images/fc5_definition.png +0 -0
  33. open_fdd/air_handling_unit/images/fc6_definition.png +0 -0
  34. open_fdd/air_handling_unit/images/fc7_definition.png +0 -0
  35. open_fdd/air_handling_unit/images/fc8_definition.png +0 -0
  36. open_fdd/air_handling_unit/images/fc9_definition.png +0 -0
  37. open_fdd/air_handling_unit/images/latex_generator.py +175 -0
  38. open_fdd/air_handling_unit/images/params.docx +0 -0
  39. open_fdd/air_handling_unit/images/params.pdf +0 -0
  40. open_fdd/air_handling_unit/images/plot_for_repo.png +0 -0
  41. open_fdd/air_handling_unit/reports/base_report.py +47 -0
  42. open_fdd/air_handling_unit/reports/report_fc7.py +3 -1
  43. open_fdd/tests/ahu/test_ahu_fc1.py +18 -1
  44. open_fdd/tests/ahu/test_ahu_fc10.py +1 -1
  45. open_fdd/tests/ahu/test_ahu_fc11.py +1 -1
  46. open_fdd/tests/ahu/test_ahu_fc12.py +1 -1
  47. open_fdd/tests/ahu/test_ahu_fc13.py +1 -1
  48. open_fdd/tests/ahu/test_ahu_fc14.py +1 -1
  49. open_fdd/tests/ahu/test_ahu_fc15.py +1 -1
  50. open_fdd/tests/ahu/test_ahu_fc2.py +1 -1
  51. open_fdd/tests/ahu/test_ahu_fc3.py +1 -1
  52. open_fdd/tests/ahu/test_ahu_fc4.py +2 -2
  53. open_fdd/tests/ahu/test_ahu_fc5.py +1 -1
  54. open_fdd/tests/ahu/test_ahu_fc6.py +2 -2
  55. open_fdd/tests/ahu/test_ahu_fc7.py +1 -1
  56. open_fdd/tests/ahu/test_ahu_fc8.py +1 -1
  57. open_fdd/tests/ahu/test_ahu_fc9.py +1 -1
  58. {open_fdd-0.1.1.dist-info → open_fdd-0.1.4.dist-info}/METADATA +34 -5
  59. open_fdd-0.1.4.dist-info/RECORD +82 -0
  60. open_fdd-0.1.1.dist-info/RECORD +0 -59
  61. {open_fdd-0.1.1.dist-info → open_fdd-0.1.4.dist-info}/LICENSE +0 -0
  62. {open_fdd-0.1.1.dist-info → open_fdd-0.1.4.dist-info}/WHEEL +0 -0
  63. {open_fdd-0.1.1.dist-info → open_fdd-0.1.4.dist-info}/top_level.txt +0 -0
@@ -1,28 +1,52 @@
1
+ import pandas as pd
1
2
  import pandas.api.types as pdtypes
2
3
  from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils
3
4
  import sys
4
5
 
6
+ """see __init__.py for fault classes"""
7
+
8
+
9
+ class MissingColumnError(Exception):
10
+ """Custom exception raised when a required column is missing or None."""
11
+
12
+ def __init__(self, message):
13
+ self.message = message
14
+ super().__init__(self.message)
15
+
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
+
5
24
 
6
25
  class FaultCondition:
7
26
  """Parent class for Fault Conditions. Methods are inherited to all children."""
8
27
 
9
- def set_attributes(self, dict_):
10
- """Passes dictionary into initialization of class instance, then uses the attributes called out below in
11
- attributes_dict to set only the attributes that match from dict_.
28
+ def __init__(self):
29
+ self.required_columns = []
12
30
 
13
- :param dict_: dictionary of all possible class attributes (loaded from config file)
14
- """
15
- for attribute in self.__dict__:
31
+ def set_attributes(self, dict_):
32
+ """Passes dictionary into initialization of class instance"""
33
+ for attribute in self.__dict__.keys():
16
34
  upper = attribute.upper()
17
- value = dict_[upper]
18
- self.__setattr__(attribute, value)
35
+ if upper in dict_:
36
+ value = dict_[upper]
37
+ self.__setattr__(attribute, value)
19
38
 
20
- def troubleshoot_cols(self, df):
21
- """print troubleshoot columns mapping
39
+ def check_required_columns(self, df: pd.DataFrame):
40
+ """Checks if required columns are present in the DataFrame."""
41
+ missing_columns = [
42
+ col for col in self.required_columns if col is None or col not in df.columns
43
+ ]
22
44
 
23
- :param df:
24
- :return:
25
- """
45
+ if missing_columns:
46
+ raise MissingColumnError(f"Missing required columns: {missing_columns}")
47
+
48
+ def troubleshoot_cols(self, df):
49
+ """Print troubleshoot columns mapping."""
26
50
  print("Troubleshoot mode enabled - not removing helper columns")
27
51
  for col in df.columns:
28
52
  print(
@@ -36,11 +60,7 @@ class FaultCondition:
36
60
  sys.stdout.flush()
37
61
 
38
62
  def check_analog_pct(self, df, columns):
39
- """check analog outputs [data with units of %] are floats only
40
-
41
- :param columns:
42
- :return:
43
- """
63
+ """Check analog outputs [data with units of %] are floats only."""
44
64
  helper = HelperUtils()
45
65
  for col in columns:
46
66
  if not pdtypes.is_float_dtype(df[col]):
@@ -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 FaultConditionEight(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.mix_degf_err_thres = float
17
20
  self.supply_degf_err_thres = float
@@ -23,45 +26,102 @@ class FaultConditionEight(FaultCondition):
23
26
  self.troubleshoot_mode = bool # default should be False
24
27
  self.rolling_window_size = int
25
28
 
26
- self.set_attributes(dict_)
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"
27
42
 
28
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
29
- if self.troubleshoot_mode:
30
- self.troubleshoot_cols(df)
43
+ self.set_attributes(dict_)
31
44
 
32
- # Check analog outputs [data with units of %] are floats only
33
- columns_to_check = [
45
+ # Set required columns specific to this fault condition
46
+ self.required_columns = [
47
+ self.mat_col,
48
+ self.sat_col,
34
49
  self.economizer_sig_col,
35
50
  self.cooling_sig_col,
36
51
  ]
37
52
 
38
- self.check_analog_pct(df, columns_to_check)
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
+ )
39
62
 
40
- df["sat_fan_mat"] = abs(
41
- df[self.sat_col] - self.delta_t_supply_fan - df[self.mat_col]
42
- )
43
- df["sat_mat_sqrted"] = np.sqrt(
44
- self.supply_degf_err_thres**2 + self.mix_degf_err_thres**2
45
- )
63
+ # Ensure all required columns are strings
64
+ self.required_columns = [str(col) for col in self.required_columns]
46
65
 
47
- df["combined_check"] = (
48
- (df["sat_fan_mat"] > df["sat_mat_sqrted"])
49
- & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
50
- & (df[self.cooling_sig_col] < 0.1)
66
+ self.mapped_columns = (
67
+ f"Your config dictionary is mapped as: {', '.join(self.required_columns)}"
51
68
  )
52
69
 
53
- # Rolling sum to count consecutive trues
54
- rolling_sum = (
55
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
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}"
56
77
  )
57
- # Set flag to 1 if rolling sum equals the window size
58
- df["fc8_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
59
78
 
60
- if self.troubleshoot_mode:
61
- print("Troubleshoot mode enabled - not removing helper columns")
62
- sys.stdout.flush()
63
- del df["sat_fan_mat"]
64
- del df["sat_mat_sqrted"]
65
- del df["combined_check"]
79
+ def apply(self, df: pd.DataFrame) -> pd.DataFrame:
80
+ try:
81
+ # Ensure all required columns are present
82
+ self.check_required_columns(df)
66
83
 
67
- return df
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 = [
89
+ self.economizer_sig_col,
90
+ self.cooling_sig_col,
91
+ ]
92
+
93
+ self.check_analog_pct(df, columns_to_check)
94
+
95
+ df["sat_fan_mat"] = abs(
96
+ df[self.sat_col] - self.delta_t_supply_fan - df[self.mat_col]
97
+ )
98
+ df["sat_mat_sqrted"] = np.sqrt(
99
+ self.supply_degf_err_thres**2 + self.mix_degf_err_thres**2
100
+ )
101
+
102
+ df["combined_check"] = (
103
+ (df["sat_fan_mat"] > df["sat_mat_sqrted"])
104
+ & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
105
+ & (df[self.cooling_sig_col] < 0.1)
106
+ )
107
+
108
+ # Rolling sum to count consecutive trues
109
+ rolling_sum = (
110
+ df["combined_check"].rolling(window=self.rolling_window_size).sum()
111
+ )
112
+ # Set flag to 1 if rolling sum equals the window size
113
+ df["fc8_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
114
+
115
+ if self.troubleshoot_mode:
116
+ print("Troubleshoot mode enabled - not removing helper columns")
117
+ sys.stdout.flush()
118
+ del df["sat_fan_mat"]
119
+ del df["sat_mat_sqrted"]
120
+ del df["combined_check"]
121
+
122
+ return df
123
+
124
+ except MissingColumnError as e:
125
+ print(f"Error: {e.message}")
126
+ sys.stdout.flush()
127
+ raise e
@@ -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
 
@@ -9,11 +10,11 @@ class FaultConditionEleven(FaultCondition):
9
10
  """Class provides the definitions for Fault Condition 11.
10
11
  Outside air temperature too low for 100% outdoor
11
12
  air cooling in economizer cooling mode.
12
-
13
13
  Economizer performance fault
14
14
  """
15
15
 
16
16
  def __init__(self, dict_):
17
+ super().__init__()
17
18
  self.delta_t_supply_fan = float
18
19
  self.outdoor_degf_err_thres = float
19
20
  self.supply_degf_err_thres = float
@@ -24,45 +25,102 @@ class FaultConditionEleven(FaultCondition):
24
25
  self.troubleshoot_mode = bool # default False
25
26
  self.rolling_window_size = int
26
27
 
27
- self.set_attributes(dict_)
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"
28
41
 
29
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
30
- if self.troubleshoot_mode:
31
- self.troubleshoot_cols(df)
42
+ self.set_attributes(dict_)
32
43
 
33
- # Check analog outputs [data with units of %] are floats only
34
- columns_to_check = [
35
- self.economizer_sig_col,
44
+ # Set required columns specific to this fault condition
45
+ self.required_columns = [
46
+ self.sat_setpoint_col,
47
+ self.oat_col,
36
48
  self.cooling_sig_col,
49
+ self.economizer_sig_col,
37
50
  ]
38
- self.check_analog_pct(df, columns_to_check)
39
51
 
40
- df["oat_plus_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
- )
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]
46
64
 
47
- df["combined_check"] = (
48
- (df["oat_plus_oaterror"] < df["satsp_delta_saterr"])
49
- # verify ahu is running in OS 3 clg mode in 100 OA
50
- & (df[self.cooling_sig_col] > 0.01)
51
- & (df[self.economizer_sig_col] > 0.9)
65
+ self.mapped_columns = (
66
+ f"Your config dictionary is mapped as: {', '.join(self.required_columns)}"
52
67
  )
53
68
 
54
- # Rolling sum to count consecutive trues
55
- rolling_sum = (
56
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
69
+ def get_required_columns(self) -> str:
70
+ """Returns a string representation of the 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}"
57
76
  )
58
- # Set flag to 1 if rolling sum equals the window size
59
- df["fc11_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
60
77
 
61
- if self.troubleshoot_mode:
62
- print("Troubleshoot mode enabled - not removing helper columns")
63
- sys.stdout.flush()
64
- del df["oat_plus_oaterror"]
65
- del df["satsp_delta_saterr"]
66
- del df["combined_check"]
78
+ def apply(self, df: pd.DataFrame) -> pd.DataFrame:
79
+ try:
80
+ # Ensure all required columns are present
81
+ self.check_required_columns(df)
82
+
83
+ if self.troubleshoot_mode:
84
+ self.troubleshoot_cols(df)
85
+
86
+ # Check analog outputs [data with units of %] are floats only
87
+ columns_to_check = [
88
+ self.economizer_sig_col,
89
+ self.cooling_sig_col,
90
+ ]
91
+ self.check_analog_pct(df, columns_to_check)
92
+
93
+ df["oat_plus_oaterror"] = df[self.oat_col] + self.outdoor_degf_err_thres
94
+ df["satsp_delta_saterr"] = (
95
+ df[self.sat_setpoint_col]
96
+ - self.delta_t_supply_fan
97
+ - self.supply_degf_err_thres
98
+ )
67
99
 
68
- return df
100
+ df["combined_check"] = (
101
+ (df["oat_plus_oaterror"] < df["satsp_delta_saterr"])
102
+ # verify ahu is running in OS 3 clg mode in 100 OA
103
+ & (df[self.cooling_sig_col] > 0.01)
104
+ & (df[self.economizer_sig_col] > 0.9)
105
+ )
106
+
107
+ # Rolling sum to count consecutive trues
108
+ rolling_sum = (
109
+ df["combined_check"].rolling(window=self.rolling_window_size).sum()
110
+ )
111
+ # Set flag to 1 if rolling sum equals the window size
112
+ df["fc11_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
113
+
114
+ if self.troubleshoot_mode:
115
+ print("Troubleshoot mode enabled - not removing helper columns")
116
+ sys.stdout.flush()
117
+ del df["oat_plus_oaterror"]
118
+ del df["satsp_delta_saterr"]
119
+ del df["combined_check"]
120
+
121
+ return df
122
+
123
+ except MissingColumnError as e:
124
+ print(f"Error: {e.message}")
125
+ sys.stdout.flush()
126
+ raise e
@@ -1,17 +1,20 @@
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
 
8
10
  class FaultConditionFifteen(FaultCondition):
9
11
  """Class provides the definitions for Fault Condition 15.
10
- Temperature rise across inactive heating coi.
12
+ Temperature rise across inactive heating coil.
11
13
  Requires coil leaving temp sensor.
12
14
  """
13
15
 
14
16
  def __init__(self, dict_):
17
+ super().__init__()
15
18
  self.delta_supply_fan = float
16
19
  self.coil_temp_enter_err_thres = float
17
20
  self.coil_temp_leav_err_thres = float
@@ -25,66 +28,125 @@ class FaultConditionFifteen(FaultCondition):
25
28
  self.troubleshoot_mode = bool # default to False
26
29
  self.rolling_window_size = int
27
30
 
28
- self.set_attributes(dict_)
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"
29
44
 
30
- def apply(self, df: pd.DataFrame) -> pd.DataFrame:
31
- if self.troubleshoot_mode:
32
- self.troubleshoot_cols(df)
45
+ self.set_attributes(dict_)
33
46
 
34
- # Check analog outputs [data with units of %] are floats only
35
- columns_to_check = [
36
- self.economizer_sig_col,
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,
37
51
  self.cooling_sig_col,
38
52
  self.heating_sig_col,
53
+ self.economizer_sig_col,
39
54
  self.supply_vfd_speed_col,
40
55
  ]
41
- self.check_analog_pct(df, columns_to_check)
42
56
 
43
- # Create helper columns
44
- df["htg_delta_temp"] = (
45
- df[self.htg_coil_leave_temp_col] - df[self.htg_coil_enter_temp_col]
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)}"
46
72
  )
47
73
 
48
- df["htg_delta_sqrted"] = (
49
- np.sqrt(
50
- self.coil_temp_enter_err_thres**2 + self.coil_temp_leav_err_thres**2
51
- )
52
- + self.delta_supply_fan
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}"
53
81
  )
54
82
 
55
- df["combined_check"] = (
56
- (
57
- (df["htg_delta_temp"] >= df["htg_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)
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
61
110
  )
62
- | (
63
- (df["htg_delta_temp"] >= df["htg_delta_sqrted"])
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)
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
+ )
67
131
  )
68
- | (
69
- (df["htg_delta_temp"] >= df["htg_delta_sqrted"])
70
- # verify AHU is running in OS 3 clg mode in 100 OA
71
- & (df[self.cooling_sig_col] > 0.01)
72
- & (df[self.economizer_sig_col] > 0.9)
132
+
133
+ # Rolling sum to count consecutive trues
134
+ rolling_sum = (
135
+ df["combined_check"].rolling(window=self.rolling_window_size).sum()
73
136
  )
74
- )
137
+ # Set flag to 1 if rolling sum equals the window size
138
+ df["fc15_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
75
139
 
76
- # Rolling sum to count consecutive trues
77
- rolling_sum = (
78
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
79
- )
80
- # Set flag to 1 if rolling sum equals the window size
81
- df["fc15_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
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"]
82
146
 
83
- if self.troubleshoot_mode:
84
- print("Troubleshoot mode enabled - not removing helper columns")
85
- sys.stdout.flush()
86
- del df["htg_delta_temp"]
87
- del df["htg_delta_sqrted"]
88
- del df["combined_check"]
147
+ return df
89
148
 
90
- return df
149
+ except MissingColumnError as e:
150
+ print(f"Error: {e.message}")
151
+ sys.stdout.flush()
152
+ raise e