diff --git a/open_fdd/air_handling_unit/faults/fault_condition_one.py b/open_fdd/air_handling_unit/faults/fault_condition_one.py index 0649440..2b53efc 100644 --- a/open_fdd/air_handling_unit/faults/fault_condition_one.py +++ b/open_fdd/air_handling_unit/faults/fault_condition_one.py @@ -8,100 +8,26 @@ class FaultConditionOne(FaultCondition): AHU low duct static pressure fan fault. """ - def __init__( - self, - brick_graph, - column_mapping, - troubleshoot_mode=False, - rolling_window_size=10, - ): - self.brick_graph = brick_graph - self.column_mapping = column_mapping # Now passed as an argument - self.troubleshoot_mode = troubleshoot_mode - self.rolling_window_size = rolling_window_size - - # Placeholder for attributes, these will be assigned dynamically - self.duct_static_col = None - self.supply_vfd_speed_col = None - self.duct_static_setpoint_col = None - - # Set default error thresholds and maximums - self.vfd_speed_percent_err_thres = 0.05 - self.vfd_speed_percent_max = 0.99 - self.duct_static_inches_err_thres = 0.1 - - # Assign the attributes by mapping URIs to DataFrame column names - self.assign_attributes() - - def assign_attributes(self): - # Use the full URIs in the queries - duct_static_sensors = self.find_entities_of_type( - "https://brickschema.org/schema/1.1/Brick#Supply_Air_Static_Pressure_Sensor" - ) - vfd_speed_sensors = self.find_entities_of_type( - "https://brickschema.org/schema/1.1/Brick#Supply_Fan_VFD_Speed_Sensor" - ) - static_pressure_setpoints = self.find_entities_of_type( - "https://brickschema.org/schema/1.1/Brick#Static_Pressure_Setpoint" - ) - - # Debugging print statements to check what's found - print("Duct Static Sensors found:", duct_static_sensors) - print("VFD Speed Sensors found:", vfd_speed_sensors) - print("Static Pressure Setpoints found:", static_pressure_setpoints) - - if not ( - duct_static_sensors and vfd_speed_sensors and static_pressure_setpoints - ): - raise ValueError("Required sensors are missing from the Brick model.") - - # Assuming you want to map the first found instance to the corresponding column - self.duct_static_col = self.column_mapping.get( - str(duct_static_sensors[0]), None - ) - self.supply_vfd_speed_col = self.column_mapping.get( - str(vfd_speed_sensors[0]), None - ) - self.duct_static_setpoint_col = self.column_mapping.get( - str(static_pressure_setpoints[0]), None - ) - - # Debugging print statements to check the column mapping results - print("Mapped duct_static_col:", self.duct_static_col) - print("Mapped supply_vfd_speed_col:", self.supply_vfd_speed_col) - print("Mapped duct_static_setpoint_col:", self.duct_static_setpoint_col) - - sys.stdout.flush() - - # Raise an error if any of the columns were not mapped correctly - if None in [ - self.duct_static_col, - self.supply_vfd_speed_col, - self.duct_static_setpoint_col, - ]: - raise ValueError("Column mapping failed for one or more sensors.") - - def find_entities_of_type(self, brick_class_uri): - query = f""" - SELECT ?s WHERE {{ - ?s rdf:type <{brick_class_uri}> . - }} + def __init__(self, dict_): + """ + :param dict_: """ - results = self.brick_graph.query(query) - entities = [row.s for row in results] - if not entities: - print(f"No entities found for type: {brick_class_uri}") - return entities + self.vfd_speed_percent_err_thres = float + self.vfd_speed_percent_max = float + self.duct_static_inches_err_thres = float + self.duct_static_col = str + self.supply_vfd_speed_col = str + self.duct_static_setpoint_col = str + self.troubleshoot_mode = bool # default should be False + self.rolling_window_size = int + + self.set_attributes(dict_) def apply(self, df: pd.DataFrame) -> pd.DataFrame: if self.troubleshoot_mode: - print( - "Troubleshoot mode enabled - additional debug information will be printed." - ) - sys.stdout.flush() self.troubleshoot_cols(df) - # Check analog outputs [data with units of %] are floats only + # check analog outputs [data with units of %] are floats only columns_to_check = [self.supply_vfd_speed_col] self.check_analog_pct(df, columns_to_check) @@ -124,13 +50,11 @@ def apply(self, df: pd.DataFrame) -> pd.DataFrame: # Set flag to 1 if rolling sum equals the window size df["fc1_flag"] = (rolling_sum == self.rolling_window_size).astype(int) - if not self.troubleshoot_mode: - # Remove helper columns if not in troubleshoot mode + if self.troubleshoot_mode: + print("Troubleshoot mode enabled - not removing helper columns") + sys.stdout.flush() del df["static_check_"] del df["fan_check_"] del df["combined_check"] - else: - print("Troubleshoot mode: retaining helper columns for debugging.") - sys.stdout.flush() - return df + return df \ No newline at end of file diff --git a/open_fdd/air_handling_unit/reports/report_fc1.py b/open_fdd/air_handling_unit/reports/report_fc1.py index cb681e1..384f1a9 100644 --- a/open_fdd/air_handling_unit/reports/report_fc1.py +++ b/open_fdd/air_handling_unit/reports/report_fc1.py @@ -4,12 +4,11 @@ class FaultCodeOneReport: - def __init__(self, fault_condition): - # We now expect the FaultConditionOne object instead of a config dictionary - self.duct_static_col = fault_condition.duct_static_col - self.supply_vfd_speed_col = fault_condition.supply_vfd_speed_col - self.duct_static_setpoint_col = fault_condition.duct_static_setpoint_col - self.vfd_speed_percent_err_thres = fault_condition.vfd_speed_percent_err_thres + def __init__(self, config): + self.vfd_speed_percent_err_thres = config["VFD_SPEED_PERCENT_ERR_THRES"] + self.duct_static_col = config["DUCT_STATIC_COL"] + self.supply_vfd_speed_col = config["SUPPLY_VFD_SPEED_COL"] + self.duct_static_setpoint_col = config["DUCT_STATIC_SETPOINT_COL"] def create_plot(self, df: pd.DataFrame, output_col: str = None): if output_col is None: @@ -113,4 +112,4 @@ def display_report_in_ipython(self, df: pd.DataFrame, output_col: str = "fc1_fla else: print("NO FAULTS FOUND - Skipping time-of-day Histogram plot") - sys.stdout.flush() + sys.stdout.flush() \ No newline at end of file diff --git a/open_fdd/tests/ahu/test_ahu_fc1.py b/open_fdd/tests/ahu/test_ahu_fc1.py index dbf7fa5..7c3c9ee 100644 --- a/open_fdd/tests/ahu/test_ahu_fc1.py +++ b/open_fdd/tests/ahu/test_ahu_fc1.py @@ -2,59 +2,38 @@ import pytest from open_fdd.air_handling_unit.faults.fault_condition_one import FaultConditionOne from open_fdd.air_handling_unit.faults.helper_utils import HelperUtils -from rdflib import Graph, Namespace, URIRef, Literal -from rdflib import RDF - - -# Initialize the Brick graph and model -BRICK = Namespace("https://brickschema.org/schema/1.1/Brick#") -BRICKFRAME = Namespace("https://brickschema.org/schema/1.1/BrickFrame#") -brick_graph = Graph() - -brick_graph.bind("brick", BRICK) -brick_graph.bind("brickframe", BRICKFRAME) - -# Create the Air Handling Unit (AHU) and related sensors in the Brick model -ahu = URIRef("http://example.com/building/AHU1") -duct_static_sensor = URIRef( - "http://example.com/building/Supply_Air_Static_Pressure_Sensor" -) -vfd_speed_sensor = URIRef("http://example.com/building/Supply_Fan_VFD_Speed_Sensor") -static_pressure_setpoint = URIRef( - "http://example.com/building/Static_Pressure_Setpoint" -) - -brick_graph.add((ahu, RDF.type, BRICK.Air_Handler_Unit)) -brick_graph.add((duct_static_sensor, RDF.type, BRICK.Supply_Air_Static_Pressure_Sensor)) -brick_graph.add((vfd_speed_sensor, RDF.type, BRICK.Supply_Fan_VFD_Speed_Sensor)) -brick_graph.add((static_pressure_setpoint, RDF.type, BRICK.Static_Pressure_Setpoint)) -brick_graph.add((ahu, BRICKFRAME.hasPoint, duct_static_sensor)) -brick_graph.add((ahu, BRICKFRAME.hasPoint, vfd_speed_sensor)) -brick_graph.add((ahu, BRICKFRAME.hasPoint, static_pressure_setpoint)) - -# Mapping from URIs to DataFrame column names -column_mapping = { - str(duct_static_sensor): "duct_static", - str(vfd_speed_sensor): "supply_vfd_speed", - str(static_pressure_setpoint): "duct_static_setpoint", + +# Constants +TEST_VFD_ERR_THRESHOLD = 0.05 +TEST_VFD_SPEED_MAX = 0.7 +TEST_DUCT_STATIC_ERR_THRESHOLD = 0.1 +TEST_DUCT_STATIC_COL = "duct_static" +TEST_DUCT_STATIC_SETPOINT_COL = "duct_static_setpoint" +TEST_SUPPLY_VFD_SPEED_COL = "supply_vfd_speed" +ROLLING_WINDOW_SIZE = 5 + +# Initialize FaultConditionOne with a dictionary +fault_condition_params = { + "VFD_SPEED_PERCENT_ERR_THRES": TEST_VFD_ERR_THRESHOLD, + "VFD_SPEED_PERCENT_MAX": TEST_VFD_SPEED_MAX, + "DUCT_STATIC_INCHES_ERR_THRES": TEST_DUCT_STATIC_ERR_THRESHOLD, + "DUCT_STATIC_COL": TEST_DUCT_STATIC_COL, + "SUPPLY_VFD_SPEED_COL": TEST_SUPPLY_VFD_SPEED_COL, + "DUCT_STATIC_SETPOINT_COL": TEST_DUCT_STATIC_SETPOINT_COL, + "TROUBLESHOOT_MODE": False, # default value + "ROLLING_WINDOW_SIZE": ROLLING_WINDOW_SIZE, # rolling sum window size } -# Initialize FaultConditionOne with Brick model and column mapping -fc1 = FaultConditionOne( - brick_graph, - column_mapping=column_mapping, - troubleshoot_mode=False, - rolling_window_size=5, -) +fc1 = FaultConditionOne(fault_condition_params) class TestNoFault: def no_fault_df(self) -> pd.DataFrame: data = { - "duct_static": [0.99, 0.99, 0.99, 0.99, 0.99, 0.99], - "duct_static_setpoint": [1.0, 1.0, 1.0, 1.0, 1.0, 1.0], - "supply_vfd_speed": [0.8, 0.8, 0.8, 0.8, 0.8, 0.8], + TEST_DUCT_STATIC_COL: [0.99, 0.99, 0.99, 0.99, 0.99, 0.99], + TEST_DUCT_STATIC_SETPOINT_COL: [1.0, 1.0, 1.0, 1.0, 1.0, 1.0], + TEST_SUPPLY_VFD_SPEED_COL: [0.8, 0.8, 0.8, 0.8, 0.8, 0.8], } return pd.DataFrame(data) @@ -70,7 +49,7 @@ class TestFault: def fault_df(self) -> pd.DataFrame: data = { - "duct_static": [ + TEST_DUCT_STATIC_COL: [ 0.7, 0.7, 0.6, @@ -89,7 +68,7 @@ def fault_df(self) -> pd.DataFrame: 0.55, 0.6, ], - "duct_static_setpoint": [ + TEST_DUCT_STATIC_SETPOINT_COL: [ 1.0, 1.0, 1.0, @@ -108,7 +87,7 @@ def fault_df(self) -> pd.DataFrame: 1.0, 1.0, ], - "supply_vfd_speed": [ + TEST_SUPPLY_VFD_SPEED_COL: [ 0.99, 0.95, 0.96, @@ -134,6 +113,11 @@ def test_fault(self): results = fc1.apply(self.fault_df()) actual = results["fc1_flag"].sum() + # accumilated 5 faults need to happen before an "official fault" + # in TEST_DUCT_STATIC_COL after the 5 first values there is 3 faults + # then artificially adjust fake fan data back to normal and another 5 + # needs happen per ROLLING_WINDOW_SIZE and then 4 faults after that. + # so expected = 3 + 4. expected = 3 + 4 message = f"FC1 fault_df actual is {actual} and expected is {expected}" assert actual == expected, message @@ -143,16 +127,16 @@ class TestFaultOnInt: def fault_df_on_output_int(self) -> pd.DataFrame: data = { - "duct_static": [0.8] * 6, - "duct_static_setpoint": [1.0] * 6, - "supply_vfd_speed": [99] * 6, + TEST_DUCT_STATIC_COL: [0.8] * 6, + TEST_DUCT_STATIC_SETPOINT_COL: [1.0] * 6, + TEST_SUPPLY_VFD_SPEED_COL: [99] * 6, } return pd.DataFrame(data) def test_fault_on_int(self): with pytest.raises( TypeError, - match=HelperUtils().float_int_check_err("supply_vfd_speed"), + match=HelperUtils().float_int_check_err(TEST_SUPPLY_VFD_SPEED_COL), ): fc1.apply(self.fault_df_on_output_int()) @@ -161,15 +145,15 @@ class TestFaultOnFloatGreaterThanOne: def fault_df_on_output_greater_than_one(self) -> pd.DataFrame: data = { - "duct_static": [0.8] * 6, - "duct_static_setpoint": [1.0] * 6, - "supply_vfd_speed": [99.0] * 6, + TEST_DUCT_STATIC_COL: [0.8] * 6, + TEST_DUCT_STATIC_SETPOINT_COL: [1.0] * 6, + TEST_SUPPLY_VFD_SPEED_COL: [99.0] * 6, } return pd.DataFrame(data) def test_fault_on_float_greater_than_one(self): with pytest.raises( TypeError, - match=HelperUtils().float_max_check_err("supply_vfd_speed"), + match=HelperUtils().float_max_check_err(TEST_SUPPLY_VFD_SPEED_COL), ): - fc1.apply(self.fault_df_on_output_greater_than_one()) + fc1.apply(self.fault_df_on_output_greater_than_one()) \ No newline at end of file