iStillWaters commited on
Commit
527087e
·
verified ·
1 Parent(s): e199b60

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +669 -518
app.py CHANGED
@@ -3,10 +3,9 @@ import pandas as pd
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,21 +15,18 @@ MODEL_REPO_ID = f"{HF_USERNAME}/{MODEL_REPO_NAME}"
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)
@@ -40,84 +36,437 @@ def load_artifacts():
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},
72
- 'bgcolor': "white",
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],
@@ -128,7 +477,7 @@ def prepare_input_data(rpm, lub_oil_p, fuel_p, coolant_p, lub_oil_t, coolant_tem
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]
@@ -137,474 +486,276 @@ def run_prediction(model, scaler, input_df):
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
- margin: 0 auto;
366
- display: block;
367
- }
368
-
369
- .metric-card {
370
- background-color: #1e1e1e;
371
- padding: 15px;
372
- border-radius: 10px;
373
- border-left: 4px solid #ff4b4b;
374
- }
375
-
376
- .status-badge {
377
- padding: 10px 20px;
378
- border-radius: 20px;
379
- font-weight: bold;
380
- text-align: center;
381
- font-size: 24px;
382
- }
383
- </style>
384
- """, unsafe_allow_html=True)
385
-
386
- # --- HEADER ---
387
- st.title("🏎️ Digital Twin: Engine Health Monitor")
388
- st.markdown("### Real-time Predictive Maintenance System")
389
-
390
- # --- DASHBOARD INPUTS ---
391
- col_left, col_center, col_right = st.columns([1.2, 2, 1.2])
392
-
393
- with col_left:
394
- st.subheader("⛽ Fuel & Air Systems")
395
- rpm = st.slider("Engine RPM", 0, 2500, 750, step=50)
396
- fuel_p = st.slider("Fuel Pressure (Bar)", 0.0, 25.0, 6.2, step=0.1)
397
- st.plotly_chart(
398
- create_gauge(rpm, "RPM", 0, 2500, color="cyan"),
399
- use_container_width=True
400
- )
401
-
402
- with col_right:
403
- st.subheader("🛢️ Cooling & Lubrication")
404
- lub_oil_p = st.slider("Oil Pressure (Bar)", 0.0, 10.0, 3.16, step=0.1)
405
- coolant_temp = st.slider("Coolant Temp (°C)", 0.0, 200.0, 80.0, step=1.0)
406
- st.plotly_chart(
407
- create_gauge(coolant_temp, "Coolant Temp (°C)", 0, 200, color="orange"),
408
- use_container_width=True
409
- )
410
-
411
- # Additional sensors
412
- with st.expander("🔧 Advanced Sensor Configuration"):
413
- col_ex1, col_ex2 = st.columns(2)
414
- with col_ex1:
415
- coolant_p = st.number_input(
416
- "Coolant Pressure (Bar)",
417
- 0.0, 10.0, 2.16, step=0.1,
418
- help="Cooling system pressure"
419
- )
420
- with col_ex2:
421
- lub_oil_t = st.number_input(
422
- "Oil Temperature (°C)",
423
- 0.0, 150.0, 80.0, step=1.0,
424
- help="Lubrication oil temperature"
425
- )
426
-
427
- # --- PREDICTION CENTER ---
428
- with col_center:
429
- st.markdown("<h2 class='diag-header'>🩺 Real-Time Diagnostics</h2>", unsafe_allow_html=True)
430
-
431
- # Validation warnings (before prediction)
432
- warnings, critical = validate_inputs(rpm, fuel_p, lub_oil_p, coolant_temp, coolant_p, lub_oil_t)
433
-
434
- if critical:
435
- for alert in critical:
436
- st.error(alert)
437
-
438
- if warnings:
439
- with st.expander("⚠️ Sensor Warnings", expanded=True):
440
- for warning in warnings:
441
- st.warning(warning)
442
-
443
- # Analysis button - centered
444
- st.markdown("<div style='margin-top: 20px; margin-bottom: 20px;'></div>", unsafe_allow_html=True)
445
- col_btn_left, col_btn_center, col_btn_right = st.columns([1, 2, 1])
446
- with col_btn_center:
447
- analyze_clicked = st.button("🔍 Analyze Engine Status")
448
-
449
- if analyze_clicked:
450
- # Prepare input
451
- input_df = prepare_input_data(rpm, lub_oil_p, fuel_p, coolant_p, lub_oil_t, coolant_temp)
452
 
453
- # Run prediction
454
- pred, prob, pred_error = run_prediction(model, scaler, input_df)
455
 
456
- if pred_error:
457
- st.error(f"Prediction error: {pred_error}")
458
- else:
459
- # Store in history
460
- st.session_state.history.append({
461
- 'timestamp': datetime.now(),
462
- 'rpm': rpm,
463
- 'probability': prob,
464
- 'status': 'FAIL' if pred == 1 else 'OK',
465
- 'coolant_temp': coolant_temp,
466
- 'oil_pressure': lub_oil_p
467
- })
468
-
469
- # Get status
470
- status_badge, status_color, status_text = get_status_info(prob)
471
-
472
- # Create probability gauge
473
- gauge_color = "red" if prob > 0.5 else "green"
474
- fig_prob = go.Figure(go.Indicator(
475
- mode = "gauge+number",
476
- value = prob * 100,
477
- title = {'text': "Failure Probability (%)", 'font': {'size': 20}},
478
- number = {'suffix': "%", 'font': {'size': 40}},
479
- gauge = {
480
- 'axis': {'range': [0, 100]},
481
- 'bar': {'color': gauge_color},
482
- 'steps': [
483
- {'range': [0, 25], 'color': "#90EE90"},
484
- {'range': [25, 50], 'color': "#FFD700"},
485
- {'range': [50, 75], 'color': "#FFA500"},
486
- {'range': [75, 100], 'color': "#FF6B6B"}
487
- ],
488
- 'threshold': {
489
- 'line': {'color': "red", 'width': 4},
490
- 'thickness': 0.75,
491
- 'value': st.session_state.alert_threshold * 100
492
- }
493
- }
494
- ))
495
- fig_prob.update_layout(height=300, margin=dict(l=20, r=20, t=50, b=20))
496
-
497
- # Display results
498
- st.markdown(
499
- f"<div class='status-badge' style='background-color: {status_color}20; "
500
- f"color: {status_color}; border: 2px solid {status_color};'>"
501
- f"{status_badge}</div>",
502
- unsafe_allow_html=True
503
- )
504
- st.markdown("<br>", unsafe_allow_html=True)
505
-
506
- # Side-by-side layout
507
- res_col1, res_col2 = st.columns([1, 1], gap="medium")
508
-
509
- # Image based on status
510
- if pred == 1 or prob > st.session_state.alert_threshold:
511
- img_url = "https://freesvg.org/img/check-engine.png"
512
- else:
513
- img_url = "https://img.freepik.com/premium-vector/check-engine-light-icon-vector-illustration_529846-559.jpg"
514
-
515
- with res_col1:
516
- st.markdown(
517
- f'<div style="display: flex; justify-content: center; align-items: center; height: 300px;">'
518
- f'<img src="{img_url}" style="max-height: 250px; max-width: 100%; border-radius: 10px;">'
519
- f'</div>',
520
- unsafe_allow_html=True
521
- )
522
-
523
- with res_col2:
524
- st.plotly_chart(fig_prob, use_container_width=True)
525
-
526
- # Feature importance
527
- if prob > 0.3:
528
- st.markdown("---")
529
- st.subheader("⚡ Key Risk Factors")
530
- feature_scores = calculate_feature_importance(input_df)
531
- sorted_factors = sorted(feature_scores.items(), key=lambda x: x[1], reverse=True)
532
-
533
- cols = st.columns(4)
534
- for idx, (factor, severity) in enumerate(sorted_factors):
535
- with cols[idx]:
536
- st.metric(
537
- factor,
538
- f"{severity*100:.1f}%",
539
- delta=None,
540
- help=f"Normalized risk score for {factor}"
541
- )
542
-
543
- # Recommendations
544
- if st.session_state.show_recommendations:
545
- st.markdown("---")
546
- recommendations = get_recommendations(input_df, prob, warnings, critical)
547
-
548
- if recommendations:
549
- st.subheader("🛠️ Recommended Actions")
550
- for i, rec in enumerate(recommendations, 1):
551
- st.write(f"{i}. {rec}")
552
-
553
- # Export report
554
- st.markdown("---")
555
- report_df = export_report(input_df, prob, pred, warnings, critical, recommendations)
556
- csv = report_df.to_csv(index=False)
557
 
558
- col_export1, col_export2 = st.columns(2)
559
- with col_export1:
560
- st.download_button(
561
- "📥 Download Report (CSV)",
562
- csv,
563
- f"engine_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
564
- "text/csv",
565
- use_container_width=True
566
- )
 
 
567
 
568
- with col_export2:
569
- json_report = report_df.to_json(orient='records', indent=2)
570
- st.download_button(
571
- "📥 Download Report (JSON)",
572
- json_report,
573
- f"engine_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
574
- "application/json",
575
- use_container_width=True
576
- )
577
- else:
578
- st.info("👆 Adjust the sensor sliders above and click **Analyze Engine Status** to run diagnostics")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
579
 
580
- # --- HISTORICAL TRENDS ---
581
- if st.session_state.history:
582
- st.markdown("---")
583
- st.subheader("📊 Historical Analysis")
584
 
585
- trend_chart = create_history_chart()
586
- if trend_chart:
587
- st.plotly_chart(trend_chart, use_container_width=True)
588
-
589
- # Summary statistics
590
- hist_df = pd.DataFrame(st.session_state.history)
591
- col_stat1, col_stat2, col_stat3, col_stat4 = st.columns(4)
 
 
 
 
 
592
 
593
- with col_stat1:
594
- st.metric("Average Risk", f"{hist_df['probability'].mean()*100:.1f}%")
595
- with col_stat2:
596
- st.metric("Max Risk", f"{hist_df['probability'].max()*100:.1f}%")
597
- with col_stat3:
598
- failures = (hist_df['status'] == 'FAIL').sum()
599
- st.metric("Failure Alerts", failures)
600
- with col_stat4:
601
- st.metric("Total Analyses", len(hist_df))
602
-
603
- # Auto-refresh logic
604
- if auto_refresh:
605
- import time
606
- time.sleep(10)
607
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
608
 
609
  if __name__ == "__main__":
610
  main()
 
3
  import joblib
4
  import os
5
  import plotly.graph_objects as go
 
6
  from huggingface_hub import hf_hub_download
7
  from datetime import datetime
8
+ import base64
9
 
10
  # --- CONFIGURATION ---
11
  HF_USERNAME = os.getenv("HF_USERNAME", "iStillWaters")
 
15
  MODEL_FILENAME = "best_engine_model.pkl"
16
  SCALER_FILENAME = "scaler.joblib"
17
 
18
+ # --- PAGE CONFIGURATION ---
19
+ st.set_page_config(
20
+ page_title="Engine Predictive Maintenance",
21
+ page_icon="🔧",
22
+ layout="wide",
23
+ initial_sidebar_state="collapsed"
24
+ )
 
 
 
25
 
26
+ # --- LOAD MODEL ---
27
  @st.cache_resource
28
  def load_artifacts():
29
+ """Load ML model and scaler"""
30
  try:
31
  model_path = hf_hub_download(repo_id=MODEL_REPO_ID, filename=MODEL_FILENAME)
32
  scaler_path = hf_hub_download(repo_id=MODEL_REPO_ID, filename=SCALER_FILENAME)
 
36
  except Exception as e:
37
  return None, None, str(e)
38
 
39
+ # --- SESSION STATE ---
40
+ if 'predictions' not in st.session_state:
41
+ st.session_state.predictions = []
42
+ if 'current_status' not in st.session_state:
43
+ st.session_state.current_status = 'unknown'
44
+
45
+ # --- CUSTOM CSS ---
46
+ def load_css():
47
+ st.markdown("""
48
+ <style>
49
+ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Rajdhani:wght@300;400;600;700&display=swap');
50
+
51
+ /* Global Styles */
52
+ .stApp {
53
+ background: linear-gradient(135deg, #0a0e27 0%, #1a1f3a 50%, #0a0e27 100%);
54
+ color: #e0e6ed;
55
+ }
56
+
57
+ .main {
58
+ background: transparent;
59
+ }
60
+
61
+ /* Hide Streamlit Elements */
62
+ #MainMenu {visibility: hidden;}
63
+ footer {visibility: hidden;}
64
+ header {visibility: hidden;}
65
+
66
+ /* Title Section */
67
+ .main-title {
68
+ font-family: 'Orbitron', sans-serif;
69
+ font-size: 3.5rem;
70
+ font-weight: 900;
71
+ text-align: center;
72
+ background: linear-gradient(135deg, #00d4ff 0%, #0090ff 50%, #0051ff 100%);
73
+ -webkit-background-clip: text;
74
+ -webkit-text-fill-color: transparent;
75
+ background-clip: text;
76
+ margin-bottom: 0.5rem;
77
+ text-transform: uppercase;
78
+ letter-spacing: 3px;
79
+ text-shadow: 0 0 30px rgba(0, 212, 255, 0.3);
80
+ }
81
+
82
+ .subtitle {
83
+ font-family: 'Rajdhani', sans-serif;
84
+ font-size: 1.2rem;
85
+ text-align: center;
86
+ color: #8b95a5;
87
+ margin-bottom: 3rem;
88
+ letter-spacing: 2px;
89
+ }
90
+
91
+ /* Dashboard Container */
92
+ .dashboard-container {
93
+ position: relative;
94
+ width: 100%;
95
+ max-width: 1400px;
96
+ margin: 0 auto;
97
+ padding: 2rem;
98
+ }
99
+
100
+ /* Central Engine Icon Container */
101
+ .engine-container {
102
+ position: relative;
103
+ width: 320px;
104
+ height: 320px;
105
+ margin: 4rem auto;
106
+ display: flex;
107
+ align-items: center;
108
+ justify-content: center;
109
+ }
110
+
111
+ /* Rotating Glow Ring */
112
+ .glow-ring {
113
+ position: absolute;
114
+ width: 100%;
115
+ height: 100%;
116
+ border-radius: 50%;
117
+ border: 3px solid transparent;
118
+ background: linear-gradient(45deg, transparent 30%, var(--ring-color, #00d4ff) 50%, transparent 70%);
119
+ animation: rotate 4s linear infinite;
120
+ opacity: 0.6;
121
+ }
122
+
123
+ @keyframes rotate {
124
+ from { transform: rotate(0deg); }
125
+ to { transform: rotate(360deg); }
126
+ }
127
+
128
+ /* Engine Circle Background */
129
+ .engine-circle {
130
+ position: absolute;
131
+ width: 280px;
132
+ height: 280px;
133
+ border-radius: 50%;
134
+ background: radial-gradient(circle at 30% 30%,
135
+ rgba(26, 31, 58, 0.9) 0%,
136
+ rgba(10, 14, 39, 0.95) 100%);
137
+ border: 4px solid var(--border-color, #1a4d7a);
138
+ box-shadow:
139
+ 0 0 40px var(--glow-color, rgba(0, 212, 255, 0.4)),
140
+ inset 0 0 30px rgba(0, 0, 0, 0.5);
141
+ display: flex;
142
+ align-items: center;
143
+ justify-content: center;
144
+ transition: all 0.5s ease;
145
+ }
146
+
147
+ /* Engine Icon */
148
+ .engine-icon {
149
+ font-size: 8rem;
150
+ filter: drop-shadow(0 0 20px var(--glow-color, rgba(0, 212, 255, 0.6)));
151
+ transition: all 0.5s ease;
152
+ }
153
+
154
+ /* Status Colors */
155
+ .status-healthy {
156
+ --border-color: #00ff88;
157
+ --glow-color: rgba(0, 255, 136, 0.5);
158
+ --ring-color: #00ff88;
159
+ }
160
+
161
+ .status-warning {
162
+ --border-color: #ffaa00;
163
+ --glow-color: rgba(255, 170, 0, 0.5);
164
+ --ring-color: #ffaa00;
165
+ }
166
+
167
+ .status-critical {
168
+ --border-color: #ff3366;
169
+ --glow-color: rgba(255, 51, 102, 0.5);
170
+ --ring-color: #ff3366;
171
+ }
172
+
173
+ .status-unknown {
174
+ --border-color: #4a5568;
175
+ --glow-color: rgba(74, 85, 104, 0.3);
176
+ --ring-color: #4a5568;
177
+ }
178
+
179
+ /* Probability Display */
180
+ .probability-display {
181
+ position: absolute;
182
+ bottom: -60px;
183
+ left: 50%;
184
+ transform: translateX(-50%);
185
+ font-family: 'Orbitron', sans-serif;
186
+ font-size: 1.8rem;
187
+ font-weight: 700;
188
+ color: var(--border-color, #00d4ff);
189
+ text-shadow: 0 0 10px var(--glow-color, rgba(0, 212, 255, 0.5));
190
+ white-space: nowrap;
191
+ }
192
+
193
+ /* Parameter Cards Grid */
194
+ .params-grid {
195
+ display: grid;
196
+ grid-template-columns: repeat(3, 1fr);
197
+ gap: 2rem;
198
+ margin-top: 4rem;
199
+ max-width: 1200px;
200
+ margin-left: auto;
201
+ margin-right: auto;
202
+ }
203
+
204
+ /* Individual Parameter Card */
205
+ .param-card {
206
+ background: linear-gradient(135deg,
207
+ rgba(26, 31, 58, 0.6) 0%,
208
+ rgba(10, 14, 39, 0.8) 100%);
209
+ border: 2px solid rgba(0, 212, 255, 0.2);
210
+ border-radius: 16px;
211
+ padding: 1.5rem;
212
+ backdrop-filter: blur(10px);
213
+ transition: all 0.3s ease;
214
+ position: relative;
215
+ overflow: hidden;
216
+ }
217
+
218
+ .param-card::before {
219
+ content: '';
220
+ position: absolute;
221
+ top: 0;
222
+ left: 0;
223
+ width: 100%;
224
+ height: 3px;
225
+ background: linear-gradient(90deg,
226
+ transparent 0%,
227
+ var(--accent-color, #00d4ff) 50%,
228
+ transparent 100%);
229
+ opacity: 0;
230
+ transition: opacity 0.3s ease;
231
+ }
232
+
233
+ .param-card:hover {
234
+ border-color: var(--accent-color, #00d4ff);
235
+ box-shadow: 0 8px 30px rgba(0, 212, 255, 0.15);
236
+ transform: translateY(-4px);
237
+ }
238
+
239
+ .param-card:hover::before {
240
+ opacity: 1;
241
+ }
242
+
243
+ /* Parameter Icon */
244
+ .param-icon {
245
+ font-size: 2.5rem;
246
+ margin-bottom: 0.5rem;
247
+ display: inline-block;
248
+ }
249
+
250
+ /* Parameter Label */
251
+ .param-label {
252
+ font-family: 'Rajdhani', sans-serif;
253
+ font-size: 1.1rem;
254
+ font-weight: 600;
255
+ color: #8b95a5;
256
+ margin-bottom: 0.5rem;
257
+ text-transform: uppercase;
258
+ letter-spacing: 1px;
259
+ }
260
+
261
+ /* Parameter Value */
262
+ .param-value {
263
+ font-family: 'Orbitron', sans-serif;
264
+ font-size: 2rem;
265
+ font-weight: 700;
266
+ color: var(--accent-color, #00d4ff);
267
+ margin-bottom: 1rem;
268
+ }
269
+
270
+ /* Color Accents for Different Parameters */
271
+ .param-rpm { --accent-color: #00d4ff; }
272
+ .param-fuel { --accent-color: #ff6b35; }
273
+ .param-oil-pressure { --accent-color: #ffaa00; }
274
+ .param-coolant-temp { --accent-color: #ff3366; }
275
+ .param-coolant-pressure { --accent-color: #00ff88; }
276
+ .param-oil-temp { --accent-color: #a855f7; }
277
+
278
+ /* Analyze Button */
279
+ .analyze-button {
280
+ display: block;
281
+ margin: 3rem auto;
282
+ padding: 1.2rem 4rem;
283
+ font-family: 'Orbitron', sans-serif;
284
+ font-size: 1.3rem;
285
+ font-weight: 700;
286
+ color: #ffffff;
287
+ background: linear-gradient(135deg, #0090ff 0%, #0051ff 100%);
288
+ border: 2px solid #00d4ff;
289
+ border-radius: 50px;
290
+ cursor: pointer;
291
+ transition: all 0.3s ease;
292
+ text-transform: uppercase;
293
+ letter-spacing: 2px;
294
+ box-shadow: 0 0 30px rgba(0, 144, 255, 0.3);
295
+ }
296
+
297
+ .analyze-button:hover {
298
+ background: linear-gradient(135deg, #00d4ff 0%, #0090ff 100%);
299
+ box-shadow: 0 0 50px rgba(0, 212, 255, 0.5);
300
+ transform: translateY(-2px);
301
+ }
302
+
303
+ /* Status Badge */
304
+ .status-badge {
305
+ display: inline-block;
306
+ padding: 0.8rem 2rem;
307
+ font-family: 'Rajdhani', sans-serif;
308
+ font-size: 1.3rem;
309
+ font-weight: 700;
310
+ border-radius: 30px;
311
+ text-transform: uppercase;
312
+ letter-spacing: 2px;
313
+ margin-top: 1rem;
314
+ }
315
+
316
+ .badge-healthy {
317
+ background: rgba(0, 255, 136, 0.2);
318
+ color: #00ff88;
319
+ border: 2px solid #00ff88;
320
+ box-shadow: 0 0 20px rgba(0, 255, 136, 0.3);
321
+ }
322
+
323
+ .badge-warning {
324
+ background: rgba(255, 170, 0, 0.2);
325
+ color: #ffaa00;
326
+ border: 2px solid #ffaa00;
327
+ box-shadow: 0 0 20px rgba(255, 170, 0, 0.3);
328
+ }
329
+
330
+ .badge-critical {
331
+ background: rgba(255, 51, 102, 0.2);
332
+ color: #ff3366;
333
+ border: 2px solid #ff3366;
334
+ box-shadow: 0 0 20px rgba(255, 51, 102, 0.3);
335
+ }
336
+
337
+ /* Info Panel */
338
+ .info-panel {
339
+ background: linear-gradient(135deg,
340
+ rgba(26, 31, 58, 0.4) 0%,
341
+ rgba(10, 14, 39, 0.6) 100%);
342
+ border-left: 4px solid #00d4ff;
343
+ border-radius: 8px;
344
+ padding: 1.5rem;
345
+ margin: 2rem 0;
346
+ backdrop-filter: blur(10px);
347
+ }
348
+
349
+ .info-title {
350
+ font-family: 'Rajdhani', sans-serif;
351
+ font-size: 1.3rem;
352
+ font-weight: 700;
353
+ color: #00d4ff;
354
+ margin-bottom: 1rem;
355
+ text-transform: uppercase;
356
+ letter-spacing: 1px;
357
+ }
358
+
359
+ .info-content {
360
+ font-family: 'Rajdhani', sans-serif;
361
+ font-size: 1.1rem;
362
+ color: #b8c5d6;
363
+ line-height: 1.6;
364
+ }
365
+
366
+ /* Recommendations List */
367
+ .recommendation-item {
368
+ font-family: 'Rajdhani', sans-serif;
369
+ font-size: 1.1rem;
370
+ color: #e0e6ed;
371
+ padding: 0.8rem 1.2rem;
372
+ margin: 0.5rem 0;
373
+ background: rgba(0, 212, 255, 0.05);
374
+ border-left: 3px solid #00d4ff;
375
+ border-radius: 4px;
376
+ transition: all 0.2s ease;
377
+ }
378
+
379
+ .recommendation-item:hover {
380
+ background: rgba(0, 212, 255, 0.1);
381
+ transform: translateX(4px);
382
+ }
383
+
384
+ /* History Section */
385
+ .history-card {
386
+ background: linear-gradient(135deg,
387
+ rgba(26, 31, 58, 0.5) 0%,
388
+ rgba(10, 14, 39, 0.7) 100%);
389
+ border: 2px solid rgba(0, 212, 255, 0.2);
390
+ border-radius: 12px;
391
+ padding: 1rem;
392
+ margin: 0.5rem 0;
393
+ }
394
+
395
+ /* Slider Customization */
396
+ .stSlider > div > div > div {
397
+ background: linear-gradient(90deg, #0051ff 0%, #00d4ff 100%);
398
+ }
399
+
400
+ /* Pulse Animation for Critical State */
401
+ @keyframes pulse {
402
+ 0%, 100% { opacity: 1; }
403
+ 50% { opacity: 0.6; }
404
+ }
405
+
406
+ .pulse-critical {
407
+ animation: pulse 1.5s ease-in-out infinite;
408
+ }
409
+
410
+ /* Footer */
411
+ .dashboard-footer {
412
+ text-align: center;
413
+ margin-top: 4rem;
414
+ padding: 2rem;
415
+ font-family: 'Rajdhani', sans-serif;
416
+ font-size: 0.9rem;
417
+ color: #6b7280;
418
+ border-top: 1px solid rgba(107, 114, 128, 0.2);
419
+ }
420
+ </style>
421
+ """, unsafe_allow_html=True)
422
 
423
  # --- HELPER FUNCTIONS ---
424
+ def get_engine_status(probability):
425
+ """Determine engine status based on failure probability"""
426
+ if probability < 0.25:
427
+ return 'healthy', '🟢', '#00ff88', 'OPTIMAL'
428
+ elif probability < 0.50:
429
+ return 'warning', '🟡', '#ffaa00', 'CAUTION'
430
+ elif probability < 0.75:
431
+ return 'warning', '🟠', '#ff9500', 'WARNING'
432
+ else:
433
+ return 'critical', '🔴', '#ff3366', 'CRITICAL'
434
+
435
+ def create_circular_gauge(value, max_value, title, color, unit=""):
436
+ """Create a minimal circular gauge"""
437
+ percentage = (value / max_value) * 100
438
 
439
  fig = go.Figure(go.Indicator(
440
+ mode = "gauge+number",
441
  value = value,
442
+ number = {'suffix': f" {unit}", 'font': {'size': 24, 'color': color}},
443
+ title = {'text': title, 'font': {'size': 14, 'color': '#8b95a5'}},
 
444
  gauge = {
445
+ 'axis': {'range': [0, max_value], 'tickwidth': 1, 'tickcolor': color},
446
+ 'bar': {'color': color, 'thickness': 0.7},
447
+ 'bgcolor': "rgba(26, 31, 58, 0.3)",
448
  'borderwidth': 2,
449
+ 'bordercolor': "rgba(255, 255, 255, 0.1)",
450
  'steps': [
451
+ {'range': [0, max_value * 0.6], 'color': 'rgba(0, 255, 136, 0.1)'},
452
+ {'range': [max_value * 0.6, max_value * 0.8], 'color': 'rgba(255, 170, 0, 0.1)'},
453
+ {'range': [max_value * 0.8, max_value], 'color': 'rgba(255, 51, 102, 0.1)'}
454
  ],
 
 
 
 
 
455
  }
456
  ))
457
+
458
+ fig.update_layout(
459
+ height=200,
460
+ margin=dict(l=10, r=10, t=40, b=10),
461
+ paper_bgcolor='rgba(0,0,0,0)',
462
+ plot_bgcolor='rgba(0,0,0,0)',
463
+ font={'family': 'Rajdhani', 'color': '#e0e6ed'}
464
+ )
465
+
466
  return fig
467
 
468
+ def prepare_input_data(rpm, fuel_p, lub_oil_p, coolant_p, lub_oil_t, coolant_temp):
469
+ """Prepare input data for model"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  return pd.DataFrame({
471
  'Engine rpm': [rpm],
472
  'Lub oil pressure': [lub_oil_p],
 
477
  })
478
 
479
  def run_prediction(model, scaler, input_df):
480
+ """Execute model prediction"""
481
  try:
482
  scaled = scaler.transform(input_df)
483
  pred = model.predict(scaled)[0]
 
486
  except Exception as e:
487
  return None, None, str(e)
488
 
489
+ def get_recommendations(input_df, prob):
490
+ """Generate maintenance recommendations"""
 
 
 
 
 
 
 
 
 
 
 
491
  recommendations = []
492
 
 
 
 
 
 
 
493
  rpm = input_df['Engine rpm'].values[0]
494
  coolant_temp = input_df['Coolant temp'].values[0]
495
  lub_oil_p = input_df['Lub oil pressure'].values[0]
496
  fuel_p = input_df['Fuel pressure'].values[0]
497
 
498
+ if prob > 0.75:
499
+ recommendations.append("⚠️ IMMEDIATE SHUTDOWN RECOMMENDED - Critical failure risk detected")
500
+ recommendations.append("📞 Contact maintenance team immediately")
501
 
502
+ if coolant_temp > 100:
503
+ recommendations.append("🌡️ Coolant temperature elevated - Check radiator and cooling system")
 
504
 
505
+ if lub_oil_p < 2.0:
506
+ recommendations.append("🛢️ Low oil pressure detected - Inspect oil pump and filter")
 
507
 
508
+ if fuel_p < 5.0:
509
+ recommendations.append(" Fuel pressure below optimal - Check fuel pump and lines")
 
510
 
511
+ if rpm > 2000:
512
+ recommendations.append("⚙️ High RPM detected - Reduce engine load")
 
 
 
 
513
 
514
+ if prob > 0.5:
515
+ recommendations.append("📅 Schedule comprehensive engine inspection within 24 hours")
516
+ elif prob > 0.25:
517
+ recommendations.append("📋 Monitor engine parameters and schedule preventive maintenance")
518
+ else:
519
+ recommendations.append("✅ Engine operating within normal parameters")
520
 
521
  return recommendations
522
 
523
+ # --- MAIN APPLICATION ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
524
  def main():
525
+ # Load CSS
526
+ load_css()
 
 
 
 
 
 
 
 
527
 
528
+ # Load Model
529
  model, scaler, error = load_artifacts()
530
 
531
+ if model is None:
532
+ st.error(f"⚠️ Model Loading Error: {error}")
 
 
533
  st.stop()
534
 
535
+ # Header
536
+ st.markdown('<h1 class="main-title">⚙️ Engine Health Monitor</h1>', unsafe_allow_html=True)
537
+ st.markdown('<p class="subtitle">AI-Powered Predictive Maintenance System</p>', unsafe_allow_html=True)
538
+
539
+ # Main Dashboard Container
540
+ st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
541
+
542
+ # Parameter Input Section
543
+ st.markdown("### 📊 Engine Parameters")
544
+
545
+ # Create 2 rows of 3 parameters each
546
+ col1, col2, col3 = st.columns(3)
547
+
548
+ with col1:
549
+ st.markdown('<div class="param-card param-rpm">', unsafe_allow_html=True)
550
+ st.markdown('<div class="param-icon">⚡</div>', unsafe_allow_html=True)
551
+ st.markdown('<div class="param-label">Engine RPM</div>', unsafe_allow_html=True)
552
+ rpm = st.slider("", 0, 2500, 750, 50, label_visibility="collapsed", key="rpm")
553
+ st.markdown(f'<div class="param-value">{rpm}</div>', unsafe_allow_html=True)
554
+ st.plotly_chart(create_circular_gauge(rpm, 2500, "RPM", "#00d4ff"), use_container_width=True)
555
+ st.markdown('</div>', unsafe_allow_html=True)
556
+
557
+ with col2:
558
+ st.markdown('<div class="param-card param-fuel">', unsafe_allow_html=True)
559
+ st.markdown('<div class="param-icon">⛽</div>', unsafe_allow_html=True)
560
+ st.markdown('<div class="param-label">Fuel Pressure</div>', unsafe_allow_html=True)
561
+ fuel_p = st.slider("", 0.0, 25.0, 6.2, 0.1, label_visibility="collapsed", key="fuel")
562
+ st.markdown(f'<div class="param-value">{fuel_p:.1f} Bar</div>', unsafe_allow_html=True)
563
+ st.plotly_chart(create_circular_gauge(fuel_p, 25, "Fuel", "#ff6b35", "Bar"), use_container_width=True)
564
+ st.markdown('</div>', unsafe_allow_html=True)
565
+
566
+ with col3:
567
+ st.markdown('<div class="param-card param-oil-pressure">', unsafe_allow_html=True)
568
+ st.markdown('<div class="param-icon">🛢️</div>', unsafe_allow_html=True)
569
+ st.markdown('<div class="param-label">Oil Pressure</div>', unsafe_allow_html=True)
570
+ lub_oil_p = st.slider("", 0.0, 10.0, 3.16, 0.1, label_visibility="collapsed", key="oil_p")
571
+ st.markdown(f'<div class="param-value">{lub_oil_p:.2f} Bar</div>', unsafe_allow_html=True)
572
+ st.plotly_chart(create_circular_gauge(lub_oil_p, 10, "Oil", "#ffaa00", "Bar"), use_container_width=True)
573
+ st.markdown('</div>', unsafe_allow_html=True)
574
+
575
+ # Second row
576
+ col4, col5, col6 = st.columns(3)
577
+
578
+ with col4:
579
+ st.markdown('<div class="param-card param-coolant-temp">', unsafe_allow_html=True)
580
+ st.markdown('<div class="param-icon">🌡️</div>', unsafe_allow_html=True)
581
+ st.markdown('<div class="param-label">Coolant Temp</div>', unsafe_allow_html=True)
582
+ coolant_temp = st.slider("", 0.0, 200.0, 80.0, 1.0, label_visibility="collapsed", key="coolant_temp")
583
+ st.markdown(f'<div class="param-value">{coolant_temp:.1f} °C</div>', unsafe_allow_html=True)
584
+ st.plotly_chart(create_circular_gauge(coolant_temp, 200, "Temp", "#ff3366", "°C"), use_container_width=True)
585
+ st.markdown('</div>', unsafe_allow_html=True)
586
+
587
+ with col5:
588
+ st.markdown('<div class="param-card param-coolant-pressure">', unsafe_allow_html=True)
589
+ st.markdown('<div class="param-icon">💧</div>', unsafe_allow_html=True)
590
+ st.markdown('<div class="param-label">Coolant Pressure</div>', unsafe_allow_html=True)
591
+ coolant_p = st.slider("", 0.0, 10.0, 2.16, 0.1, label_visibility="collapsed", key="coolant_p")
592
+ st.markdown(f'<div class="param-value">{coolant_p:.2f} Bar</div>', unsafe_allow_html=True)
593
+ st.plotly_chart(create_circular_gauge(coolant_p, 10, "Coolant P", "#00ff88", "Bar"), use_container_width=True)
594
+ st.markdown('</div>', unsafe_allow_html=True)
595
+
596
+ with col6:
597
+ st.markdown('<div class="param-card param-oil-temp">', unsafe_allow_html=True)
598
+ st.markdown('<div class="param-icon">🔥</div>', unsafe_allow_html=True)
599
+ st.markdown('<div class="param-label">Oil Temperature</div>', unsafe_allow_html=True)
600
+ lub_oil_t = st.slider("", 0.0, 150.0, 80.0, 1.0, label_visibility="collapsed", key="oil_temp")
601
+ st.markdown(f'<div class="param-value">{lub_oil_t:.1f} °C</div>', unsafe_allow_html=True)
602
+ st.plotly_chart(create_circular_gauge(lub_oil_t, 150, "Oil Temp", "#a855f7", "°C"), use_container_width=True)
603
+ st.markdown('</div>', unsafe_allow_html=True)
604
+
605
+ st.markdown('</div>', unsafe_allow_html=True)
606
+
607
+ # Central Engine Status Display
608
+ st.markdown("---")
609
+
610
+ # Analyze Button
611
+ col_center = st.columns([1, 2, 1])
612
+ with col_center[1]:
613
+ analyze = st.button("🔍 ANALYZE ENGINE STATUS", key="analyze", use_container_width=True)
614
+
615
+ # Prediction Results
616
+ if analyze:
617
+ input_df = prepare_input_data(rpm, fuel_p, lub_oil_p, coolant_p, lub_oil_t, coolant_temp)
618
+ pred, prob, error = run_prediction(model, scaler, input_df)
619
 
620
+ if error:
621
+ st.error(f"Prediction Error: {error}")
 
 
 
 
 
 
 
622
  else:
623
+ # Store prediction
624
+ st.session_state.predictions.append({
625
+ 'timestamp': datetime.now(),
626
+ 'probability': prob,
627
+ 'prediction': pred,
628
+ 'rpm': rpm,
629
+ 'coolant_temp': coolant_temp
630
+ })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
631
 
632
+ status, emoji, color, status_text = get_engine_status(prob)
633
+ st.session_state.current_status = status
634
 
635
+ # Display Central Engine Status
636
+ st.markdown("### 🔧 Engine Status")
637
+
638
+ # Create centered layout for engine icon
639
+ col_left, col_center, col_right = st.columns([1, 1, 1])
640
+
641
+ with col_center:
642
+ # Engine status display
643
+ pulse_class = "pulse-critical" if status == 'critical' else ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
644
 
645
+ st.markdown(f"""
646
+ <div class="engine-container">
647
+ <div class="glow-ring"></div>
648
+ <div class="engine-circle status-{status} {pulse_class}">
649
+ <div class="engine-icon">{emoji}</div>
650
+ </div>
651
+ <div class="probability-display">
652
+ {prob*100:.1f}% Failure Risk
653
+ </div>
654
+ </div>
655
+ """, unsafe_allow_html=True)
656
 
657
+ # Status badge
658
+ st.markdown(f"""
659
+ <div style="text-align: center;">
660
+ <span class="status-badge badge-{status}">{status_text}</span>
661
+ </div>
662
+ """, unsafe_allow_html=True)
663
+
664
+ # Recommendations
665
+ st.markdown("---")
666
+ st.markdown("### 📋 Recommendations")
667
+
668
+ recommendations = get_recommendations(input_df, prob)
669
+
670
+ for rec in recommendations:
671
+ st.markdown(f'<div class="recommendation-item">{rec}</div>', unsafe_allow_html=True)
672
+
673
+ # Detailed Analysis
674
+ st.markdown("---")
675
+ st.markdown("### 📊 Detailed Analysis")
676
+
677
+ col_detail1, col_detail2 = st.columns(2)
678
+
679
+ with col_detail1:
680
+ st.markdown(f"""
681
+ <div class="info-panel">
682
+ <div class="info-title">Prediction Details</div>
683
+ <div class="info-content">
684
+ <strong>Failure Probability:</strong> {prob*100:.2f}%<br>
685
+ <strong>Classification:</strong> {'FAILURE RISK' if pred == 1 else 'OPERATIONAL'}<br>
686
+ <strong>Status Level:</strong> {status_text}<br>
687
+ <strong>Timestamp:</strong> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
688
+ </div>
689
+ </div>
690
+ """, unsafe_allow_html=True)
691
+
692
+ with col_detail2:
693
+ st.markdown(f"""
694
+ <div class="info-panel">
695
+ <div class="info-title">Critical Parameters</div>
696
+ <div class="info-content">
697
+ <strong>Engine RPM:</strong> {rpm} {'⚠️' if rpm > 2000 else '✓'}<br>
698
+ <strong>Coolant Temp:</strong> {coolant_temp:.1f}°C {'⚠️' if coolant_temp > 100 else '✓'}<br>
699
+ <strong>Oil Pressure:</strong> {lub_oil_p:.2f} Bar {'⚠️' if lub_oil_p < 2.0 else '✓'}<br>
700
+ <strong>Fuel Pressure:</strong> {fuel_p:.1f} Bar {'⚠️' if fuel_p < 5.0 else '✓'}
701
+ </div>
702
+ </div>
703
+ """, unsafe_allow_html=True)
704
 
705
+ else:
706
+ # Default display before analysis
707
+ st.markdown("### 🔧 Engine Status")
708
+ col_left, col_center, col_right = st.columns([1, 1, 1])
709
 
710
+ with col_center:
711
+ st.markdown(f"""
712
+ <div class="engine-container">
713
+ <div class="glow-ring"></div>
714
+ <div class="engine-circle status-unknown">
715
+ <div class="engine-icon">⚙️</div>
716
+ </div>
717
+ <div class="probability-display">
718
+ Awaiting Analysis
719
+ </div>
720
+ </div>
721
+ """, unsafe_allow_html=True)
722
 
723
+ st.markdown(f"""
724
+ <div style="text-align: center; margin-top: 2rem;">
725
+ <p style="font-family: 'Rajdhani', sans-serif; color: #8b95a5; font-size: 1.1rem;">
726
+ Configure engine parameters above and click "ANALYZE ENGINE STATUS"
727
+ </p>
728
+ </div>
729
+ """, unsafe_allow_html=True)
730
+
731
+ # Analysis History
732
+ if st.session_state.predictions:
733
+ st.markdown("---")
734
+ st.markdown("### 📈 Analysis History")
735
+
736
+ history_df = pd.DataFrame(st.session_state.predictions)
737
+
738
+ # Display last 5 predictions
739
+ st.markdown(f"**Total Analyses:** {len(history_df)} | **Showing last 5**")
740
+
741
+ for idx, row in history_df.tail(5).iterrows():
742
+ status, emoji, color, status_text = get_engine_status(row['probability'])
743
+ st.markdown(f"""
744
+ <div class="history-card">
745
+ <strong>{emoji} {row['timestamp'].strftime('%H:%M:%S')}</strong> -
746
+ Failure Risk: <span style="color: {color};">{row['probability']*100:.1f}%</span> -
747
+ Status: {status_text}
748
+ </div>
749
+ """, unsafe_allow_html=True)
750
+
751
+ # Footer
752
+ st.markdown("""
753
+ <div class="dashboard-footer">
754
+ <p><strong>Engine Predictive Maintenance System</strong></p>
755
+ <p>Powered by Machine Learning | Real-time Monitoring & Analysis</p>
756
+ <p style="margin-top: 1rem; font-size: 0.8rem;">Capstone Project - 2024</p>
757
+ </div>
758
+ """, unsafe_allow_html=True)
759
 
760
  if __name__ == "__main__":
761
  main()