iStillWaters commited on
Commit
fb740e9
Β·
verified Β·
1 Parent(s): 52d0b0d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +356 -674
app.py CHANGED
@@ -5,7 +5,6 @@ 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,9 +14,9 @@ MODEL_REPO_ID = f"{HF_USERNAME}/{MODEL_REPO_NAME}"
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"
@@ -26,566 +25,310 @@ st.set_page_config(
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)
33
- model = joblib.load(model_path)
34
- scaler = joblib.load(scaler_path)
35
- return model, scaler, None
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
- /* Hide slider labels */
67
- .stSlider > label {
68
- display: none !important;
69
  }
70
 
71
- /* Hide the container/bar above sliders */
72
- .stSlider > div[data-baseweb="slider"] > div:first-child {
73
- display: none !important;
74
- }
75
-
76
- /* Compact number inputs */
77
- .stNumberInput > label {
78
- display: none !important;
79
- }
80
-
81
- .stNumberInput > div {
82
- margin-bottom: 0 !important;
83
- }
84
-
85
- /* Hide bars above number inputs */
86
- .stNumberInput > div > div:first-child {
87
- display: none !important;
88
- }
89
-
90
- .stNumberInput input {
91
- font-size: 0.9rem !important;
92
- padding: 0.3rem !important;
93
- height: 2.2rem !important;
94
- }
95
-
96
- /* Reduce spacing in Streamlit elements */
97
- .block-container {
98
  padding-top: 1rem !important;
99
- padding-bottom: 0rem !important;
100
- }
101
-
102
- /* Compact dividers */
103
- hr {
104
- margin: 0.5rem 0 !important;
105
- }
106
-
107
- /* Compact markdown headers */
108
- h3 {
109
- margin-top: 0.5rem !important;
110
- margin-bottom: 0.5rem !important;
111
- font-size: 1.3rem !important;
112
  }
113
 
114
- h4 {
115
- margin-top: 0.5rem !important;
116
- margin-bottom: 0.5rem !important;
117
- font-size: 1.1rem !important;
118
- font-family: 'Rajdhani', sans-serif !important;
119
- }
120
 
121
- /* Remove extra spacing from columns */
122
- [data-testid="column"] {
123
- padding: 0.3rem !important;
124
- }
125
-
126
- /* Title Section */
127
  .main-title {
128
  font-family: 'Orbitron', sans-serif;
129
- font-size: 2.2rem;
130
  font-weight: 900;
131
  text-align: center;
132
- background: linear-gradient(135deg, #00d4ff 0%, #0090ff 50%, #0051ff 100%);
133
  -webkit-background-clip: text;
134
  -webkit-text-fill-color: transparent;
135
- background-clip: text;
136
- margin-bottom: 0.3rem;
137
  text-transform: uppercase;
138
  letter-spacing: 2px;
139
- text-shadow: 0 0 30px rgba(0, 212, 255, 0.3);
140
  }
141
 
142
  .subtitle {
143
  font-family: 'Rajdhani', sans-serif;
144
- font-size: 1rem;
145
  text-align: center;
146
  color: #8b95a5;
147
  margin-bottom: 1rem;
148
- letter-spacing: 1px;
149
- }
150
-
151
- /* Dashboard Container */
152
- .dashboard-container {
153
- position: relative;
154
- width: 100%;
155
- max-width: 1400px;
156
- margin: 0 auto;
157
- padding: 0.5rem;
158
- }
159
-
160
- /* Central Engine Icon Container */
161
- .engine-container {
162
- position: relative;
163
- width: 180px;
164
- height: 180px;
165
- margin: 1rem auto 3rem auto;
166
- display: flex;
167
- align-items: center;
168
- justify-content: center;
169
- }
170
-
171
- /* Rotating Glow Ring */
172
- .glow-ring {
173
- position: absolute;
174
- width: 100%;
175
- height: 100%;
176
- border-radius: 50%;
177
- border: 2px solid transparent;
178
- background: linear-gradient(45deg, transparent 30%, var(--ring-color, #00d4ff) 50%, transparent 70%);
179
- animation: rotate 4s linear infinite;
180
- opacity: 0.6;
181
- }
182
-
183
- @keyframes rotate {
184
- from { transform: rotate(0deg); }
185
- to { transform: rotate(360deg); }
186
- }
187
-
188
- /* Engine Circle Background */
189
- .engine-circle {
190
- position: absolute;
191
- width: 160px;
192
- height: 160px;
193
- border-radius: 50%;
194
- background: radial-gradient(circle at 30% 30%,
195
- rgba(26, 31, 58, 0.9) 0%,
196
- rgba(10, 14, 39, 0.95) 100%);
197
- border: 3px solid var(--border-color, #1a4d7a);
198
- box-shadow:
199
- 0 0 30px var(--glow-color, rgba(0, 212, 255, 0.4)),
200
- inset 0 0 20px rgba(0, 0, 0, 0.5);
201
- display: flex;
202
- align-items: center;
203
- justify-content: center;
204
- transition: all 0.5s ease;
205
- }
206
-
207
- /* Engine Icon */
208
- .engine-icon {
209
- font-size: 5rem;
210
- filter: drop-shadow(0 0 15px var(--glow-color, rgba(0, 212, 255, 0.6)));
211
- transition: all 0.5s ease;
212
- }
213
-
214
- /* Status Colors */
215
- .status-healthy {
216
- --border-color: #00ff88;
217
- --glow-color: rgba(0, 255, 136, 0.5);
218
- --ring-color: #00ff88;
219
- }
220
-
221
- .status-warning {
222
- --border-color: #ffaa00;
223
- --glow-color: rgba(255, 170, 0, 0.5);
224
- --ring-color: #ffaa00;
225
- }
226
-
227
- .status-critical {
228
- --border-color: #ff3366;
229
- --glow-color: rgba(255, 51, 102, 0.5);
230
- --ring-color: #ff3366;
231
- }
232
-
233
- .status-unknown {
234
- --border-color: #4a5568;
235
- --glow-color: rgba(74, 85, 104, 0.3);
236
- --ring-color: #4a5568;
237
  }
238
 
239
- /* Probability Display */
240
- .probability-display {
241
- position: absolute;
242
- bottom: -35px;
243
- left: 50%;
244
- transform: translateX(-50%);
245
- font-family: 'Orbitron', sans-serif;
246
- font-size: 1.2rem;
247
- font-weight: 700;
248
- color: var(--border-color, #00d4ff);
249
- text-shadow: 0 0 10px var(--glow-color, rgba(0, 212, 255, 0.5));
250
- white-space: nowrap;
251
- }
252
-
253
- /* Parameter Cards Grid */
254
- .params-grid {
255
- display: grid;
256
- grid-template-columns: repeat(3, 1fr);
257
- gap: 1rem;
258
- margin-top: 1rem;
259
- max-width: 1200px;
260
- margin-left: auto;
261
- margin-right: auto;
262
- }
263
-
264
- /* Individual Parameter Card */
265
  .param-card {
266
- background: linear-gradient(135deg,
267
- rgba(26, 31, 58, 0.6) 0%,
268
- rgba(10, 14, 39, 0.8) 100%);
269
  border: 2px solid rgba(0, 212, 255, 0.2);
270
- border-radius: 12px;
271
  padding: 0.8rem;
272
- backdrop-filter: blur(10px);
273
- transition: all 0.3s ease;
274
- position: relative;
275
- overflow: hidden;
276
- }
277
-
278
- .param-card::before {
279
- content: '';
280
- position: absolute;
281
- top: 0;
282
- left: 0;
283
- width: 100%;
284
- height: 3px;
285
- background: linear-gradient(90deg,
286
- transparent 0%,
287
- var(--accent-color, #00d4ff) 50%,
288
- transparent 100%);
289
- opacity: 0;
290
- transition: opacity 0.3s ease;
291
- }
292
-
293
- .param-card:hover {
294
- border-color: var(--accent-color, #00d4ff);
295
- box-shadow: 0 8px 30px rgba(0, 212, 255, 0.15);
296
- transform: translateY(-4px);
297
  }
298
 
299
- .param-card:hover::before {
300
- opacity: 1;
 
 
301
  }
302
 
303
- /* Parameter Icon */
304
  .param-icon {
305
- font-size: 1.8rem;
306
- margin-bottom: 0.3rem;
307
- display: inline-block;
308
  }
309
 
310
- /* Parameter Label */
311
- .param-label {
312
  font-family: 'Rajdhani', sans-serif;
313
  font-size: 0.9rem;
314
  font-weight: 600;
315
  color: #8b95a5;
316
- margin-bottom: 0.3rem;
317
  text-transform: uppercase;
318
- letter-spacing: 1px;
319
  }
320
 
321
- /* Parameter Value */
322
- .param-value {
323
  font-family: 'Orbitron', sans-serif;
324
- font-size: 1.3rem;
325
  font-weight: 700;
326
- color: var(--accent-color, #00d4ff);
327
- margin-bottom: 0.5rem;
328
  }
329
 
330
- /* Color Accents for Different Parameters */
331
- .param-rpm { --accent-color: #00d4ff; }
332
- .param-fuel { --accent-color: #ff6b35; }
333
- .param-oil-pressure { --accent-color: #ffaa00; }
334
- .param-coolant-temp { --accent-color: #ff3366; }
335
- .param-coolant-pressure { --accent-color: #00ff88; }
336
- .param-oil-temp { --accent-color: #a855f7; }
337
-
338
- /* Analyze Button */
339
- .analyze-button {
340
- display: block;
341
- margin: 1.5rem auto;
342
- padding: 0.8rem 3rem;
343
- font-family: 'Orbitron', sans-serif;
344
- font-size: 1.1rem;
345
- font-weight: 700;
346
- color: #ffffff;
347
- background: linear-gradient(135deg, #0090ff 0%, #0051ff 100%);
348
- border: 2px solid #00d4ff;
349
- border-radius: 50px;
350
- cursor: pointer;
351
- transition: all 0.3s ease;
352
- text-transform: uppercase;
353
- letter-spacing: 2px;
354
- box-shadow: 0 0 30px rgba(0, 144, 255, 0.3);
355
  }
356
 
357
- .analyze-button:hover {
358
- background: linear-gradient(135deg, #00d4ff 0%, #0090ff 100%);
359
- box-shadow: 0 0 50px rgba(0, 212, 255, 0.5);
360
- transform: translateY(-2px);
 
361
  }
362
 
363
- /* Status Badge */
364
  .status-badge {
365
  display: inline-block;
366
  padding: 0.5rem 1.5rem;
367
  font-family: 'Rajdhani', sans-serif;
368
- font-size: 1.1rem;
369
  font-weight: 700;
370
- border-radius: 30px;
 
371
  text-transform: uppercase;
372
- letter-spacing: 2px;
373
- margin-top: 1rem;
374
  }
375
 
376
- .badge-healthy {
377
- background: rgba(0, 255, 136, 0.2);
378
- color: #00ff88;
379
- border: 2px solid #00ff88;
380
- box-shadow: 0 0 20px rgba(0, 255, 136, 0.3);
 
 
 
381
  }
382
 
383
- .badge-warning {
384
- background: rgba(255, 170, 0, 0.2);
 
385
  color: #ffaa00;
386
- border: 2px solid #ffaa00;
387
- box-shadow: 0 0 20px rgba(255, 170, 0, 0.3);
388
  }
389
 
390
- .badge-critical {
391
- background: rgba(255, 51, 102, 0.2);
 
392
  color: #ff3366;
393
- border: 2px solid #ff3366;
394
- box-shadow: 0 0 20px rgba(255, 51, 102, 0.3);
395
- }
396
-
397
- /* Info Panel */
398
- .info-panel {
399
- background: linear-gradient(135deg,
400
- rgba(26, 31, 58, 0.4) 0%,
401
- rgba(10, 14, 39, 0.6) 100%);
402
- border-left: 4px solid #00d4ff;
403
- border-radius: 8px;
404
- padding: 0.8rem;
405
- margin: 0.5rem 0;
406
- backdrop-filter: blur(10px);
407
  }
408
 
409
- .info-title {
410
- font-family: 'Rajdhani', sans-serif;
411
- font-size: 1.1rem;
412
- font-weight: 700;
413
- color: #00d4ff;
414
- margin-bottom: 0.5rem;
415
- text-transform: uppercase;
416
- letter-spacing: 1px;
417
- }
418
-
419
- .info-content {
420
- font-family: 'Rajdhani', sans-serif;
421
- font-size: 0.95rem;
422
- color: #b8c5d6;
423
- line-height: 1.5;
424
- }
425
-
426
- /* Recommendations List */
427
- .recommendation-item {
428
- font-family: 'Rajdhani', sans-serif;
429
- font-size: 0.95rem;
430
- color: #e0e6ed;
431
- padding: 0.5rem 1rem;
432
  margin: 0.3rem 0;
433
  background: rgba(0, 212, 255, 0.05);
434
  border-left: 3px solid #00d4ff;
435
  border-radius: 4px;
436
- transition: all 0.2s ease;
 
 
437
  }
438
 
439
- .recommendation-item:hover {
440
- background: rgba(0, 212, 255, 0.1);
441
- transform: translateX(4px);
 
 
 
 
 
 
 
 
442
  }
443
 
444
- /* History Section */
445
- .history-card {
446
- background: linear-gradient(135deg,
447
- rgba(26, 31, 58, 0.5) 0%,
448
- rgba(10, 14, 39, 0.7) 100%);
449
- border: 2px solid rgba(0, 212, 255, 0.2);
450
- border-radius: 12px;
451
- padding: 1rem;
452
  margin: 0.5rem 0;
453
  }
454
 
455
- /* Slider Customization */
456
- .stSlider > div > div > div {
457
- background: linear-gradient(90deg, #0051ff 0%, #00d4ff 100%);
458
- }
459
-
460
- /* Pulse Animation for Critical State */
461
- @keyframes pulse {
462
- 0%, 100% { opacity: 1; }
463
- 50% { opacity: 0.6; }
464
  }
465
 
466
- .pulse-critical {
467
- animation: pulse 1.5s ease-in-out infinite;
 
 
 
468
  }
469
 
470
- /* Footer */
471
- .dashboard-footer {
472
- text-align: center;
473
- margin-top: 1rem;
474
- padding: 0.5rem;
475
- font-family: 'Rajdhani', sans-serif;
476
- font-size: 0.85rem;
477
- color: #6b7280;
478
- border-top: 1px solid rgba(107, 114, 128, 0.2);
479
  }
480
- </style>
481
- """, unsafe_allow_html=True)
482
 
483
  # --- HELPER FUNCTIONS ---
484
- def get_engine_status(probability):
485
- """Determine engine status based on failure probability"""
486
- if probability < 0.25:
487
- return 'healthy', '🟒', '#00ff88', 'OPTIMAL'
488
- elif probability < 0.50:
489
- return 'warning', '🟑', '#ffaa00', 'CAUTION'
490
- elif probability < 0.75:
491
- return 'warning', '🟠', '#ff9500', 'WARNING'
492
- else:
493
- return 'critical', 'πŸ”΄', '#ff3366', 'CRITICAL'
494
-
495
- def create_circular_gauge(value, max_value, title, color, unit=""):
496
- """Create a minimal circular gauge"""
497
- percentage = (value / max_value) * 100
498
-
499
  fig = go.Figure(go.Indicator(
500
- mode = "gauge+number",
501
- value = value,
502
- number = {'suffix': f" {unit}", 'font': {'size': 18, 'color': color}},
503
- title = {'text': title, 'font': {'size': 12, 'color': '#8b95a5'}},
504
- gauge = {
505
- 'axis': {'range': [0, max_value], 'tickwidth': 1, 'tickcolor': color},
506
- 'bar': {'color': color, 'thickness': 0.7},
507
  'bgcolor': "rgba(26, 31, 58, 0.3)",
508
- 'borderwidth': 2,
509
  'bordercolor': "rgba(255, 255, 255, 0.1)",
510
  'steps': [
511
  {'range': [0, max_value * 0.6], 'color': 'rgba(0, 255, 136, 0.1)'},
512
  {'range': [max_value * 0.6, max_value * 0.8], 'color': 'rgba(255, 170, 0, 0.1)'},
513
  {'range': [max_value * 0.8, max_value], 'color': 'rgba(255, 51, 102, 0.1)'}
514
- ],
515
  }
516
  ))
517
 
518
  fig.update_layout(
519
- height=140,
520
- margin=dict(l=10, r=10, t=30, b=5),
521
  paper_bgcolor='rgba(0,0,0,0)',
522
  plot_bgcolor='rgba(0,0,0,0)',
523
- font={'family': 'Rajdhani', 'color': '#e0e6ed'}
524
  )
525
 
526
  return fig
527
 
528
- def prepare_input_data(rpm, fuel_p, lub_oil_p, coolant_p, lub_oil_t, coolant_temp):
529
- """Prepare input data for model"""
530
- return pd.DataFrame({
531
- 'Engine rpm': [rpm],
532
- 'Lub oil pressure': [lub_oil_p],
533
- 'Fuel pressure': [fuel_p],
534
- 'Coolant pressure': [coolant_p],
535
- 'lub oil temp': [lub_oil_t],
536
- 'Coolant temp': [coolant_temp]
537
- })
 
538
 
539
- def run_prediction(model, scaler, input_df):
540
- """Execute model prediction"""
541
- try:
542
- scaled = scaler.transform(input_df)
543
- pred = model.predict(scaled)[0]
544
- prob = model.predict_proba(scaled)[0][1] if hasattr(model, 'predict_proba') else 0.0
545
- return pred, prob, None
546
- except Exception as e:
547
- return None, None, str(e)
 
548
 
549
- def get_recommendations(input_df, prob):
550
- """Generate maintenance recommendations"""
551
- recommendations = []
552
-
553
- rpm = input_df['Engine rpm'].values[0]
554
- coolant_temp = input_df['Coolant temp'].values[0]
555
- lub_oil_p = input_df['Lub oil pressure'].values[0]
556
- fuel_p = input_df['Fuel pressure'].values[0]
 
 
 
 
557
 
558
- if prob > 0.75:
559
- recommendations.append("⚠️ IMMEDIATE SHUTDOWN RECOMMENDED - Critical failure risk detected")
560
- recommendations.append("πŸ“ž Contact maintenance team immediately")
561
 
562
- if coolant_temp > 100:
563
- recommendations.append("🌑️ Coolant temperature elevated - Check radiator and cooling system")
564
 
565
- if lub_oil_p < 2.0:
566
- recommendations.append("πŸ›’οΈ Low oil pressure detected - Inspect oil pump and filter")
567
 
568
- if fuel_p < 5.0:
569
- recommendations.append("β›½ Fuel pressure below optimal - Check fuel pump and lines")
570
 
571
- if rpm > 2000:
572
- recommendations.append("βš™οΈ High RPM detected - Reduce engine load")
573
 
574
- if prob > 0.5:
575
- recommendations.append("πŸ“… Schedule comprehensive engine inspection within 24 hours")
576
- elif prob > 0.25:
577
- recommendations.append("πŸ“‹ Monitor engine parameters and schedule preventive maintenance")
 
 
578
  else:
579
- recommendations.append("βœ… Engine operating within normal parameters")
580
 
581
- return recommendations
582
 
583
- # --- MAIN APPLICATION ---
584
  def main():
585
- # Load CSS
586
- load_css()
587
-
588
- # Load Model
589
  model, scaler, error = load_artifacts()
590
 
591
  if model is None:
@@ -596,243 +339,191 @@ def main():
596
  st.markdown('<h1 class="main-title">βš™οΈ Engine Health Monitor</h1>', unsafe_allow_html=True)
597
  st.markdown('<p class="subtitle">AI-Powered Predictive Maintenance System</p>', unsafe_allow_html=True)
598
 
599
- # Main Dashboard Container
600
- st.markdown('<div class="dashboard-container">', unsafe_allow_html=True)
 
 
 
 
 
 
 
601
 
602
- # Parameter Input Section
603
  st.markdown("### πŸ“Š Engine Parameters")
604
 
605
- # Initialize session state for parameter values if not exists
606
- if 'rpm_val' not in st.session_state:
607
- st.session_state.rpm_val = 750
608
- if 'fuel_val' not in st.session_state:
609
- st.session_state.fuel_val = 6.2
610
- if 'oil_p_val' not in st.session_state:
611
- st.session_state.oil_p_val = 3.16
612
- if 'coolant_t_val' not in st.session_state:
613
- st.session_state.coolant_t_val = 80.0
614
- if 'coolant_p_val' not in st.session_state:
615
- st.session_state.coolant_p_val = 2.16
616
- if 'oil_t_val' not in st.session_state:
617
- st.session_state.oil_t_val = 80.0
618
-
619
- # Create 2 rows of 3 parameters each
620
  col1, col2, col3 = st.columns(3)
621
 
 
622
  with col1:
623
- st.markdown('<div class="param-card param-rpm">', unsafe_allow_html=True)
624
- st.markdown('<div class="param-icon">⚑</div>', unsafe_allow_html=True)
625
- st.markdown('<div class="param-label">Engine RPM</div>', unsafe_allow_html=True)
626
-
627
- # Manual input and slider in columns
628
- rpm_col1, rpm_col2 = st.columns([1, 2])
629
- with rpm_col1:
630
- rpm_input = st.number_input("", 0, 2500, st.session_state.rpm_val, 50, label_visibility="collapsed", key="rpm_input")
631
- with rpm_col2:
632
- rpm_slider = st.slider("", 0, 2500, st.session_state.rpm_val, 50, label_visibility="collapsed", key="rpm_slider")
633
-
634
- # Update session state based on which changed
635
- if rpm_input != st.session_state.rpm_val:
636
- st.session_state.rpm_val = rpm_input
637
- elif rpm_slider != st.session_state.rpm_val:
638
- st.session_state.rpm_val = rpm_slider
639
-
640
- rpm = st.session_state.rpm_val
641
-
642
- st.markdown(f'<div class="param-value">{rpm}</div>', unsafe_allow_html=True)
643
- st.plotly_chart(create_circular_gauge(rpm, 2500, "RPM", "#00d4ff"), use_container_width=True, key="gauge_rpm")
644
  st.markdown('</div>', unsafe_allow_html=True)
645
 
646
  with col2:
647
- st.markdown('<div class="param-card param-fuel">', unsafe_allow_html=True)
648
- st.markdown('<div class="param-icon">β›½</div>', unsafe_allow_html=True)
649
- st.markdown('<div class="param-label">Fuel Pressure</div>', unsafe_allow_html=True)
650
-
651
- fuel_col1, fuel_col2 = st.columns([1, 2])
652
- with fuel_col1:
653
- fuel_input = st.number_input("", 0.0, 25.0, st.session_state.fuel_val, 0.1, label_visibility="collapsed", key="fuel_input")
654
- with fuel_col2:
655
- fuel_slider = st.slider("", 0.0, 25.0, st.session_state.fuel_val, 0.1, label_visibility="collapsed", key="fuel_slider")
656
-
657
- if fuel_input != st.session_state.fuel_val:
658
- st.session_state.fuel_val = fuel_input
659
- elif fuel_slider != st.session_state.fuel_val:
660
- st.session_state.fuel_val = fuel_slider
661
-
662
- fuel_p = st.session_state.fuel_val
663
-
664
- st.markdown(f'<div class="param-value">{fuel_p:.1f} Bar</div>', unsafe_allow_html=True)
665
- st.plotly_chart(create_circular_gauge(fuel_p, 25, "Fuel", "#ff6b35", "Bar"), use_container_width=True, key="gauge_fuel")
666
  st.markdown('</div>', unsafe_allow_html=True)
667
 
668
  with col3:
669
- st.markdown('<div class="param-card param-oil-pressure">', unsafe_allow_html=True)
670
- st.markdown('<div class="param-icon">πŸ›’οΈ</div>', unsafe_allow_html=True)
671
- st.markdown('<div class="param-label">Oil Pressure</div>', unsafe_allow_html=True)
672
-
673
- oil_p_col1, oil_p_col2 = st.columns([1, 2])
674
- with oil_p_col1:
675
- oil_p_input = st.number_input("", 0.0, 10.0, st.session_state.oil_p_val, 0.1, label_visibility="collapsed", key="oil_p_input")
676
- with oil_p_col2:
677
- oil_p_slider = st.slider("", 0.0, 10.0, st.session_state.oil_p_val, 0.1, label_visibility="collapsed", key="oil_p_slider")
678
-
679
- if oil_p_input != st.session_state.oil_p_val:
680
- st.session_state.oil_p_val = oil_p_input
681
- elif oil_p_slider != st.session_state.oil_p_val:
682
- st.session_state.oil_p_val = oil_p_slider
683
-
684
- lub_oil_p = st.session_state.oil_p_val
685
-
686
- st.markdown(f'<div class="param-value">{lub_oil_p:.2f} Bar</div>', unsafe_allow_html=True)
687
- st.plotly_chart(create_circular_gauge(lub_oil_p, 10, "Oil", "#ffaa00", "Bar"), use_container_width=True, key="gauge_oil_p")
688
  st.markdown('</div>', unsafe_allow_html=True)
689
 
690
- # Second row
691
  col4, col5, col6 = st.columns(3)
692
 
693
  with col4:
694
- st.markdown('<div class="param-card param-coolant-temp">', unsafe_allow_html=True)
695
- st.markdown('<div class="param-icon">🌑️</div>', unsafe_allow_html=True)
696
- st.markdown('<div class="param-label">Coolant Temp</div>', unsafe_allow_html=True)
697
-
698
- coolant_t_col1, coolant_t_col2 = st.columns([1, 2])
699
- with coolant_t_col1:
700
- coolant_t_input = st.number_input("", 0.0, 200.0, st.session_state.coolant_t_val, 1.0, label_visibility="collapsed", key="coolant_temp_input")
701
- with coolant_t_col2:
702
- coolant_t_slider = st.slider("", 0.0, 200.0, st.session_state.coolant_t_val, 1.0, label_visibility="collapsed", key="coolant_temp_slider")
703
-
704
- if coolant_t_input != st.session_state.coolant_t_val:
705
- st.session_state.coolant_t_val = coolant_t_input
706
- elif coolant_t_slider != st.session_state.coolant_t_val:
707
- st.session_state.coolant_t_val = coolant_t_slider
708
-
709
- coolant_temp = st.session_state.coolant_t_val
710
-
711
- st.markdown(f'<div class="param-value">{coolant_temp:.1f} Β°C</div>', unsafe_allow_html=True)
712
- st.plotly_chart(create_circular_gauge(coolant_temp, 200, "Temp", "#ff3366", "Β°C"), use_container_width=True, key="gauge_coolant_t")
713
  st.markdown('</div>', unsafe_allow_html=True)
714
 
715
  with col5:
716
- st.markdown('<div class="param-card param-coolant-pressure">', unsafe_allow_html=True)
717
- st.markdown('<div class="param-icon">πŸ’§</div>', unsafe_allow_html=True)
718
- st.markdown('<div class="param-label">Coolant Pressure</div>', unsafe_allow_html=True)
719
-
720
- coolant_p_col1, coolant_p_col2 = st.columns([1, 2])
721
- with coolant_p_col1:
722
- coolant_p_input = st.number_input("", 0.0, 10.0, st.session_state.coolant_p_val, 0.1, label_visibility="collapsed", key="coolant_p_input")
723
- with coolant_p_col2:
724
- coolant_p_slider = st.slider("", 0.0, 10.0, st.session_state.coolant_p_val, 0.1, label_visibility="collapsed", key="coolant_p_slider")
725
-
726
- if coolant_p_input != st.session_state.coolant_p_val:
727
- st.session_state.coolant_p_val = coolant_p_input
728
- elif coolant_p_slider != st.session_state.coolant_p_val:
729
- st.session_state.coolant_p_val = coolant_p_slider
730
-
731
- coolant_p = st.session_state.coolant_p_val
732
-
733
- st.markdown(f'<div class="param-value">{coolant_p:.2f} Bar</div>', unsafe_allow_html=True)
734
- st.plotly_chart(create_circular_gauge(coolant_p, 10, "Coolant P", "#00ff88", "Bar"), use_container_width=True, key="gauge_coolant_p")
735
  st.markdown('</div>', unsafe_allow_html=True)
736
 
737
  with col6:
738
- st.markdown('<div class="param-card param-oil-temp">', unsafe_allow_html=True)
739
- st.markdown('<div class="param-icon">πŸ”₯</div>', unsafe_allow_html=True)
740
- st.markdown('<div class="param-label">Oil Temperature</div>', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
741
 
742
- oil_t_col1, oil_t_col2 = st.columns([1, 2])
743
- with oil_t_col1:
744
- oil_t_input = st.number_input("", 0.0, 150.0, st.session_state.oil_t_val, 1.0, label_visibility="collapsed", key="oil_temp_input")
745
- with oil_t_col2:
746
- oil_t_slider = st.slider("", 0.0, 150.0, st.session_state.oil_t_val, 1.0, label_visibility="collapsed", key="oil_temp_slider")
747
 
748
- if oil_t_input != st.session_state.oil_t_val:
749
- st.session_state.oil_t_val = oil_t_input
750
- elif oil_t_slider != st.session_state.oil_t_val:
751
- st.session_state.oil_t_val = oil_t_slider
 
 
 
 
 
 
 
 
 
 
 
 
752
 
753
- lub_oil_t = st.session_state.oil_t_val
 
 
 
 
754
 
755
- st.markdown(f'<div class="param-value">{lub_oil_t:.1f} Β°C</div>', unsafe_allow_html=True)
756
- st.plotly_chart(create_circular_gauge(lub_oil_t, 150, "Oil Temp", "#a855f7", "Β°C"), use_container_width=True, key="gauge_oil_t")
757
- st.markdown('</div>', unsafe_allow_html=True)
758
-
759
- st.markdown('</div>', unsafe_allow_html=True)
760
-
761
- # Central Engine Status Display
762
-
763
- # Analyze Button
764
- col_center = st.columns([1, 2, 1])
765
- with col_center[1]:
766
- analyze = st.button("πŸ” ANALYZE ENGINE STATUS", key="analyze", use_container_width=True)
767
-
768
- # Prediction Results
769
- if analyze:
770
- input_df = prepare_input_data(rpm, fuel_p, lub_oil_p, coolant_p, lub_oil_t, coolant_temp)
771
- pred, prob, error = run_prediction(model, scaler, input_df)
772
-
773
- if error:
774
- st.error(f"Prediction Error: {error}")
775
- else:
776
- # Store prediction
777
- st.session_state.predictions.append({
778
- 'timestamp': datetime.now(),
779
- 'probability': prob,
780
- 'prediction': pred,
781
- 'rpm': rpm,
782
- 'coolant_temp': coolant_temp
783
- })
784
 
785
- status, emoji, color, status_text = get_engine_status(prob)
786
- st.session_state.current_status = status
787
 
788
- # Display Central Engine Status
789
- st.markdown("#### πŸ”§ Engine Status")
 
 
 
 
790
 
791
- # Create centered layout for engine icon
792
- col_left, col_center, col_right = st.columns([1, 1, 1])
793
 
794
- with col_center:
795
- # Engine status display
796
- pulse_class = "pulse-critical" if status == 'critical' else ""
797
-
798
- st.markdown(f"""
799
- <div class="engine-container">
800
- <div class="glow-ring"></div>
801
- <div class="engine-circle status-{status} {pulse_class}">
802
- <div class="engine-icon">{emoji}</div>
803
  </div>
804
- <div class="probability-display">
805
- {prob*100:.1f}% Failure Risk
806
- </div>
807
- </div>
808
- <div style="height: 10px;"></div>
809
- """, unsafe_allow_html=True)
810
-
811
- # Status badge
812
- st.markdown(f"""
813
- <div style="text-align: center;">
814
- <span class="status-badge badge-{status}">{status_text}</span>
815
- </div>
816
- """, unsafe_allow_html=True)
817
 
818
- # Recommendations
819
- st.markdown("#### πŸ“‹ Recommendations")
820
 
821
- recommendations = get_recommendations(input_df, prob)
822
 
823
- for rec in recommendations:
824
- st.markdown(f'<div class="recommendation-item">{rec}</div>', unsafe_allow_html=True)
825
 
826
- # Detailed Analysis
827
- st.markdown("#### πŸ“Š Analysis Details")
828
 
829
- col_detail1, col_detail2 = st.columns(2)
830
 
831
- with col_detail1:
832
  st.markdown(f"""
833
- <div class="info-panel">
834
- <div class="info-title">Prediction Details</div>
835
- <div class="info-content">
836
  <strong>Failure Probability:</strong> {prob*100:.2f}%<br>
837
  <strong>Classification:</strong> {'FAILURE RISK' if pred == 1 else 'OPERATIONAL'}<br>
838
  <strong>Status Level:</strong> {status_text}<br>
@@ -841,52 +532,43 @@ def main():
841
  </div>
842
  """, unsafe_allow_html=True)
843
 
844
- with col_detail2:
845
  st.markdown(f"""
846
- <div class="info-panel">
847
- <div class="info-title">Critical Parameters</div>
848
- <div class="info-content">
849
  <strong>Engine RPM:</strong> {rpm} {'⚠️' if rpm > 2000 else 'βœ“'}<br>
850
  <strong>Coolant Temp:</strong> {coolant_temp:.1f}Β°C {'⚠️' if coolant_temp > 100 else 'βœ“'}<br>
851
- <strong>Oil Pressure:</strong> {lub_oil_p:.2f} Bar {'⚠️' if lub_oil_p < 2.0 else 'βœ“'}<br>
852
  <strong>Fuel Pressure:</strong> {fuel_p:.1f} Bar {'⚠️' if fuel_p < 5.0 else 'βœ“'}
853
  </div>
854
  </div>
855
  """, unsafe_allow_html=True)
 
 
 
856
 
857
  else:
858
- # Default display before analysis
859
- st.markdown("#### πŸ”§ Engine Status")
860
- col_left, col_center, col_right = st.columns([1, 1, 1])
861
-
862
- with col_center:
863
- st.markdown(f"""
864
- <div class="engine-container">
865
- <div class="glow-ring"></div>
866
- <div class="engine-circle status-unknown">
867
- <div class="engine-icon">βš™οΈ</div>
868
- </div>
869
- <div class="probability-display">
870
- Awaiting Analysis
871
- </div>
872
- </div>
873
- <div style="height: 10px;"></div>
874
- """, unsafe_allow_html=True)
875
-
876
- st.markdown(f"""
877
- <div style="text-align: center; margin-top: 1rem;">
878
- <p style="font-family: 'Rajdhani', sans-serif; color: #8b95a5; font-size: 1.1rem;">
879
- Configure engine parameters above and click "ANALYZE ENGINE STATUS"
880
- </p>
881
- </div>
882
- """, unsafe_allow_html=True)
883
 
884
  # Footer
885
  st.markdown("""
886
- <div class="dashboard-footer">
887
- <p style="margin: 0.5rem 0; font-size: 0.8rem;">Engine Predictive Maintenance System | Powered by Machine Learning</p>
 
 
888
  </div>
889
  """, unsafe_allow_html=True)
890
 
891
  if __name__ == "__main__":
892
- main()
 
5
  import plotly.graph_objects as go
6
  from huggingface_hub import hf_hub_download
7
  from datetime import datetime
 
8
 
9
  # --- CONFIGURATION ---
10
  HF_USERNAME = os.getenv("HF_USERNAME", "iStillWaters")
 
14
  MODEL_FILENAME = "best_engine_model.pkl"
15
  SCALER_FILENAME = "scaler.joblib"
16
 
17
+ # --- PAGE CONFIG ---
18
  st.set_page_config(
19
+ page_title="Engine Health Monitor",
20
  page_icon="πŸ”§",
21
  layout="wide",
22
  initial_sidebar_state="collapsed"
 
25
  # --- LOAD MODEL ---
26
  @st.cache_resource
27
  def load_artifacts():
 
28
  try:
29
  model_path = hf_hub_download(repo_id=MODEL_REPO_ID, filename=MODEL_FILENAME)
30
  scaler_path = hf_hub_download(repo_id=MODEL_REPO_ID, filename=SCALER_FILENAME)
31
+ return joblib.load(model_path), joblib.load(scaler_path), None
 
 
32
  except Exception as e:
33
  return None, None, str(e)
34
 
 
 
 
 
 
 
35
  # --- CUSTOM CSS ---
36
+ st.markdown("""
37
+ <style>
38
+ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@700;900&family=Rajdhani:wght@400;600;700&display=swap');
 
39
 
40
+ /* Global */
41
  .stApp {
42
+ background: linear-gradient(135deg, #0a0e27 0%, #1a1f3a 100%);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  }
44
 
45
+ .main .block-container {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  padding-top: 1rem !important;
47
+ padding-bottom: 1rem !important;
48
+ max-width: 1400px !important;
 
 
 
 
 
 
 
 
 
 
 
49
  }
50
 
51
+ /* Hide Streamlit UI */
52
+ #MainMenu, footer, header {visibility: hidden;}
 
 
 
 
53
 
54
+ /* Title */
 
 
 
 
 
55
  .main-title {
56
  font-family: 'Orbitron', sans-serif;
57
+ font-size: 2rem;
58
  font-weight: 900;
59
  text-align: center;
60
+ background: linear-gradient(135deg, #00d4ff, #0090ff);
61
  -webkit-background-clip: text;
62
  -webkit-text-fill-color: transparent;
63
+ margin-bottom: 0.5rem;
 
64
  text-transform: uppercase;
65
  letter-spacing: 2px;
 
66
  }
67
 
68
  .subtitle {
69
  font-family: 'Rajdhani', sans-serif;
70
+ font-size: 0.9rem;
71
  text-align: center;
72
  color: #8b95a5;
73
  margin-bottom: 1rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  }
75
 
76
+ /* Parameter Card */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  .param-card {
78
+ background: linear-gradient(135deg, rgba(26, 31, 58, 0.6), rgba(10, 14, 39, 0.8));
 
 
79
  border: 2px solid rgba(0, 212, 255, 0.2);
80
+ border-radius: 10px;
81
  padding: 0.8rem;
82
+ margin-bottom: 0.5rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  }
84
 
85
+ .param-header {
86
+ display: flex;
87
+ align-items: center;
88
+ margin-bottom: 0.5rem;
89
  }
90
 
 
91
  .param-icon {
92
+ font-size: 1.5rem;
93
+ margin-right: 0.5rem;
 
94
  }
95
 
96
+ .param-name {
 
97
  font-family: 'Rajdhani', sans-serif;
98
  font-size: 0.9rem;
99
  font-weight: 600;
100
  color: #8b95a5;
 
101
  text-transform: uppercase;
 
102
  }
103
 
104
+ .param-value-display {
 
105
  font-family: 'Orbitron', sans-serif;
106
+ font-size: 1.5rem;
107
  font-weight: 700;
108
+ text-align: center;
109
+ margin: 0.3rem 0;
110
  }
111
 
112
+ /* Status Colors */
113
+ .status-healthy { color: #00ff88; }
114
+ .status-caution { color: #ffaa00; }
115
+ .status-warning { color: #ff9500; }
116
+ .status-critical { color: #ff3366; }
117
+
118
+ /* Engine Status */
119
+ .engine-status-container {
120
+ text-align: center;
121
+ padding: 1rem;
122
+ margin: 1rem 0;
123
+ }
124
+
125
+ .engine-icon-display {
126
+ font-size: 6rem;
127
+ margin: 0.5rem 0;
128
+ filter: drop-shadow(0 0 20px currentColor);
 
 
 
 
 
 
 
 
129
  }
130
 
131
+ .probability-text {
132
+ font-family: 'Orbitron', sans-serif;
133
+ font-size: 1.8rem;
134
+ font-weight: 700;
135
+ margin: 0.5rem 0;
136
  }
137
 
 
138
  .status-badge {
139
  display: inline-block;
140
  padding: 0.5rem 1.5rem;
141
  font-family: 'Rajdhani', sans-serif;
142
+ font-size: 1rem;
143
  font-weight: 700;
144
+ border-radius: 20px;
145
+ border: 2px solid;
146
  text-transform: uppercase;
147
+ margin: 0.5rem 0;
 
148
  }
149
 
150
+ /* Alert Box */
151
+ .alert-box {
152
+ padding: 0.6rem 1rem;
153
+ border-radius: 6px;
154
+ margin: 0.3rem 0;
155
+ font-family: 'Rajdhani', sans-serif;
156
+ font-size: 0.9rem;
157
+ border-left: 4px solid;
158
  }
159
 
160
+ .alert-warning {
161
+ background: rgba(255, 170, 0, 0.1);
162
+ border-color: #ffaa00;
163
  color: #ffaa00;
 
 
164
  }
165
 
166
+ .alert-critical {
167
+ background: rgba(255, 51, 102, 0.1);
168
+ border-color: #ff3366;
169
  color: #ff3366;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  }
171
 
172
+ /* Recommendation Item */
173
+ .rec-item {
174
+ padding: 0.5rem 0.8rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  margin: 0.3rem 0;
176
  background: rgba(0, 212, 255, 0.05);
177
  border-left: 3px solid #00d4ff;
178
  border-radius: 4px;
179
+ font-family: 'Rajdhani', sans-serif;
180
+ font-size: 0.9rem;
181
+ color: #e0e6ed;
182
  }
183
 
184
+ /* Feature Importance */
185
+ .feature-bar {
186
+ height: 25px;
187
+ background: linear-gradient(90deg, var(--color), transparent);
188
+ border-radius: 4px;
189
+ margin: 0.3rem 0;
190
+ position: relative;
191
+ padding: 0.2rem 0.5rem;
192
+ font-family: 'Rajdhani', sans-serif;
193
+ font-size: 0.85rem;
194
+ color: white;
195
  }
196
 
197
+ /* Analysis Details */
198
+ .detail-box {
199
+ background: linear-gradient(135deg, rgba(26, 31, 58, 0.4), rgba(10, 14, 39, 0.6));
200
+ border-left: 3px solid #00d4ff;
201
+ border-radius: 6px;
202
+ padding: 0.8rem;
 
 
203
  margin: 0.5rem 0;
204
  }
205
 
206
+ .detail-title {
207
+ font-family: 'Rajdhani', sans-serif;
208
+ font-size: 1rem;
209
+ font-weight: 700;
210
+ color: #00d4ff;
211
+ margin-bottom: 0.5rem;
212
+ text-transform: uppercase;
 
 
213
  }
214
 
215
+ .detail-content {
216
+ font-family: 'Rajdhani', sans-serif;
217
+ font-size: 0.9rem;
218
+ color: #b8c5d6;
219
+ line-height: 1.6;
220
  }
221
 
222
+ /* Section Headers */
223
+ h3 {
224
+ font-family: 'Rajdhani', sans-serif !important;
225
+ font-size: 1.2rem !important;
226
+ color: #00d4ff !important;
227
+ margin-top: 0.5rem !important;
228
+ margin-bottom: 0.5rem !important;
229
+ text-transform: uppercase;
230
+ letter-spacing: 1px;
231
  }
232
+ </style>
233
+ """, unsafe_allow_html=True)
234
 
235
  # --- HELPER FUNCTIONS ---
236
+ def create_gauge(value, max_value, color):
237
+ """Create compact gauge chart"""
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  fig = go.Figure(go.Indicator(
239
+ mode="gauge+number",
240
+ value=value,
241
+ number={'font': {'size': 16, 'color': color}},
242
+ gauge={
243
+ 'axis': {'range': [0, max_value], 'tickwidth': 1},
244
+ 'bar': {'color': color, 'thickness': 0.6},
 
245
  'bgcolor': "rgba(26, 31, 58, 0.3)",
246
+ 'borderwidth': 1,
247
  'bordercolor': "rgba(255, 255, 255, 0.1)",
248
  'steps': [
249
  {'range': [0, max_value * 0.6], 'color': 'rgba(0, 255, 136, 0.1)'},
250
  {'range': [max_value * 0.6, max_value * 0.8], 'color': 'rgba(255, 170, 0, 0.1)'},
251
  {'range': [max_value * 0.8, max_value], 'color': 'rgba(255, 51, 102, 0.1)'}
252
+ ]
253
  }
254
  ))
255
 
256
  fig.update_layout(
257
+ height=120,
258
+ margin=dict(l=5, r=5, t=5, b=5),
259
  paper_bgcolor='rgba(0,0,0,0)',
260
  plot_bgcolor='rgba(0,0,0,0)',
261
+ font={'family': 'Rajdhani'}
262
  )
263
 
264
  return fig
265
 
266
+ def validate_parameter(name, value, thresholds):
267
+ """Validate parameter and return status"""
268
+ if value > thresholds.get('critical_high', float('inf')):
269
+ return 'critical', f"πŸ”΄ {name}: Critically high ({value})"
270
+ elif value < thresholds.get('critical_low', float('-inf')):
271
+ return 'critical', f"πŸ”΄ {name}: Critically low ({value})"
272
+ elif value > thresholds.get('warning_high', float('inf')):
273
+ return 'warning', f"⚠️ {name}: High ({value})"
274
+ elif value < thresholds.get('warning_low', float('-inf')):
275
+ return 'warning', f"⚠️ {name}: Low ({value})"
276
+ return 'normal', None
277
 
278
+ def get_status_info(probability):
279
+ """Get status based on probability"""
280
+ if probability < 0.25:
281
+ return 'healthy', '🟒', 'HEALTHY', '#00ff88'
282
+ elif probability < 0.50:
283
+ return 'caution', '🟑', 'CAUTION', '#ffaa00'
284
+ elif probability < 0.75:
285
+ return 'warning', '🟠', 'WARNING', '#ff9500'
286
+ else:
287
+ return 'critical', 'πŸ”΄', 'CRITICAL', '#ff3366'
288
 
289
+ def calculate_feature_importance(params):
290
+ """Calculate normalized feature scores"""
291
+ return {
292
+ 'Engine RPM': params['rpm'] / 2500,
293
+ 'Coolant Temperature': params['coolant_temp'] / 200,
294
+ 'Oil Pressure (Risk)': max(0, 1 - params['oil_pressure'] / 10),
295
+ 'Fuel Pressure': params['fuel_pressure'] / 25
296
+ }
297
+
298
+ def get_recommendations(params, probability, warnings, criticals):
299
+ """Generate smart recommendations"""
300
+ recs = []
301
 
302
+ if criticals:
303
+ recs.append("🚨 IMMEDIATE ACTION: Critical parameters detected - Engine shutdown recommended")
304
+ recs.extend(criticals)
305
 
306
+ if params['coolant_temp'] > 100:
307
+ recs.append("πŸ”§ Check cooling system - radiator, thermostat, and coolant level")
308
 
309
+ if params['oil_pressure'] < 2.0:
310
+ recs.append("πŸ”§ Inspect oil pump and filter - check for leaks")
311
 
312
+ if params['fuel_pressure'] < 5.0:
313
+ recs.append("πŸ”§ Examine fuel system - pump, filter, and fuel lines")
314
 
315
+ if params['rpm'] > 2000:
316
+ recs.append("βš™οΈ Reduce engine load immediately")
317
 
318
+ if probability > 0.75:
319
+ recs.append("πŸ“… Schedule emergency maintenance inspection")
320
+ elif probability > 0.5:
321
+ recs.append("πŸ“… Schedule maintenance within 24 hours")
322
+ elif probability > 0.25:
323
+ recs.append("πŸ“‹ Monitor closely and schedule preventive maintenance")
324
  else:
325
+ recs.append("βœ… Continue normal operations with routine monitoring")
326
 
327
+ return recs if recs else ["βœ… All systems operating normally"]
328
 
329
+ # --- MAIN APP ---
330
  def main():
331
+ # Load model
 
 
 
332
  model, scaler, error = load_artifacts()
333
 
334
  if model is None:
 
339
  st.markdown('<h1 class="main-title">βš™οΈ Engine Health Monitor</h1>', unsafe_allow_html=True)
340
  st.markdown('<p class="subtitle">AI-Powered Predictive Maintenance System</p>', unsafe_allow_html=True)
341
 
342
+ # Parameter thresholds
343
+ thresholds = {
344
+ 'rpm': {'warning_high': 2000, 'critical_high': 2300},
345
+ 'fuel_pressure': {'warning_low': 5.0, 'critical_low': 4.0},
346
+ 'oil_pressure': {'warning_low': 2.0, 'critical_low': 1.5},
347
+ 'coolant_temp': {'warning_high': 100, 'critical_high': 120},
348
+ 'coolant_pressure': {'warning_low': 1.5, 'critical_low': 1.0},
349
+ 'oil_temp': {'warning_high': 110, 'critical_high': 130}
350
+ }
351
 
352
+ # --- PARAMETER INPUTS ---
353
  st.markdown("### πŸ“Š Engine Parameters")
354
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
355
  col1, col2, col3 = st.columns(3)
356
 
357
+ # Row 1
358
  with col1:
359
+ st.markdown('<div class="param-card">', unsafe_allow_html=True)
360
+ st.markdown('<div class="param-header"><span class="param-icon">⚑</span><span class="param-name">Engine RPM</span></div>', unsafe_allow_html=True)
361
+ rpm_input = st.number_input("RPM Value", 0, 2500, 750, 50, key="rpm_num", label_visibility="collapsed")
362
+ rpm_slider = st.slider("RPM Slider", 0, 2500, rpm_input, 50, key="rpm_sld", label_visibility="collapsed")
363
+ rpm = rpm_slider if rpm_slider != rpm_input else rpm_input
364
+ st.markdown(f'<div class="param-value-display" style="color: #00d4ff;">{rpm}</div>', unsafe_allow_html=True)
365
+ st.plotly_chart(create_gauge(rpm, 2500, "#00d4ff"), use_container_width=True, config={'displayModeBar': False})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
  st.markdown('</div>', unsafe_allow_html=True)
367
 
368
  with col2:
369
+ st.markdown('<div class="param-card">', unsafe_allow_html=True)
370
+ st.markdown('<div class="param-header"><span class="param-icon">β›½</span><span class="param-name">Fuel Pressure (Bar)</span></div>', unsafe_allow_html=True)
371
+ fuel_input = st.number_input("Fuel Value", 0.0, 25.0, 6.2, 0.1, key="fuel_num", label_visibility="collapsed")
372
+ fuel_slider = st.slider("Fuel Slider", 0.0, 25.0, fuel_input, 0.1, key="fuel_sld", label_visibility="collapsed")
373
+ fuel_p = fuel_slider if fuel_slider != fuel_input else fuel_input
374
+ st.markdown(f'<div class="param-value-display" style="color: #ff6b35;">{fuel_p:.1f}</div>', unsafe_allow_html=True)
375
+ st.plotly_chart(create_gauge(fuel_p, 25, "#ff6b35"), use_container_width=True, config={'displayModeBar': False})
 
 
 
 
 
 
 
 
 
 
 
 
376
  st.markdown('</div>', unsafe_allow_html=True)
377
 
378
  with col3:
379
+ st.markdown('<div class="param-card">', unsafe_allow_html=True)
380
+ st.markdown('<div class="param-header"><span class="param-icon">πŸ›’οΈ</span><span class="param-name">Oil Pressure (Bar)</span></div>', unsafe_allow_html=True)
381
+ oil_p_input = st.number_input("Oil P Value", 0.0, 10.0, 3.16, 0.1, key="oil_p_num", label_visibility="collapsed")
382
+ oil_p_slider = st.slider("Oil P Slider", 0.0, 10.0, oil_p_input, 0.1, key="oil_p_sld", label_visibility="collapsed")
383
+ oil_p = oil_p_slider if oil_p_slider != oil_p_input else oil_p_input
384
+ st.markdown(f'<div class="param-value-display" style="color: #ffaa00;">{oil_p:.2f}</div>', unsafe_allow_html=True)
385
+ st.plotly_chart(create_gauge(oil_p, 10, "#ffaa00"), use_container_width=True, config={'displayModeBar': False})
 
 
 
 
 
 
 
 
 
 
 
 
386
  st.markdown('</div>', unsafe_allow_html=True)
387
 
388
+ # Row 2
389
  col4, col5, col6 = st.columns(3)
390
 
391
  with col4:
392
+ st.markdown('<div class="param-card">', unsafe_allow_html=True)
393
+ st.markdown('<div class="param-header"><span class="param-icon">🌑️</span><span class="param-name">Coolant Temp (°C)</span></div>', unsafe_allow_html=True)
394
+ coolant_t_input = st.number_input("Coolant T Value", 0.0, 200.0, 80.0, 1.0, key="coolant_t_num", label_visibility="collapsed")
395
+ coolant_t_slider = st.slider("Coolant T Slider", 0.0, 200.0, coolant_t_input, 1.0, key="coolant_t_sld", label_visibility="collapsed")
396
+ coolant_temp = coolant_t_slider if coolant_t_slider != coolant_t_input else coolant_t_input
397
+ st.markdown(f'<div class="param-value-display" style="color: #ff3366;">{coolant_temp:.1f}</div>', unsafe_allow_html=True)
398
+ st.plotly_chart(create_gauge(coolant_temp, 200, "#ff3366"), use_container_width=True, config={'displayModeBar': False})
 
 
 
 
 
 
 
 
 
 
 
 
399
  st.markdown('</div>', unsafe_allow_html=True)
400
 
401
  with col5:
402
+ st.markdown('<div class="param-card">', unsafe_allow_html=True)
403
+ st.markdown('<div class="param-header"><span class="param-icon">πŸ’§</span><span class="param-name">Coolant Pressure (Bar)</span></div>', unsafe_allow_html=True)
404
+ coolant_p_input = st.number_input("Coolant P Value", 0.0, 10.0, 2.16, 0.1, key="coolant_p_num", label_visibility="collapsed")
405
+ coolant_p_slider = st.slider("Coolant P Slider", 0.0, 10.0, coolant_p_input, 0.1, key="coolant_p_sld", label_visibility="collapsed")
406
+ coolant_p = coolant_p_slider if coolant_p_slider != coolant_p_input else coolant_p_input
407
+ st.markdown(f'<div class="param-value-display" style="color: #00ff88;">{coolant_p:.2f}</div>', unsafe_allow_html=True)
408
+ st.plotly_chart(create_gauge(coolant_p, 10, "#00ff88"), use_container_width=True, config={'displayModeBar': False})
 
 
 
 
 
 
 
 
 
 
 
 
409
  st.markdown('</div>', unsafe_allow_html=True)
410
 
411
  with col6:
412
+ st.markdown('<div class="param-card">', unsafe_allow_html=True)
413
+ st.markdown('<div class="param-header"><span class="param-icon">πŸ”₯</span><span class="param-name">Oil Temp (Β°C)</span></div>', unsafe_allow_html=True)
414
+ oil_t_input = st.number_input("Oil T Value", 0.0, 150.0, 80.0, 1.0, key="oil_t_num", label_visibility="collapsed")
415
+ oil_t_slider = st.slider("Oil T Slider", 0.0, 150.0, oil_t_input, 1.0, key="oil_t_sld", label_visibility="collapsed")
416
+ oil_temp = oil_t_slider if oil_t_slider != oil_t_input else oil_t_input
417
+ st.markdown(f'<div class="param-value-display" style="color: #a855f7;">{oil_temp:.1f}</div>', unsafe_allow_html=True)
418
+ st.plotly_chart(create_gauge(oil_temp, 150, "#a855f7"), use_container_width=True, config={'displayModeBar': False})
419
+ st.markdown('</div>', unsafe_allow_html=True)
420
+
421
+ # --- REAL-TIME VALIDATION ---
422
+ warnings = []
423
+ criticals = []
424
+
425
+ for param_name, value, threshold_key in [
426
+ ('RPM', rpm, 'rpm'),
427
+ ('Fuel Pressure', fuel_p, 'fuel_pressure'),
428
+ ('Oil Pressure', oil_p, 'oil_pressure'),
429
+ ('Coolant Temp', coolant_temp, 'coolant_temp'),
430
+ ('Coolant Pressure', coolant_p, 'coolant_pressure'),
431
+ ('Oil Temp', oil_temp, 'oil_temp')
432
+ ]:
433
+ status, msg = validate_parameter(param_name, value, thresholds.get(threshold_key, {}))
434
+ if status == 'critical' and msg:
435
+ criticals.append(msg)
436
+ elif status == 'warning' and msg:
437
+ warnings.append(msg)
438
+
439
+ # Display alerts
440
+ if criticals or warnings:
441
+ st.markdown("### ⚠️ Real-Time Alerts")
442
+ col_alert1, col_alert2 = st.columns(2)
443
 
444
+ with col_alert1:
445
+ if criticals:
446
+ for alert in criticals:
447
+ st.markdown(f'<div class="alert-box alert-critical">{alert}</div>', unsafe_allow_html=True)
 
448
 
449
+ with col_alert2:
450
+ if warnings:
451
+ for alert in warnings:
452
+ st.markdown(f'<div class="alert-box alert-warning">{alert}</div>', unsafe_allow_html=True)
453
+
454
+ # --- ANALYZE BUTTON ---
455
+ if st.button("πŸ” ANALYZE ENGINE STATUS", type="primary", use_container_width=True):
456
+ # Prepare input
457
+ input_df = pd.DataFrame({
458
+ 'Engine rpm': [rpm],
459
+ 'Lub oil pressure': [oil_p],
460
+ 'Fuel pressure': [fuel_p],
461
+ 'Coolant pressure': [coolant_p],
462
+ 'lub oil temp': [oil_temp],
463
+ 'Coolant temp': [coolant_temp]
464
+ })
465
 
466
+ # Run prediction
467
+ try:
468
+ scaled = scaler.transform(input_df)
469
+ pred = model.predict(scaled)[0]
470
+ prob = model.predict_proba(scaled)[0][1] if hasattr(model, 'predict_proba') else 0.0
471
 
472
+ status, emoji, status_text, color = get_status_info(prob)
473
+
474
+ # --- ENGINE STATUS DISPLAY ---
475
+ st.markdown("### πŸ”§ Engine Status")
476
+
477
+ st.markdown(f"""
478
+ <div class="engine-status-container">
479
+ <div class="engine-icon-display" style="color: {color};">{emoji}</div>
480
+ <div class="probability-text" style="color: {color};">{prob*100:.1f}% Failure Risk</div>
481
+ <div class="status-badge status-{status}" style="border-color: {color}; color: {color};">
482
+ {status_text}
483
+ </div>
484
+ </div>
485
+ """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
486
 
487
+ # --- FEATURE IMPORTANCE ---
488
+ st.markdown("### ⚑ Risk Factor Analysis")
489
 
490
+ params_dict = {
491
+ 'rpm': rpm,
492
+ 'coolant_temp': coolant_temp,
493
+ 'oil_pressure': oil_p,
494
+ 'fuel_pressure': fuel_p
495
+ }
496
 
497
+ importance = calculate_feature_importance(params_dict)
498
+ sorted_features = sorted(importance.items(), key=lambda x: x[1], reverse=True)
499
 
500
+ for feature, score in sorted_features:
501
+ if score > 0.3: # Only show significant factors
502
+ bar_color = '#ff3366' if score > 0.7 else '#ffaa00' if score > 0.5 else '#00d4ff'
503
+ st.markdown(f"""
504
+ <div class="feature-bar" style="--color: {bar_color}; width: {score*100}%;">
505
+ <strong>{feature}:</strong> {score*100:.1f}%
 
 
 
506
  </div>
507
+ """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
508
 
509
+ # --- RECOMMENDATIONS ---
510
+ st.markdown("### πŸ“‹ Maintenance Recommendations")
511
 
512
+ recommendations = get_recommendations(params_dict, prob, warnings, criticals)
513
 
514
+ for rec in recommendations[:6]: # Show top 6
515
+ st.markdown(f'<div class="rec-item">{rec}</div>', unsafe_allow_html=True)
516
 
517
+ # --- DETAILED ANALYSIS ---
518
+ st.markdown("### πŸ“Š Analysis Details")
519
 
520
+ col_det1, col_det2 = st.columns(2)
521
 
522
+ with col_det1:
523
  st.markdown(f"""
524
+ <div class="detail-box">
525
+ <div class="detail-title">Prediction Summary</div>
526
+ <div class="detail-content">
527
  <strong>Failure Probability:</strong> {prob*100:.2f}%<br>
528
  <strong>Classification:</strong> {'FAILURE RISK' if pred == 1 else 'OPERATIONAL'}<br>
529
  <strong>Status Level:</strong> {status_text}<br>
 
532
  </div>
533
  """, unsafe_allow_html=True)
534
 
535
+ with col_det2:
536
  st.markdown(f"""
537
+ <div class="detail-box">
538
+ <div class="detail-title">Parameter Health Check</div>
539
+ <div class="detail-content">
540
  <strong>Engine RPM:</strong> {rpm} {'⚠️' if rpm > 2000 else 'βœ“'}<br>
541
  <strong>Coolant Temp:</strong> {coolant_temp:.1f}Β°C {'⚠️' if coolant_temp > 100 else 'βœ“'}<br>
542
+ <strong>Oil Pressure:</strong> {oil_p:.2f} Bar {'⚠️' if oil_p < 2.0 else 'βœ“'}<br>
543
  <strong>Fuel Pressure:</strong> {fuel_p:.1f} Bar {'⚠️' if fuel_p < 5.0 else 'βœ“'}
544
  </div>
545
  </div>
546
  """, unsafe_allow_html=True)
547
+
548
+ except Exception as e:
549
+ st.error(f"Prediction Error: {str(e)}")
550
 
551
  else:
552
+ # Default state
553
+ st.markdown("### πŸ”§ Engine Status")
554
+ st.markdown("""
555
+ <div class="engine-status-container">
556
+ <div class="engine-icon-display" style="color: #4a5568;">βš™οΈ</div>
557
+ <div class="probability-text" style="color: #8b95a5;">Awaiting Analysis</div>
558
+ <p style="font-family: 'Rajdhani', sans-serif; color: #8b95a5; font-size: 0.9rem;">
559
+ Configure parameters above and click "ANALYZE ENGINE STATUS"
560
+ </p>
561
+ </div>
562
+ """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
563
 
564
  # Footer
565
  st.markdown("""
566
+ <div style="text-align: center; margin-top: 1rem; padding: 0.5rem; border-top: 1px solid rgba(107, 114, 128, 0.2);">
567
+ <p style="font-family: 'Rajdhani', sans-serif; color: #6b7280; font-size: 0.8rem; margin: 0;">
568
+ Engine Predictive Maintenance System | Powered by Machine Learning
569
+ </p>
570
  </div>
571
  """, unsafe_allow_html=True)
572
 
573
  if __name__ == "__main__":
574
+ main()