fix(lstm): change the algorithm for LSTM
Browse files- app.py +35 -9
- controller.py +79 -66
- database.py +27 -11
- model.py +5 -11
- model/{model_lstm_regression_cyclical.h5 → model_health_best.keras} +2 -2
- model/{scaler_X.pkl → scaler_lstm.pkl} +2 -2
- model/scaler_y.pkl +0 -3
app.py
CHANGED
|
@@ -44,19 +44,26 @@ def greet_json():
|
|
| 44 |
def predict_machine():
|
| 45 |
logger.info("Prediction request received")
|
| 46 |
|
| 47 |
-
sensor = database.get_sensor_readings()
|
| 48 |
logger.info(f"Retrieved sensor: {sensor}")
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
if sensor is None:
|
| 51 |
-
|
| 52 |
return {
|
| 53 |
"success": False,
|
| 54 |
"error": "No sensor data available"
|
| 55 |
}
|
| 56 |
|
| 57 |
sensor_udi = sensor.get("udi")
|
| 58 |
-
|
| 59 |
-
logger.debug(f"Processing sensor data for machine_id: {sensor.get('machine_id')}")
|
| 60 |
|
| 61 |
controller.set_sensor_data(sensor)
|
| 62 |
|
|
@@ -70,18 +77,33 @@ def predict_machine():
|
|
| 70 |
if binary_result.get("failure_predicted"):
|
| 71 |
logger.info("Failure predicted - running classification and time series analysis")
|
| 72 |
classification_result = controller.predict_classification()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
time_series_result = controller.predict_time_series()
|
| 74 |
|
| 75 |
if classification_result.get("success") and time_series_result.get("success"):
|
| 76 |
logger.info(f"Prediction successful - failure_type: {classification_result.get('failure_type')}")
|
| 77 |
|
|
|
|
| 78 |
save_result = database.create_new_predictions(
|
| 79 |
machine_id=sensor.get("machine_id"),
|
| 80 |
timestamp=sensor.get("timestamp"),
|
| 81 |
risk_score=classification_result.get("risk_score"),
|
| 82 |
failure_predicted=True,
|
| 83 |
failure_type=classification_result.get("failure_type"),
|
| 84 |
-
predicted_failure_time=time_series_result.get("predictions", {}).get("
|
| 85 |
confidence=classification_result.get("confidence")
|
| 86 |
)
|
| 87 |
|
|
@@ -94,6 +116,7 @@ def predict_machine():
|
|
| 94 |
}
|
| 95 |
logger.info(f"Prediction successfully saved for UDI {sensor_udi}")
|
| 96 |
database.mark_as_processed(sensor_udi)
|
|
|
|
| 97 |
return {
|
| 98 |
"success": True,
|
| 99 |
"failure_predicted": True,
|
|
@@ -102,15 +125,17 @@ def predict_machine():
|
|
| 102 |
"risk_score": classification_result.get("risk_score"),
|
| 103 |
"risk_level": classification_result.get("risk_level"),
|
| 104 |
"all_probabilities": classification_result.get("all_probabilities"),
|
| 105 |
-
"
|
| 106 |
"timestamp": sensor.get("timestamp")
|
| 107 |
}
|
| 108 |
else:
|
| 109 |
logger.error(f"Classification or time series prediction failed for UDI {sensor_udi}")
|
| 110 |
-
if
|
| 111 |
database.reset_last_processed_id()
|
| 112 |
return binary_result
|
| 113 |
-
else:
|
|
|
|
|
|
|
| 114 |
save_result = database.create_new_predictions(
|
| 115 |
machine_id=sensor.get("machine_id"),
|
| 116 |
timestamp=sensor.get("timestamp"),
|
|
@@ -123,4 +148,5 @@ def predict_machine():
|
|
| 123 |
|
| 124 |
if save_result and save_result.get("success"):
|
| 125 |
database.mark_as_processed(sensor_udi)
|
| 126 |
-
|
|
|
|
|
|
| 44 |
def predict_machine():
|
| 45 |
logger.info("Prediction request received")
|
| 46 |
|
| 47 |
+
sensor = database.get_sensor_readings(1)
|
| 48 |
logger.info(f"Retrieved sensor: {sensor}")
|
| 49 |
|
| 50 |
+
# Check if sensor is a duplicate error response
|
| 51 |
+
if isinstance(sensor, dict) and sensor.get("message") == "Data already predicted":
|
| 52 |
+
logger.warning("Data already predicted - returning error")
|
| 53 |
+
return {
|
| 54 |
+
"success": False,
|
| 55 |
+
"error": "Data already predicted"
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
if sensor is None:
|
| 59 |
+
logger.warning("No sensor data available")
|
| 60 |
return {
|
| 61 |
"success": False,
|
| 62 |
"error": "No sensor data available"
|
| 63 |
}
|
| 64 |
|
| 65 |
sensor_udi = sensor.get("udi")
|
| 66 |
+
logger.debug(f"Processing sensor data for machine_id: {sensor.get('machine_id')}, UDI: {sensor_udi}")
|
|
|
|
| 67 |
|
| 68 |
controller.set_sensor_data(sensor)
|
| 69 |
|
|
|
|
| 77 |
if binary_result.get("failure_predicted"):
|
| 78 |
logger.info("Failure predicted - running classification and time series analysis")
|
| 79 |
classification_result = controller.predict_classification()
|
| 80 |
+
|
| 81 |
+
target_machine = sensor.get("machine_id")
|
| 82 |
+
|
| 83 |
+
sensor_lstm = database.get_sensor_readings(30, machine_id=target_machine)
|
| 84 |
+
if sensor_lstm is None or (isinstance(sensor_lstm, dict) and sensor_lstm.get("message")):
|
| 85 |
+
logger.warning("Not enough time-series data available")
|
| 86 |
+
sensor_lstm = []
|
| 87 |
+
|
| 88 |
+
# Set time series data for RUL prediction
|
| 89 |
+
if sensor_lstm:
|
| 90 |
+
controller.set_sensor_data(sensor_lstm)
|
| 91 |
+
else:
|
| 92 |
+
controller.set_sensor_data(sensor)
|
| 93 |
+
|
| 94 |
time_series_result = controller.predict_time_series()
|
| 95 |
|
| 96 |
if classification_result.get("success") and time_series_result.get("success"):
|
| 97 |
logger.info(f"Prediction successful - failure_type: {classification_result.get('failure_type')}")
|
| 98 |
|
| 99 |
+
# Uncomment below when ready to save predictions
|
| 100 |
save_result = database.create_new_predictions(
|
| 101 |
machine_id=sensor.get("machine_id"),
|
| 102 |
timestamp=sensor.get("timestamp"),
|
| 103 |
risk_score=classification_result.get("risk_score"),
|
| 104 |
failure_predicted=True,
|
| 105 |
failure_type=classification_result.get("failure_type"),
|
| 106 |
+
predicted_failure_time=time_series_result.get("predictions", {}).get("predicted_failure_date"),
|
| 107 |
confidence=classification_result.get("confidence")
|
| 108 |
)
|
| 109 |
|
|
|
|
| 116 |
}
|
| 117 |
logger.info(f"Prediction successfully saved for UDI {sensor_udi}")
|
| 118 |
database.mark_as_processed(sensor_udi)
|
| 119 |
+
|
| 120 |
return {
|
| 121 |
"success": True,
|
| 122 |
"failure_predicted": True,
|
|
|
|
| 125 |
"risk_score": classification_result.get("risk_score"),
|
| 126 |
"risk_level": classification_result.get("risk_level"),
|
| 127 |
"all_probabilities": classification_result.get("all_probabilities"),
|
| 128 |
+
"rul_prediction": time_series_result.get("predictions"),
|
| 129 |
"timestamp": sensor.get("timestamp")
|
| 130 |
}
|
| 131 |
else:
|
| 132 |
logger.error(f"Classification or time series prediction failed for UDI {sensor_udi}")
|
| 133 |
+
if not (isinstance(classification_result, dict) and classification_result.get("error") == "Data already predicted"):
|
| 134 |
database.reset_last_processed_id()
|
| 135 |
return binary_result
|
| 136 |
+
else:
|
| 137 |
+
logger.info(f"No failure predicted for UDI {sensor_udi}")
|
| 138 |
+
# Uncomment below when ready to save predictions
|
| 139 |
save_result = database.create_new_predictions(
|
| 140 |
machine_id=sensor.get("machine_id"),
|
| 141 |
timestamp=sensor.get("timestamp"),
|
|
|
|
| 148 |
|
| 149 |
if save_result and save_result.get("success"):
|
| 150 |
database.mark_as_processed(sensor_udi)
|
| 151 |
+
|
| 152 |
+
return binary_result
|
controller.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
import logging
|
| 2 |
|
| 3 |
-
from database import Database
|
| 4 |
from model import Model
|
| 5 |
from utils import (
|
| 6 |
calculate_risk_score,
|
|
@@ -12,6 +11,8 @@ from utils import (
|
|
| 12 |
prepare_prediction_data,
|
| 13 |
prepare_sensor_data_for_anomaly,
|
| 14 |
)
|
|
|
|
|
|
|
| 15 |
|
| 16 |
logger = logging.getLogger(__name__)
|
| 17 |
|
|
@@ -24,7 +25,10 @@ class Controller:
|
|
| 24 |
|
| 25 |
def set_sensor_data(self, sensor):
|
| 26 |
self.__sensor = sensor
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
# Binary method
|
| 30 |
def predict_binary(self):
|
|
@@ -35,7 +39,8 @@ class Controller:
|
|
| 35 |
"error": "No sensor data available from database.",
|
| 36 |
}
|
| 37 |
|
| 38 |
-
|
|
|
|
| 39 |
logger.info(f"Skipping binary prediction - data already predicted for UDI: {self.__sensor.get('udi')}")
|
| 40 |
return {
|
| 41 |
"success": False,
|
|
@@ -53,8 +58,11 @@ class Controller:
|
|
| 53 |
}
|
| 54 |
|
| 55 |
try:
|
|
|
|
|
|
|
|
|
|
| 56 |
X_scaled = prepare_sensor_data_for_anomaly(
|
| 57 |
-
|
| 58 |
)
|
| 59 |
|
| 60 |
if X_scaled is None:
|
|
@@ -100,7 +108,8 @@ class Controller:
|
|
| 100 |
"error": "No sensor data available from database.",
|
| 101 |
}
|
| 102 |
|
| 103 |
-
|
|
|
|
| 104 |
logger.info(f"Skipping classification prediction - data already predicted for UDI: {self.__sensor.get('udi')}")
|
| 105 |
return {
|
| 106 |
"success": False,
|
|
@@ -115,8 +124,11 @@ class Controller:
|
|
| 115 |
return {"success": False, "error": "Model or scalers not loaded."}
|
| 116 |
|
| 117 |
try:
|
|
|
|
|
|
|
|
|
|
| 118 |
X_scaled = prepare_sensor_data_for_anomaly(
|
| 119 |
-
|
| 120 |
)
|
| 121 |
|
| 122 |
if X_scaled is None:
|
|
@@ -164,82 +176,83 @@ class Controller:
|
|
| 164 |
|
| 165 |
# Time series method
|
| 166 |
def predict_time_series(self):
|
|
|
|
| 167 |
if self.__sensor is None:
|
| 168 |
logger.warning("Time series prediction attempted with no sensor data")
|
| 169 |
-
return {
|
| 170 |
-
"success": False,
|
| 171 |
-
"error": "No sensor data available from database.",
|
| 172 |
-
}
|
| 173 |
|
| 174 |
-
if self.__sensor.get("message") == "Data already predicted":
|
| 175 |
-
logger.info(f"Skipping time series
|
| 176 |
-
return {
|
| 177 |
-
"success": False,
|
| 178 |
-
"error": "Data already predicted",
|
| 179 |
-
}
|
| 180 |
|
| 181 |
-
if
|
| 182 |
-
|
| 183 |
-
or self.__model.scaler_x is None
|
| 184 |
-
or self.__model.scaler_y is None
|
| 185 |
-
):
|
| 186 |
-
logger.error("LSTM model or scalers not loaded - cannot perform prediction")
|
| 187 |
return {"success": False, "error": "Model or scalers not loaded."}
|
| 188 |
|
| 189 |
try:
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
X_sequence = prepare_prediction_data(
|
| 193 |
-
self.__sensor, timestamp, self.__model.scaler_x, 32
|
| 194 |
-
)
|
| 195 |
-
|
| 196 |
-
if X_sequence is None:
|
| 197 |
-
logger.error("Failed to prepare prediction data for time series")
|
| 198 |
-
return {"success": False, "error": "Failed to prepare prediction data."}
|
| 199 |
-
|
| 200 |
-
scaled_prediction = self.__model.model_lstm.predict(X_sequence, verbose=0)
|
| 201 |
-
|
| 202 |
-
prediction = self.__model.scaler_y.inverse_transform(scaled_prediction)
|
| 203 |
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
)
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
|
| 222 |
return {
|
| 223 |
"success": True,
|
| 224 |
"predictions": {
|
| 225 |
-
"
|
| 226 |
-
"
|
| 227 |
-
"
|
| 228 |
-
"
|
| 229 |
-
"
|
|
|
|
| 230 |
},
|
| 231 |
-
"
|
| 232 |
-
"air_temp": self.__sensor.get("air_temp"),
|
| 233 |
-
"process_temp": self.__sensor.get("process_temp"),
|
| 234 |
-
"rotational_speed": self.__sensor.get("rotational_speed"),
|
| 235 |
-
"torque": self.__sensor.get("torque"),
|
| 236 |
-
"tool_wear": self.__sensor.get("tool_wear"),
|
| 237 |
-
},
|
| 238 |
-
"input_timestamp": str(timestamp),
|
| 239 |
}
|
| 240 |
except Exception as e:
|
| 241 |
logger.error(f"Exception in time series prediction: {str(e)}")
|
| 242 |
return {
|
| 243 |
"success": False,
|
| 244 |
"message": f"Failed to predict time-series data: {str(e)}"
|
| 245 |
-
}
|
|
|
|
| 1 |
import logging
|
| 2 |
|
|
|
|
| 3 |
from model import Model
|
| 4 |
from utils import (
|
| 5 |
calculate_risk_score,
|
|
|
|
| 11 |
prepare_prediction_data,
|
| 12 |
prepare_sensor_data_for_anomaly,
|
| 13 |
)
|
| 14 |
+
import numpy as np
|
| 15 |
+
from datetime import datetime, timedelta
|
| 16 |
|
| 17 |
logger = logging.getLogger(__name__)
|
| 18 |
|
|
|
|
| 25 |
|
| 26 |
def set_sensor_data(self, sensor):
|
| 27 |
self.__sensor = sensor
|
| 28 |
+
if isinstance(sensor, list):
|
| 29 |
+
logger.debug(f"Sensor data updated with {len(sensor)} readings")
|
| 30 |
+
else:
|
| 31 |
+
logger.debug(f"Sensor data updated for machine_id: {sensor.get('machine_id')}")
|
| 32 |
|
| 33 |
# Binary method
|
| 34 |
def predict_binary(self):
|
|
|
|
| 39 |
"error": "No sensor data available from database.",
|
| 40 |
}
|
| 41 |
|
| 42 |
+
# Handle both list and dict cases
|
| 43 |
+
if isinstance(self.__sensor, dict) and self.__sensor.get("message") == "Data already predicted":
|
| 44 |
logger.info(f"Skipping binary prediction - data already predicted for UDI: {self.__sensor.get('udi')}")
|
| 45 |
return {
|
| 46 |
"success": False,
|
|
|
|
| 58 |
}
|
| 59 |
|
| 60 |
try:
|
| 61 |
+
# For binary, we always use single sensor data (dict)
|
| 62 |
+
sensor_dict = self.__sensor if isinstance(self.__sensor, dict) else self.__sensor[0]
|
| 63 |
+
|
| 64 |
X_scaled = prepare_sensor_data_for_anomaly(
|
| 65 |
+
sensor_dict, self.__model.preprocessor_anomaly
|
| 66 |
)
|
| 67 |
|
| 68 |
if X_scaled is None:
|
|
|
|
| 108 |
"error": "No sensor data available from database.",
|
| 109 |
}
|
| 110 |
|
| 111 |
+
# Handle both list and dict cases
|
| 112 |
+
if isinstance(self.__sensor, dict) and self.__sensor.get("message") == "Data already predicted":
|
| 113 |
logger.info(f"Skipping classification prediction - data already predicted for UDI: {self.__sensor.get('udi')}")
|
| 114 |
return {
|
| 115 |
"success": False,
|
|
|
|
| 124 |
return {"success": False, "error": "Model or scalers not loaded."}
|
| 125 |
|
| 126 |
try:
|
| 127 |
+
# For classification, we always use single sensor data (dict)
|
| 128 |
+
sensor_dict = self.__sensor if isinstance(self.__sensor, dict) else self.__sensor[0]
|
| 129 |
+
|
| 130 |
X_scaled = prepare_sensor_data_for_anomaly(
|
| 131 |
+
sensor_dict, self.__model.preprocessor_anomaly
|
| 132 |
)
|
| 133 |
|
| 134 |
if X_scaled is None:
|
|
|
|
| 176 |
|
| 177 |
# Time series method
|
| 178 |
def predict_time_series(self):
|
| 179 |
+
# 1. Validation Checks
|
| 180 |
if self.__sensor is None:
|
| 181 |
logger.warning("Time series prediction attempted with no sensor data")
|
| 182 |
+
return {"success": False, "error": "No sensor data available."}
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
+
if isinstance(self.__sensor, dict) and self.__sensor.get("message") == "Data already predicted":
|
| 185 |
+
logger.info(f"Skipping time series - data already predicted for UDI: {self.__sensor.get('udi')}")
|
| 186 |
+
return {"success": False, "error": "Data already predicted"}
|
|
|
|
|
|
|
|
|
|
| 187 |
|
| 188 |
+
if self.__model.model_lstm is None or self.__model.scaler_lstm is None:
|
| 189 |
+
logger.error("LSTM model or scaler_x not loaded")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
return {"success": False, "error": "Model or scalers not loaded."}
|
| 191 |
|
| 192 |
try:
|
| 193 |
+
sensor_data = self.__sensor if isinstance(self.__sensor, list) else [self.__sensor]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
|
| 195 |
+
input_data = []
|
| 196 |
+
for row in sensor_data:
|
| 197 |
+
input_data.append([
|
| 198 |
+
row.get("air_temp"),
|
| 199 |
+
row.get("process_temp"),
|
| 200 |
+
row.get("rotational_speed"),
|
| 201 |
+
row.get("torque"),
|
| 202 |
+
row.get("tool_wear", 0)
|
| 203 |
+
])
|
| 204 |
+
|
| 205 |
+
input_array = np.array(input_data)
|
| 206 |
+
if len(input_array) < 30:
|
| 207 |
+
missing = 30 - len(input_array)
|
| 208 |
+
padding = np.tile(input_array[0], (missing, 1))
|
| 209 |
+
input_array = np.vstack([padding, input_array])
|
| 210 |
+
elif len(input_array) > 30:
|
| 211 |
+
input_array = input_array[-30:]
|
| 212 |
+
|
| 213 |
+
input_scaled = self.__model.scaler_lstm.transform(input_array)
|
| 214 |
+
input_reshaped = input_scaled.reshape(1, 30, 5)
|
| 215 |
+
health_score = float(self.__model.model_lstm.predict(input_reshaped, verbose=0)[0][0])
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
MAX_LIFE_DAYS = 7.0
|
| 219 |
+
MAX_LIFE_MINUTES = MAX_LIFE_DAYS * 24 * 60
|
| 220 |
+
|
| 221 |
+
rul_minutes_left = health_score * MAX_LIFE_MINUTES
|
| 222 |
+
days_remaining = health_score * MAX_LIFE_DAYS
|
| 223 |
+
|
| 224 |
+
now_str = sensor_data[0].get("timestamp")
|
| 225 |
+
if isinstance(now_str, str):
|
| 226 |
+
now = datetime.fromisoformat(now_str.replace('Z', '+00:00'))
|
| 227 |
+
else:
|
| 228 |
+
now = datetime.now()
|
| 229 |
+
|
| 230 |
+
failure_date = now + timedelta(minutes=rul_minutes_left)
|
| 231 |
+
|
| 232 |
+
if days_remaining < 1.0:
|
| 233 |
+
status = "Critical"
|
| 234 |
+
elif days_remaining < 3.0:
|
| 235 |
+
status = "Warning"
|
| 236 |
+
else:
|
| 237 |
+
status = "Good"
|
| 238 |
+
|
| 239 |
+
logger.info(f"Health: {health_score*100:.1f}% -> {days_remaining:.2f} Days Left")
|
| 240 |
|
| 241 |
return {
|
| 242 |
"success": True,
|
| 243 |
"predictions": {
|
| 244 |
+
"health_score": round(health_score, 4), # e.g., 0.95
|
| 245 |
+
"health_percentage": round(health_score * 100, 2), # e.g., 95.0%
|
| 246 |
+
"rul_minutes": round(rul_minutes_left, 2),
|
| 247 |
+
"days_remaining": round(days_remaining, 2),
|
| 248 |
+
"predicted_failure_date": failure_date.strftime('%Y-%m-%d %H:%M:%S'),
|
| 249 |
+
"status": status
|
| 250 |
},
|
| 251 |
+
"input_timestamp": str(now_str),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
}
|
| 253 |
except Exception as e:
|
| 254 |
logger.error(f"Exception in time series prediction: {str(e)}")
|
| 255 |
return {
|
| 256 |
"success": False,
|
| 257 |
"message": f"Failed to predict time-series data: {str(e)}"
|
| 258 |
+
}
|
database.py
CHANGED
|
@@ -32,19 +32,29 @@ class Database():
|
|
| 32 |
logger.info("Reset last_processed_id - data can be reprocessed")
|
| 33 |
|
| 34 |
|
| 35 |
-
def get_sensor_readings(self):
|
| 36 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
response = (
|
| 38 |
-
|
| 39 |
-
.select("*")
|
| 40 |
.order("created_at", desc=True)
|
| 41 |
-
.limit(
|
| 42 |
.execute()
|
| 43 |
)
|
| 44 |
|
| 45 |
-
if response.data:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
current_reading = response.data[0]
|
| 47 |
|
|
|
|
| 48 |
if self.__last_processed_id == current_reading.get("udi"):
|
| 49 |
logger.warning(f"Duplicate data detected - UDI {current_reading.get('udi')} already processed")
|
| 50 |
return {
|
|
@@ -52,11 +62,17 @@ class Database():
|
|
| 52 |
"message": "Data already predicted",
|
| 53 |
}
|
| 54 |
|
| 55 |
-
|
|
|
|
| 56 |
return current_reading
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
except Exception as e:
|
| 61 |
logger.error(f"Failed to retrieve sensor readings: {str(e)}")
|
| 62 |
return None
|
|
@@ -76,8 +92,8 @@ class Database():
|
|
| 76 |
})
|
| 77 |
.execute()
|
| 78 |
)
|
| 79 |
-
logger.info(f"Prediction saved - Machine: {machine_id}, Failure: {failure_predicted}, Risk: {risk_score}")
|
| 80 |
return {"success": True, "data": response}
|
| 81 |
except Exception as e:
|
| 82 |
logger.error(f"Failed to save prediction to database: {str(e)}")
|
| 83 |
-
return {"success": False, "error": str(e)}
|
|
|
|
| 32 |
logger.info("Reset last_processed_id - data can be reprocessed")
|
| 33 |
|
| 34 |
|
| 35 |
+
def get_sensor_readings(self, limit, machine_id=None):
|
| 36 |
try:
|
| 37 |
+
query = self.__supabase.table("sensor_readings").select("*")
|
| 38 |
+
|
| 39 |
+
if machine_id:
|
| 40 |
+
query = query.eq("machine_id", machine_id)
|
| 41 |
+
|
| 42 |
response = (
|
| 43 |
+
query
|
|
|
|
| 44 |
.order("created_at", desc=True)
|
| 45 |
+
.limit(limit)
|
| 46 |
.execute()
|
| 47 |
)
|
| 48 |
|
| 49 |
+
if not response.data:
|
| 50 |
+
logger.warning("No sensor readings found in database")
|
| 51 |
+
return None
|
| 52 |
+
|
| 53 |
+
if limit == 1:
|
| 54 |
+
# Single reading mode - check for duplicates
|
| 55 |
current_reading = response.data[0]
|
| 56 |
|
| 57 |
+
# Prevent reprocessing the same UDI
|
| 58 |
if self.__last_processed_id == current_reading.get("udi"):
|
| 59 |
logger.warning(f"Duplicate data detected - UDI {current_reading.get('udi')} already processed")
|
| 60 |
return {
|
|
|
|
| 62 |
"message": "Data already predicted",
|
| 63 |
}
|
| 64 |
|
| 65 |
+
self.__last_processed_id = current_reading.get("udi")
|
| 66 |
+
logger.debug(f"Retrieved single sensor reading - UDI: {current_reading.get('udi')}, Machine: {current_reading.get('machine_id')}")
|
| 67 |
return current_reading
|
| 68 |
+
|
| 69 |
+
else:
|
| 70 |
+
# Multiple readings mode - no duplicate check, used for time-series
|
| 71 |
+
logger.debug(f"Retrieved {len(response.data)} sensor readings for time-series prediction")
|
| 72 |
+
history_data = response.data[::-1]
|
| 73 |
+
|
| 74 |
+
return history_data
|
| 75 |
+
|
| 76 |
except Exception as e:
|
| 77 |
logger.error(f"Failed to retrieve sensor readings: {str(e)}")
|
| 78 |
return None
|
|
|
|
| 92 |
})
|
| 93 |
.execute()
|
| 94 |
)
|
| 95 |
+
logger.info(f"Prediction saved - Machine: {machine_id}, Failure: {failure_predicted}, Risk: {risk_score:.2f}")
|
| 96 |
return {"success": True, "data": response}
|
| 97 |
except Exception as e:
|
| 98 |
logger.error(f"Failed to save prediction to database: {str(e)}")
|
| 99 |
+
return {"success": False, "error": str(e)}
|
model.py
CHANGED
|
@@ -7,15 +7,14 @@ from keras.models import load_model
|
|
| 7 |
class Model:
|
| 8 |
def __init__(self) -> None:
|
| 9 |
# Time-Series
|
| 10 |
-
self.__model_lstm = os.path.join("model", "
|
| 11 |
|
| 12 |
# Anomaly Detection
|
| 13 |
self.__model_binary = os.path.join("model", "model_binary_smote.pkl")
|
| 14 |
self.__model_multiclass = os.path.join("model", "model_multi.pkl")
|
| 15 |
|
| 16 |
# Scaler Time-Series
|
| 17 |
-
self.
|
| 18 |
-
self.__scaler_X = os.path.join("model", "scaler_X.pkl")
|
| 19 |
|
| 20 |
# Scaler Anomaly
|
| 21 |
self.__preprocessor_anomaly = os.path.join("model", "scaler_anomaly.pkl")
|
|
@@ -48,17 +47,12 @@ class Model:
|
|
| 48 |
else:
|
| 49 |
print(f"⚠ preprocessor_anomaly not found at {self.__preprocessor_anomaly}")
|
| 50 |
|
| 51 |
-
if os.path.exists(self.__scaler_X):
|
| 52 |
-
self.scaler_x = joblib.load(self.__scaler_X)
|
| 53 |
-
print("✓ scaler_X loaded successfully")
|
| 54 |
-
else:
|
| 55 |
-
print(f"⚠ scaler_X not found at {self.__scaler_X}")
|
| 56 |
|
| 57 |
-
if os.path.exists(self.
|
| 58 |
-
self.
|
| 59 |
print("scaler_y loaded successfully")
|
| 60 |
else:
|
| 61 |
-
print(f"⚠ scaler_y not found at {self.
|
| 62 |
|
| 63 |
except Exception as e:
|
| 64 |
print(f"✗ Error loading model/scalers: {str(e)}")
|
|
|
|
| 7 |
class Model:
|
| 8 |
def __init__(self) -> None:
|
| 9 |
# Time-Series
|
| 10 |
+
self.__model_lstm = os.path.join("model", "model_health_best.keras")
|
| 11 |
|
| 12 |
# Anomaly Detection
|
| 13 |
self.__model_binary = os.path.join("model", "model_binary_smote.pkl")
|
| 14 |
self.__model_multiclass = os.path.join("model", "model_multi.pkl")
|
| 15 |
|
| 16 |
# Scaler Time-Series
|
| 17 |
+
self.__scaler_lstm = os.path.join("model", "scaler_lstm.pkl")
|
|
|
|
| 18 |
|
| 19 |
# Scaler Anomaly
|
| 20 |
self.__preprocessor_anomaly = os.path.join("model", "scaler_anomaly.pkl")
|
|
|
|
| 47 |
else:
|
| 48 |
print(f"⚠ preprocessor_anomaly not found at {self.__preprocessor_anomaly}")
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
+
if os.path.exists(self.__scaler_lstm):
|
| 52 |
+
self.scaler_lstm = joblib.load(self.__scaler_lstm)
|
| 53 |
print("scaler_y loaded successfully")
|
| 54 |
else:
|
| 55 |
+
print(f"⚠ scaler_y not found at {self.__scaler_lstm}")
|
| 56 |
|
| 57 |
except Exception as e:
|
| 58 |
print(f"✗ Error loading model/scalers: {str(e)}")
|
model/{model_lstm_regression_cyclical.h5 → model_health_best.keras}
RENAMED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5f1b298c8549b14c17adda8005dd25881ce00238b7c8c045dd2d592111ca6f1b
|
| 3 |
+
size 908439
|
model/{scaler_X.pkl → scaler_lstm.pkl}
RENAMED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e9474087a5515ba74ad828d7d503584213b0d1e3fbc70990f3aceaccb75ab19f
|
| 3 |
+
size 719
|
model/scaler_y.pkl
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:8650fc69829b340007d54b0fd7c686c8eadb382b15fc3d34fbafe7a5e39af04f
|
| 3 |
-
size 1351
|
|
|
|
|
|
|
|
|
|
|
|