SHELLAPANDIANGANHUNGING commited on
Commit
b2fd496
·
verified ·
1 Parent(s): d361fe3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +75 -56
app.py CHANGED
@@ -1155,8 +1155,17 @@ except Exception as e:
1155
 
1156
  # ... (kode sebelumnya tetap sama) ...
1157
 
 
 
 
 
 
 
 
 
1158
  # =================== OBJECTIVE 5: Operator Fatigue Risk Gradient Dashboard =====================
1159
- st.subheader("OBJECTIVE 5: See your teams fatigue risk gradient at a glance!")
 
1160
  # Custom CSS untuk tampilan ala market saham yang sangat fancy dan profesional
1161
  st.markdown("""
1162
  <style>
@@ -1294,12 +1303,9 @@ else:
1294
  st.info("No operator data after filtering.")
1295
  st.stop()
1296
 
1297
- # Pastikan col_operator bukan None sebelum digunakan
1298
- if col_operator is None:
1299
- st.error(f"Operator column could not be auto-detected. Please check your data.")
1300
- st.stop()
1301
-
1302
- df_op["year_week"] = df_op["start"].dt.strftime("%Y-W%U")
1303
 
1304
  # Fuzzy match fleet names
1305
  fleet_clean = df_op[col_fleet_type].str.strip().str.upper()
@@ -1309,52 +1315,60 @@ else:
1309
  ob_data = df_op[df_op["is_ob"]]
1310
  coal_data = df_op[df_op["is_coal"]]
1311
 
1312
- # Fungsi hitung top 10 (untuk bar chart) - berdasarkan weekly avg events tertinggi
1313
  def get_top10_with_slope(data):
1314
  if data.empty:
1315
  st.warning("Data is empty in get_top10_with_slope.")
1316
  return pd.DataFrame()
 
1317
  # Pastikan col_operator tidak None dan ada di data
1318
  if col_operator is None or col_operator not in data.columns:
1319
  st.error(f"Operator column '{col_operator}' not found in data subset for get_top10.")
1320
  return pd.DataFrame()
1321
 
1322
- weekly = data.groupby([col_operator, "year_week"]).size().reset_index(name="weekly_sum")
 
 
1323
  metrics = []
1324
  try:
1325
- for nik, grp in weekly.groupby(col_operator):
1326
  # Lewati jika nik adalah None
1327
  if pd.isna(nik):
1328
  continue
1329
- grp = grp.sort_values("year_week")
1330
- counts = grp["weekly_sum"].values
1331
- weeks = np.arange(len(counts))
1332
- weekly_avg = counts.mean()
1333
  total_events = counts.sum()
1334
- n_weeks = len(counts)
1335
- if n_weeks >= 2:
1336
- x_mean = weeks.mean()
 
1337
  y_mean = counts.mean()
1338
- numerator = np.sum((weeks - x_mean) * (counts - y_mean))
1339
- denominator = np.sum((weeks - x_mean) ** 2)
1340
  slope = numerator / denominator if denominator != 0 else 0.0
1341
  else:
1342
  slope = 0.0
 
1343
  metrics.append({
1344
  col_operator: nik,
1345
- "weekly_avg": weekly_avg,
1346
  "slope": slope,
1347
  "total_events": total_events,
1348
- "n_weeks": n_weeks
1349
  })
1350
  except KeyError as e:
1351
  st.error(f"KeyError in get_top10_with_slope: {e}. This might happen if the operator column contains invalid data types or unexpected values.")
1352
  return pd.DataFrame()
1353
- # Ambil top 10 berdasarkan weekly_avg (descending order)
 
1354
  if not metrics:
1355
  st.warning("No valid operator data found for slope calculation in get_top10.")
1356
  return pd.DataFrame()
1357
- return pd.DataFrame(metrics).nlargest(10, "weekly_avg")
 
 
1358
 
1359
  top_ob = get_top10_with_slope(ob_data)
1360
  top_coal = get_top10_with_slope(coal_data)
@@ -1364,45 +1378,52 @@ else:
1364
  if data.empty:
1365
  st.warning("Data is empty in get_all_operators_with_slope.")
1366
  return pd.DataFrame()
 
1367
  # Pastikan col_operator tidak None dan ada di data
1368
  if col_operator is None or col_operator not in data.columns:
1369
  st.error(f"Operator column '{col_operator}' not found in data subset for get_all.")
1370
  return pd.DataFrame()
1371
 
1372
- weekly = data.groupby([col_operator, "year_week"]).size().reset_index(name="weekly_sum")
 
 
1373
  metrics = []
1374
  try:
1375
- for nik, grp in weekly.groupby(col_operator):
1376
  # Lewati jika nik adalah None
1377
  if pd.isna(nik):
1378
  continue
1379
- grp = grp.sort_values("year_week")
1380
- counts = grp["weekly_sum"].values
1381
- weeks = np.arange(len(counts))
1382
- weekly_avg = counts.mean()
1383
  total_events = counts.sum()
1384
- n_weeks = len(counts)
1385
- if n_weeks >= 2:
1386
- x_mean = weeks.mean()
 
1387
  y_mean = counts.mean()
1388
- numerator = np.sum((weeks - x_mean) * (counts - y_mean))
1389
- denominator = np.sum((weeks - x_mean) ** 2)
1390
  slope = numerator / denominator if denominator != 0 else 0.0
1391
  else:
1392
  slope = 0.0
 
1393
  metrics.append({
1394
  col_operator: nik,
1395
- "weekly_avg": weekly_avg,
1396
  "slope": slope,
1397
  "total_events": total_events,
1398
- "n_weeks": n_weeks
1399
  })
1400
  except KeyError as e:
1401
  st.error(f"KeyError in get_all_operators_with_slope: {e}. This might happen if the operator column contains invalid data types or unexpected values.")
1402
  return pd.DataFrame()
 
1403
  if not metrics:
1404
  st.warning("No valid operator data found for slope calculation in get_all.")
1405
  return pd.DataFrame()
 
1406
  return pd.DataFrame(metrics)
1407
 
1408
  all_ob = get_all_operators_with_slope(ob_data)
@@ -1459,7 +1480,7 @@ else:
1459
  <span>Stable (0)</span>
1460
  </div>
1461
  <br>
1462
- <i>Note: Only appears when operator data shows consistent behavior within a single week observation period.</i>
1463
  </div>
1464
  </div>
1465
  """, unsafe_allow_html=True)
@@ -1480,8 +1501,8 @@ else:
1480
  fig.update_layout(height=350, title=title)
1481
  return fig
1482
 
1483
- # Urutkan data berdasarkan weekly_avg dari besar ke kecil
1484
- data_sorted = data.sort_values('weekly_avg', ascending=False)
1485
 
1486
  # Kategorisasi warna berdasarkan slope dengan gradasi yang berbeda
1487
  def get_color(slope):
@@ -1513,22 +1534,22 @@ else:
1513
  # Buat trace bar, TANPA argumen 'title'
1514
  bar_trace = go.Bar(
1515
  x=data_sorted[col_operator].astype(str),
1516
- y=data_sorted["weekly_avg"],
1517
  marker=dict(
1518
  color=colors,
1519
  line=dict(width=2, color="rgba(0,0,0,0.2)")
1520
  ),
1521
- text=[f"{v:.1f}" for v in data_sorted["weekly_avg"]],
1522
  textposition="outside",
1523
  hovertemplate=(
1524
  "<b>%{x}</b><br>" +
1525
- "Weekly Avg: %{y:.2f}<br>" +
1526
  "Trend Slope: %{customdata[0]:+.3f}<br>" +
1527
  "Total Events: %{customdata[1]}<br>" +
1528
- "Weeks Active: %{customdata[2]}<br>" +
1529
  "<extra></extra>"
1530
  ),
1531
- customdata=np.stack([data_sorted["slope"], data_sorted["total_events"], data_sorted["n_weeks"]], axis=-1)
1532
  )
1533
 
1534
  # Buat figure dan tambahkan trace
@@ -1541,7 +1562,7 @@ else:
1541
  height=450,
1542
  margin=dict(l=50, r=20, t=60, b=120),
1543
  xaxis_title="<b>Operator ID</b>",
1544
- yaxis_title="<b>Weekly Avg Events</b>",
1545
  font=dict(family="Segoe UI", size=12),
1546
  bargap=0.3,
1547
  plot_bgcolor="rgba(0,0,0,0)",
@@ -1573,14 +1594,14 @@ else:
1573
  st.markdown("### OB HAULER Analysis")
1574
  ob_worsening = len(top_ob[top_ob['slope'] > 0])
1575
  ob_improving = len(top_ob[top_ob['slope'] < 0])
1576
- ob_avg_risk = top_ob['weekly_avg'].mean()
1577
- ob_max_risk = top_ob['weekly_avg'].max()
1578
  ob_insights = []
1579
  if ob_worsening > ob_improving:
1580
  ob_insights.append(f"{ob_worsening} out of 10 top risk operators are showing <span class='trend-up'>worsening</span> trends, indicating potential fatigue issues in this fleet type.")
1581
  else:
1582
  ob_insights.append(f"{ob_improving} out of 10 top risk operators are showing <span class='trend-down'>improvement</span>, suggesting effective fatigue management strategies.")
1583
- ob_insights.append(f"Average risk level among top 10 operators is {ob_avg_risk:.2f} events per week with maximum {ob_max_risk:.2f}.")
1584
 
1585
  for insight in ob_insights:
1586
  st.markdown(f"""
@@ -1598,14 +1619,14 @@ else:
1598
  st.markdown("### HAULING COAL Analysis")
1599
  coal_worsening = len(top_coal[top_coal['slope'] > 0])
1600
  coal_improving = len(top_coal[top_coal['slope'] < 0])
1601
- coal_avg_risk = top_coal['weekly_avg'].mean()
1602
- coal_max_risk = top_coal['weekly_avg'].max()
1603
  coal_insights = []
1604
  if coal_worsening > coal_improving:
1605
  coal_insights.append(f"{coal_worsening} out of 10 top risk operators are showing <span class='trend-up'>worsening</span> trends, requiring immediate attention.")
1606
  else:
1607
  coal_insights.append(f"{coal_improving} out of 10 top risk operators are showing <span class='trend-down'>improvement</span>, indicating positive trends in safety management.")
1608
- coal_insights.append(f"Average risk level among top 10 operators is {coal_avg_risk:.2f} events per week with maximum {coal_max_risk:.2f}.")
1609
 
1610
  for insight in coal_insights:
1611
  st.markdown(f"""
@@ -1632,7 +1653,7 @@ else:
1632
  recommendations = {}
1633
  if not top_ob.empty:
1634
  ob_worsening = len(top_ob[top_ob['slope'] > 0])
1635
- ob_avg_risk = top_ob['weekly_avg'].mean()
1636
  if ob_worsening > 5: # Lebih dari setengah
1637
  recommendations['ob'] = "Implement immediate fatigue monitoring protocols for operators showing worsening trends."
1638
  reason_ob = "High percentage of operators showing increasing risk trends indicates potential systemic fatigue issues requiring immediate intervention."
@@ -1646,7 +1667,7 @@ else:
1646
 
1647
  if not top_coal.empty:
1648
  coal_worsening = len(top_coal[top_coal['slope'] > 0])
1649
- coal_avg_risk = top_coal['weekly_avg'].mean()
1650
  if coal_worsening > 5: # Lebih dari setengah
1651
  recommendations['coal'] = "Implement immediate fatigue monitoring protocols for operators showing worsening trends."
1652
  reason_coal = "High percentage of operators showing increasing risk trends indicates potential systemic fatigue issues requiring immediate intervention."
@@ -1693,9 +1714,7 @@ else:
1693
  except Exception as e:
1694
  st.error(f"Error in Top 10 Operator analysis: {str(e)}")
1695
  st.code(f"Error: {e}", language="python")
1696
-
1697
-
1698
-
1699
 
1700
  # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
1701
  st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
 
1155
 
1156
  # ... (kode sebelumnya tetap sama) ...
1157
 
1158
+
1159
+
1160
+ ```python
1161
+ import streamlit as st
1162
+ import pandas as pd
1163
+ import plotly.graph_objects as go
1164
+ import numpy as np
1165
+
1166
  # =================== OBJECTIVE 5: Operator Fatigue Risk Gradient Dashboard =====================
1167
+ st.subheader("OBJECTIVE 5: See your team's fatigue risk gradient at a glance!")
1168
+
1169
  # Custom CSS untuk tampilan ala market saham yang sangat fancy dan profesional
1170
  st.markdown("""
1171
  <style>
 
1303
  st.info("No operator data after filtering.")
1304
  st.stop()
1305
 
1306
+ # Buat kolom date dan daily_count
1307
+ df_op["date"] = df_op["start"].dt.date
1308
+ daily = df_op.groupby([col_operator, "date"]).size().reset_index(name="daily_count")
 
 
 
1309
 
1310
  # Fuzzy match fleet names
1311
  fleet_clean = df_op[col_fleet_type].str.strip().str.upper()
 
1315
  ob_data = df_op[df_op["is_ob"]]
1316
  coal_data = df_op[df_op["is_coal"]]
1317
 
1318
+ # Fungsi hitung top 10 (untuk bar chart) - berdasarkan daily avg events tertinggi
1319
  def get_top10_with_slope(data):
1320
  if data.empty:
1321
  st.warning("Data is empty in get_top10_with_slope.")
1322
  return pd.DataFrame()
1323
+
1324
  # Pastikan col_operator tidak None dan ada di data
1325
  if col_operator is None or col_operator not in data.columns:
1326
  st.error(f"Operator column '{col_operator}' not found in data subset for get_top10.")
1327
  return pd.DataFrame()
1328
 
1329
+ # Hitung daily count untuk masing-masing operator dan tanggal
1330
+ daily_data = data.groupby([col_operator, "date"]).size().reset_index(name="daily_count")
1331
+
1332
  metrics = []
1333
  try:
1334
+ for nik, grp in daily_data.groupby(col_operator):
1335
  # Lewati jika nik adalah None
1336
  if pd.isna(nik):
1337
  continue
1338
+ grp = grp.sort_values("date")
1339
+ counts = grp["daily_count"].values
1340
+ dates = np.arange(len(counts))
1341
+ daily_avg = counts.mean()
1342
  total_events = counts.sum()
1343
+ n_days = len(counts)
1344
+
1345
+ if n_days >= 2:
1346
+ x_mean = dates.mean()
1347
  y_mean = counts.mean()
1348
+ numerator = np.sum((dates - x_mean) * (counts - y_mean))
1349
+ denominator = np.sum((dates - x_mean) ** 2)
1350
  slope = numerator / denominator if denominator != 0 else 0.0
1351
  else:
1352
  slope = 0.0
1353
+
1354
  metrics.append({
1355
  col_operator: nik,
1356
+ "daily_avg": daily_avg,
1357
  "slope": slope,
1358
  "total_events": total_events,
1359
+ "n_days": n_days
1360
  })
1361
  except KeyError as e:
1362
  st.error(f"KeyError in get_top10_with_slope: {e}. This might happen if the operator column contains invalid data types or unexpected values.")
1363
  return pd.DataFrame()
1364
+
1365
+ # Ambil top 10 berdasarkan daily_avg (descending order)
1366
  if not metrics:
1367
  st.warning("No valid operator data found for slope calculation in get_top10.")
1368
  return pd.DataFrame()
1369
+
1370
+ df_metrics = pd.DataFrame(metrics)
1371
+ return df_metrics.nlargest(10, "daily_avg")
1372
 
1373
  top_ob = get_top10_with_slope(ob_data)
1374
  top_coal = get_top10_with_slope(coal_data)
 
1378
  if data.empty:
1379
  st.warning("Data is empty in get_all_operators_with_slope.")
1380
  return pd.DataFrame()
1381
+
1382
  # Pastikan col_operator tidak None dan ada di data
1383
  if col_operator is None or col_operator not in data.columns:
1384
  st.error(f"Operator column '{col_operator}' not found in data subset for get_all.")
1385
  return pd.DataFrame()
1386
 
1387
+ # Hitung daily count untuk masing-masing operator dan tanggal
1388
+ daily_data = data.groupby([col_operator, "date"]).size().reset_index(name="daily_count")
1389
+
1390
  metrics = []
1391
  try:
1392
+ for nik, grp in daily_data.groupby(col_operator):
1393
  # Lewati jika nik adalah None
1394
  if pd.isna(nik):
1395
  continue
1396
+ grp = grp.sort_values("date")
1397
+ counts = grp["daily_count"].values
1398
+ dates = np.arange(len(counts))
1399
+ daily_avg = counts.mean()
1400
  total_events = counts.sum()
1401
+ n_days = len(counts)
1402
+
1403
+ if n_days >= 2:
1404
+ x_mean = dates.mean()
1405
  y_mean = counts.mean()
1406
+ numerator = np.sum((dates - x_mean) * (counts - y_mean))
1407
+ denominator = np.sum((dates - x_mean) ** 2)
1408
  slope = numerator / denominator if denominator != 0 else 0.0
1409
  else:
1410
  slope = 0.0
1411
+
1412
  metrics.append({
1413
  col_operator: nik,
1414
+ "daily_avg": daily_avg,
1415
  "slope": slope,
1416
  "total_events": total_events,
1417
+ "n_days": n_days
1418
  })
1419
  except KeyError as e:
1420
  st.error(f"KeyError in get_all_operators_with_slope: {e}. This might happen if the operator column contains invalid data types or unexpected values.")
1421
  return pd.DataFrame()
1422
+
1423
  if not metrics:
1424
  st.warning("No valid operator data found for slope calculation in get_all.")
1425
  return pd.DataFrame()
1426
+
1427
  return pd.DataFrame(metrics)
1428
 
1429
  all_ob = get_all_operators_with_slope(ob_data)
 
1480
  <span>Stable (0)</span>
1481
  </div>
1482
  <br>
1483
+ <i>Note: Only appears when operator data shows consistent behavior within a single day observation period.</i>
1484
  </div>
1485
  </div>
1486
  """, unsafe_allow_html=True)
 
1501
  fig.update_layout(height=350, title=title)
1502
  return fig
1503
 
1504
+ # Urutkan data berdasarkan daily_avg dari besar ke kecil
1505
+ data_sorted = data.sort_values('daily_avg', ascending=False)
1506
 
1507
  # Kategorisasi warna berdasarkan slope dengan gradasi yang berbeda
1508
  def get_color(slope):
 
1534
  # Buat trace bar, TANPA argumen 'title'
1535
  bar_trace = go.Bar(
1536
  x=data_sorted[col_operator].astype(str),
1537
+ y=data_sorted["daily_avg"],
1538
  marker=dict(
1539
  color=colors,
1540
  line=dict(width=2, color="rgba(0,0,0,0.2)")
1541
  ),
1542
+ text=[f"{v:.1f}" for v in data_sorted["daily_avg"]],
1543
  textposition="outside",
1544
  hovertemplate=(
1545
  "<b>%{x}</b><br>" +
1546
+ "Daily Avg: %{y:.2f}<br>" +
1547
  "Trend Slope: %{customdata[0]:+.3f}<br>" +
1548
  "Total Events: %{customdata[1]}<br>" +
1549
+ "Days Active: %{customdata[2]}<br>" +
1550
  "<extra></extra>"
1551
  ),
1552
+ customdata=np.stack([data_sorted["slope"], data_sorted["total_events"], data_sorted["n_days"]], axis=-1)
1553
  )
1554
 
1555
  # Buat figure dan tambahkan trace
 
1562
  height=450,
1563
  margin=dict(l=50, r=20, t=60, b=120),
1564
  xaxis_title="<b>Operator ID</b>",
1565
+ yaxis_title="<b>Daily Avg Events</b>",
1566
  font=dict(family="Segoe UI", size=12),
1567
  bargap=0.3,
1568
  plot_bgcolor="rgba(0,0,0,0)",
 
1594
  st.markdown("### OB HAULER Analysis")
1595
  ob_worsening = len(top_ob[top_ob['slope'] > 0])
1596
  ob_improving = len(top_ob[top_ob['slope'] < 0])
1597
+ ob_avg_risk = top_ob['daily_avg'].mean()
1598
+ ob_max_risk = top_ob['daily_avg'].max()
1599
  ob_insights = []
1600
  if ob_worsening > ob_improving:
1601
  ob_insights.append(f"{ob_worsening} out of 10 top risk operators are showing <span class='trend-up'>worsening</span> trends, indicating potential fatigue issues in this fleet type.")
1602
  else:
1603
  ob_insights.append(f"{ob_improving} out of 10 top risk operators are showing <span class='trend-down'>improvement</span>, suggesting effective fatigue management strategies.")
1604
+ ob_insights.append(f"Average risk level among top 10 operators is {ob_avg_risk:.2f} events per day with maximum {ob_max_risk:.2f}.")
1605
 
1606
  for insight in ob_insights:
1607
  st.markdown(f"""
 
1619
  st.markdown("### HAULING COAL Analysis")
1620
  coal_worsening = len(top_coal[top_coal['slope'] > 0])
1621
  coal_improving = len(top_coal[top_coal['slope'] < 0])
1622
+ coal_avg_risk = top_coal['daily_avg'].mean()
1623
+ coal_max_risk = top_coal['daily_avg'].max()
1624
  coal_insights = []
1625
  if coal_worsening > coal_improving:
1626
  coal_insights.append(f"{coal_worsening} out of 10 top risk operators are showing <span class='trend-up'>worsening</span> trends, requiring immediate attention.")
1627
  else:
1628
  coal_insights.append(f"{coal_improving} out of 10 top risk operators are showing <span class='trend-down'>improvement</span>, indicating positive trends in safety management.")
1629
+ coal_insights.append(f"Average risk level among top 10 operators is {coal_avg_risk:.2f} events per day with maximum {coal_max_risk:.2f}.")
1630
 
1631
  for insight in coal_insights:
1632
  st.markdown(f"""
 
1653
  recommendations = {}
1654
  if not top_ob.empty:
1655
  ob_worsening = len(top_ob[top_ob['slope'] > 0])
1656
+ ob_avg_risk = top_ob['daily_avg'].mean()
1657
  if ob_worsening > 5: # Lebih dari setengah
1658
  recommendations['ob'] = "Implement immediate fatigue monitoring protocols for operators showing worsening trends."
1659
  reason_ob = "High percentage of operators showing increasing risk trends indicates potential systemic fatigue issues requiring immediate intervention."
 
1667
 
1668
  if not top_coal.empty:
1669
  coal_worsening = len(top_coal[top_coal['slope'] > 0])
1670
+ coal_avg_risk = top_coal['daily_avg'].mean()
1671
  if coal_worsening > 5: # Lebih dari setengah
1672
  recommendations['coal'] = "Implement immediate fatigue monitoring protocols for operators showing worsening trends."
1673
  reason_coal = "High percentage of operators showing increasing risk trends indicates potential systemic fatigue issues requiring immediate intervention."
 
1714
  except Exception as e:
1715
  st.error(f"Error in Top 10 Operator analysis: {str(e)}")
1716
  st.code(f"Error: {e}", language="python")
1717
+ ```
 
 
1718
 
1719
  # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
1720
  st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")