SHELLAPANDIANGANHUNGING commited on
Commit
17f07e4
·
verified ·
1 Parent(s): 683fc34

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +56 -75
app.py CHANGED
@@ -1153,11 +1153,10 @@ except Exception as e:
1153
 
1154
  # =================== OBJECTIVE 5: Operator Fatigue Risk Gradient Dashboard =====================
1155
 
1156
-
1157
 
1158
  # =================== OBJECTIVE 5: Operator Fatigue Risk Gradient Dashboard =====================
1159
- st.subheader("OBJECTIVE 5: See your team's fatigue risk gradient at a glance!")
1160
-
1161
  # Custom CSS untuk tampilan ala market saham yang sangat fancy dan profesional
1162
  st.markdown("""
1163
  <style>
@@ -1295,18 +1294,12 @@ else:
1295
  st.info("No operator data after filtering.")
1296
  st.stop()
1297
 
1298
- # Tambahkan kolom shift jika ada
1299
- if 'shift' in df_op.columns:
1300
- df_op['shift'] = df_op['shift'].fillna('Unknown')
1301
- else:
1302
- # Jika tidak ada kolom shift, buat kolom shift berdasarkan waktu
1303
- df_op['shift'] = df_op['start'].dt.hour.apply(
1304
- lambda x: 'Shift 1' if 6 <= x < 14 else ('Shift 2' if 14 <= x < 22 else 'Shift 3')
1305
- )
1306
 
1307
- # Buat kolom date dan daily_count
1308
- df_op["date"] = df_op["start"].dt.date
1309
- daily = df_op.groupby([col_operator, "date"]).size().reset_index(name="daily_count")
1310
 
1311
  # Fuzzy match fleet names
1312
  fleet_clean = df_op[col_fleet_type].str.strip().str.upper()
@@ -1316,60 +1309,52 @@ else:
1316
  ob_data = df_op[df_op["is_ob"]]
1317
  coal_data = df_op[df_op["is_coal"]]
1318
 
1319
- # Fungsi hitung top 10 (untuk bar chart) - berdasarkan daily avg events tertinggi
1320
  def get_top10_with_slope(data):
1321
  if data.empty:
1322
  st.warning("Data is empty in get_top10_with_slope.")
1323
  return pd.DataFrame()
1324
-
1325
  # Pastikan col_operator tidak None dan ada di data
1326
  if col_operator is None or col_operator not in data.columns:
1327
  st.error(f"Operator column '{col_operator}' not found in data subset for get_top10.")
1328
  return pd.DataFrame()
1329
 
1330
- # Hitung daily count untuk masing-masing operator dan tanggal
1331
- daily_data = data.groupby([col_operator, "date"]).size().reset_index(name="daily_count")
1332
-
1333
  metrics = []
1334
  try:
1335
- for nik, grp in daily_data.groupby(col_operator):
1336
  # Lewati jika nik adalah None
1337
  if pd.isna(nik):
1338
  continue
1339
- grp = grp.sort_values("date")
1340
- counts = grp["daily_count"].values
1341
- dates = np.arange(len(counts))
1342
- daily_avg = counts.mean()
1343
  total_events = counts.sum()
1344
- n_days = len(counts)
1345
-
1346
- if n_days >= 2:
1347
- x_mean = dates.mean()
1348
  y_mean = counts.mean()
1349
- numerator = np.sum((dates - x_mean) * (counts - y_mean))
1350
- denominator = np.sum((dates - x_mean) ** 2)
1351
  slope = numerator / denominator if denominator != 0 else 0.0
1352
  else:
1353
  slope = 0.0
1354
-
1355
  metrics.append({
1356
  col_operator: nik,
1357
- "daily_avg": daily_avg,
1358
  "slope": slope,
1359
  "total_events": total_events,
1360
- "n_days": n_days
1361
  })
1362
  except KeyError as e:
1363
  st.error(f"KeyError in get_top10_with_slope: {e}. This might happen if the operator column contains invalid data types or unexpected values.")
1364
  return pd.DataFrame()
1365
-
1366
- # Ambil top 10 berdasarkan daily_avg (descending order)
1367
  if not metrics:
1368
  st.warning("No valid operator data found for slope calculation in get_top10.")
1369
  return pd.DataFrame()
1370
-
1371
- df_metrics = pd.DataFrame(metrics)
1372
- return df_metrics.nlargest(10, "daily_avg")
1373
 
1374
  top_ob = get_top10_with_slope(ob_data)
1375
  top_coal = get_top10_with_slope(coal_data)
@@ -1379,52 +1364,45 @@ else:
1379
  if data.empty:
1380
  st.warning("Data is empty in get_all_operators_with_slope.")
1381
  return pd.DataFrame()
1382
-
1383
  # Pastikan col_operator tidak None dan ada di data
1384
  if col_operator is None or col_operator not in data.columns:
1385
  st.error(f"Operator column '{col_operator}' not found in data subset for get_all.")
1386
  return pd.DataFrame()
1387
 
1388
- # Hitung daily count untuk masing-masing operator dan tanggal
1389
- daily_data = data.groupby([col_operator, "date"]).size().reset_index(name="daily_count")
1390
-
1391
  metrics = []
1392
  try:
1393
- for nik, grp in daily_data.groupby(col_operator):
1394
  # Lewati jika nik adalah None
1395
  if pd.isna(nik):
1396
  continue
1397
- grp = grp.sort_values("date")
1398
- counts = grp["daily_count"].values
1399
- dates = np.arange(len(counts))
1400
- daily_avg = counts.mean()
1401
  total_events = counts.sum()
1402
- n_days = len(counts)
1403
-
1404
- if n_days >= 2:
1405
- x_mean = dates.mean()
1406
  y_mean = counts.mean()
1407
- numerator = np.sum((dates - x_mean) * (counts - y_mean))
1408
- denominator = np.sum((dates - x_mean) ** 2)
1409
  slope = numerator / denominator if denominator != 0 else 0.0
1410
  else:
1411
  slope = 0.0
1412
-
1413
  metrics.append({
1414
  col_operator: nik,
1415
- "daily_avg": daily_avg,
1416
  "slope": slope,
1417
  "total_events": total_events,
1418
- "n_days": n_days
1419
  })
1420
  except KeyError as e:
1421
  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.")
1422
  return pd.DataFrame()
1423
-
1424
  if not metrics:
1425
  st.warning("No valid operator data found for slope calculation in get_all.")
1426
  return pd.DataFrame()
1427
-
1428
  return pd.DataFrame(metrics)
1429
 
1430
  all_ob = get_all_operators_with_slope(ob_data)
@@ -1481,7 +1459,7 @@ else:
1481
  <span>Stable (0)</span>
1482
  </div>
1483
  <br>
1484
- <i>Note: Only appears when operator data shows consistent behavior within a single day observation period.</i>
1485
  </div>
1486
  </div>
1487
  """, unsafe_allow_html=True)
@@ -1502,8 +1480,8 @@ else:
1502
  fig.update_layout(height=350, title=title)
1503
  return fig
1504
 
1505
- # Urutkan data berdasarkan daily_avg dari besar ke kecil
1506
- data_sorted = data.sort_values('daily_avg', ascending=False)
1507
 
1508
  # Kategorisasi warna berdasarkan slope dengan gradasi yang berbeda
1509
  def get_color(slope):
@@ -1535,22 +1513,22 @@ else:
1535
  # Buat trace bar, TANPA argumen 'title'
1536
  bar_trace = go.Bar(
1537
  x=data_sorted[col_operator].astype(str),
1538
- y=data_sorted["daily_avg"],
1539
  marker=dict(
1540
  color=colors,
1541
  line=dict(width=2, color="rgba(0,0,0,0.2)")
1542
  ),
1543
- text=[f"{v:.1f}" for v in data_sorted["daily_avg"]],
1544
  textposition="outside",
1545
  hovertemplate=(
1546
  "<b>%{x}</b><br>" +
1547
- "Daily Avg: %{y:.2f}<br>" +
1548
  "Trend Slope: %{customdata[0]:+.3f}<br>" +
1549
  "Total Events: %{customdata[1]}<br>" +
1550
- "Days Active: %{customdata[2]}<br>" +
1551
  "<extra></extra>"
1552
  ),
1553
- customdata=np.stack([data_sorted["slope"], data_sorted["total_events"], data_sorted["n_days"]], axis=-1)
1554
  )
1555
 
1556
  # Buat figure dan tambahkan trace
@@ -1563,7 +1541,7 @@ else:
1563
  height=450,
1564
  margin=dict(l=50, r=20, t=60, b=120),
1565
  xaxis_title="<b>Operator ID</b>",
1566
- yaxis_title="<b>Daily Avg Events</b>",
1567
  font=dict(family="Segoe UI", size=12),
1568
  bargap=0.3,
1569
  plot_bgcolor="rgba(0,0,0,0)",
@@ -1595,14 +1573,14 @@ else:
1595
  st.markdown("### OB HAULER Analysis")
1596
  ob_worsening = len(top_ob[top_ob['slope'] > 0])
1597
  ob_improving = len(top_ob[top_ob['slope'] < 0])
1598
- ob_avg_risk = top_ob['daily_avg'].mean()
1599
- ob_max_risk = top_ob['daily_avg'].max()
1600
  ob_insights = []
1601
  if ob_worsening > ob_improving:
1602
  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.")
1603
  else:
1604
  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.")
1605
- 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}.")
1606
 
1607
  for insight in ob_insights:
1608
  st.markdown(f"""
@@ -1620,14 +1598,14 @@ else:
1620
  st.markdown("### HAULING COAL Analysis")
1621
  coal_worsening = len(top_coal[top_coal['slope'] > 0])
1622
  coal_improving = len(top_coal[top_coal['slope'] < 0])
1623
- coal_avg_risk = top_coal['daily_avg'].mean()
1624
- coal_max_risk = top_coal['daily_avg'].max()
1625
  coal_insights = []
1626
  if coal_worsening > coal_improving:
1627
  coal_insights.append(f"{coal_worsening} out of 10 top risk operators are showing <span class='trend-up'>worsening</span> trends, requiring immediate attention.")
1628
  else:
1629
  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.")
1630
- 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}.")
1631
 
1632
  for insight in coal_insights:
1633
  st.markdown(f"""
@@ -1654,7 +1632,7 @@ else:
1654
  recommendations = {}
1655
  if not top_ob.empty:
1656
  ob_worsening = len(top_ob[top_ob['slope'] > 0])
1657
- ob_avg_risk = top_ob['daily_avg'].mean()
1658
  if ob_worsening > 5: # Lebih dari setengah
1659
  recommendations['ob'] = "Implement immediate fatigue monitoring protocols for operators showing worsening trends."
1660
  reason_ob = "High percentage of operators showing increasing risk trends indicates potential systemic fatigue issues requiring immediate intervention."
@@ -1668,7 +1646,7 @@ else:
1668
 
1669
  if not top_coal.empty:
1670
  coal_worsening = len(top_coal[top_coal['slope'] > 0])
1671
- coal_avg_risk = top_coal['daily_avg'].mean()
1672
  if coal_worsening > 5: # Lebih dari setengah
1673
  recommendations['coal'] = "Implement immediate fatigue monitoring protocols for operators showing worsening trends."
1674
  reason_coal = "High percentage of operators showing increasing risk trends indicates potential systemic fatigue issues requiring immediate intervention."
@@ -1716,6 +1694,9 @@ else:
1716
  st.error(f"Error in Top 10 Operator analysis: {str(e)}")
1717
  st.code(f"Error: {e}", language="python")
1718
 
 
 
 
1719
  # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
1720
  st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
1721
 
 
1153
 
1154
  # =================== OBJECTIVE 5: Operator Fatigue Risk Gradient Dashboard =====================
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
  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
  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
  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
  <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
  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
  # 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
  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
  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
  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
  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
 
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."
 
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")
1702