iStillWaters commited on
Commit
1fa0e21
·
verified ·
1 Parent(s): 21bf736

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +551 -120
app.py CHANGED
@@ -3,7 +3,10 @@ import pandas as pd
3
  import joblib
4
  import os
5
  import plotly.graph_objects as go
 
6
  from huggingface_hub import hf_hub_download
 
 
7
 
8
  # --- CONFIGURATION ---
9
  HF_USERNAME = os.getenv("HF_USERNAME", "iStillWaters")
@@ -13,26 +16,56 @@ MODEL_REPO_ID = f"{HF_USERNAME}/{MODEL_REPO_NAME}"
13
  MODEL_FILENAME = "best_engine_model.pkl"
14
  SCALER_FILENAME = "scaler.joblib"
15
 
 
 
 
 
 
 
 
 
 
 
 
16
  # --- LOAD ARTIFACTS ---
17
  @st.cache_resource
18
  def load_artifacts():
 
19
  try:
20
  model_path = hf_hub_download(repo_id=MODEL_REPO_ID, filename=MODEL_FILENAME)
21
  scaler_path = hf_hub_download(repo_id=MODEL_REPO_ID, filename=SCALER_FILENAME)
22
- return joblib.load(model_path), joblib.load(scaler_path)
 
 
23
  except Exception as e:
24
- st.error(f"Failed to load model: {e}")
25
- return None, None
26
 
27
- model, scaler = load_artifacts()
 
 
 
 
 
 
 
 
 
 
28
 
29
- # --- HELPER: CREATE GAUGE CHART ---
30
- def create_gauge(value, title, min_val, max_val, color="blue"):
 
 
 
 
 
 
31
  fig = go.Figure(go.Indicator(
32
- mode = "gauge+number",
33
  value = value,
34
- domain = {'x': [0, 1], 'y': [0, 1]},
35
  title = {'text': title, 'font': {'size': 18}},
 
 
36
  gauge = {
37
  'axis': {'range': [min_val, max_val]},
38
  'bar': {'color': color},
@@ -40,133 +73,531 @@ def create_gauge(value, title, min_val, max_val, color="blue"):
40
  'borderwidth': 2,
41
  'bordercolor': "gray",
42
  'steps': [
43
- {'range': [min_val, max_val*0.5], 'color': "#e6e6e6"},
44
- {'range': [max_val*0.5, max_val], 'color': "#c0c0c0"}],
 
 
 
 
 
 
 
45
  }
46
  ))
47
- # Fixed height ensures alignment with images
48
  fig.update_layout(height=250, margin=dict(l=20, r=20, t=50, b=20))
49
  return fig
50
 
51
- # --- UI LAYOUT ---
52
- st.set_page_config(layout="wide", page_title="Engine Dashboard", page_icon="🏎️")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- # --- CUSTOM CSS ---
55
- st.markdown("""
56
- <style>
57
- .main {background-color: #0e1117;}
58
- h1 {text-align: center; color: white;}
 
59
 
60
- /* Center the Diagnostics Header */
61
- .diag-header {
62
- text-align: center;
63
- font-weight: bold;
64
- margin-bottom: 20px;
65
  }
66
 
67
- /* Increase Button Size */
68
- div.stButton > button {
69
- font-size: 24px !important;
70
- font-weight: bold !important;
71
- padding: 10px 20px !important;
72
- width: 100%;
73
- background-color: #ff4b4b;
74
- color: white;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  }
76
- </style>
77
- """, unsafe_allow_html=True)
78
-
79
- st.title("🏎️ Digital Twin: Engine Health Monitor")
80
-
81
- # --- DASHBOARD INPUTS (Left & Right Panels) ---
82
- col_left, col_center, col_right = st.columns([1.2, 2, 1.2])
83
-
84
- with col_left:
85
- st.subheader("Fuel & Air")
86
- rpm = st.slider("Engine RPM", 0, 2500, 750)
87
- fuel_p = st.slider("Fuel Pressure (Bar)", 0.0, 25.0, 6.2)
88
- st.plotly_chart(create_gauge(rpm, "RPM", 0, 2500, "cyan"), use_container_width=True)
89
-
90
- with col_right:
91
- st.subheader("Cooling & Oil")
92
- lub_oil_p = st.slider("Oil Pressure", 0.0, 10.0, 3.16)
93
- coolant_temp = st.slider("Coolant Temp (°C)", 0.0, 200.0, 80.0)
94
- st.plotly_chart(create_gauge(coolant_temp, "Coolant Temp", 0, 200, "orange"), use_container_width=True)
95
-
96
- # Additional Inputs
97
- with st.expander("More Sensors (Fine Tuning)"):
98
- col_ex1, col_ex2 = st.columns(2)
99
- with col_ex1:
100
- coolant_p = st.number_input("Coolant Pressure", 0.0, 10.0, 2.16)
101
- with col_ex2:
102
- lub_oil_t = st.number_input("Oil Temp", 0.0, 100.0, 80.0)
103
-
104
- # --- PREDICTION CENTERPIECE ---
105
- with col_center:
106
- # Center Aligned Header using HTML
107
- st.markdown("<h2 class='diag-header'>🩺 Real-Time Diagnostics</h2>", unsafe_allow_html=True)
108
-
109
- if st.button("Analyze Engine Status"):
110
- if model and scaler:
111
- # Prepare Data
112
- input_df = pd.DataFrame({
113
- 'Engine rpm': [rpm],
114
- 'Lub oil pressure': [lub_oil_p],
115
- 'Fuel pressure': [fuel_p],
116
- 'Coolant pressure': [coolant_p],
117
- 'lub oil temp': [lub_oil_t],
118
- 'Coolant temp': [coolant_temp]
119
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
 
121
- # Scale & Predict
122
- scaled = scaler.transform(input_df)
123
- pred = model.predict(scaled)[0]
124
- try:
125
- prob = model.predict_proba(scaled)[0][1]
126
- except:
127
- prob = 0.0
128
-
129
- # --- DYNAMIC DASHBOARD DISPLAY ---
130
 
131
- # Create Gauge Figure (Used for both cases)
132
- gauge_color = "red" if prob > 0.5 else "green"
133
- fig_prob = go.Figure(go.Indicator(
134
- mode = "gauge+number",
135
- value = prob * 100,
136
- title = {'text': "Failure Probability (%)", 'font': {'size': 20}},
137
- gauge = {
138
- 'axis': {'range': [0, 100]},
139
- 'bar': {'color': gauge_color},
140
- 'threshold': {
141
- 'line': {'color': "red", 'width': 4},
142
- 'thickness': 0.75,
143
- 'value': 90
144
- }
145
- }
146
- ))
147
- fig_prob.update_layout(height=300, margin=dict(l=20, r=20, t=50, b=20))
148
-
149
- # --- SIDE-BY-SIDE LAYOUT ---
150
- # Create two columns within the center column
151
- res_col1, res_col2 = st.columns([1, 1], gap="medium")
152
-
153
- if pred == 1:
154
- st.error("🚨 CRITICAL FAILURE DETECTED")
155
- img_url = "https://freesvg.org/img/check-engine.png"
156
  else:
157
- st.success("✅ SYSTEMS NOMINAL")
158
- img_url = "https://img.freepik.com/premium-vector/check-engine-light-icon-vector-illustration_529846-559.jpg"
159
-
160
- with res_col1:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  st.markdown(
162
- f'<div style="display: flex; justify-content: center; align-items: center; height: 300px;">'
163
- f'<img src="{img_url}" style="max-height: 250px; max-width: 100%; border-radius: 10px;">'
164
- f'</div>',
165
  unsafe_allow_html=True
166
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
- with res_col2:
169
- st.plotly_chart(fig_prob, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
- else:
172
- st.info("Adjust sliders and click Analyze")
 
3
  import joblib
4
  import os
5
  import plotly.graph_objects as go
6
+ from plotly.subplots import make_subplots
7
  from huggingface_hub import hf_hub_download
8
+ from datetime import datetime
9
+ import json
10
 
11
  # --- CONFIGURATION ---
12
  HF_USERNAME = os.getenv("HF_USERNAME", "iStillWaters")
 
16
  MODEL_FILENAME = "best_engine_model.pkl"
17
  SCALER_FILENAME = "scaler.joblib"
18
 
19
+ # Sensor thresholds for warnings
20
+ SENSOR_THRESHOLDS = {
21
+ 'rpm_warning': 2000,
22
+ 'rpm_critical': 2300,
23
+ 'coolant_temp_warning': 100,
24
+ 'coolant_temp_critical': 120,
25
+ 'oil_pressure_low': 2.0,
26
+ 'oil_pressure_critical': 1.5,
27
+ 'fuel_pressure_low': 5.0,
28
+ }
29
+
30
  # --- LOAD ARTIFACTS ---
31
  @st.cache_resource
32
  def load_artifacts():
33
+ """Load model and scaler from HuggingFace Hub with error handling"""
34
  try:
35
  model_path = hf_hub_download(repo_id=MODEL_REPO_ID, filename=MODEL_FILENAME)
36
  scaler_path = hf_hub_download(repo_id=MODEL_REPO_ID, filename=SCALER_FILENAME)
37
+ model = joblib.load(model_path)
38
+ scaler = joblib.load(scaler_path)
39
+ return model, scaler, None
40
  except Exception as e:
41
+ return None, None, str(e)
 
42
 
43
+ # --- SESSION STATE INITIALIZATION ---
44
+ def init_session_state():
45
+ """Initialize session state variables"""
46
+ if 'history' not in st.session_state:
47
+ st.session_state.history = []
48
+ if 'alert_threshold' not in st.session_state:
49
+ st.session_state.alert_threshold = 0.7
50
+ if 'dark_mode' not in st.session_state:
51
+ st.session_state.dark_mode = True
52
+ if 'show_recommendations' not in st.session_state:
53
+ st.session_state.show_recommendations = True
54
 
55
+ # --- HELPER FUNCTIONS ---
56
+ def create_gauge(value, title, min_val, max_val, thresholds=None, color="blue"):
57
+ """Create an enhanced gauge chart with color-coded zones"""
58
+ if thresholds is None:
59
+ # Default thresholds: green (0-60%), yellow (60-80%), red (80-100%)
60
+ thresholds = [min_val, min_val + (max_val-min_val)*0.6,
61
+ min_val + (max_val-min_val)*0.8, max_val]
62
+
63
  fig = go.Figure(go.Indicator(
64
+ mode = "gauge+number+delta",
65
  value = value,
 
66
  title = {'text': title, 'font': {'size': 18}},
67
+ delta = {'reference': (max_val + min_val) / 2, 'relative': False},
68
+ domain = {'x': [0, 1], 'y': [0, 1]},
69
  gauge = {
70
  'axis': {'range': [min_val, max_val]},
71
  'bar': {'color': color},
 
73
  'borderwidth': 2,
74
  'bordercolor': "gray",
75
  'steps': [
76
+ {'range': [thresholds[0], thresholds[1]], 'color': "#90EE90"},
77
+ {'range': [thresholds[1], thresholds[2]], 'color': "#FFD700"},
78
+ {'range': [thresholds[2], thresholds[3]], 'color': "#FFB6C1"}
79
+ ],
80
+ 'threshold': {
81
+ 'line': {'color': "red", 'width': 4},
82
+ 'thickness': 0.75,
83
+ 'value': thresholds[2]
84
+ }
85
  }
86
  ))
 
87
  fig.update_layout(height=250, margin=dict(l=20, r=20, t=50, b=20))
88
  return fig
89
 
90
+ def validate_inputs(rpm, fuel_p, lub_oil_p, coolant_temp, coolant_p, lub_oil_t):
91
+ """Validate sensor readings and return warnings"""
92
+ warnings = []
93
+ critical = []
94
+
95
+ # RPM checks
96
+ if rpm > SENSOR_THRESHOLDS['rpm_critical']:
97
+ critical.append("🔴 CRITICAL: RPM exceeding safe limits!")
98
+ elif rpm > SENSOR_THRESHOLDS['rpm_warning']:
99
+ warnings.append("⚠️ RPM approaching redline")
100
+
101
+ # Coolant temperature checks
102
+ if coolant_temp > SENSOR_THRESHOLDS['coolant_temp_critical']:
103
+ critical.append("🔴 CRITICAL: Engine overheating!")
104
+ elif coolant_temp > SENSOR_THRESHOLDS['coolant_temp_warning']:
105
+ warnings.append("⚠️ Coolant temperature elevated")
106
+
107
+ # Oil pressure checks
108
+ if lub_oil_p < SENSOR_THRESHOLDS['oil_pressure_critical']:
109
+ critical.append("🔴 CRITICAL: Oil pressure dangerously low!")
110
+ elif lub_oil_p < SENSOR_THRESHOLDS['oil_pressure_low']:
111
+ warnings.append("⚠️ Oil pressure below normal")
112
+
113
+ # Fuel pressure checks
114
+ if fuel_p < SENSOR_THRESHOLDS['fuel_pressure_low']:
115
+ warnings.append("⚠️ Fuel pressure low")
116
+
117
+ return warnings, critical
118
+
119
+ def prepare_input_data(rpm, lub_oil_p, fuel_p, coolant_p, lub_oil_t, coolant_temp):
120
+ """Prepare input data for model prediction"""
121
+ return pd.DataFrame({
122
+ 'Engine rpm': [rpm],
123
+ 'Lub oil pressure': [lub_oil_p],
124
+ 'Fuel pressure': [fuel_p],
125
+ 'Coolant pressure': [coolant_p],
126
+ 'lub oil temp': [lub_oil_t],
127
+ 'Coolant temp': [coolant_temp]
128
+ })
129
+
130
+ def run_prediction(model, scaler, input_df):
131
+ """Run model prediction with error handling"""
132
+ try:
133
+ scaled = scaler.transform(input_df)
134
+ pred = model.predict(scaled)[0]
135
+ prob = model.predict_proba(scaled)[0][1] if hasattr(model, 'predict_proba') else 0.0
136
+ return pred, prob, None
137
+ except Exception as e:
138
+ return None, None, str(e)
139
+
140
+ def get_status_info(prob):
141
+ """Get status badge and color based on probability"""
142
+ if prob > 0.75:
143
+ return "🔴 CRITICAL", "#ff4b4b", "CRITICAL FAILURE RISK"
144
+ elif prob > 0.5:
145
+ return "🟡 WARNING", "#ffa500", "ELEVATED FAILURE RISK"
146
+ elif prob > 0.25:
147
+ return "🟠 CAUTION", "#ff8c00", "MODERATE RISK"
148
+ else:
149
+ return "🟢 HEALTHY", "#00cc00", "SYSTEMS NOMINAL"
150
+
151
+ def get_recommendations(input_df, prob, warnings, critical):
152
+ """Generate actionable recommendations based on sensor readings and prediction"""
153
+ recommendations = []
154
+
155
+ # Critical alerts first
156
+ if critical:
157
+ recommendations.extend(critical)
158
+ recommendations.append("🚨 IMMEDIATE ACTION REQUIRED - SHUT DOWN ENGINE")
159
+
160
+ # Sensor-specific recommendations
161
+ rpm = input_df['Engine rpm'].values[0]
162
+ coolant_temp = input_df['Coolant temp'].values[0]
163
+ lub_oil_p = input_df['Lub oil pressure'].values[0]
164
+ fuel_p = input_df['Fuel pressure'].values[0]
165
+
166
+ if coolant_temp > SENSOR_THRESHOLDS['coolant_temp_warning']:
167
+ recommendations.append("🔧 Check coolant level and radiator for blockages")
168
+ recommendations.append("🔧 Inspect water pump operation")
169
+
170
+ if lub_oil_p < SENSOR_THRESHOLDS['oil_pressure_low']:
171
+ recommendations.append("🔧 Check engine oil level immediately")
172
+ recommendations.append("🔧 Inspect oil pump and filter for blockages")
173
+
174
+ if fuel_p < SENSOR_THRESHOLDS['fuel_pressure_low']:
175
+ recommendations.append("🔧 Check fuel filter for clogs")
176
+ recommendations.append("🔧 Inspect fuel pump performance")
177
+
178
+ if rpm > SENSOR_THRESHOLDS['rpm_warning'] and prob > 0.5:
179
+ recommendations.append("🔧 Reduce engine load immediately")
180
+ recommendations.append("🔧 Schedule comprehensive engine inspection")
181
+
182
+ # Model-based recommendations
183
+ if prob > 0.7:
184
+ recommendations.append("📅 Schedule immediate maintenance inspection")
185
+ recommendations.append("📊 Consider engine diagnostics scan")
186
+ elif prob > 0.4:
187
+ recommendations.append("📅 Schedule preventive maintenance within 48 hours")
188
+
189
+ if not recommendations and not warnings:
190
+ recommendations.append("✅ All systems operating within normal parameters")
191
+ recommendations.append("📅 Continue regular maintenance schedule")
192
+
193
+ return recommendations
194
 
195
+ def calculate_feature_importance(input_df):
196
+ """Calculate normalized feature importance scores"""
197
+ rpm_score = input_df['Engine rpm'].values[0] / 2500
198
+ coolant_score = input_df['Coolant temp'].values[0] / 200
199
+ oil_score = max(0, 1 - (input_df['Lub oil pressure'].values[0] / 10))
200
+ fuel_score = input_df['Fuel pressure'].values[0] / 25
201
 
202
+ return {
203
+ 'Engine RPM': rpm_score,
204
+ 'Coolant Temperature': coolant_score,
205
+ 'Oil Pressure (Risk)': oil_score,
206
+ 'Fuel Pressure': fuel_score
207
  }
208
 
209
+ def create_history_chart():
210
+ """Create historical trend chart"""
211
+ if len(st.session_state.history) < 2:
212
+ return None
213
+
214
+ hist_df = pd.DataFrame(st.session_state.history)
215
+
216
+ fig = make_subplots(
217
+ rows=2, cols=1,
218
+ subplot_titles=('Failure Probability Over Time', 'Engine RPM Over Time'),
219
+ vertical_spacing=0.15
220
+ )
221
+
222
+ # Probability trend
223
+ fig.add_trace(
224
+ go.Scatter(
225
+ x=hist_df['timestamp'],
226
+ y=hist_df['probability']*100,
227
+ mode='lines+markers',
228
+ name='Failure Risk (%)',
229
+ line=dict(color='red', width=2),
230
+ marker=dict(size=8)
231
+ ),
232
+ row=1, col=1
233
+ )
234
+
235
+ # Add threshold line
236
+ fig.add_hline(
237
+ y=st.session_state.alert_threshold*100,
238
+ line_dash="dash",
239
+ line_color="orange",
240
+ annotation_text="Alert Threshold",
241
+ row=1, col=1
242
+ )
243
+
244
+ # RPM trend
245
+ fig.add_trace(
246
+ go.Scatter(
247
+ x=hist_df['timestamp'],
248
+ y=hist_df['rpm'],
249
+ mode='lines+markers',
250
+ name='RPM',
251
+ line=dict(color='cyan', width=2),
252
+ marker=dict(size=8)
253
+ ),
254
+ row=2, col=1
255
+ )
256
+
257
+ fig.update_xaxes(title_text="Time", row=2, col=1)
258
+ fig.update_yaxes(title_text="Probability (%)", row=1, col=1)
259
+ fig.update_yaxes(title_text="RPM", row=2, col=1)
260
+
261
+ fig.update_layout(height=500, showlegend=True)
262
+ return fig
263
+
264
+ def export_report(input_df, prob, pred, warnings, critical, recommendations):
265
+ """Generate downloadable report"""
266
+ report = {
267
+ 'Timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
268
+ 'Status': 'FAILURE RISK' if pred == 1 else 'OPERATIONAL',
269
+ 'Failure Probability': f"{prob*100:.2f}%",
270
+ 'Engine RPM': input_df['Engine rpm'].values[0],
271
+ 'Fuel Pressure (Bar)': input_df['Fuel pressure'].values[0],
272
+ 'Oil Pressure': input_df['Lub oil pressure'].values[0],
273
+ 'Oil Temperature': input_df['lub oil temp'].values[0],
274
+ 'Coolant Pressure': input_df['Coolant pressure'].values[0],
275
+ 'Coolant Temperature': input_df['Coolant temp'].values[0],
276
+ 'Warnings': '; '.join(warnings) if warnings else 'None',
277
+ 'Critical Alerts': '; '.join(critical) if critical else 'None',
278
+ 'Recommendations': '; '.join(recommendations[:3]) if recommendations else 'None'
279
  }
280
+ return pd.DataFrame([report])
281
+
282
+ # --- MAIN APP ---
283
+ def main():
284
+ # Page config
285
+ st.set_page_config(
286
+ layout="wide",
287
+ page_title="Engine Health Monitor",
288
+ page_icon="🏎️",
289
+ initial_sidebar_state="expanded"
290
+ )
291
+
292
+ # Initialize session state
293
+ init_session_state()
294
+
295
+ # Load model
296
+ model, scaler, error = load_artifacts()
297
+
298
+ # Check if model loaded successfully
299
+ if model is None or scaler is None:
300
+ st.error(f"⚠️ Failed to load model: {error}")
301
+ st.info("Please check your HuggingFace configuration and ensure the model repository is accessible.")
302
+ st.stop()
303
+
304
+ # --- SIDEBAR ---
305
+ with st.sidebar:
306
+ st.image("https://img.icons8.com/color/96/000000/engine.png", width=80)
307
+ st.title("⚙️ Settings")
308
+
309
+ # Model info
310
+ st.subheader("Model Information")
311
+ st.info(f"**Repository:** {MODEL_REPO_ID}")
312
+ st.info(f"**Model File:** {MODEL_FILENAME}")
313
+
314
+ # Settings
315
+ st.subheader("Configuration")
316
+ st.session_state.alert_threshold = st.slider(
317
+ "Alert Threshold",
318
+ 0.0, 1.0,
319
+ st.session_state.alert_threshold,
320
+ help="Probability threshold for failure alerts"
321
+ )
322
+
323
+ st.session_state.show_recommendations = st.checkbox(
324
+ "Show Recommendations",
325
+ st.session_state.show_recommendations
326
+ )
327
+
328
+ auto_refresh = st.checkbox("🔄 Auto-refresh (10s)", value=False)
329
+
330
+ # History management
331
+ st.subheader("History")
332
+ if st.session_state.history:
333
+ st.metric("Total Analyses", len(st.session_state.history))
334
+ if st.button("Clear History"):
335
+ st.session_state.history = []
336
+ st.rerun()
337
+ else:
338
+ st.info("No analysis history yet")
339
+
340
+ # Info
341
+ st.divider()
342
+ st.caption("🏎️ Digital Twin Engine Monitor v2.0")
343
+ st.caption("Powered by Streamlit & ML")
344
+
345
+ # --- CUSTOM CSS ---
346
+ st.markdown("""
347
+ <style>
348
+ .main {background-color: #0e1117;}
349
+ h1 {text-align: center; color: white;}
350
+
351
+ .diag-header {
352
+ text-align: center;
353
+ font-weight: bold;
354
+ margin-bottom: 20px;
355
+ }
356
+
357
+ div.stButton > button {
358
+ font-size: 20px !important;
359
+ font-weight: bold !important;
360
+ padding: 10px 20px !important;
361
+ width: 100%;
362
+ background-color: #ff4b4b;
363
+ color: white;
364
+ border-radius: 10px;
365
+ }
366
+
367
+ .metric-card {
368
+ background-color: #1e1e1e;
369
+ padding: 15px;
370
+ border-radius: 10px;
371
+ border-left: 4px solid #ff4b4b;
372
+ }
373
+
374
+ .status-badge {
375
+ padding: 10px 20px;
376
+ border-radius: 20px;
377
+ font-weight: bold;
378
+ text-align: center;
379
+ font-size: 24px;
380
+ }
381
+ </style>
382
+ """, unsafe_allow_html=True)
383
+
384
+ # --- HEADER ---
385
+ st.title("🏎️ Digital Twin: Engine Health Monitor")
386
+ st.markdown("### Real-time Predictive Maintenance System")
387
+
388
+ # --- DASHBOARD INPUTS ---
389
+ col_left, col_center, col_right = st.columns([1.2, 2, 1.2])
390
+
391
+ with col_left:
392
+ st.subheader("⛽ Fuel & Air Systems")
393
+ rpm = st.slider("Engine RPM", 0, 2500, 750, step=50)
394
+ fuel_p = st.slider("Fuel Pressure (Bar)", 0.0, 25.0, 6.2, step=0.1)
395
+ st.plotly_chart(
396
+ create_gauge(rpm, "RPM", 0, 2500, color="cyan"),
397
+ use_container_width=True
398
+ )
399
+
400
+ with col_right:
401
+ st.subheader("🛢️ Cooling & Lubrication")
402
+ lub_oil_p = st.slider("Oil Pressure (Bar)", 0.0, 10.0, 3.16, step=0.1)
403
+ coolant_temp = st.slider("Coolant Temp (°C)", 0.0, 200.0, 80.0, step=1.0)
404
+ st.plotly_chart(
405
+ create_gauge(coolant_temp, "Coolant Temp (°C)", 0, 200, color="orange"),
406
+ use_container_width=True
407
+ )
408
+
409
+ # Additional sensors
410
+ with st.expander("🔧 Advanced Sensor Configuration"):
411
+ col_ex1, col_ex2 = st.columns(2)
412
+ with col_ex1:
413
+ coolant_p = st.number_input(
414
+ "Coolant Pressure (Bar)",
415
+ 0.0, 10.0, 2.16, step=0.1,
416
+ help="Cooling system pressure"
417
+ )
418
+ with col_ex2:
419
+ lub_oil_t = st.number_input(
420
+ "Oil Temperature (°C)",
421
+ 0.0, 150.0, 80.0, step=1.0,
422
+ help="Lubrication oil temperature"
423
+ )
424
+
425
+ # --- PREDICTION CENTER ---
426
+ with col_center:
427
+ st.markdown("<h2 class='diag-header'>🩺 Real-Time Diagnostics</h2>", unsafe_allow_html=True)
428
+
429
+ # Validation warnings (before prediction)
430
+ warnings, critical = validate_inputs(rpm, fuel_p, lub_oil_p, coolant_temp, coolant_p, lub_oil_t)
431
+
432
+ if critical:
433
+ for alert in critical:
434
+ st.error(alert)
435
+
436
+ if warnings:
437
+ with st.expander("⚠️ Sensor Warnings", expanded=True):
438
+ for warning in warnings:
439
+ st.warning(warning)
440
+
441
+ # Analysis button
442
+ if st.button("🔍 Analyze Engine Status"):
443
+ # Prepare input
444
+ input_df = prepare_input_data(rpm, lub_oil_p, fuel_p, coolant_p, lub_oil_t, coolant_temp)
445
 
446
+ # Run prediction
447
+ pred, prob, pred_error = run_prediction(model, scaler, input_df)
 
 
 
 
 
 
 
448
 
449
+ if pred_error:
450
+ st.error(f"Prediction error: {pred_error}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
451
  else:
452
+ # Store in history
453
+ st.session_state.history.append({
454
+ 'timestamp': datetime.now(),
455
+ 'rpm': rpm,
456
+ 'probability': prob,
457
+ 'status': 'FAIL' if pred == 1 else 'OK',
458
+ 'coolant_temp': coolant_temp,
459
+ 'oil_pressure': lub_oil_p
460
+ })
461
+
462
+ # Get status
463
+ status_badge, status_color, status_text = get_status_info(prob)
464
+
465
+ # Create probability gauge
466
+ gauge_color = "red" if prob > 0.5 else "green"
467
+ fig_prob = go.Figure(go.Indicator(
468
+ mode = "gauge+number",
469
+ value = prob * 100,
470
+ title = {'text': "Failure Probability (%)", 'font': {'size': 20}},
471
+ number = {'suffix': "%", 'font': {'size': 40}},
472
+ gauge = {
473
+ 'axis': {'range': [0, 100]},
474
+ 'bar': {'color': gauge_color},
475
+ 'steps': [
476
+ {'range': [0, 25], 'color': "#90EE90"},
477
+ {'range': [25, 50], 'color': "#FFD700"},
478
+ {'range': [50, 75], 'color': "#FFA500"},
479
+ {'range': [75, 100], 'color': "#FF6B6B"}
480
+ ],
481
+ 'threshold': {
482
+ 'line': {'color': "red", 'width': 4},
483
+ 'thickness': 0.75,
484
+ 'value': st.session_state.alert_threshold * 100
485
+ }
486
+ }
487
+ ))
488
+ fig_prob.update_layout(height=300, margin=dict(l=20, r=20, t=50, b=20))
489
+
490
+ # Display results
491
  st.markdown(
492
+ f"<div class='status-badge' style='background-color: {status_color}20; "
493
+ f"color: {status_color}; border: 2px solid {status_color};'>"
494
+ f"{status_badge}</div>",
495
  unsafe_allow_html=True
496
  )
497
+ st.markdown("<br>", unsafe_allow_html=True)
498
+
499
+ # Side-by-side layout
500
+ res_col1, res_col2 = st.columns([1, 1], gap="medium")
501
+
502
+ # Image based on status
503
+ if pred == 1 or prob > st.session_state.alert_threshold:
504
+ img_url = "https://freesvg.org/img/check-engine.png"
505
+ else:
506
+ img_url = "https://img.freepik.com/premium-vector/check-engine-light-icon-vector-illustration_529846-559.jpg"
507
+
508
+ with res_col1:
509
+ st.markdown(
510
+ f'<div style="display: flex; justify-content: center; align-items: center; height: 300px;">'
511
+ f'<img src="{img_url}" style="max-height: 250px; max-width: 100%; border-radius: 10px;">'
512
+ f'</div>',
513
+ unsafe_allow_html=True
514
+ )
515
+
516
+ with res_col2:
517
+ st.plotly_chart(fig_prob, use_container_width=True)
518
+
519
+ # Feature importance
520
+ if prob > 0.3:
521
+ st.markdown("---")
522
+ st.subheader("⚡ Key Risk Factors")
523
+ feature_scores = calculate_feature_importance(input_df)
524
+ sorted_factors = sorted(feature_scores.items(), key=lambda x: x[1], reverse=True)
525
+
526
+ cols = st.columns(4)
527
+ for idx, (factor, severity) in enumerate(sorted_factors):
528
+ with cols[idx]:
529
+ st.metric(
530
+ factor,
531
+ f"{severity*100:.1f}%",
532
+ delta=None,
533
+ help=f"Normalized risk score for {factor}"
534
+ )
535
+
536
+ # Recommendations
537
+ if st.session_state.show_recommendations:
538
+ st.markdown("---")
539
+ recommendations = get_recommendations(input_df, prob, warnings, critical)
540
+
541
+ if recommendations:
542
+ st.subheader("🛠️ Recommended Actions")
543
+ for i, rec in enumerate(recommendations, 1):
544
+ st.write(f"{i}. {rec}")
545
+
546
+ # Export report
547
+ st.markdown("---")
548
+ report_df = export_report(input_df, prob, pred, warnings, critical, recommendations)
549
+ csv = report_df.to_csv(index=False)
550
+
551
+ col_export1, col_export2 = st.columns(2)
552
+ with col_export1:
553
+ st.download_button(
554
+ "📥 Download Report (CSV)",
555
+ csv,
556
+ f"engine_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
557
+ "text/csv",
558
+ use_container_width=True
559
+ )
560
+
561
+ with col_export2:
562
+ json_report = report_df.to_json(orient='records', indent=2)
563
+ st.download_button(
564
+ "📥 Download Report (JSON)",
565
+ json_report,
566
+ f"engine_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
567
+ "application/json",
568
+ use_container_width=True
569
+ )
570
+ else:
571
+ st.info("👆 Adjust the sensor sliders above and click **Analyze Engine Status** to run diagnostics")
572
+
573
+ # --- HISTORICAL TRENDS ---
574
+ if st.session_state.history:
575
+ st.markdown("---")
576
+ st.subheader("📊 Historical Analysis")
577
+
578
+ trend_chart = create_history_chart()
579
+ if trend_chart:
580
+ st.plotly_chart(trend_chart, use_container_width=True)
581
 
582
+ # Summary statistics
583
+ hist_df = pd.DataFrame(st.session_state.history)
584
+ col_stat1, col_stat2, col_stat3, col_stat4 = st.columns(4)
585
+
586
+ with col_stat1:
587
+ st.metric("Average Risk", f"{hist_df['probability'].mean()*100:.1f}%")
588
+ with col_stat2:
589
+ st.metric("Max Risk", f"{hist_df['probability'].max()*100:.1f}%")
590
+ with col_stat3:
591
+ failures = (hist_df['status'] == 'FAIL').sum()
592
+ st.metric("Failure Alerts", failures)
593
+ with col_stat4:
594
+ st.metric("Total Analyses", len(hist_df))
595
+
596
+ # Auto-refresh logic
597
+ if auto_refresh:
598
+ import time
599
+ time.sleep(10)
600
+ st.rerun()
601
 
602
+ if __name__ == "__main__":
603
+ main()