SHELLAPANDIANGANHUNGING commited on
Commit
7e791d0
·
verified ·
1 Parent(s): 9cfa8a9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +140 -58
app.py CHANGED
@@ -8,7 +8,6 @@ import folium
8
  from streamlit_folium import st_folium
9
  from sklearn.linear_model import LinearRegression
10
  import os
11
-
12
  # ================= CONFIG =================
13
  st.set_page_config(
14
  page_title="Michelin Mining Tyre Analytics",
@@ -17,7 +16,7 @@ st.set_page_config(
17
  initial_sidebar_state="expanded"
18
  )
19
 
20
- # ================= CUSTOM CSS =================
21
  st.markdown("""
22
  <style>
23
  /* ================= ROOT & COLORS ================= */
@@ -48,6 +47,7 @@ label, .stSelectbox label, .stMultiselect label, .stCheckbox label {
48
  text-align: center !important;
49
  }
50
 
 
51
  .stMarkdown ul, .stMarkdown ol {
52
  text-align: left !important;
53
  margin-left: auto;
@@ -68,6 +68,7 @@ label, .stSelectbox label, .stMultiselect label, .stCheckbox label {
68
  margin-bottom: 12px;
69
  }
70
 
 
71
  [data-testid="stSelectbox"] div[data-baseweb="select"],
72
  [data-testid="stMultiselect"] div[data-baseweb="select"] {
73
  background-color: white !important;
@@ -84,10 +85,12 @@ label, .stSelectbox label, .stMultiselect label, .stCheckbox label {
84
  font-weight: 500;
85
  }
86
 
 
87
  [data-testid="stMultiselect"] div[data-baseweb="select"] .stMultiSelectTag {
88
  display: none !important;
89
  }
90
 
 
91
  [data-testid="stSidebar"] .stButton > button {
92
  width: 100%;
93
  background: var(--accent-yellow);
@@ -106,7 +109,20 @@ label, .stSelectbox label, .stMultiselect label, .stCheckbox label {
106
  box-shadow: 0 8px 16px rgba(0,0,0,0.12);
107
  }
108
 
109
- /* ================= OBJECTIVE TITLE ================= */
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  .objective-title {
111
  text-align: center !important;
112
  font-size: 1.6rem;
@@ -115,7 +131,7 @@ label, .stSelectbox label, .stMultiselect label, .stCheckbox label {
115
  margin: 40px 0 24px 0;
116
  }
117
 
118
- /* ================= INSIGHT BOX ================= */
119
  .insight-box {
120
  background: var(--surface-alt);
121
  border: 1px solid var(--border);
@@ -137,42 +153,49 @@ label, .stSelectbox label, .stMultiselect label, .stCheckbox label {
137
  text-align: left;
138
  }
139
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  /* ================= FOOTER ================= */
141
  .footer {
142
  text-align: center;
143
- padding: 12px 0;
144
- margin-top: 20px;
145
- font-family: Arial, sans-serif;
146
- border-top: 1px solid var(--border);
147
- }
148
- .footer-main {
149
- font-weight: bold;
150
- color: var(--michelin-blue);
151
- margin-bottom: 4px;
152
- }
153
- .footer-copy {
154
- font-size: 10px;
155
  color: var(--text-muted);
 
 
 
156
  }
157
 
158
- /* ================= HEADER ================= */
159
- .header-box {
160
- display: flex;
161
- justify-content: space-between;
162
- align-items: center;
163
- padding: 10px 20px;
164
- background: white;
165
- border-bottom: 1px solid var(--border);
166
- margin-bottom: 20px;
167
  }
168
-
169
- .header-title {
170
- text-align: center;
171
- flex: 1;
172
  }
173
  </style>
174
  """, unsafe_allow_html=True)
175
 
 
176
  # ================= LOAD DATA =================
177
  @st.cache_data
178
  def load_data():
@@ -182,15 +205,20 @@ def load_data():
182
  st.error("❌ File `df_final.xlsx` not found. Please ensure it's in the same directory.")
183
  st.stop()
184
 
 
185
  df.columns = df.columns.str.replace("Â", "")
186
  for col in df.select_dtypes(include='object').columns:
187
  df[col] = df[col].astype(str).str.replace("Â", "")
188
 
 
189
  df['Time'] = pd.to_datetime(df['Time'], errors='coerce')
190
  df = df.dropna(subset=['Time'])
191
  df['hour'] = df['Time'].dt.hour
 
 
192
  df['is_alarm'] = (~df['Alarm Status'].str.contains('No Alarm', na=False)).astype(int)
193
 
 
194
  p = df['Pressure (psi)']
195
  p_red_high = df['Red High Press (psi)']
196
  p_amber_high = df['Amber High Press (psi)']
@@ -208,44 +236,98 @@ def load_data():
208
  elif score >= 0.3: return 'Moderate Risk'
209
  else: return 'Slight Risk'
210
  df['Risk Level'] = df['risk_score'].apply(get_risk_label)
 
 
211
  df['Position Group'] = df['Position'].apply(lambda x: 'Front' if x in [1, 2] else 'Rear')
 
212
  return df
213
 
214
  df = load_data()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
- # ================= HEADER LOGO EMBEDDED (PASTI MUNCUL) =================
217
- st.markdown('<div class="header-box">', unsafe_allow_html=True)
218
- col1, col2, col3 = st.columns([1, 5, 1])
219
 
220
- # LEFT MICHELIN (SAMA DENGAN PLN)
221
- with col1:
222
- # Try to load logo.png with error handling
223
- try:
224
- st.image("logo.png", width=50)
225
- except:
226
- # If logo.png doesn't exist, show a placeholder or text
227
- st.markdown("")
228
 
229
- # CENTER TITLE
230
- with col2:
231
- st.markdown("""
232
- <div class="header-title">
233
- <h1 style="margin-bottom:6px;">
234
- Proactive Safety Intelligence & Analytics Dashboard
235
- </h1>
236
- </div>
237
- """, unsafe_allow_html=True)
238
 
239
- # RIGHT — BTECH (SAMA DENGAN PLN)
240
- with col3:
241
- # Try to load btech.png with error handling
242
- try:
243
- st.image("btech.png", width=50)
244
- except:
245
- # If btech.png doesn't exist, show a placeholder or text
246
- st.markdown("")
247
 
248
- st.markdown('</div>', unsafe_allow_html=True)
249
  # ================= OBJECTIVES (1–6) =================
250
 
251
  # # ================= HEADER =================
 
8
  from streamlit_folium import st_folium
9
  from sklearn.linear_model import LinearRegression
10
  import os
 
11
  # ================= CONFIG =================
12
  st.set_page_config(
13
  page_title="Michelin Mining Tyre Analytics",
 
16
  initial_sidebar_state="expanded"
17
  )
18
 
19
+ # ================= CUSTOM CSS
20
  st.markdown("""
21
  <style>
22
  /* ================= ROOT & COLORS ================= */
 
47
  text-align: center !important;
48
  }
49
 
50
+ /* Fix bullet/number list centering */
51
  .stMarkdown ul, .stMarkdown ol {
52
  text-align: left !important;
53
  margin-left: auto;
 
68
  margin-bottom: 12px;
69
  }
70
 
71
+ /* Power BI-style dropdowns */
72
  [data-testid="stSelectbox"] div[data-baseweb="select"],
73
  [data-testid="stMultiselect"] div[data-baseweb="select"] {
74
  background-color: white !important;
 
85
  font-weight: 500;
86
  }
87
 
88
+ /* Remove red tags from multiselect */
89
  [data-testid="stMultiselect"] div[data-baseweb="select"] .stMultiSelectTag {
90
  display: none !important;
91
  }
92
 
93
+ /* Submit button */
94
  [data-testid="stSidebar"] .stButton > button {
95
  width: 100%;
96
  background: var(--accent-yellow);
 
109
  box-shadow: 0 8px 16px rgba(0,0,0,0.12);
110
  }
111
 
112
+ /* ================= HEADER ================= */
113
+ .main-header h1 {
114
+ font-size: 2.4rem;
115
+ margin-bottom: 6px;
116
+ font-weight: 800;
117
+ color: var(--michelin-blue);
118
+ }
119
+ .main-header p {
120
+ font-size: 1.15rem;
121
+ color: var(--text-muted);
122
+ margin-top: 0;
123
+ }
124
+
125
+ /* ================= OBJECTIVE TITLE (NO BACKGROUND BOX) ================= */
126
  .objective-title {
127
  text-align: center !important;
128
  font-size: 1.6rem;
 
131
  margin: 40px 0 24px 0;
132
  }
133
 
134
+ /* ================= INSIGHT LLM-STYLE (Like Screenshot) ================= */
135
  .insight-box {
136
  background: var(--surface-alt);
137
  border: 1px solid var(--border);
 
153
  text-align: left;
154
  }
155
 
156
+ .insight-box .tag {
157
+ position: absolute;
158
+ top: 12px;
159
+ right: 16px;
160
+ background: var(--michelin-blue);
161
+ color: white;
162
+ font-size: 0.85rem;
163
+ font-weight: 700;
164
+ padding: 6px 12px;
165
+ border-radius: 8px;
166
+ letter-spacing: 0.5px;
167
+ }
168
+
169
+ /* ================= PLOTLY ================= */
170
+ .plotly-graph-div {
171
+ border-radius: 12px;
172
+ overflow: hidden;
173
+ box-shadow: var(--shadow-sm);
174
+ border: 1px solid var(--border);
175
+ }
176
+
177
  /* ================= FOOTER ================= */
178
  .footer {
179
  text-align: center;
180
+ font-size: 0.9rem;
 
 
 
 
 
 
 
 
 
 
 
181
  color: var(--text-muted);
182
+ margin-top: 50px;
183
+ padding: 20px 0;
184
+ border-top: 1px solid var(--border);
185
  }
186
 
187
+ /* ================= STREAMLIT TWEAKS ================= */
188
+ div.block-container {
189
+ padding-top: 2rem;
 
 
 
 
 
 
190
  }
191
+ section[data-testid="stSidebar"] {
192
+ width: 280px !important;
193
+ min-width: 280px !important;
 
194
  }
195
  </style>
196
  """, unsafe_allow_html=True)
197
 
198
+
199
  # ================= LOAD DATA =================
200
  @st.cache_data
201
  def load_data():
 
205
  st.error("❌ File `df_final.xlsx` not found. Please ensure it's in the same directory.")
206
  st.stop()
207
 
208
+ # Fix encoding (e.g., '°C' → '°C')
209
  df.columns = df.columns.str.replace("Â", "")
210
  for col in df.select_dtypes(include='object').columns:
211
  df[col] = df[col].astype(str).str.replace("Â", "")
212
 
213
+ # Parse datetime
214
  df['Time'] = pd.to_datetime(df['Time'], errors='coerce')
215
  df = df.dropna(subset=['Time'])
216
  df['hour'] = df['Time'].dt.hour
217
+
218
+ # Alarm flag
219
  df['is_alarm'] = (~df['Alarm Status'].str.contains('No Alarm', na=False)).astype(int)
220
 
221
+ # Dynamic risk score
222
  p = df['Pressure (psi)']
223
  p_red_high = df['Red High Press (psi)']
224
  p_amber_high = df['Amber High Press (psi)']
 
236
  elif score >= 0.3: return 'Moderate Risk'
237
  else: return 'Slight Risk'
238
  df['Risk Level'] = df['risk_score'].apply(get_risk_label)
239
+
240
+ # Add Position Group
241
  df['Position Group'] = df['Position'].apply(lambda x: 'Front' if x in [1, 2] else 'Rear')
242
+
243
  return df
244
 
245
  df = load_data()
246
+ # @st.cache_data
247
+ # def load_data():
248
+ # try:
249
+ # # Load main data
250
+ # df = pd.read_excel("df_final.xlsx", sheet_name="Sheet1")
251
+ # # Load health index data
252
+ # hi_data = pd.read_excel("hi_final.xlsx")
253
+ # except FileNotFoundError as e:
254
+ # st.error(f"❌ File not found: `{e.filename}`")
255
+ # st.stop()
256
+ # except Exception as e:
257
+ # st.error(f"❌ Error loading data: {e}")
258
+ # st.stop()
259
+
260
+ # # === Proses df_final.xlsx ===
261
+ # # Fix encoding
262
+ # df.columns = df.columns.str.replace("Â", "")
263
+ # for col in df.select_dtypes(include='object').columns:
264
+ # df[col] = df[col].astype(str).str.replace("Â", "")
265
+
266
+ # # Parse datetime
267
+ # df['Time'] = pd.to_datetime(df['Time'], errors='coerce')
268
+ # df = df.dropna(subset=['Time']).copy()
269
+ # df['hour'] = df['Time'].dt.hour
270
+
271
+ # # Alarm flag
272
+ # df['is_alarm'] = (~df['Alarm Status'].fillna('').str.contains('No Alarm', case=False)).astype(int)
273
+
274
+ # # Dynamic risk score
275
+ # p = df['Pressure (psi)']
276
+ # p_red_high = df['Red High Press (psi)']
277
+ # p_amber_high = df['Amber High Press (psi)']
278
+ # t = df['Temperature (°C)']
279
+ # t_red = df['Absolute Red Temp (°C)']
280
+ # t_amber = df['Absolute Amber Temp (°C)']
281
+
282
+ # # Avoid division by zero
283
+ # p_denom = (p_red_high - p_amber_high).replace(0, np.nan)
284
+ # p_norm = np.clip((p - p_amber_high) / p_denom, 0, 1).fillna(0)
285
+ # t_denom = (t_red - t_amber).replace(0, np.nan)
286
+ # t_norm = np.clip((t - t_amber) / t_denom, 0, 1).fillna(0)
287
+ # df['risk_score'] = 0.6 * p_norm + 0.4 * t_norm
288
+
289
+ # def get_risk_label(score):
290
+ # if score >= 0.8: return 'Very High Risk'
291
+ # elif score >= 0.6: return 'High Risk'
292
+ # elif score >= 0.3: return 'Moderate Risk'
293
+ # else: return 'Slight Risk'
294
+ # df['Risk Level'] = df['risk_score'].apply(get_risk_label)
295
+
296
+ # # Position Group
297
+ # df['Position Group'] = df['Position'].map({1: 'Front', 2: 'Front', 3: 'Rear', 4: 'Rear'}).fillna('Other')
298
+
299
+ # return df, hi_data # Kembalikan 2 data
300
+ # # Load both datasets
301
+ # df, hi_data = load_data()
302
+ # # Optional: Info ringkas di sidebar (opsional)
303
+ # with st.sidebar:
304
+ # st.markdown("### 📊 Dataset Overview")
305
+ # st.metric("Total Records", f"{len(df):,}")
306
+ # st.metric("Date Range", f"{df['Time'].min().date()} → {df['Time'].max().date()}")
307
+ # st.metric("Alarms (Red/Amber)", f"{df['is_alarm'].sum():,} ({df['is_alarm'].mean():.1%})")
308
+
309
+ # ================= HEADER =================
310
+ st.markdown("""
311
+ <div style="text-align:center; font-family:Arial, sans-serif; margin-bottom:16px;">
312
+ <h1 style="color:#154D9C; font-weight:bold; margin:0;">
313
+ Tyre Pressure Monitoring System (TPMS) Analytics for Mining Equipments
314
+ </h1>
315
 
316
+ <p style="font-size:12px; color:#7d7d7d; margin:4px 0 2px 0;">
317
+ Daily trend insights derived from 13–16 December 2023 data
318
+ </p>
319
 
320
+ <p style="font-size:11px; color:#5DA698; margin:6px 0 2px 0; font-weight:600;">
321
+ Special Design from <span style="color:#154D9C;">Bukit Technology Digital</span>
322
+ </p>
 
 
 
 
 
323
 
324
+ <p style="font-size:10px; color:#7d7d7d; margin:2px 0 0 0;">
325
+ &copy; 2025 Bukit Technology Digital. All rights reserved.
326
+ </p>
327
+ </div>
328
+ """, unsafe_allow_html=True)
 
 
 
 
329
 
 
 
 
 
 
 
 
 
330
 
 
331
  # ================= OBJECTIVES (1–6) =================
332
 
333
  # # ================= HEADER =================