open-fdd 0.1.4__py3-none-any.whl → 0.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. open_fdd/air_handling_unit/faults/__init__.py +301 -301
  2. open_fdd/air_handling_unit/reports/__init__.py +988 -0
  3. open_fdd/air_handling_unit/reports/fault_report.py +42 -0
  4. open_fdd/tests/ahu/test_ahu_fc16.py +190 -0
  5. {open_fdd-0.1.4.dist-info → open_fdd-0.1.6.dist-info}/METADATA +21 -12
  6. open_fdd-0.1.6.dist-info/RECORD +31 -0
  7. {open_fdd-0.1.4.dist-info → open_fdd-0.1.6.dist-info}/WHEEL +1 -1
  8. open_fdd/air_handling_unit/faults/fault_condition_eight.py +0 -127
  9. open_fdd/air_handling_unit/faults/fault_condition_eleven.py +0 -126
  10. open_fdd/air_handling_unit/faults/fault_condition_fifteen.py +0 -152
  11. open_fdd/air_handling_unit/faults/fault_condition_five.py +0 -123
  12. open_fdd/air_handling_unit/faults/fault_condition_four.py +0 -168
  13. open_fdd/air_handling_unit/faults/fault_condition_fourteen.py +0 -143
  14. open_fdd/air_handling_unit/faults/fault_condition_nine.py +0 -128
  15. open_fdd/air_handling_unit/faults/fault_condition_one.py +0 -112
  16. open_fdd/air_handling_unit/faults/fault_condition_seven.py +0 -114
  17. open_fdd/air_handling_unit/faults/fault_condition_six.py +0 -181
  18. open_fdd/air_handling_unit/faults/fault_condition_ten.py +0 -123
  19. open_fdd/air_handling_unit/faults/fault_condition_thirteen.py +0 -127
  20. open_fdd/air_handling_unit/faults/fault_condition_three.py +0 -113
  21. open_fdd/air_handling_unit/faults/fault_condition_twelve.py +0 -132
  22. open_fdd/air_handling_unit/faults/fault_condition_two.py +0 -113
  23. open_fdd/air_handling_unit/images/ahu1_fc1_2024-06_1.jpg +0 -0
  24. open_fdd/air_handling_unit/images/ahu1_fc1_2024-06_2.jpg +0 -0
  25. open_fdd/air_handling_unit/images/example1.jpg +0 -0
  26. open_fdd/air_handling_unit/images/example2.jpg +0 -0
  27. open_fdd/air_handling_unit/images/fc10_definition.png +0 -0
  28. open_fdd/air_handling_unit/images/fc11_definition.png +0 -0
  29. open_fdd/air_handling_unit/images/fc12_definition.png +0 -0
  30. open_fdd/air_handling_unit/images/fc13_definition.png +0 -0
  31. open_fdd/air_handling_unit/images/fc1_definition.png +0 -0
  32. open_fdd/air_handling_unit/images/fc1_report_screenshot_all.png +0 -0
  33. open_fdd/air_handling_unit/images/fc2_definition.png +0 -0
  34. open_fdd/air_handling_unit/images/fc3_definition.png +0 -0
  35. open_fdd/air_handling_unit/images/fc4_definition.png +0 -0
  36. open_fdd/air_handling_unit/images/fc5_definition.png +0 -0
  37. open_fdd/air_handling_unit/images/fc6_definition.png +0 -0
  38. open_fdd/air_handling_unit/images/fc7_definition.png +0 -0
  39. open_fdd/air_handling_unit/images/fc8_definition.png +0 -0
  40. open_fdd/air_handling_unit/images/fc9_definition.png +0 -0
  41. open_fdd/air_handling_unit/images/latex_generator.py +0 -175
  42. open_fdd/air_handling_unit/images/params.docx +0 -0
  43. open_fdd/air_handling_unit/images/params.pdf +0 -0
  44. open_fdd/air_handling_unit/images/plot_for_repo.png +0 -0
  45. open_fdd/air_handling_unit/reports/base_report.py +0 -47
  46. open_fdd/air_handling_unit/reports/report_fc1.py +0 -115
  47. open_fdd/air_handling_unit/reports/report_fc10.py +0 -126
  48. open_fdd/air_handling_unit/reports/report_fc11.py +0 -128
  49. open_fdd/air_handling_unit/reports/report_fc12.py +0 -126
  50. open_fdd/air_handling_unit/reports/report_fc13.py +0 -126
  51. open_fdd/air_handling_unit/reports/report_fc14.py +0 -124
  52. open_fdd/air_handling_unit/reports/report_fc15.py +0 -124
  53. open_fdd/air_handling_unit/reports/report_fc2.py +0 -119
  54. open_fdd/air_handling_unit/reports/report_fc3.py +0 -119
  55. open_fdd/air_handling_unit/reports/report_fc4.py +0 -148
  56. open_fdd/air_handling_unit/reports/report_fc5.py +0 -132
  57. open_fdd/air_handling_unit/reports/report_fc6.py +0 -156
  58. open_fdd/air_handling_unit/reports/report_fc7.py +0 -126
  59. open_fdd/air_handling_unit/reports/report_fc8.py +0 -118
  60. open_fdd/air_handling_unit/reports/report_fc9.py +0 -120
  61. open_fdd-0.1.4.dist-info/RECORD +0 -82
  62. {open_fdd-0.1.4.dist-info → open_fdd-0.1.6.dist-info}/LICENSE +0 -0
  63. {open_fdd-0.1.4.dist-info → open_fdd-0.1.6.dist-info}/top_level.txt +0 -0
@@ -12,6 +12,8 @@ import sys
12
12
  class FaultConditionOne(FaultCondition):
13
13
  """Class provides the definitions for Fault Condition 1.
14
14
  AHU low duct static pressure fan fault.
15
+
16
+ py -3.12 -m pytest open_fdd/tests/ahu/test_ahu_fc1.py -rP -s
15
17
  """
16
18
 
17
19
  def __init__(self, dict_):
@@ -101,34 +103,25 @@ class FaultConditionOne(FaultCondition):
101
103
  columns_to_check = [self.supply_vfd_speed_col]
102
104
  self.check_analog_pct(df, columns_to_check)
103
105
 
104
- df["static_check_"] = (
106
+ # Perform checks
107
+ static_check = (
105
108
  df[self.duct_static_col]
106
109
  < df[self.duct_static_setpoint_col] - self.duct_static_inches_err_thres
107
110
  )
108
- df["fan_check_"] = (
111
+ fan_check = (
109
112
  df[self.supply_vfd_speed_col]
110
113
  >= self.vfd_speed_percent_max - self.vfd_speed_percent_err_thres
111
114
  )
112
115
 
113
116
  # Combined condition check
114
- df["combined_check"] = df["static_check_"] & df["fan_check_"]
117
+ combined_check = static_check & fan_check
115
118
 
116
119
  # Rolling sum to count consecutive trues
117
- rolling_sum = (
118
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
119
- )
120
+ rolling_sum = combined_check.rolling(window=self.rolling_window_size).sum()
121
+
120
122
  # Set flag to 1 if rolling sum equals the window size
121
123
  df["fc1_flag"] = (rolling_sum == self.rolling_window_size).astype(int)
122
124
 
123
- if self.troubleshoot_mode:
124
- print("Troubleshoot mode enabled - not removing helper columns")
125
- sys.stdout.flush()
126
-
127
- # Optionally remove temporary columns
128
- df.drop(
129
- columns=["static_check_", "fan_check_", "combined_check"], inplace=True
130
- )
131
-
132
125
  return df
133
126
 
134
127
  except MissingColumnError as e:
@@ -232,33 +225,23 @@ class FaultConditionTwo(FaultCondition):
232
225
  columns_to_check = [self.supply_vfd_speed_col]
233
226
  self.check_analog_pct(df, columns_to_check)
234
227
 
235
- # Fault condition-specific checks / flags
236
- df["mat_check"] = df[self.mat_col] + self.mix_degf_err_thres
237
- df["temp_min_check"] = np.minimum(
228
+ # Perform checks
229
+ mat_check = df[self.mat_col] + self.mix_degf_err_thres
230
+ temp_min_check = np.minimum(
238
231
  df[self.rat_col] - self.return_degf_err_thres,
239
232
  df[self.oat_col] - self.outdoor_degf_err_thres,
240
233
  )
241
234
 
242
- df["combined_check"] = (df["mat_check"] < df["temp_min_check"]) & (
235
+ combined_check = (mat_check < temp_min_check) & (
243
236
  df[self.supply_vfd_speed_col] > 0.01
244
237
  )
245
238
 
246
239
  # Rolling sum to count consecutive trues
247
- rolling_sum = (
248
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
249
- )
240
+ rolling_sum = combined_check.rolling(window=self.rolling_window_size).sum()
241
+
250
242
  # Set flag to 1 if rolling sum equals the window size
251
243
  df["fc2_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
252
244
 
253
- if self.troubleshoot_mode:
254
- print("Troubleshoot mode enabled - not removing helper columns")
255
- sys.stdout.flush()
256
-
257
- # Optionally remove temporary columns
258
- df.drop(
259
- columns=["mat_check", "temp_min_check", "combined_check"], inplace=True
260
- )
261
-
262
245
  return df
263
246
 
264
247
  except MissingColumnError as e:
@@ -362,33 +345,23 @@ class FaultConditionThree(FaultCondition):
362
345
  columns_to_check = [self.supply_vfd_speed_col]
363
346
  self.check_analog_pct(df, columns_to_check)
364
347
 
365
- # Fault condition-specific checks / flags
366
- df["mat_check"] = df[self.mat_col] - self.mix_degf_err_thres
367
- df["temp_max_check"] = np.maximum(
348
+ # Perform checks
349
+ mat_check = df[self.mat_col] - self.mix_degf_err_thres
350
+ temp_max_check = np.maximum(
368
351
  df[self.rat_col] + self.return_degf_err_thres,
369
352
  df[self.oat_col] + self.outdoor_degf_err_thres,
370
353
  )
371
354
 
372
- df["combined_check"] = (df["mat_check"] > df["temp_max_check"]) & (
355
+ combined_check = (mat_check > temp_max_check) & (
373
356
  df[self.supply_vfd_speed_col] > 0.01
374
357
  )
375
358
 
376
359
  # Rolling sum to count consecutive trues
377
- rolling_sum = (
378
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
379
- )
360
+ rolling_sum = combined_check.rolling(window=self.rolling_window_size).sum()
361
+
380
362
  # Set flag to 1 if rolling sum equals the window size
381
363
  df["fc3_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
382
364
 
383
- if self.troubleshoot_mode:
384
- print("Troubleshoot mode enabled - not removing helper columns")
385
- sys.stdout.flush()
386
-
387
- # Optionally remove temporary columns
388
- df.drop(
389
- columns=["mat_check", "temp_max_check", "combined_check"], inplace=True
390
- )
391
-
392
365
  return df
393
366
 
394
367
  except MissingColumnError as e:
@@ -675,40 +648,28 @@ class FaultConditionFive(FaultCondition):
675
648
  # Ensure all required columns are present
676
649
  self.check_required_columns(df)
677
650
 
678
- if self.troubleshoot_mode:
679
- self.troubleshoot_cols(df)
680
-
681
651
  # Check analog outputs [data with units of %] are floats only
682
652
  columns_to_check = [self.supply_vfd_speed_col, self.heating_sig_col]
653
+ self.check_analog_pct(df, columns_to_check)
683
654
 
684
- for col in columns_to_check:
685
- self.check_analog_pct(df, [col])
686
-
687
- df["sat_check"] = df[self.sat_col] + self.supply_degf_err_thres
688
- df["mat_check"] = (
655
+ # Perform checks
656
+ sat_check = df[self.sat_col] + self.supply_degf_err_thres
657
+ mat_check = (
689
658
  df[self.mat_col] - self.mix_degf_err_thres + self.delta_t_supply_fan
690
659
  )
691
660
 
692
- df["combined_check"] = (
693
- (df["sat_check"] <= df["mat_check"])
661
+ combined_check = (
662
+ (sat_check <= mat_check)
694
663
  & (df[self.heating_sig_col] > 0.01)
695
664
  & (df[self.supply_vfd_speed_col] > 0.01)
696
665
  )
697
666
 
698
667
  # Rolling sum to count consecutive trues
699
- rolling_sum = (
700
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
701
- )
668
+ rolling_sum = combined_check.rolling(window=self.rolling_window_size).sum()
669
+
702
670
  # Set flag to 1 if rolling sum equals the window size
703
671
  df["fc5_flag"] = (rolling_sum == self.rolling_window_size).astype(int)
704
672
 
705
- if self.troubleshoot_mode:
706
- print("Troubleshoot mode enabled - not removing helper columns")
707
- sys.stdout.flush()
708
-
709
- # Optionally remove temporary columns
710
- df.drop(columns=["mat_check", "sat_check", "combined_check"], inplace=True)
711
-
712
673
  return df
713
674
 
714
675
  except MissingColumnError as e:
@@ -839,9 +800,6 @@ class FaultConditionSix(FaultCondition):
839
800
  # Ensure all required columns are present
840
801
  self.check_required_columns(df)
841
802
 
842
- if self.troubleshoot_mode:
843
- self.troubleshoot_cols(df)
844
-
845
803
  # Check analog outputs [data with units of %] are floats only
846
804
  columns_to_check = [
847
805
  self.supply_vfd_speed_col,
@@ -849,75 +807,45 @@ class FaultConditionSix(FaultCondition):
849
807
  self.heating_sig_col,
850
808
  self.cooling_sig_col,
851
809
  ]
810
+ self.check_analog_pct(df, columns_to_check)
852
811
 
853
- for col in columns_to_check:
854
- self.check_analog_pct(df, [col])
855
-
856
- # Create helper columns
857
- df["rat_minus_oat"] = abs(df[self.rat_col] - df[self.oat_col])
858
- df["percent_oa_calc"] = (df[self.mat_col] - df[self.rat_col]) / (
812
+ # Calculate intermediate values
813
+ rat_minus_oat = abs(df[self.rat_col] - df[self.oat_col])
814
+ percent_oa_calc = (df[self.mat_col] - df[self.rat_col]) / (
859
815
  df[self.oat_col] - df[self.rat_col]
860
816
  )
861
817
 
862
- # Weed out any negative values
863
- df["percent_oa_calc"] = df["percent_oa_calc"].apply(
864
- lambda x: x if x > 0 else 0
865
- )
818
+ # Replace negative values in percent_oa_calc with zero using vectorized operation
819
+ percent_oa_calc = percent_oa_calc.clip(lower=0)
866
820
 
867
- df["perc_OAmin"] = (
868
- self.ahu_min_oa_cfm_design / df[self.supply_fan_air_volume_col]
869
- )
821
+ perc_OAmin = self.ahu_min_oa_cfm_design / df[self.supply_fan_air_volume_col]
822
+ percent_oa_calc_minus_perc_OAmin = abs(percent_oa_calc - perc_OAmin)
870
823
 
871
- df["percent_oa_calc_minus_perc_OAmin"] = abs(
872
- df["percent_oa_calc"] - df["perc_OAmin"]
824
+ # Combined checks for OS 1 and OS 4 modes
825
+ os1_htg_mode_check = (
826
+ (rat_minus_oat >= self.oat_rat_delta_min)
827
+ & (percent_oa_calc_minus_perc_OAmin > self.airflow_err_thres)
828
+ & (df[self.heating_sig_col] > 0.0)
829
+ & (df[self.supply_vfd_speed_col] > 0.0)
873
830
  )
874
831
 
875
- df["combined_check"] = operator.or_(
876
- # OS 1 htg mode
877
- (
878
- (df["rat_minus_oat"] >= self.oat_rat_delta_min)
879
- & (df["percent_oa_calc_minus_perc_OAmin"] > self.airflow_err_thres)
880
- )
881
- # Verify AHU is running in OS 1 htg mode in min OA
882
- & (
883
- (df[self.heating_sig_col] > 0.0)
884
- & (df[self.supply_vfd_speed_col] > 0.0)
885
- ), # OR
886
- # OS 4 mech clg mode
887
- (
888
- (df["rat_minus_oat"] >= self.oat_rat_delta_min)
889
- & (df["percent_oa_calc_minus_perc_OAmin"] > self.airflow_err_thres)
890
- )
891
- # Verify AHU is running in OS 4 clg mode in min OA
832
+ os4_clg_mode_check = (
833
+ (rat_minus_oat >= self.oat_rat_delta_min)
834
+ & (percent_oa_calc_minus_perc_OAmin > self.airflow_err_thres)
892
835
  & (df[self.heating_sig_col] == 0.0)
893
836
  & (df[self.cooling_sig_col] > 0.0)
894
837
  & (df[self.supply_vfd_speed_col] > 0.0)
895
- & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr),
838
+ & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr)
896
839
  )
897
840
 
841
+ combined_check = os1_htg_mode_check | os4_clg_mode_check
842
+
898
843
  # Rolling sum to count consecutive trues
899
- rolling_sum = (
900
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
901
- )
844
+ rolling_sum = combined_check.rolling(window=self.rolling_window_size).sum()
845
+
902
846
  # Set flag to 1 if rolling sum equals the window size
903
847
  df["fc6_flag"] = (rolling_sum == self.rolling_window_size).astype(int)
904
848
 
905
- if self.troubleshoot_mode:
906
- print("Troubleshoot mode enabled - not removing helper columns")
907
- sys.stdout.flush()
908
-
909
- # Optionally remove temporary columns
910
- df.drop(
911
- columns=[
912
- "rat_minus_oat",
913
- "percent_oa_calc",
914
- "perc_OAmin",
915
- "percent_oa_calc_minus_perc_OAmin",
916
- "combined_check",
917
- ],
918
- inplace=True,
919
- )
920
-
921
849
  return df
922
850
 
923
851
  except MissingColumnError as e:
@@ -1013,36 +941,25 @@ class FaultConditionSeven(FaultCondition):
1013
941
  # Ensure all required columns are present
1014
942
  self.check_required_columns(df)
1015
943
 
1016
- if self.troubleshoot_mode:
1017
- self.troubleshoot_cols(df)
1018
-
1019
944
  # Check analog outputs [data with units of %] are floats only
1020
945
  columns_to_check = [self.supply_vfd_speed_col, self.heating_sig_col]
1021
946
  self.check_analog_pct(df, columns_to_check)
1022
947
 
1023
- # Fault condition-specific checks / flags
1024
- df["sat_check"] = df[self.sat_setpoint_col] - self.supply_degf_err_thres
948
+ # Perform checks
949
+ sat_check = df[self.sat_setpoint_col] - self.supply_degf_err_thres
1025
950
 
1026
- df["combined_check"] = (
1027
- (df[self.sat_col] < df["sat_check"])
951
+ combined_check = (
952
+ (df[self.sat_col] < sat_check)
1028
953
  & (df[self.heating_sig_col] > 0.9)
1029
954
  & (df[self.supply_vfd_speed_col] > 0)
1030
955
  )
1031
956
 
1032
957
  # Rolling sum to count consecutive trues
1033
- rolling_sum = (
1034
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
1035
- )
958
+ rolling_sum = combined_check.rolling(window=self.rolling_window_size).sum()
959
+
1036
960
  # Set flag to 1 if rolling sum equals the window size
1037
961
  df["fc7_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
1038
962
 
1039
- if self.troubleshoot_mode:
1040
- print("Troubleshoot mode enabled - not removing helper columns")
1041
- sys.stdout.flush()
1042
-
1043
- # Optionally remove temporary columns
1044
- df.drop(columns=["sat_check", "combined_check"], inplace=True)
1045
-
1046
963
  return df
1047
964
 
1048
965
  except MissingColumnError as e:
@@ -1147,9 +1064,6 @@ class FaultConditionEight(FaultCondition):
1147
1064
  # Ensure all required columns are present
1148
1065
  self.check_required_columns(df)
1149
1066
 
1150
- if self.troubleshoot_mode:
1151
- self.troubleshoot_cols(df)
1152
-
1153
1067
  # Check analog outputs [data with units of %] are floats only
1154
1068
  columns_to_check = [
1155
1069
  self.economizer_sig_col,
@@ -1157,36 +1071,26 @@ class FaultConditionEight(FaultCondition):
1157
1071
  ]
1158
1072
  self.check_analog_pct(df, columns_to_check)
1159
1073
 
1160
- df["sat_fan_mat"] = abs(
1074
+ # Perform checks
1075
+ sat_fan_mat = abs(
1161
1076
  df[self.sat_col] - self.delta_t_supply_fan - df[self.mat_col]
1162
1077
  )
1163
- df["sat_mat_sqrted"] = np.sqrt(
1078
+ sat_mat_sqrted = np.sqrt(
1164
1079
  self.supply_degf_err_thres**2 + self.mix_degf_err_thres**2
1165
1080
  )
1166
1081
 
1167
- df["combined_check"] = (
1168
- (df["sat_fan_mat"] > df["sat_mat_sqrted"])
1082
+ combined_check = (
1083
+ (sat_fan_mat > sat_mat_sqrted)
1169
1084
  & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
1170
1085
  & (df[self.cooling_sig_col] < 0.1)
1171
1086
  )
1172
1087
 
1173
1088
  # Rolling sum to count consecutive trues
1174
- rolling_sum = (
1175
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
1176
- )
1089
+ rolling_sum = combined_check.rolling(window=self.rolling_window_size).sum()
1090
+
1177
1091
  # Set flag to 1 if rolling sum equals the window size
1178
1092
  df["fc8_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
1179
1093
 
1180
- if self.troubleshoot_mode:
1181
- print("Troubleshoot mode enabled - not removing helper columns")
1182
- sys.stdout.flush()
1183
-
1184
- # Optionally remove temporary columns
1185
- df.drop(
1186
- columns=["sat_fan_mat", "sat_mat_sqrted", "combined_check"],
1187
- inplace=True,
1188
- )
1189
-
1190
1094
  return df
1191
1095
 
1192
1096
  except MissingColumnError as e:
@@ -1291,9 +1195,6 @@ class FaultConditionNine(FaultCondition):
1291
1195
  # Ensure all required columns are present
1292
1196
  self.check_required_columns(df)
1293
1197
 
1294
- if self.troubleshoot_mode:
1295
- self.troubleshoot_cols(df)
1296
-
1297
1198
  # Check analog outputs [data with units of %] are floats only
1298
1199
  columns_to_check = [
1299
1200
  self.economizer_sig_col,
@@ -1301,38 +1202,27 @@ class FaultConditionNine(FaultCondition):
1301
1202
  ]
1302
1203
  self.check_analog_pct(df, columns_to_check)
1303
1204
 
1304
- # Create helper columns
1305
- df["oat_minus_oaterror"] = df[self.oat_col] - self.outdoor_degf_err_thres
1306
- df["satsp_delta_saterr"] = (
1205
+ # Perform calculations
1206
+ oat_minus_oaterror = df[self.oat_col] - self.outdoor_degf_err_thres
1207
+ satsp_delta_saterr = (
1307
1208
  df[self.sat_setpoint_col]
1308
1209
  - self.delta_t_supply_fan
1309
1210
  + self.supply_degf_err_thres
1310
1211
  )
1311
1212
 
1312
- df["combined_check"] = (
1313
- (df["oat_minus_oaterror"] > df["satsp_delta_saterr"])
1213
+ combined_check = (
1214
+ (oat_minus_oaterror > satsp_delta_saterr)
1314
1215
  # verify AHU is in OS2 only free cooling mode
1315
1216
  & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
1316
1217
  & (df[self.cooling_sig_col] < 0.1)
1317
1218
  )
1318
1219
 
1319
1220
  # Rolling sum to count consecutive trues
1320
- rolling_sum = (
1321
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
1322
- )
1221
+ rolling_sum = combined_check.rolling(window=self.rolling_window_size).sum()
1222
+
1323
1223
  # Set flag to 1 if rolling sum equals the window size
1324
1224
  df["fc9_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
1325
1225
 
1326
- if self.troubleshoot_mode:
1327
- print("Troubleshoot mode enabled - not removing helper columns")
1328
- sys.stdout.flush()
1329
-
1330
- # Optionally remove temporary columns
1331
- df.drop(
1332
- columns=["oat_minus_oaterror", "satsp_delta_saterr", "combined_check"],
1333
- inplace=True,
1334
- )
1335
-
1336
1226
  return df
1337
1227
 
1338
1228
  except MissingColumnError as e:
@@ -1433,9 +1323,6 @@ class FaultConditionTen(FaultCondition):
1433
1323
  # Ensure all required columns are present
1434
1324
  self.check_required_columns(df)
1435
1325
 
1436
- if self.troubleshoot_mode:
1437
- self.troubleshoot_cols(df)
1438
-
1439
1326
  # Check analog outputs [data with units of %] are floats only
1440
1327
  columns_to_check = [
1441
1328
  self.economizer_sig_col,
@@ -1443,35 +1330,25 @@ class FaultConditionTen(FaultCondition):
1443
1330
  ]
1444
1331
  self.check_analog_pct(df, columns_to_check)
1445
1332
 
1446
- df["abs_mat_minus_oat"] = abs(df[self.mat_col] - df[self.oat_col])
1447
- df["mat_oat_sqrted"] = np.sqrt(
1333
+ # Perform calculations
1334
+ abs_mat_minus_oat = abs(df[self.mat_col] - df[self.oat_col])
1335
+ mat_oat_sqrted = np.sqrt(
1448
1336
  self.mix_degf_err_thres**2 + self.outdoor_degf_err_thres**2
1449
1337
  )
1450
1338
 
1451
- df["combined_check"] = (
1452
- (df["abs_mat_minus_oat"] > df["mat_oat_sqrted"])
1453
- # verify AHU is running in OS 3 clg mode in min OA
1339
+ combined_check = (
1340
+ (abs_mat_minus_oat > mat_oat_sqrted)
1341
+ # Verify AHU is running in OS 3 cooling mode with minimum OA
1454
1342
  & (df[self.cooling_sig_col] > 0.01)
1455
1343
  & (df[self.economizer_sig_col] > 0.9)
1456
1344
  )
1457
1345
 
1458
1346
  # Rolling sum to count consecutive trues
1459
- rolling_sum = (
1460
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
1461
- )
1347
+ rolling_sum = combined_check.rolling(window=self.rolling_window_size).sum()
1348
+
1462
1349
  # Set flag to 1 if rolling sum equals the window size
1463
1350
  df["fc10_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
1464
1351
 
1465
- if self.troubleshoot_mode:
1466
- print("Troubleshoot mode enabled - not removing helper columns")
1467
- sys.stdout.flush()
1468
-
1469
- # Optionally remove temporary columns
1470
- df.drop(
1471
- columns=["abs_mat_minus_oat", "mat_oat_sqrted", "combined_check"],
1472
- inplace=True,
1473
- )
1474
-
1475
1352
  return df
1476
1353
 
1477
1354
  except MissingColumnError as e:
@@ -1575,9 +1452,6 @@ class FaultConditionEleven(FaultCondition):
1575
1452
  # Ensure all required columns are present
1576
1453
  self.check_required_columns(df)
1577
1454
 
1578
- if self.troubleshoot_mode:
1579
- self.troubleshoot_cols(df)
1580
-
1581
1455
  # Check analog outputs [data with units of %] are floats only
1582
1456
  columns_to_check = [
1583
1457
  self.economizer_sig_col,
@@ -1585,37 +1459,27 @@ class FaultConditionEleven(FaultCondition):
1585
1459
  ]
1586
1460
  self.check_analog_pct(df, columns_to_check)
1587
1461
 
1588
- df["oat_plus_oaterror"] = df[self.oat_col] + self.outdoor_degf_err_thres
1589
- df["satsp_delta_saterr"] = (
1462
+ # Perform calculations without creating DataFrame columns
1463
+ oat_plus_oaterror = df[self.oat_col] + self.outdoor_degf_err_thres
1464
+ satsp_delta_saterr = (
1590
1465
  df[self.sat_setpoint_col]
1591
1466
  - self.delta_t_supply_fan
1592
1467
  - self.supply_degf_err_thres
1593
1468
  )
1594
1469
 
1595
- df["combined_check"] = (
1596
- (df["oat_plus_oaterror"] < df["satsp_delta_saterr"])
1597
- # verify ahu is running in OS 3 clg mode in 100 OA
1470
+ combined_check = (
1471
+ (oat_plus_oaterror < satsp_delta_saterr)
1472
+ # Verify AHU is running in OS 3 cooling mode with 100% OA
1598
1473
  & (df[self.cooling_sig_col] > 0.01)
1599
1474
  & (df[self.economizer_sig_col] > 0.9)
1600
1475
  )
1601
1476
 
1602
1477
  # Rolling sum to count consecutive trues
1603
- rolling_sum = (
1604
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
1605
- )
1478
+ rolling_sum = combined_check.rolling(window=self.rolling_window_size).sum()
1479
+
1606
1480
  # Set flag to 1 if rolling sum equals the window size
1607
1481
  df["fc11_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
1608
1482
 
1609
- if self.troubleshoot_mode:
1610
- print("Troubleshoot mode enabled - not removing helper columns")
1611
- sys.stdout.flush()
1612
-
1613
- # Optionally remove temporary columns
1614
- df.drop(
1615
- columns=["oat_plus_oaterror", "satsp_delta_saterr", "combined_check"],
1616
- inplace=True,
1617
- )
1618
-
1619
1483
  return df
1620
1484
 
1621
1485
  except MissingColumnError as e:
@@ -1720,9 +1584,6 @@ class FaultConditionTwelve(FaultCondition):
1720
1584
  # Ensure all required columns are present
1721
1585
  self.check_required_columns(df)
1722
1586
 
1723
- if self.troubleshoot_mode:
1724
- self.troubleshoot_cols(df)
1725
-
1726
1587
  # Check analog outputs [data with units of %] are floats only
1727
1588
  columns_to_check = [
1728
1589
  self.economizer_sig_col,
@@ -1730,45 +1591,32 @@ class FaultConditionTwelve(FaultCondition):
1730
1591
  ]
1731
1592
  self.check_analog_pct(df, columns_to_check)
1732
1593
 
1733
- # Create helper columns
1734
- df["sat_minus_saterr_delta_supply_fan"] = (
1594
+ # Perform calculations without creating DataFrame columns
1595
+ sat_minus_saterr_delta_supply_fan = (
1735
1596
  df[self.sat_col] - self.supply_degf_err_thres - self.delta_t_supply_fan
1736
1597
  )
1737
- df["mat_plus_materr"] = df[self.mat_col] + self.mix_degf_err_thres
1598
+ mat_plus_materr = df[self.mat_col] + self.mix_degf_err_thres
1738
1599
 
1739
- df["combined_check"] = operator.or_(
1740
- # OS4 AHU state clg @ min OA
1741
- (df["sat_minus_saterr_delta_supply_fan"] > df["mat_plus_materr"])
1742
- # verify AHU in OS4 mode
1600
+ # Combined check without adding to DataFrame columns
1601
+ combined_check = operator.or_(
1602
+ # OS4 AHU state cooling @ min OA
1603
+ (sat_minus_saterr_delta_supply_fan > mat_plus_materr)
1604
+ # Verify AHU in OS4 mode
1743
1605
  & (df[self.cooling_sig_col] > 0.01)
1744
- & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr), # OR
1745
- (df["sat_minus_saterr_delta_supply_fan"] > df["mat_plus_materr"])
1746
- # verify AHU is running in OS 3 clg mode in 100 OA
1606
+ & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr),
1607
+ # OR
1608
+ (sat_minus_saterr_delta_supply_fan > mat_plus_materr)
1609
+ # Verify AHU is running in OS3 cooling mode in 100% OA
1747
1610
  & (df[self.cooling_sig_col] > 0.01)
1748
1611
  & (df[self.economizer_sig_col] > 0.9),
1749
1612
  )
1750
1613
 
1751
1614
  # Rolling sum to count consecutive trues
1752
- rolling_sum = (
1753
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
1754
- )
1615
+ rolling_sum = combined_check.rolling(window=self.rolling_window_size).sum()
1616
+
1755
1617
  # Set flag to 1 if rolling sum equals the window size
1756
1618
  df["fc12_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
1757
1619
 
1758
- if self.troubleshoot_mode:
1759
- print("Troubleshoot mode enabled - not removing helper columns")
1760
- sys.stdout.flush()
1761
-
1762
- # Optionally remove temporary columns
1763
- df.drop(
1764
- columns=[
1765
- "sat_minus_saterr_delta_supply_fan",
1766
- "mat_plus_materr",
1767
- "combined_check",
1768
- ],
1769
- inplace=True,
1770
- )
1771
-
1772
1620
  return df
1773
1621
 
1774
1622
  except MissingColumnError as e:
@@ -1869,9 +1717,6 @@ class FaultConditionThirteen(FaultCondition):
1869
1717
  # Ensure all required columns are present
1870
1718
  self.check_required_columns(df)
1871
1719
 
1872
- if self.troubleshoot_mode:
1873
- self.troubleshoot_cols(df)
1874
-
1875
1720
  # Check analog outputs [data with units of %] are floats only
1876
1721
  columns_to_check = [
1877
1722
  self.economizer_sig_col,
@@ -1879,40 +1724,30 @@ class FaultConditionThirteen(FaultCondition):
1879
1724
  ]
1880
1725
  self.check_analog_pct(df, columns_to_check)
1881
1726
 
1882
- # Create helper columns
1883
- df["sat_greater_than_sp_calc"] = (
1727
+ # Perform calculation without creating DataFrame columns
1728
+ sat_greater_than_sp_calc = (
1884
1729
  df[self.sat_col]
1885
1730
  > df[self.sat_setpoint_col] + self.supply_degf_err_thres
1886
1731
  )
1887
1732
 
1888
- df["combined_check"] = operator.or_(
1889
- ((df["sat_greater_than_sp_calc"]))
1890
- # OS4 AHU state clg @ min OA
1733
+ # Combined check without adding to DataFrame columns
1734
+ combined_check = operator.or_(
1735
+ # OS4 AHU state cooling @ min OA
1736
+ (sat_greater_than_sp_calc)
1891
1737
  & (df[self.cooling_sig_col] > 0.01)
1892
- & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr), # OR
1893
- ((df["sat_greater_than_sp_calc"]))
1894
- # verify ahu is running in OS 3 clg mode in 100 OA
1738
+ & (df[self.economizer_sig_col] == self.ahu_min_oa_dpr),
1739
+ # OR verify AHU is running in OS 3 cooling mode in 100% OA
1740
+ (sat_greater_than_sp_calc)
1895
1741
  & (df[self.cooling_sig_col] > 0.01)
1896
1742
  & (df[self.economizer_sig_col] > 0.9),
1897
1743
  )
1898
1744
 
1899
1745
  # Rolling sum to count consecutive trues
1900
- rolling_sum = (
1901
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
1902
- )
1746
+ rolling_sum = combined_check.rolling(window=self.rolling_window_size).sum()
1747
+
1903
1748
  # Set flag to 1 if rolling sum equals the window size
1904
1749
  df["fc13_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
1905
1750
 
1906
- if self.troubleshoot_mode:
1907
- print("Troubleshoot mode enabled - not removing helper columns")
1908
- sys.stdout.flush()
1909
-
1910
- # Optionally remove temporary columns
1911
- df.drop(
1912
- columns=["sat_greater_than_sp_calc", "combined_check"],
1913
- inplace=True,
1914
- )
1915
-
1916
1751
  return df
1917
1752
 
1918
1753
  except MissingColumnError as e:
@@ -2020,9 +1855,6 @@ class FaultConditionFourteen(FaultCondition):
2020
1855
  # Ensure all required columns are present
2021
1856
  self.check_required_columns(df)
2022
1857
 
2023
- if self.troubleshoot_mode:
2024
- self.troubleshoot_cols(df)
2025
-
2026
1858
  # Check analog outputs [data with units of %] are floats only
2027
1859
  columns_to_check = [
2028
1860
  self.economizer_sig_col,
@@ -2032,46 +1864,33 @@ class FaultConditionFourteen(FaultCondition):
2032
1864
  ]
2033
1865
  self.check_analog_pct(df, columns_to_check)
2034
1866
 
2035
- # Create helper columns
2036
- df["clg_delta_temp"] = (
1867
+ # Calculate necessary checks
1868
+ clg_delta_temp = (
2037
1869
  df[self.clg_coil_enter_temp_col] - df[self.clg_coil_leave_temp_col]
2038
1870
  )
2039
-
2040
- df["clg_delta_sqrted"] = (
1871
+ clg_delta_sqrted = (
2041
1872
  np.sqrt(
2042
1873
  self.coil_temp_enter_err_thres**2 + self.coil_temp_leav_err_thres**2
2043
1874
  )
2044
1875
  + self.delta_t_supply_fan
2045
1876
  )
2046
1877
 
2047
- df["combined_check"] = operator.or_(
2048
- (df["clg_delta_temp"] >= df["clg_delta_sqrted"])
2049
- # verify AHU is in OS2 only free cooling mode
1878
+ # Perform combined checks without adding intermediate columns to DataFrame
1879
+ combined_check = operator.or_(
1880
+ (clg_delta_temp >= clg_delta_sqrted)
2050
1881
  & (df[self.economizer_sig_col] > self.ahu_min_oa_dpr)
2051
1882
  & (df[self.cooling_sig_col] < 0.1), # OR
2052
- (df["clg_delta_temp"] >= df["clg_delta_sqrted"])
2053
- # verify AHU is running in OS 1 at near full heat
1883
+ (clg_delta_temp >= clg_delta_sqrted)
2054
1884
  & (df[self.heating_sig_col] > 0.0)
2055
1885
  & (df[self.supply_vfd_speed_col] > 0.0),
2056
1886
  )
2057
1887
 
2058
1888
  # Rolling sum to count consecutive trues
2059
- rolling_sum = (
2060
- df["combined_check"].rolling(window=self.rolling_window_size).sum()
2061
- )
1889
+ rolling_sum = combined_check.rolling(window=self.rolling_window_size).sum()
1890
+
2062
1891
  # Set flag to 1 if rolling sum equals the window size
2063
1892
  df["fc14_flag"] = (rolling_sum >= self.rolling_window_size).astype(int)
2064
1893
 
2065
- if self.troubleshoot_mode:
2066
- print("Troubleshoot mode enabled - not removing helper columns")
2067
- sys.stdout.flush()
2068
-
2069
- # Optionally remove temporary columns
2070
- df.drop(
2071
- columns=["clg_delta_temp", "clg_delta_sqrted", "combined_check"],
2072
- inplace=True,
2073
- )
2074
-
2075
1894
  return df
2076
1895
 
2077
1896
  except MissingColumnError as e:
@@ -2251,3 +2070,184 @@ class FaultConditionFifteen(FaultCondition):
2251
2070
  print(f"Error: {e.message}")
2252
2071
  sys.stdout.flush()
2253
2072
  raise e
2073
+
2074
+
2075
+ class FaultConditionSixteen(FaultCondition):
2076
+ """Class provides the definitions for Fault Condition 16.
2077
+ ERV Ineffective Process based on outdoor air temperature ranges.
2078
+ """
2079
+
2080
+ def __init__(self, dict_):
2081
+ super().__init__()
2082
+
2083
+ # Threshold parameters for efficiency ranges based on heating and cooling
2084
+ self.erv_efficiency_min_heating = dict_.get("ERV_EFFICIENCY_MIN_HEATING", 0.7)
2085
+ self.erv_efficiency_max_heating = dict_.get("ERV_EFFICIENCY_MAX_HEATING", 0.8)
2086
+ self.erv_efficiency_min_cooling = dict_.get("ERV_EFFICIENCY_MIN_COOLING", 0.5)
2087
+ self.erv_efficiency_max_cooling = dict_.get("ERV_EFFICIENCY_MAX_COOLING", 0.6)
2088
+
2089
+ self.oat_low_threshold = dict_.get("OAT_LOW_THRES", 32.0)
2090
+ self.oat_high_threshold = dict_.get("OAT_HIGH_THRES", 80.0)
2091
+
2092
+ # Validate that threshold parameters are floats and within 0.0 and 1.0 for efficiency values
2093
+ for param, value in [
2094
+ ("erv_efficiency_min_heating", self.erv_efficiency_min_heating),
2095
+ ("erv_efficiency_max_heating", self.erv_efficiency_max_heating),
2096
+ ("erv_efficiency_min_cooling", self.erv_efficiency_min_cooling),
2097
+ ("erv_efficiency_max_cooling", self.erv_efficiency_max_cooling),
2098
+ ("oat_low_threshold", self.oat_low_threshold),
2099
+ ("oat_high_threshold", self.oat_high_threshold),
2100
+ ]:
2101
+ if not isinstance(value, float):
2102
+ raise InvalidParameterError(
2103
+ f"The parameter '{param}' should be a float, but got {type(value).__name__}."
2104
+ )
2105
+ if "erv_efficiency" in param and not (0.0 <= value <= 1.0):
2106
+ raise InvalidParameterError(
2107
+ f"The parameter '{param}' should be a float between 0.0 and 1.0 to represent a percentage, but got {value}."
2108
+ )
2109
+
2110
+ # Other attributes
2111
+ self.erv_oat_enter_col = dict_.get("ERV_OAT_ENTER_COL", "erv_oat_enter")
2112
+ self.erv_oat_leaving_col = dict_.get("ERV_OAT_LEAVING_COL", "erv_oat_leaving")
2113
+ self.erv_eat_enter_col = dict_.get("ERV_EAT_ENTER_COL", "erv_eat_enter")
2114
+ self.erv_eat_leaving_col = dict_.get("ERV_EAT_LEAVING_COL", "erv_eat_leaving")
2115
+ self.supply_vfd_speed_col = dict_.get(
2116
+ "SUPPLY_VFD_SPEED_COL", "supply_vfd_speed"
2117
+ )
2118
+ self.rolling_window_size = dict_.get("ROLLING_WINDOW_SIZE", 1)
2119
+ self.troubleshoot_mode = dict_.get("TROUBLESHOOT_MODE", False)
2120
+
2121
+ self.equation_string = (
2122
+ "fc16_flag = 1 if temperature deltas and expected efficiency is ineffective "
2123
+ "for N consecutive values else 0 \n"
2124
+ )
2125
+ self.description_string = (
2126
+ "Fault Condition 16: ERV is an ineffective heat transfer fault. "
2127
+ "This fault occurs when the ERV's efficiency "
2128
+ "is outside the acceptable range based on the delta temperature across the "
2129
+ "ERV outside air enter temperature and ERV outside air leaving temperature, "
2130
+ "indicating poor heat transfer. "
2131
+ "It considers both heating and cooling conditions where each have acceptable "
2132
+ "ranges in percentage for expected heat transfer efficiency. The percentage needs "
2133
+ "to be a float between 0.0 and 1.0."
2134
+ )
2135
+ self.required_column_description = (
2136
+ "Required inputs are the ERV outside air entering temperature, ERV outside air leaving temperature, "
2137
+ "ERV exhaust entering temperature, ERV exhaust leaving temperature, "
2138
+ "and AHU supply fan VFD speed."
2139
+ )
2140
+ self.error_string = "One or more required columns are missing or None."
2141
+
2142
+ self.set_attributes(dict_)
2143
+
2144
+ # Set required columns specific to this fault condition
2145
+ self.required_columns = [
2146
+ self.erv_oat_enter_col,
2147
+ self.erv_oat_leaving_col,
2148
+ self.erv_eat_enter_col,
2149
+ self.erv_eat_leaving_col,
2150
+ self.supply_vfd_speed_col,
2151
+ ]
2152
+
2153
+ # Check if any of the required columns are None
2154
+ if any(col is None for col in self.required_columns):
2155
+ raise MissingColumnError(
2156
+ f"{self.error_string}\n"
2157
+ f"{self.equation_string}\n"
2158
+ f"{self.description_string}\n"
2159
+ f"{self.required_column_description}\n"
2160
+ f"Missing columns: {self.required_columns}"
2161
+ )
2162
+
2163
+ # Ensure all required columns are strings
2164
+ self.required_columns = [str(col) for col in self.required_columns]
2165
+
2166
+ self.mapped_columns = (
2167
+ f"Your config dictionary is mapped as: {', '.join(self.required_columns)}"
2168
+ )
2169
+
2170
+ def get_required_columns(self) -> str:
2171
+ """Returns a string representation of the required columns."""
2172
+ return (
2173
+ f"{self.equation_string}"
2174
+ f"{self.description_string}\n"
2175
+ f"{self.required_column_description}\n"
2176
+ f"{self.mapped_columns}"
2177
+ )
2178
+
2179
+ def calculate_erv_efficiency(self, df: pd.DataFrame) -> pd.DataFrame:
2180
+ # Calculate the temperature differences
2181
+ delta_temp_oa = df[self.erv_oat_leaving_col] - df[self.erv_oat_enter_col]
2182
+ delta_temp_ea = df[self.erv_eat_enter_col] - df[self.erv_oat_enter_col]
2183
+
2184
+ # Use the absolute value to handle both heating and cooling applications
2185
+ df["erv_efficiency_oa"] = np.abs(delta_temp_oa) / np.abs(delta_temp_ea)
2186
+
2187
+ return df
2188
+
2189
+ def apply(self, df: pd.DataFrame) -> pd.DataFrame:
2190
+ try:
2191
+ # Calculate ERV efficiency
2192
+ df = self.calculate_erv_efficiency(df)
2193
+
2194
+ # Fan must be on for a fault to be considered
2195
+ fan_on = df[self.supply_vfd_speed_col] > 0.1
2196
+
2197
+ # Combined heating and cooling checks
2198
+ cold_outside = df[self.erv_oat_enter_col] <= self.oat_low_threshold
2199
+ hot_outside = df[self.erv_oat_enter_col] >= self.oat_high_threshold
2200
+
2201
+ heating_fault = (
2202
+ (
2203
+ (df["erv_efficiency_oa"] < self.erv_efficiency_min_heating)
2204
+ | (df["erv_efficiency_oa"] > self.erv_efficiency_max_heating)
2205
+ )
2206
+ & cold_outside
2207
+ & fan_on
2208
+ )
2209
+
2210
+ cooling_fault = (
2211
+ (
2212
+ (df["erv_efficiency_oa"] < self.erv_efficiency_min_cooling)
2213
+ | (df["erv_efficiency_oa"] > self.erv_efficiency_max_cooling)
2214
+ )
2215
+ & hot_outside
2216
+ & fan_on
2217
+ )
2218
+
2219
+ df["combined_checks"] = heating_fault | cooling_fault
2220
+
2221
+ # Apply rolling sum
2222
+ df["fc16_flag"] = (
2223
+ df["combined_checks"]
2224
+ .rolling(window=self.rolling_window_size)
2225
+ .sum()
2226
+ .ge(self.rolling_window_size)
2227
+ .astype(int)
2228
+ )
2229
+
2230
+ if self.troubleshoot_mode:
2231
+ print("Troubleshoot mode enabled - not removing helper columns")
2232
+ sys.stdout.flush()
2233
+
2234
+ # Drop helper cols if not in troubleshoot mode
2235
+ if not self.troubleshoot_mode:
2236
+ df.drop(
2237
+ columns=[
2238
+ "combined_checks",
2239
+ "erv_efficiency_oa",
2240
+ ],
2241
+ inplace=True,
2242
+ )
2243
+
2244
+ return df
2245
+
2246
+ except MissingColumnError as e:
2247
+ print(f"Error: {e.message}")
2248
+ sys.stdout.flush()
2249
+ raise e
2250
+ except InvalidParameterError as e:
2251
+ print(f"Error: {e.message}")
2252
+ sys.stdout.flush()
2253
+ raise e