""" Engine Predictive Maintenance - Deployment App Final submission: Load model from Hugging Face hub; get inputs and save into dataframe; predict. Designed for Streamlit on Hugging Face Spaces. """ import streamlit as st import pandas as pd import joblib import os import plotly.graph_objects as go from huggingface_hub import hf_hub_download FEATURES = [ "Engine_RPM", "Lub_Oil_Pressure", "Fuel_Pressure", "Coolant_Pressure", "Lub_Oil_Temperature", "Coolant_Temperature", ] MODEL_REPO = "ananttripathiak/engine-pm-model" MODEL_FILENAME = "best_model.joblib" # Default sensor values = row with lowest maintenance prob in train set (~44% โ†’ Normal) DEFAULT_SENSORS = { "Engine_RPM": 1437, "Lub_Oil_Pressure": 1.9, "Fuel_Pressure": 3.8, "Coolant_Pressure": 3.8, "Lub_Oil_Temperature": 77.5, "Coolant_Temperature": 79.8, } # Must be first Streamlit command st.set_page_config( page_title="Engine Predictive Maintenance", page_icon="๐Ÿ”ง", layout="wide", initial_sidebar_state="expanded", ) # Custom CSS for better visuals st.markdown(""" """, unsafe_allow_html=True) @st.cache_resource def load_model(): path = hf_hub_download( repo_id=MODEL_REPO, filename=MODEL_FILENAME, repo_type="model", token=os.getenv("HF_TOKEN"), ) return joblib.load(path) def main(): # Sidebar: info and legend with st.sidebar: st.markdown("### ๐Ÿ”ง About") st.markdown("Predict **engine condition** from six sensor readings. The model was trained on engine maintenance data and is hosted on the Hugging Face model hub.") st.markdown("---") st.markdown("**Sensors:**") st.markdown("- **RPM** โ€“ engine speed") st.markdown("- **Pressures** โ€“ lubricating oil, fuel, coolant (bar)") st.markdown("- **Temperatures** โ€“ oil & coolant (ยฐC)") st.markdown("---") st.markdown("**Model:** Gradient Boosting (best by F1)") st.markdown("---") st.markdown("**Tip:** Change sensor values and click **Get prediction** again โ€” the probability should change. If it stays the same, clear the app cache (โ‹ฎ โ†’ Clear cache) or re-run the GitHub pipeline to refresh the model on the hub.") # Header st.markdown("""

๐Ÿ”ง Engine Predictive Maintenance

Enter sensor readings below โ€” get a Normal or Maintenance Required prediction

""", unsafe_allow_html=True) try: model = load_model() except Exception as e: st.error(f"Could not load model from Hugging Face ({MODEL_REPO}). Error: {e}") st.info("Ensure the model is uploaded to the hub and HF_TOKEN is set if the repo is private.") return # Inputs OUTSIDE form so values update immediately; button triggers prediction # Defaults = row with lowest maintenance prob in train set (model gives ~44%) st.markdown("#### ๐Ÿ“Š Sensor inputs") c1, c2 = st.columns(2) with c1: engine_rpm = st.number_input("Engine RPM", min_value=0, max_value=5000, value=DEFAULT_SENSORS["Engine_RPM"], key="rpm", help="Revolutions per minute") lub_oil_pressure = st.number_input("Lubricating oil pressure (bar)", min_value=0.0, max_value=15.0, value=DEFAULT_SENSORS["Lub_Oil_Pressure"], step=0.1, key="lop") fuel_pressure = st.number_input("Fuel pressure (bar)", min_value=0.0, max_value=25.0, value=DEFAULT_SENSORS["Fuel_Pressure"], step=0.1, key="fp") with c2: coolant_pressure = st.number_input("Coolant pressure (bar)", min_value=0.0, max_value=10.0, value=DEFAULT_SENSORS["Coolant_Pressure"], step=0.1, key="cp") lub_oil_temp = st.number_input("Lubricating oil temperature (ยฐC)", min_value=50.0, max_value=120.0, value=DEFAULT_SENSORS["Lub_Oil_Temperature"], step=0.5, key="lot") coolant_temp = st.number_input("Coolant temperature (ยฐC)", min_value=50.0, max_value=200.0, value=DEFAULT_SENSORS["Coolant_Temperature"], step=0.5, key="ct") submitted = st.button("๐Ÿš€ Get prediction") # Build input from CURRENT widget values (no form = always in sync) input_df = pd.DataFrame([{ "Engine_RPM": engine_rpm, "Lub_Oil_Pressure": lub_oil_pressure, "Fuel_Pressure": fuel_pressure, "Coolant_Pressure": coolant_pressure, "Lub_Oil_Temperature": lub_oil_temp, "Coolant_Temperature": coolant_temp, }]) if submitted: # Ensure exact feature order and single row for the pipeline X = input_df[FEATURES].copy() st.caption(f"Predicting with: RPM={int(engine_rpm)}, oil P={lub_oil_pressure}, fuel P={fuel_pressure}, coolant P={coolant_pressure}, oil T={lub_oil_temp}, coolant T={coolant_temp}") prediction = model.predict(X)[0] proba = model.predict_proba(X)[0] # proba[0] = Normal, proba[1] = Maintenance Required prob_maintenance = float(proba[1]) prob_normal = float(proba[0]) label = "Maintenance Required" if prediction == 1 else "Normal" # Visual result card if prediction == 1: st.markdown(f"""
โš ๏ธ {label}
Consider scheduling maintenance based on sensor readings.
""", unsafe_allow_html=True) else: st.markdown(f"""
โœ“ {label}
Engine parameters look within normal range.
""", unsafe_allow_html=True) # Probability as metric + progress bar (so you can verify it changes with inputs) st.markdown("**Probability (Maintenance)**") fill_color = "#f59e0b" if prob_maintenance > 0.5 else "#10b981" st.markdown(f"""
""", unsafe_allow_html=True) st.metric("", f"{prob_maintenance:.1%}") st.caption(f"Normal: {prob_normal:.1%} ยท Maintenance: {prob_maintenance:.1%} (should change when you change sensor values)") # Visual summary: radar only, full width st.markdown("---") st.markdown("#### ๐Ÿ“ˆ Visual summary") sensor_labels = ["Engine RPM", "Oil pressure", "Fuel pressure", "Coolant pressure", "Oil temp.", "Coolant temp."] mins = [0, 0, 0, 0, 50, 50] maxs = [5000, 15, 25, 10, 120, 200] units = ["RPM", "bar", "bar", "bar", "ยฐC", "ยฐC"] raw = [engine_rpm, lub_oil_pressure, fuel_pressure, coolant_pressure, lub_oil_temp, coolant_temp] pct = [100 * (v - mn) / (mx - mn) if mx > mn else 0 for v, mn, mx in zip(raw, mins, maxs)] r_filled = pct + [pct[0]] theta_labels = sensor_labels + [sensor_labels[0]] actual_str = [f"{raw[i]:.1f} {units[i]}" if i >= 1 else f"{int(raw[0])} {units[0]}" for i in range(6)] actual_str += [actual_str[0]] fig = go.Figure() fig.add_trace(go.Scatterpolar( r=[100] * 7, theta=theta_labels, fill="toself", fillcolor="rgba(148, 163, 184, 0.08)", line=dict(color="#94a3b8", width=1, dash="dot"), name="Max range", hoverinfo="skip", )) fig.add_trace(go.Scatterpolar( r=r_filled, theta=theta_labels, fill="toself", fillcolor="rgba(59, 130, 246, 0.28)", line=dict(color="#2563eb", width=2.2), name="Readings", customdata=actual_str, hovertemplate="%{theta}
Actual: %{customdata}", )) fig.update_layout( polar=dict( radialaxis=dict( visible=True, range=[0, 105], tickfont=dict(size=12, color="#64748b", family="Inter, system-ui, sans-serif"), tickvals=[20, 40, 60, 80, 100], ticktext=["20", "40", "60", "80", "100"], gridcolor="rgba(203, 213, 225, 0.8)", gridwidth=0.5, linecolor="#e2e8f0", linewidth=0.8, ), angularaxis=dict( tickfont=dict(size=13, color="#1e293b", family="Inter, system-ui, sans-serif"), gridcolor="rgba(226, 232, 240, 0.9)", gridwidth=0.5, linecolor="#e2e8f0", ), bgcolor="#fafbfc", ), showlegend=False, height=420, margin=dict(l=115, r=115, t=45, b=45), paper_bgcolor="#ffffff", plot_bgcolor="#ffffff", font=dict(size=13, color="#1e293b", family="Inter, system-ui, sans-serif"), annotations=[ dict( text="Scale: 0 = min, 100 = max allowed (hover for actual values)", x=0.5, y=-0.08, xref="paper", yref="paper", showarrow=False, font=dict(size=11, color="#94a3b8"), xanchor="center", ), ], ) st.plotly_chart(fig, use_container_width=True, config={"displayModeBar": False}) st.markdown("**Sensor readings (actual values)**") c1, c2, c3 = st.columns(3) with c1: st.markdown(f"Engine RPM: **{int(engine_rpm)}** RPM") st.markdown(f"Oil pressure: **{lub_oil_pressure}** bar") with c2: st.markdown(f"Fuel pressure: **{fuel_pressure}** bar") st.markdown(f"Coolant pressure: **{coolant_pressure}** bar") with c3: st.markdown(f"Oil temp.: **{lub_oil_temp}** ยฐC") st.markdown(f"Coolant temp.: **{coolant_temp}** ยฐC") # Suggested focus: use final estimator (pipeline wraps scaler + clf; only clf has feature_importances_) clf = model[-1] if hasattr(model, "steps") else model if prediction == 1 and hasattr(clf, "feature_importances_"): imp = clf.feature_importances_ idx_sorted = sorted(range(6), key=lambda i: imp[i], reverse=True) top_sensors = [sensor_labels[i] for i in idx_sorted[:3]] extreme = [] for i in range(6): if pct[i] >= 85: extreme.append(f"{sensor_labels[i]} (high: {raw[i]:.1f} {units[i]})") elif pct[i] <= 15: extreme.append(f"{sensor_labels[i]} (low: {raw[i]:.1f} {units[i]})") st.markdown("---") st.markdown("#### ๐Ÿ” Suggested focus (Maintenance Required)") st.markdown("Sensors the model weighs most in this prediction:") st.markdown("**" + " โ†’ ".join(top_sensors) + "**") if extreme: st.markdown("Readings that are high or low in this run:") for e in extreme: st.markdown(f"- {e}") with st.expander("๐Ÿ“‹ Inputs (saved as dataframe)"): st.dataframe(input_df, use_container_width=True) if __name__ == "__main__": main()