|
|
import os |
|
|
|
|
|
import joblib |
|
|
import pandas as pd |
|
|
import streamlit as st |
|
|
from huggingface_hub import hf_hub_download |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
st.set_page_config(page_title="Predictive Maintenance", layout="centered") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BANNER_URL = "https://huggingface.co/spaces/Yashwanthsairam/engine-predictive-maintenance/resolve/main/predictive_maintenance_banner.png" |
|
|
|
|
|
try: |
|
|
st.image(BANNER_URL, use_column_width=True) |
|
|
except TypeError: |
|
|
|
|
|
st.image(BANNER_URL) |
|
|
|
|
|
|
|
|
|
|
|
st.title("Predictive Maintenance for Engine Health") |
|
|
st.write("Enter engine sensor values and get a maintenance prediction.") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HF_MODEL_REPO_ID = os.getenv("HF_MODEL_REPO_ID", "Yashwanthsairam/mlops-predictive-maintenance-engine-final-xgb").strip() |
|
|
MODEL_FILENAME = os.getenv("MODEL_FILENAME", "model.joblib").strip() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@st.cache_resource(show_spinner=True) |
|
|
def load_model(repo_id: str, filename: str): |
|
|
model_path = hf_hub_download(repo_id=repo_id, filename=filename, repo_type="model") |
|
|
return joblib.load(model_path) |
|
|
|
|
|
try: |
|
|
model = load_model(HF_MODEL_REPO_ID, MODEL_FILENAME) |
|
|
st.caption(f"✅ Model loaded from HF Model Hub: `{HF_MODEL_REPO_ID}`") |
|
|
except Exception as e: |
|
|
st.error("❌ Failed to load model from Hugging Face Model Hub.") |
|
|
st.code(str(e)) |
|
|
st.stop() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FEATURES = [ |
|
|
"engine_rpm", |
|
|
"lub_oil_pressure", |
|
|
"fuel_pressure", |
|
|
"coolant_pressure", |
|
|
"lub_oil_temp", |
|
|
"coolant_temp", |
|
|
] |
|
|
|
|
|
LABELS = { |
|
|
"engine_rpm": "Engine RPM", |
|
|
"lub_oil_pressure": "Lub Oil Pressure", |
|
|
"fuel_pressure": "Fuel Pressure", |
|
|
"coolant_pressure": "Coolant Pressure", |
|
|
"lub_oil_temp": "Lub Oil Temperature", |
|
|
"coolant_temp": "Coolant Temperature", |
|
|
} |
|
|
|
|
|
DEFAULTS = { |
|
|
"engine_rpm": 1200.0, |
|
|
"lub_oil_pressure": 3.0, |
|
|
"fuel_pressure": 20.0, |
|
|
"coolant_pressure": 2.0, |
|
|
"lub_oil_temp": 80.0, |
|
|
"coolant_temp": 85.0, |
|
|
} |
|
|
|
|
|
|
|
|
MODEL_EXPECTED_FEATURES = list(model.feature_names_in_) if hasattr(model, "feature_names_in_") else FEATURES |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
st.subheader("Single Prediction") |
|
|
|
|
|
with st.form("predict_form"): |
|
|
st.write("Provide sensor values and click **Predict**.") |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
|
|
with col1: |
|
|
engine_rpm = st.number_input(LABELS["engine_rpm"], min_value=0.0, value=DEFAULTS["engine_rpm"]) |
|
|
lub_oil_pressure = st.number_input(LABELS["lub_oil_pressure"], min_value=0.0, value=DEFAULTS["lub_oil_pressure"]) |
|
|
fuel_pressure = st.number_input(LABELS["fuel_pressure"], min_value=0.0, value=DEFAULTS["fuel_pressure"]) |
|
|
|
|
|
with col2: |
|
|
coolant_pressure = st.number_input(LABELS["coolant_pressure"], min_value=0.0, value=DEFAULTS["coolant_pressure"]) |
|
|
lub_oil_temp = st.number_input(LABELS["lub_oil_temp"], min_value=0.0, value=DEFAULTS["lub_oil_temp"]) |
|
|
coolant_temp = st.number_input(LABELS["coolant_temp"], min_value=0.0, value=DEFAULTS["coolant_temp"]) |
|
|
|
|
|
submitted = st.form_submit_button("Predict") |
|
|
|
|
|
if submitted: |
|
|
user_input = { |
|
|
"engine_rpm": engine_rpm, |
|
|
"lub_oil_pressure": lub_oil_pressure, |
|
|
"fuel_pressure": fuel_pressure, |
|
|
"coolant_pressure": coolant_pressure, |
|
|
"lub_oil_temp": lub_oil_temp, |
|
|
"coolant_temp": coolant_temp, |
|
|
} |
|
|
|
|
|
input_df = pd.DataFrame([user_input]) |
|
|
|
|
|
|
|
|
missing = [c for c in MODEL_EXPECTED_FEATURES if c not in input_df.columns] |
|
|
if missing: |
|
|
st.error("❌ Missing required input columns for this model:") |
|
|
st.write(missing) |
|
|
st.stop() |
|
|
|
|
|
input_df = input_df[MODEL_EXPECTED_FEATURES] |
|
|
|
|
|
st.subheader("Input Data (Aligned to Model Features)") |
|
|
st.dataframe(input_df, use_container_width=True) |
|
|
|
|
|
try: |
|
|
pred = model.predict(input_df)[0] |
|
|
except Exception as e: |
|
|
st.error("❌ Prediction failed. Likely feature mismatch or datatype issue.") |
|
|
st.code(str(e)) |
|
|
st.stop() |
|
|
|
|
|
st.subheader("Prediction Result") |
|
|
if int(pred) == 1: |
|
|
st.error("⚠️ Maintenance Required") |
|
|
else: |
|
|
st.success("✅ Engine Healthy") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
st.divider() |
|
|
st.subheader("Bulk Prediction (CSV Upload)") |
|
|
|
|
|
st.write( |
|
|
"Upload a CSV with columns (snake_case): " |
|
|
"`engine_rpm, lub_oil_pressure, fuel_pressure, coolant_pressure, lub_oil_temp, coolant_temp`" |
|
|
) |
|
|
|
|
|
uploaded_file = st.file_uploader("Upload CSV", type=["csv"]) |
|
|
|
|
|
if uploaded_file is not None: |
|
|
try: |
|
|
bulk_df = pd.read_csv(uploaded_file) |
|
|
except Exception as e: |
|
|
st.error("❌ Failed to read CSV.") |
|
|
st.code(str(e)) |
|
|
st.stop() |
|
|
|
|
|
|
|
|
bulk_df.columns = ( |
|
|
bulk_df.columns.astype(str) |
|
|
.str.strip() |
|
|
.str.lower() |
|
|
.str.replace(r"[^a-z0-9]+", "_", regex=True) |
|
|
.str.strip("_") |
|
|
) |
|
|
|
|
|
missing_cols = [c for c in MODEL_EXPECTED_FEATURES if c not in bulk_df.columns] |
|
|
if missing_cols: |
|
|
st.error("❌ Missing required columns in uploaded file:") |
|
|
st.write(missing_cols) |
|
|
st.stop() |
|
|
|
|
|
X_bulk = bulk_df[MODEL_EXPECTED_FEATURES].copy() |
|
|
|
|
|
try: |
|
|
bulk_pred = model.predict(X_bulk) |
|
|
except Exception as e: |
|
|
st.error("❌ Bulk prediction failed.") |
|
|
st.code(str(e)) |
|
|
st.stop() |
|
|
|
|
|
result_df = bulk_df.copy() |
|
|
result_df["prediction"] = bulk_pred |
|
|
result_df["prediction_label"] = result_df["prediction"].map( |
|
|
{0: "Engine Healthy", 1: "Maintenance Required"} |
|
|
) |
|
|
|
|
|
st.success(f"✅ Bulk prediction completed for {len(result_df)} rows.") |
|
|
st.dataframe(result_df.head(50), use_container_width=True) |
|
|
|
|
|
out_csv = result_df.to_csv(index=False).encode("utf-8") |
|
|
st.download_button( |
|
|
label="⬇️ Download Predictions CSV", |
|
|
data=out_csv, |
|
|
file_name="bulk_predictions.csv", |
|
|
mime="text/csv", |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with st.expander("About this app"): |
|
|
st.write( |
|
|
""" |
|
|
This Streamlit app loads a trained XGBoost model from the Hugging Face **Model Hub** |
|
|
and predicts whether an engine requires maintenance based on sensor inputs. |
|
|
|
|
|
It supports: |
|
|
- **Single prediction** via manual input form |
|
|
- **Bulk prediction** via CSV upload + downloadable results |
|
|
|
|
|
The input dataframe is aligned to the model’s training feature schema to prevent |
|
|
feature mismatch issues. |
|
|
""" |
|
|
) |
|
|
|