SHELLAPANDIANGANHUNGING commited on
Commit
b94ce53
·
verified ·
1 Parent(s): faab708

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +74 -104
app.py CHANGED
@@ -1159,10 +1159,9 @@ import plotly.graph_objects as go
1159
 
1160
  # =================== OBJECTIVE 5: Operator Fatigue Risk Gradient Dashboard =====================
1161
  # ... (kode sebelumnya tetap sama) ...
1162
-
1163
  st.subheader("OBJECTIVE 5: See your team’s fatigue Fatigue Hazard Profile!")
1164
 
1165
- # Custom CSS — diperbarui dengan styling list untuk rekomendasi
1166
  st.markdown("""
1167
  <style>
1168
  .big-title {
@@ -1263,30 +1262,20 @@ st.markdown("""
1263
  .recommendation-title {
1264
  font-weight: bold;
1265
  color: white;
1266
- margin-bottom: 12px;
1267
  font-size: 14px;
1268
  background: rgba(255,255,255,0.2);
1269
  padding: 8px;
1270
  border-radius: 5px;
1271
  border-left: 4px solid white;
1272
  }
1273
- .recommendation-box ul {
1274
- padding-left: 24px;
1275
- margin: 12px 0;
1276
- line-height: 1.6;
1277
- font-size: 14px;
1278
- }
1279
- .recommendation-box ul li {
1280
- margin-bottom: 8px;
1281
- }
1282
  .recommendation-reason {
1283
  font-size: 12px;
1284
- margin-top: 12px;
1285
  padding: 8px;
1286
- background: rgba(255,255,255,0.15);
1287
  border-radius: 5px;
1288
- border-left: 3px solid rgba(255,255,255,0.4);
1289
- font-style: italic;
1290
  }
1291
  </style>
1292
  """, unsafe_allow_html=True)
@@ -1459,9 +1448,7 @@ else:
1459
  </div>
1460
  """, unsafe_allow_html=True)
1461
 
1462
- # ===============================================================
1463
- # PLOT FUNCTION — UPDATED: color for slope=0 is now #FFD700
1464
- # ===============================================================
1465
  def plot_chart(data, title):
1466
  if data.empty:
1467
  fig = go.Figure()
@@ -1545,7 +1532,7 @@ else:
1545
  st.plotly_chart(plot_chart(top_coal, "HAULING COAL Operators (Hazard Gradient)"), use_container_width=True)
1546
 
1547
  # ===============================================================
1548
- # AI INSIGHTS — tetap dalam bahasa Inggris, tanpa emoticon
1549
  # ===============================================================
1550
  col_insight1, col_insight2 = st.columns(2)
1551
 
@@ -1557,6 +1544,7 @@ else:
1557
  ob_one_time = len(top_ob[top_ob['slope'] == 0])
1558
  ob_avg_risk = top_ob['weekly_avg'].mean()
1559
  ob_max_risk = top_ob['weekly_avg'].max()
 
1560
  ob_insights = []
1561
  if ob_worsening > ob_improving:
1562
  ob_insights.append(f"{ob_worsening} out of 10 top risk operators are showing <span class='trend-up'>worsening</span> trends.")
@@ -1564,15 +1552,20 @@ else:
1564
  ob_insights.append(f"{ob_improving} out of 10 top risk operators are showing <span class='trend-down'>improvement</span>.")
1565
  if ob_one_time > 0:
1566
  ob_insights.append(f"{ob_one_time} operators are classified as <b>One Time Event</b> (single-week activity).")
 
 
1567
  ob_insights.append(f"Average risk: {ob_avg_risk:.2f} events/week (max: {ob_max_risk:.2f}).")
1568
 
1569
- for insight in ob_insights:
1570
- st.markdown(f"""
1571
- <div class="ai-insight-box">
1572
- <div class="ai-insight-title">Risk Summary</div>
1573
- <p>{insight}</p>
1574
- </div>
1575
- """, unsafe_allow_html=True)
 
 
 
1576
  else:
1577
  st.info("No OB HAULER data for analysis.")
1578
 
@@ -1584,6 +1577,7 @@ else:
1584
  coal_one_time = len(top_coal[top_coal['slope'] == 0])
1585
  coal_avg_risk = top_coal['weekly_avg'].mean()
1586
  coal_max_risk = top_coal['weekly_avg'].max()
 
1587
  coal_insights = []
1588
  if coal_worsening > coal_improving:
1589
  coal_insights.append(f"{coal_worsening} out of 10 top risk operators are showing <span class='trend-up'>worsening</span> trends.")
@@ -1591,116 +1585,92 @@ else:
1591
  coal_insights.append(f"{coal_improving} out of 10 top risk operators are showing <span class='trend-down'>improvement</span>.")
1592
  if coal_one_time > 0:
1593
  coal_insights.append(f"{coal_one_time} operators are classified as <b>One Time Event</b> (single-week activity).")
 
 
1594
  coal_insights.append(f"Average risk: {coal_avg_risk:.2f} events/week (max: {coal_max_risk:.2f}).")
1595
 
1596
- for insight in coal_insights:
1597
- st.markdown(f"""
1598
- <div class="ai-insight-box">
1599
- <div class="ai-insight-title">Risk Summary</div>
1600
- <p>{insight}</p>
1601
- </div>
1602
- """, unsafe_allow_html=True)
 
 
 
1603
  else:
1604
  st.info("No HAULING COAL data for analysis.")
1605
 
1606
  # ===============================================================
1607
- # RECOMMENDATIONS — FORMAT BARU: Judul + 3 List Langsung
1608
  # ===============================================================
1609
  col_rec1, col_rec2 = st.columns(2)
1610
 
1611
- # === OB HAULER RECOMMENDATIONS (3-point list) ===
1612
- with col_rec1:
1613
  if not top_ob.empty:
1614
  w = len(top_ob[top_ob['slope'] > 0])
1615
  ot = len(top_ob[top_ob['slope'] == 0])
1616
  avg = top_ob['weekly_avg'].mean()
1617
-
1618
- rec_list_ob = []
1619
-
1620
- # Point 1: Trend-driven action
1621
  if w > 5:
1622
- rec_list_ob.append("Conduct targeted fatigue risk assessments for operators with worsening trends (slope > 0).")
 
1623
  elif ot > 4:
1624
- rec_list_ob.append("Investigate data completeness — high count of One Time Events may reflect inconsistent reporting.")
 
1625
  elif avg > 8:
1626
- rec_list_ob.append("Review shift scheduling and rest-break compliance to reduce event frequency.")
 
1627
  else:
1628
- rec_list_ob.append("Continue current protocols with routine monitoring of top-risk operators.")
 
 
 
1629
 
1630
- # Point 2: One-Time Event follow-up
1631
- if ot > 0:
1632
- rec_list_ob.append(f"Re-engage {ot} operators flagged as <b>One Time Event</b> to verify activity continuity.")
 
 
 
 
 
 
 
 
 
 
1633
  else:
1634
- rec_list_ob.append("No One Time Event operators identified trend analysis remains reliable.")
1635
-
1636
- # Point 3: Benchmarking
1637
- rec_list_ob.append(f"Use cohort average ({avg:.2f} events/week) as baseline for monthly fatigue KPI reviews.")
 
1638
 
1639
- # Ensure exactly 3 items
1640
- while len(rec_list_ob) < 3:
1641
- rec_list_ob.append("—")
1642
 
 
 
1643
  st.markdown("### OB HAULER Recommendations")
1644
  st.markdown(f"""
1645
  <div class="recommendation-box">
1646
  <div class="recommendation-title">Action Plan</div>
1647
- <ul>
1648
- <li>{rec_list_ob[0]}</li>
1649
- <li>{rec_list_ob[1]}</li>
1650
- <li>{rec_list_ob[2]}</li>
1651
- </ul>
1652
- <div class="recommendation-reason">
1653
- Based on trend slope, activity duration, and cohort event frequency.
1654
- </div>
1655
  </div>
1656
  """, unsafe_allow_html=True)
1657
  else:
1658
  st.info("No OB HAULER recommendations.")
1659
 
1660
- # === HAULING COAL RECOMMENDATIONS (3-point list) ===
1661
  with col_rec2:
1662
- if not top_coal.empty:
1663
- w = len(top_coal[top_coal['slope'] > 0])
1664
- ot = len(top_coal[top_coal['slope'] == 0])
1665
- avg = top_coal['weekly_avg'].mean()
1666
-
1667
- rec_list_coal = []
1668
-
1669
- # Point 1: Trend-driven action
1670
- if w > 5:
1671
- rec_list_coal.append("Conduct targeted fatigue risk assessments for operators with worsening trends (slope > 0).")
1672
- elif ot > 4:
1673
- rec_list_coal.append("Investigate data completeness — high count of One Time Events may reflect inconsistent reporting.")
1674
- elif avg > 8:
1675
- rec_list_coal.append("Review shift scheduling and rest-break compliance to reduce event frequency.")
1676
- else:
1677
- rec_list_coal.append("Continue current protocols with routine monitoring of top-risk operators.")
1678
-
1679
- # Point 2: One-Time Event follow-up
1680
- if ot > 0:
1681
- rec_list_coal.append(f"Re-engage {ot} operators flagged as <b>One Time Event</b> to verify activity continuity.")
1682
- else:
1683
- rec_list_coal.append("No One Time Event operators identified — trend analysis remains reliable.")
1684
-
1685
- # Point 3: Benchmarking
1686
- rec_list_coal.append(f"Use cohort average ({avg:.2f} events/week) as baseline for monthly fatigue KPI reviews.")
1687
-
1688
- # Ensure exactly 3 items
1689
- while len(rec_list_coal) < 3:
1690
- rec_list_coal.append("—")
1691
-
1692
  st.markdown("### HAULING COAL Recommendations")
1693
  st.markdown(f"""
1694
  <div class="recommendation-box">
1695
  <div class="recommendation-title">Action Plan</div>
1696
- <ul>
1697
- <li>{rec_list_coal[0]}</li>
1698
- <li>{rec_list_coal[1]}</li>
1699
- <li>{rec_list_coal[2]}</li>
1700
- </ul>
1701
- <div class="recommendation-reason">
1702
- Based on trend slope, activity duration, and cohort event frequency.
1703
- </div>
1704
  </div>
1705
  """, unsafe_allow_html=True)
1706
  else:
@@ -1708,8 +1678,8 @@ else:
1708
 
1709
  except Exception as e:
1710
  st.error(f"Error in Top 10 Operator analysis: {str(e)}")
1711
- # st.exception(e) # Uncomment during development only
1712
- # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
1713
  st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
1714
 
1715
  # Membagi tampilan menjadi dua kolom
 
1159
 
1160
  # =================== OBJECTIVE 5: Operator Fatigue Risk Gradient Dashboard =====================
1161
  # ... (kode sebelumnya tetap sama) ...
 
1162
  st.subheader("OBJECTIVE 5: See your team’s fatigue Fatigue Hazard Profile!")
1163
 
1164
+ # Custom CSS — tetap seperti sebelumnya (sudah sesuai preferensi)
1165
  st.markdown("""
1166
  <style>
1167
  .big-title {
 
1262
  .recommendation-title {
1263
  font-weight: bold;
1264
  color: white;
1265
+ margin-bottom: 8px;
1266
  font-size: 14px;
1267
  background: rgba(255,255,255,0.2);
1268
  padding: 8px;
1269
  border-radius: 5px;
1270
  border-left: 4px solid white;
1271
  }
 
 
 
 
 
 
 
 
 
1272
  .recommendation-reason {
1273
  font-size: 12px;
1274
+ margin-top: 10px;
1275
  padding: 8px;
1276
+ background: rgba(255,255,255,0.1);
1277
  border-radius: 5px;
1278
+ border-left: 3px solid rgba(255,255,255,0.3);
 
1279
  }
1280
  </style>
1281
  """, unsafe_allow_html=True)
 
1448
  </div>
1449
  """, unsafe_allow_html=True)
1450
 
1451
+ # ✅ Function definition at correct indentation level
 
 
1452
  def plot_chart(data, title):
1453
  if data.empty:
1454
  fig = go.Figure()
 
1532
  st.plotly_chart(plot_chart(top_coal, "HAULING COAL Operators (Hazard Gradient)"), use_container_width=True)
1533
 
1534
  # ===============================================================
1535
+ # AI INSIGHTS — DIPERBAIKI: Risk Summary jadi 1 box + 3 list
1536
  # ===============================================================
1537
  col_insight1, col_insight2 = st.columns(2)
1538
 
 
1544
  ob_one_time = len(top_ob[top_ob['slope'] == 0])
1545
  ob_avg_risk = top_ob['weekly_avg'].mean()
1546
  ob_max_risk = top_ob['weekly_avg'].max()
1547
+
1548
  ob_insights = []
1549
  if ob_worsening > ob_improving:
1550
  ob_insights.append(f"{ob_worsening} out of 10 top risk operators are showing <span class='trend-up'>worsening</span> trends.")
 
1552
  ob_insights.append(f"{ob_improving} out of 10 top risk operators are showing <span class='trend-down'>improvement</span>.")
1553
  if ob_one_time > 0:
1554
  ob_insights.append(f"{ob_one_time} operators are classified as <b>One Time Event</b> (single-week activity).")
1555
+ else:
1556
+ ob_insights.append("No operators classified as <b>One Time Event</b>.")
1557
  ob_insights.append(f"Average risk: {ob_avg_risk:.2f} events/week (max: {ob_max_risk:.2f}).")
1558
 
1559
+ st.markdown(f"""
1560
+ <div class="ai-insight-box">
1561
+ <div class="ai-insight-title">Risk Summary</div>
1562
+ <ul style="padding-left: 20px; margin: 8px 0; line-height: 1.5;">
1563
+ <li>{ob_insights[0]}</li>
1564
+ <li>{ob_insights[1]}</li>
1565
+ <li>{ob_insights[2]}</li>
1566
+ </ul>
1567
+ </div>
1568
+ """, unsafe_allow_html=True)
1569
  else:
1570
  st.info("No OB HAULER data for analysis.")
1571
 
 
1577
  coal_one_time = len(top_coal[top_coal['slope'] == 0])
1578
  coal_avg_risk = top_coal['weekly_avg'].mean()
1579
  coal_max_risk = top_coal['weekly_avg'].max()
1580
+
1581
  coal_insights = []
1582
  if coal_worsening > coal_improving:
1583
  coal_insights.append(f"{coal_worsening} out of 10 top risk operators are showing <span class='trend-up'>worsening</span> trends.")
 
1585
  coal_insights.append(f"{coal_improving} out of 10 top risk operators are showing <span class='trend-down'>improvement</span>.")
1586
  if coal_one_time > 0:
1587
  coal_insights.append(f"{coal_one_time} operators are classified as <b>One Time Event</b> (single-week activity).")
1588
+ else:
1589
+ coal_insights.append("No operators classified as <b>One Time Event</b>.")
1590
  coal_insights.append(f"Average risk: {coal_avg_risk:.2f} events/week (max: {coal_max_risk:.2f}).")
1591
 
1592
+ st.markdown(f"""
1593
+ <div class="ai-insight-box">
1594
+ <div class="ai-insight-title">Risk Summary</div>
1595
+ <ul style="padding-left: 20px; margin: 8px 0; line-height: 1.5;">
1596
+ <li>{coal_insights[0]}</li>
1597
+ <li>{coal_insights[1]}</li>
1598
+ <li>{coal_insights[2]}</li>
1599
+ </ul>
1600
+ </div>
1601
+ """, unsafe_allow_html=True)
1602
  else:
1603
  st.info("No HAULING COAL data for analysis.")
1604
 
1605
  # ===============================================================
1606
+ # RECOMMENDATIONS — TIDAK DIUBAH SAMA SEKALI
1607
  # ===============================================================
1608
  col_rec1, col_rec2 = st.columns(2)
1609
 
1610
+ def generate_recommendations(top_ob, top_coal):
1611
+ rec = {}
1612
  if not top_ob.empty:
1613
  w = len(top_ob[top_ob['slope'] > 0])
1614
  ot = len(top_ob[top_ob['slope'] == 0])
1615
  avg = top_ob['weekly_avg'].mean()
 
 
 
 
1616
  if w > 5:
1617
+ r = "Prioritize fatigue intervention for operators with worsening trends."
1618
+ reason = "High proportion of deteriorating operators signals emerging fatigue risks."
1619
  elif ot > 4:
1620
+ r = "Validate data completeness — high One Time Event count may indicate reporting gaps."
1621
+ reason = "Operators with single-week data cannot yield reliable trend analysis."
1622
  elif avg > 8:
1623
+ r = "Review scheduling and rest protocols to reduce event frequency."
1624
+ reason = "Elevated average event rate increases cumulative fatigue exposure."
1625
  else:
1626
+ r = "Maintain current protocols with targeted monitoring."
1627
+ reason = "Risk profile is stable; focus on sustaining safe practices."
1628
+ rec['ob'] = r
1629
+ rec['ob_reason'] = reason
1630
 
1631
+ if not top_coal.empty:
1632
+ w = len(top_coal[top_coal['slope'] > 0])
1633
+ ot = len(top_coal[top_coal['slope'] == 0])
1634
+ avg = top_coal['weekly_avg'].mean()
1635
+ if w > 5:
1636
+ r = "Prioritize fatigue intervention for operators with worsening trends."
1637
+ reason = "High proportion of deteriorating operators signals emerging fatigue risks."
1638
+ elif ot > 4:
1639
+ r = "Validate data completeness — high One Time Event count may indicate reporting gaps."
1640
+ reason = "Operators with single-week data cannot yield reliable trend analysis."
1641
+ elif avg > 8:
1642
+ r = "Review scheduling and rest protocols to reduce event frequency."
1643
+ reason = "Elevated average event rate increases cumulative fatigue exposure."
1644
  else:
1645
+ r = "Maintain current protocols with targeted monitoring."
1646
+ reason = "Risk profile is stable; focus on sustaining safe practices."
1647
+ rec['coal'] = r
1648
+ rec['coal_reason'] = reason
1649
+ return rec
1650
 
1651
+ ai_rec = generate_recommendations(top_ob, top_coal)
 
 
1652
 
1653
+ with col_rec1:
1654
+ if 'ob' in ai_rec:
1655
  st.markdown("### OB HAULER Recommendations")
1656
  st.markdown(f"""
1657
  <div class="recommendation-box">
1658
  <div class="recommendation-title">Action Plan</div>
1659
+ <div>{ai_rec['ob']}</div>
1660
+ <div class="recommendation-reason">AI Reasoning: {ai_rec['ob_reason']}</div>
 
 
 
 
 
 
1661
  </div>
1662
  """, unsafe_allow_html=True)
1663
  else:
1664
  st.info("No OB HAULER recommendations.")
1665
 
 
1666
  with col_rec2:
1667
+ if 'coal' in ai_rec:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1668
  st.markdown("### HAULING COAL Recommendations")
1669
  st.markdown(f"""
1670
  <div class="recommendation-box">
1671
  <div class="recommendation-title">Action Plan</div>
1672
+ <div>{ai_rec['coal']}</div>
1673
+ <div class="recommendation-reason">AI Reasoning: {ai_rec['coal_reason']}</div>
 
 
 
 
 
 
1674
  </div>
1675
  """, unsafe_allow_html=True)
1676
  else:
 
1678
 
1679
  except Exception as e:
1680
  st.error(f"Error in Top 10 Operator analysis: {str(e)}")
1681
+ st.exception(e)
1682
+ # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
1683
  st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
1684
 
1685
  # Membagi tampilan menjadi dua kolom