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
from datetime import datetime
# --- CONFIGURATION ---
HF_USERNAME = os.getenv("HF_USERNAME", "iStillWaters")
MODEL_REPO_NAME = os.getenv("MODEL_REPO_NAME", "auto_predictive_maintenance_model")
MODEL_REPO_ID = f"{HF_USERNAME}/{MODEL_REPO_NAME}"
MODEL_FILENAME = "best_engine_model.pkl"
SCALER_FILENAME = "scaler.joblib"
# --- PAGE CONFIG ---
st.set_page_config(
page_title="Engine Health Monitor",
page_icon="🔧",
layout="wide",
initial_sidebar_state="collapsed"
)
# --- LOAD MODEL ---
@st.cache_resource
def load_artifacts():
try:
model_path = hf_hub_download(repo_id=MODEL_REPO_ID, filename=MODEL_FILENAME)
scaler_path = hf_hub_download(repo_id=MODEL_REPO_ID, filename=SCALER_FILENAME)
return joblib.load(model_path), joblib.load(scaler_path), None
except Exception as e:
return None, None, str(e)
# --- INITIALIZE SESSION STATE ---
if 'rpm' not in st.session_state:
st.session_state.rpm = 750
if 'fuel_p' not in st.session_state:
st.session_state.fuel_p = 6.2
if 'oil_p' not in st.session_state:
st.session_state.oil_p = 3.16
if 'coolant_temp' not in st.session_state:
st.session_state.coolant_temp = 80.0
if 'coolant_p' not in st.session_state:
st.session_state.coolant_p = 2.16
if 'oil_temp' not in st.session_state:
st.session_state.oil_temp = 80.0
# Callback functions to sync values
def update_rpm_from_num():
st.session_state.rpm = st.session_state.rpm_num
def update_rpm_from_sld():
st.session_state.rpm = st.session_state.rpm_sld
def update_fuel_from_num():
st.session_state.fuel_p = st.session_state.fuel_num
def update_fuel_from_sld():
st.session_state.fuel_p = st.session_state.fuel_sld
def update_oil_p_from_num():
st.session_state.oil_p = st.session_state.oil_p_num
def update_oil_p_from_sld():
st.session_state.oil_p = st.session_state.oil_p_sld
def update_coolant_t_from_num():
st.session_state.coolant_temp = st.session_state.coolant_t_num
def update_coolant_t_from_sld():
st.session_state.coolant_temp = st.session_state.coolant_t_sld
def update_coolant_p_from_num():
st.session_state.coolant_p = st.session_state.coolant_p_num
def update_coolant_p_from_sld():
st.session_state.coolant_p = st.session_state.coolant_p_sld
def update_oil_t_from_num():
st.session_state.oil_temp = st.session_state.oil_t_num
def update_oil_t_from_sld():
st.session_state.oil_temp = st.session_state.oil_t_sld
# --- CUSTOM CSS ---
st.markdown("""
""", unsafe_allow_html=True)
# --- HELPER FUNCTIONS ---
def create_gauge(value, max_value, color):
fig = go.Figure(go.Indicator(
mode="gauge+number",
value=value,
number={'font': {'size': 11, 'color': color}},
gauge={
'axis': {'range': [0, max_value], 'tickwidth': 1},
'bar': {'color': color, 'thickness': 0.5},
'bgcolor': "rgba(26, 31, 58, 0.3)",
'borderwidth': 1,
'bordercolor': "rgba(255, 255, 255, 0.1)",
'steps': [
{'range': [0, max_value * 0.6], 'color': 'rgba(0, 255, 136, 0.1)'},
{'range': [max_value * 0.6, max_value * 0.8], 'color': 'rgba(255, 170, 0, 0.1)'},
{'range': [max_value * 0.8, max_value], 'color': 'rgba(255, 51, 102, 0.1)'}
]
}
))
fig.update_layout(
height=70,
margin=dict(l=5, r=5, t=5, b=5),
paper_bgcolor='rgba(0,0,0,0)',
plot_bgcolor='rgba(0,0,0,0)',
font={'family': 'Rajdhani'}
)
return fig
def validate_parameter(name, value, thresholds):
if value > thresholds.get('critical_high', float('inf')):
return 'critical', f"🔴 {name}: Critically high ({value})"
elif value < thresholds.get('critical_low', float('-inf')):
return 'critical', f"🔴 {name}: Critically low ({value})"
elif value > thresholds.get('warning_high', float('inf')):
return 'warning', f"⚠️ {name}: High ({value})"
elif value < thresholds.get('warning_low', float('-inf')):
return 'warning', f"⚠️ {name}: Low ({value})"
return 'normal', None
def get_status_info(probability):
if probability < 0.25:
return 'healthy', '🟢', 'HEALTHY', '#00ff88'
elif probability < 0.50:
return 'caution', '🟡', 'CAUTION', '#ffaa00'
elif probability < 0.75:
return 'warning', '🟠', 'WARNING', '#ff9500'
else:
return 'critical', '🔴', 'CRITICAL', '#ff3366'
def calculate_feature_importance(params):
return {
'Engine RPM': params['rpm'] / 2500,
'Coolant Temperature': params['coolant_temp'] / 200,
'Oil Pressure (Risk)': max(0, 1 - params['oil_pressure'] / 10),
'Fuel Pressure': params['fuel_pressure'] / 25
}
def get_recommendations(params, probability, warnings, criticals):
recs = []
if criticals:
recs.append("🚨 IMMEDIATE ACTION: Critical parameters detected")
recs.extend(criticals[:2])
if params['coolant_temp'] > 100:
recs.append("🔧 Check cooling system - radiator and thermostat")
if params['oil_pressure'] < 2.0:
recs.append("🔧 Inspect oil pump and filter")
if params['fuel_pressure'] < 5.0:
recs.append("🔧 Examine fuel system components")
if params['rpm'] > 2000:
recs.append("⚙️ Reduce engine load immediately")
if probability > 0.75:
recs.append("📅 Emergency maintenance required")
elif probability > 0.5:
recs.append("📅 Schedule maintenance within 24 hours")
elif probability > 0.25:
recs.append("📋 Monitor closely")
else:
recs.append("✅ Continue normal operations")
return recs[:6] if recs else ["✅ All systems normal"]
# --- MAIN APP ---
def main():
model, scaler, error = load_artifacts()
if model is None:
st.error(f"⚠️ Model Loading Error: {error}")
st.stop()
# Header
st.markdown('
⚙️ Engine Health Monitor
', unsafe_allow_html=True)
st.markdown('AI-Powered Predictive Maintenance System
', unsafe_allow_html=True)
# --- HORIZONTAL LAYOUT: LEFT (INPUTS) | RIGHT (ANALYSIS) ---
left_col, right_col = st.columns([1, 1.5])
# ========== LEFT COLUMN: PARAMETER INPUTS (2x3 GRID) ==========
with left_col:
st.markdown('📊 Engine Parameters
', unsafe_allow_html=True)
# Parameter thresholds
thresholds = {
'rpm': {'warning_high': 2000, 'critical_high': 2300},
'fuel_pressure': {'warning_low': 5.0, 'critical_low': 4.0},
'oil_pressure': {'warning_low': 2.0, 'critical_low': 1.5},
'coolant_temp': {'warning_high': 100, 'critical_high': 120},
'coolant_pressure': {'warning_low': 1.5, 'critical_low': 1.0},
'oil_temp': {'warning_high': 110, 'critical_high': 130}
}
# ROW 1: RPM and FUEL
row1_col1, row1_col2 = st.columns(2)
with row1_col1:
st.markdown('', unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
input_col1, input_col2 = st.columns([1, 3])
with input_col1:
st.number_input("", 0, 2500, value=st.session_state.rpm, step=50, key="rpm_num", label_visibility="collapsed", on_change=update_rpm_from_num)
with input_col2:
st.slider("", 0, 2500, value=st.session_state.rpm, step=50, key="rpm_sld", label_visibility="collapsed", on_change=update_rpm_from_sld)
st.markdown(f'
{st.session_state.rpm}
', unsafe_allow_html=True)
st.plotly_chart(create_gauge(st.session_state.rpm, 2500, "#00d4ff"), use_container_width=True, config={'displayModeBar': False}, key="g_rpm")
st.markdown('
', unsafe_allow_html=True)
with row1_col2:
st.markdown('', unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
input_col1, input_col2 = st.columns([1, 3])
with input_col1:
st.number_input("", 0.0, 25.0, value=st.session_state.fuel_p, step=0.1, key="fuel_num", label_visibility="collapsed", on_change=update_fuel_from_num)
with input_col2:
st.slider("", 0.0, 25.0, value=st.session_state.fuel_p, step=0.1, key="fuel_sld", label_visibility="collapsed", on_change=update_fuel_from_sld)
st.markdown(f'
{st.session_state.fuel_p:.1f}
', unsafe_allow_html=True)
st.plotly_chart(create_gauge(st.session_state.fuel_p, 25, "#ff6b35"), use_container_width=True, config={'displayModeBar': False}, key="g_fuel")
st.markdown('
', unsafe_allow_html=True)
# ROW 2: OIL PRESSURE and COOLANT TEMP
row2_col1, row2_col2 = st.columns(2)
with row2_col1:
st.markdown('', unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
input_col1, input_col2 = st.columns([1, 3])
with input_col1:
st.number_input("", 0.0, 10.0, value=st.session_state.oil_p, step=0.1, key="oil_p_num", label_visibility="collapsed", on_change=update_oil_p_from_num)
with input_col2:
st.slider("", 0.0, 10.0, value=st.session_state.oil_p, step=0.1, key="oil_p_sld", label_visibility="collapsed", on_change=update_oil_p_from_sld)
st.markdown(f'
{st.session_state.oil_p:.2f}
', unsafe_allow_html=True)
st.plotly_chart(create_gauge(st.session_state.oil_p, 10, "#ffaa00"), use_container_width=True, config={'displayModeBar': False}, key="g_oil_p")
st.markdown('
', unsafe_allow_html=True)
with row2_col2:
st.markdown('', unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
input_col1, input_col2 = st.columns([1, 3])
with input_col1:
st.number_input("", 0.0, 200.0, value=st.session_state.coolant_temp, step=1.0, key="coolant_t_num", label_visibility="collapsed", on_change=update_coolant_t_from_num)
with input_col2:
st.slider("", 0.0, 200.0, value=st.session_state.coolant_temp, step=1.0, key="coolant_t_sld", label_visibility="collapsed", on_change=update_coolant_t_from_sld)
st.markdown(f'
{st.session_state.coolant_temp:.1f}
', unsafe_allow_html=True)
st.plotly_chart(create_gauge(st.session_state.coolant_temp, 200, "#ff3366"), use_container_width=True, config={'displayModeBar': False}, key="g_coolant_t")
st.markdown('
', unsafe_allow_html=True)
# ROW 3: COOLANT PRESSURE and OIL TEMP
row3_col1, row3_col2 = st.columns(2)
with row3_col1:
st.markdown('', unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
input_col1, input_col2 = st.columns([1, 3])
with input_col1:
st.number_input("", 0.0, 10.0, value=st.session_state.coolant_p, step=0.1, key="coolant_p_num", label_visibility="collapsed", on_change=update_coolant_p_from_num)
with input_col2:
st.slider("", 0.0, 10.0, value=st.session_state.coolant_p, step=0.1, key="coolant_p_sld", label_visibility="collapsed", on_change=update_coolant_p_from_sld)
st.markdown(f'
{st.session_state.coolant_p:.2f}
', unsafe_allow_html=True)
st.plotly_chart(create_gauge(st.session_state.coolant_p, 10, "#00ff88"), use_container_width=True, config={'displayModeBar': False}, key="g_coolant_p")
st.markdown('
', unsafe_allow_html=True)
with row3_col2:
st.markdown('', unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
input_col1, input_col2 = st.columns([1, 3])
with input_col1:
st.number_input("", 0.0, 150.0, value=st.session_state.oil_temp, step=1.0, key="oil_t_num", label_visibility="collapsed", on_change=update_oil_t_from_num)
with input_col2:
st.slider("", 0.0, 150.0, value=st.session_state.oil_temp, step=1.0, key="oil_t_sld", label_visibility="collapsed", on_change=update_oil_t_from_sld)
st.markdown(f'
{st.session_state.oil_temp:.1f}
', unsafe_allow_html=True)
st.plotly_chart(create_gauge(st.session_state.oil_temp, 150, "#a855f7"), use_container_width=True, config={'displayModeBar': False}, key="g_oil_t")
st.markdown('
', unsafe_allow_html=True)
# ========== RIGHT COLUMN: ANALYSIS ==========
with right_col:
st.markdown('🔍 Analysis & Results
', unsafe_allow_html=True)
# Real-time validation
warnings = []
criticals = []
for param_name, value, threshold_key in [
('RPM', st.session_state.rpm, 'rpm'),
('Fuel Pressure', st.session_state.fuel_p, 'fuel_pressure'),
('Oil Pressure', st.session_state.oil_p, 'oil_pressure'),
('Coolant Temp', st.session_state.coolant_temp, 'coolant_temp'),
('Coolant Pressure', st.session_state.coolant_p, 'coolant_pressure'),
('Oil Temp', st.session_state.oil_temp, 'oil_temp')
]:
status, msg = validate_parameter(param_name, value, thresholds.get(threshold_key, {}))
if status == 'critical' and msg:
criticals.append(msg)
elif status == 'warning' and msg:
warnings.append(msg)
# Display alerts
if criticals or warnings:
if criticals:
for alert in criticals:
st.markdown(f'{alert}
', unsafe_allow_html=True)
if warnings:
for alert in warnings:
st.markdown(f'{alert}
', unsafe_allow_html=True)
# Analyze button
if st.button("🔍 ANALYZE ENGINE STATUS", type="primary", use_container_width=True):
input_df = pd.DataFrame({
'Engine rpm': [st.session_state.rpm],
'Lub oil pressure': [st.session_state.oil_p],
'Fuel pressure': [st.session_state.fuel_p],
'Coolant pressure': [st.session_state.coolant_p],
'lub oil temp': [st.session_state.oil_temp],
'Coolant temp': [st.session_state.coolant_temp]
})
try:
scaled = scaler.transform(input_df)
pred = model.predict(scaled)[0]
prob = model.predict_proba(scaled)[0][1] if hasattr(model, 'predict_proba') else 0.0
status, emoji, status_text, color = get_status_info(prob)
# Engine Status
st.markdown(f"""
{emoji}
{prob*100:.1f}% Failure Risk
{status_text}
""", unsafe_allow_html=True)
# Feature Importance
st.markdown('⚡ Risk Factors
', unsafe_allow_html=True)
params_dict = {
'rpm': st.session_state.rpm,
'coolant_temp': st.session_state.coolant_temp,
'oil_pressure': st.session_state.oil_p,
'fuel_pressure': st.session_state.fuel_p
}
importance = calculate_feature_importance(params_dict)
sorted_features = sorted(importance.items(), key=lambda x: x[1], reverse=True)
for feature, score in sorted_features:
if score > 0.3:
bar_color = '#ff3366' if score > 0.7 else '#ffaa00' if score > 0.5 else '#00d4ff'
st.markdown(f"""
{feature}: {score*100:.1f}%
""", unsafe_allow_html=True)
# Recommendations
st.markdown('📋 Recommendations
', unsafe_allow_html=True)
recommendations = get_recommendations(params_dict, prob, warnings, criticals)
for rec in recommendations:
st.markdown(f'{rec}
', unsafe_allow_html=True)
# Analysis Details
st.markdown('📊 Details
', unsafe_allow_html=True)
st.markdown(f"""
Prediction Summary
Probability: {prob*100:.2f}% |
Classification: {'FAILURE RISK' if pred == 1 else 'OPERATIONAL'} |
Status: {status_text}
Time: {datetime.now().strftime('%H:%M:%S')}
""", unsafe_allow_html=True)
st.markdown(f"""
Parameter Check
RPM: {st.session_state.rpm} {'⚠️' if st.session_state.rpm > 2000 else '✓'} |
Coolant: {st.session_state.coolant_temp:.1f}°C {'⚠️' if st.session_state.coolant_temp > 100 else '✓'}
Oil P: {st.session_state.oil_p:.2f} Bar {'⚠️' if st.session_state.oil_p < 2.0 else '✓'} |
Fuel P: {st.session_state.fuel_p:.1f} Bar {'⚠️' if st.session_state.fuel_p < 5.0 else '✓'}
""", unsafe_allow_html=True)
except Exception as e:
st.error(f"Prediction Error: {str(e)}")
else:
st.markdown("""
⚙️
Awaiting Analysis
Configure parameters and click "ANALYZE"
""", unsafe_allow_html=True)
if __name__ == "__main__":
main()