SHELLAPANDIANGANHUNGING commited on
Commit
90b1a02
·
verified ·
1 Parent(s): 41cdbc8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +75 -100
app.py CHANGED
@@ -1658,204 +1658,182 @@ else:
1658
  st.error(f"Error in Top 10 Operator analysis: {str(e)}")
1659
  st.exception(e) # optionally show full traceback during dev
1660
 
1661
-
1662
-
1663
  # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
1664
  st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
1665
 
1666
- # Membagi menjadi 2 kolom
1667
  col_insights, col_recs = st.columns(2)
1668
 
1669
- # ============================
1670
- # KIRI — INSIGHTS
1671
- # ============================
1672
  with col_insights:
1673
  st.subheader("Insights by Advanced Analytics")
1674
 
1675
- # 1. Critical Hour Analysis
1676
  critical_hours = [2, 3, 4, 5]
1677
  critical_alerts = df[df['hour'].isin(critical_hours)]
1678
  critical_pct = (len(critical_alerts) / len(df)) * 100 if len(df) > 0 else 0
1679
 
1680
- st.markdown("**Critical Hour Risk (36 AM)**")
1681
- bg_color = (
1682
- "#ffcccc" if critical_pct > 50 else
1683
- "#ffebcc" if critical_pct > 25 else
1684
- "#ffffcc" if critical_pct > 10 else "#e6ffe6"
1685
- )
1686
  st.markdown(
1687
- f'<div style="background-color:{bg_color}; padding:10px; border-radius:5px;">'
1688
- f'Critical Hour Alerts: {len(critical_alerts)} ({critical_pct:.1f}% of total alerts)'
1689
- f'</div>',
 
1690
  unsafe_allow_html=True
1691
  )
1692
-
1693
  if critical_pct > 10:
1694
- st.warning(f"High risk: {critical_pct:.1f}% of fatigue alerts occur during critical hours.")
1695
  else:
1696
- st.info(f"{critical_pct:.1f}% of alerts occur during critical hours. Within acceptable range.")
1697
 
1698
  # 2. High-Speed Fatigue Analysis
1699
  if col_speed and col_speed in df.columns:
1700
- high_speed_threshold = df[col_speed].quantile(0.75)
1701
- high_speed_fatigue = df[df[col_speed] >= high_speed_threshold]
1702
- high_speed_pct = (len(high_speed_fatigue) / len(df)) * 100
1703
-
1704
  st.markdown(f"**High-Speed Fatigue Risk (Speed > {high_speed_threshold:.0f} km/h)**")
1705
- st.metric("High-Speed Fatigue Events", f"{len(high_speed_fatigue)}", f"{high_speed_pct:.1f}% of total alerts")
1706
-
 
 
 
 
 
 
 
 
1707
  if high_speed_pct > 20:
1708
- st.warning(f"High risk: {high_speed_pct:.1f}% of alerts occur at high speeds.")
1709
  else:
1710
- st.info(f"{high_speed_pct:.1f}% occur at high speeds. Acceptable.")
1711
- else:
1712
- st.info("Speed data not available for High-Speed Fatigue Analysis.")
1713
-
1714
-
1715
- # ============================
1716
- # KANAN — RECOMMENDATIONS
1717
- # ============================
1718
- with col_recs:
1719
- st.subheader("AI-Driven Recommendations")
1720
 
1721
  # 3. Shift Pattern Analysis
1722
  if col_shift and col_shift in df.columns:
1723
  shift_counts = df[col_shift].value_counts()
1724
- st.markdown("**Shift Pattern Risk**")
1725
-
1726
- shift_color = "#d32f2f"
1727
-
1728
  for shift_val in shift_counts.index:
1729
  shift_pct = (shift_counts[shift_val] / len(df)) * 100
1730
-
1731
  st.markdown(
1732
- f"""
1733
- <div style="padding:12px 16px; border-radius:10px; background-color:#fff;
1734
- border:1px solid #eee; margin-bottom:10px;">
1735
- <div style="font-size:16px; font-weight:600;">Shift {shift_val} Alerts</div>
1736
- <div style="font-size:28px; font-weight:700;">{shift_counts[shift_val]}</div>
1737
- <div style="font-size:14px; font-weight:700;">
1738
- <span style="color:{shift_color};">{shift_pct:.1f}% of total alerts</span>
1739
- </div>
1740
- </div>
1741
- """,
1742
  unsafe_allow_html=True
1743
  )
1744
-
1745
  if shift_pct > 50:
1746
- st.warning(f"Shift {shift_val} has disproportionately high alerts ({shift_pct:.1f}%).")
1747
  else:
1748
- st.info(f"Shift {shift_val} distribution is acceptable ({shift_pct:.1f}%).")
1749
- else:
1750
- st.info("Shift data not available for Shift Pattern Analysis.")
1751
 
1752
  # 4. Operator Risk Profiling
1753
  if col_operator and col_operator in df.columns:
1754
  operator_alerts = df[col_operator].value_counts()
1755
  top_risk_operators = operator_alerts.head(5)
1756
-
1757
- st.markdown("**High-Risk Operator Identification**")
1758
-
1759
- # Warna ranking
1760
- colors = ["#d32f2f", "#e57373", "#ef9a9a", "#ffcdd2", "#ffe1e4"]
1761
-
1762
- for idx, (op_name, count) in enumerate(top_risk_operators.items()):
1763
  op_pct = (count / len(df)) * 100
1764
- color = colors[idx] if idx < len(colors) else colors[-1]
1765
-
1766
- # Nama & jumlah
1767
- st.markdown(f"**Operator:** {op_name} \n**Alerts:** {count}")
1768
-
1769
- # Share berwarna
1770
  st.markdown(
1771
- f"<b>Share:</b> <span style='color:{color}; font-weight:700'>{op_pct:.1f}% of total alerts</span>",
 
 
1772
  unsafe_allow_html=True
1773
  )
1774
-
1775
  if op_pct > 5:
1776
- st.warning(f"Operator {op_name} shows high fatigue tendency ({op_pct:.1f}%).")
1777
  else:
1778
- st.info(f"Operator {op_name} is within the expected alert range ({op_pct:.1f}%).")
 
1779
  else:
1780
  st.info("Operator data not available for Operator Risk Profiling.")
1781
 
1782
-
1783
-
1784
  # Kolom kanan: AI Recommendations
1785
  with col_recs:
1786
  st.subheader("Recommendations")
1787
  ai_recs = []
1788
- insights_found = [] # Untuk menyimpan insight yang ditemukan
1789
 
1790
  # Peak hour
1791
  if "hour" in df.columns and not df.empty:
1792
  peak_hour = df["hour"].value_counts().idxmax()
1793
  critical_hours = [2, 3, 4, 5]
1794
  if peak_hour in critical_hours:
1795
- insights_found.append(f" Most fatigue risk occurs at **{peak_hour}:00** — during critical circadian low period (3-6 AM). Consider enhanced monitoring.")
1796
  else:
1797
  insights_found.append(f"Most fatigue risk occurs at **{peak_hour}:00** — likely due to circadian drop.")
1798
 
1799
  # Risk shift
1800
  if col_shift and not df.empty:
1801
  worst_shift = df[col_shift].value_counts().idxmax()
1802
- insights_found.append(f" Highest fatigue recorded in **Shift {worst_shift}** — review scheduling & workload.")
1803
 
1804
  # Worst operator
1805
  if col_operator and not df.empty:
1806
  worst_operator = df[col_operator].value_counts().idxmax()
1807
- insights_found.append(f" Operator at highest risk: **{worst_operator}** — suggested coaching or rest plan.")
1808
 
1809
  # Duration risk
1810
  if "duration_sec" in df.columns and not df.empty:
1811
  avg_duration = df["duration_sec"].mean()
1812
  if not pd.isna(avg_duration) and avg_duration > 10:
1813
- insights_found.append(" Long fatigue event duration suggests slow response — improve alerting training.")
1814
 
1815
- # Generate recommendations based on found insights
1816
  if insights_found:
1817
- # Contoh rekomendasi berdasarkan insight
1818
  if any("circadian low" in i.lower() for i in insights_found):
1819
  ai_recs.append({
1820
  "recommendation": "Deploy enhanced fatigue monitoring systems (e.g., EOR) specifically during 3-6 AM shifts.",
1821
- "data_point": f"Critical Hour Alerts: {len(critical_alerts)} ({critical_pct:.1f}% of total alerts)",
1822
- "reason": "High percentage of alerts occurring during the known circadian low period (3-6 AM) indicates increased risk during these hours."
 
1823
  })
1824
  if any("shift" in i.lower() for i in insights_found):
 
1825
  ai_recs.append({
1826
  "recommendation": "Review shift rotation schedules to minimize consecutive high-risk shifts.",
1827
- "data_point": f"Shift {worst_shift} Alerts: {df[col_shift].value_counts()[worst_shift]} ({(df[col_shift].value_counts()[worst_shift] / len(df)) * 100:.1f}% of total alerts)",
1828
- "reason": f"The identified high-risk shift ({worst_shift}) has the highest number of fatigue alerts, suggesting scheduling or workload issues."
 
1829
  })
1830
  if any("operator" in i.lower() for i in insights_found):
 
1831
  ai_recs.append({
1832
  "recommendation": "Initiate individual coaching or mandatory rest periods for high-risk operators.",
1833
- "data_point": f"Operator {worst_operator} Alerts: {df[col_operator].value_counts()[worst_operator]} ({(df[col_operator].value_counts()[worst_operator] / len(df)) * 100:.1f}% of total alerts)",
1834
- "reason": f"The identified high-risk operator ({worst_operator}) has the highest number of fatigue alerts, indicating a need for targeted intervention."
 
1835
  })
1836
  if any("duration" in i.lower() for i in insights_found):
1837
  ai_recs.append({
1838
  "recommendation": "Review and improve alert response protocols and training.",
1839
  "data_point": f"Average Fatigue Event Duration: {avg_duration:.2f} seconds",
1840
- "reason": "Long average duration suggests potential delays in response time or alert acknowledgment, requiring protocol review."
1841
  })
1842
  if any("high-speed" in i.lower() for i in insights_found):
1843
  ai_recs.append({
1844
  "recommendation": "Implement speed management strategies in conjunction with fatigue monitoring.",
1845
- "data_point": f"High-Speed Fatigue Events: {len(high_speed_fatigue)} ({high_speed_pct:.1f}% of total alerts)",
1846
- "reason": "A significant percentage of alerts occur at high speeds, increasing accident severity risk. Speed control is crucial."
 
1847
  })
1848
  if not ai_recs:
1849
  ai_recs.append({
1850
- "recommendation": "Data quality is sufficient. Focus on implementing recommendations from Objectives 1-5.",
1851
  "data_point": "General Data Quality Check",
1852
- "reason": "No specific high-impact insights were automatically identified from the aggregated data in this section."
1853
  })
1854
 
1855
- # Menampilkan rekomendasi dalam format kotak yang sesuai dengan permintaan
1856
  for rec in ai_recs:
1857
- # Gunakan div dengan class khusus untuk membuat kotak rekomendasi di kolom kanan
1858
- # Gaya diambil dari .insight-box untuk konsistensi dan menghindari warna ungu
1859
  st.markdown(f"""
1860
  <div style="
1861
  background: #f8f9fa;
@@ -1866,9 +1844,6 @@ with col_recs:
1866
  color: #2c3e50;
1867
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
1868
  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
1869
- display: flex;
1870
- flex-direction: column;
1871
- justify-content: space-between;
1872
  ">
1873
  <div style="
1874
  font-weight: bold;
@@ -1896,4 +1871,4 @@ with col_recs:
1896
 
1897
  # ================= FOOTER ===========================
1898
  st.markdown("---")
1899
- st.markdown('<div class="footer">FatigueAnalyzer - Transforming Mining Safety with Intelligent Analytics | Contact: info@bukittechnology.com</div>', unsafe_allow_html=True)
 
1658
  st.error(f"Error in Top 10 Operator analysis: {str(e)}")
1659
  st.exception(e) # optionally show full traceback during dev
1660
 
 
 
1661
  # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
1662
  st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
1663
 
1664
+ # Membagi tampilan menjadi dua kolom
1665
  col_insights, col_recs = st.columns(2)
1666
 
1667
+ # Kolom kiri: Insights by Advanced Analytics
 
 
1668
  with col_insights:
1669
  st.subheader("Insights by Advanced Analytics")
1670
 
1671
+ # 1. Critical Hour Analysis (2-5 AM)
1672
  critical_hours = [2, 3, 4, 5]
1673
  critical_alerts = df[df['hour'].isin(critical_hours)]
1674
  critical_pct = (len(critical_alerts) / len(df)) * 100 if len(df) > 0 else 0
1675
 
1676
+ st.markdown(f"**Critical Hour Risk (3-6 AM)**")
1677
+ # Use conditional formatting for background color
1678
+ bg_color = "#ffcccc" if critical_pct > 50 else "#ffebcc" if critical_pct > 25 else "#ffffcc" if critical_pct > 10 else "#e6ffe6"
1679
+
1680
+ # Highlight percentage in red
 
1681
  st.markdown(
1682
+ f'<div style="background-color: {bg_color}; padding: 10px; border-radius: 5px;">'
1683
+ f'Critical Hour Alerts: {len(critical_alerts)} '
1684
+ f'<span style="color:#d32f2f; font-weight:bold;">({critical_pct:.1f}% of total alerts)</span>'
1685
+ '</div>',
1686
  unsafe_allow_html=True
1687
  )
1688
+
1689
  if critical_pct > 10:
1690
+ st.warning(f"High risk: <span style='color:#d32f2f'>{critical_pct:.1f}%</span> of fatigue alerts occur during critical hours (3-6 AM). This is a known circadian dip period.", icon="⚠️")
1691
  else:
1692
+ st.info(f"<span style='color:#d32f2f'>{critical_pct:.1f}%</span> of alerts occur during critical hours. This is within acceptable range.")
1693
 
1694
  # 2. High-Speed Fatigue Analysis
1695
  if col_speed and col_speed in df.columns:
1696
+ high_speed_threshold = df[col_speed].quantile(0.75) if not df[col_speed].dropna().empty else 0
1697
+ high_speed_fatigue = df[df[col_speed] >= high_speed_threshold] if high_speed_threshold > 0 else pd.DataFrame()
1698
+ high_speed_pct = (len(high_speed_fatigue) / len(df)) * 100 if len(df) > 0 else 0
1699
+
1700
  st.markdown(f"**High-Speed Fatigue Risk (Speed > {high_speed_threshold:.0f} km/h)**")
1701
+
1702
+ # ✅ st.metric doesn't support HTML, so replace with markdown
1703
+ st.markdown(
1704
+ f"<div style='font-size:1.1em; margin:8px 0;'>"
1705
+ f"High-Speed Fatigue Events: <b>{len(high_speed_fatigue)}</b> "
1706
+ f"<span style='color:#d32f2f; font-weight:bold;'>({high_speed_pct:.1f}% of total alerts)</span>"
1707
+ "</div>",
1708
+ unsafe_allow_html=True
1709
+ )
1710
+
1711
  if high_speed_pct > 20:
1712
+ st.warning(f"High risk: <span style='color:#d32f2f'>{high_speed_pct:.1f}%</span> of fatigue alerts occur at high speeds. This increases accident severity potential.")
1713
  else:
1714
+ st.info(f"<span style='color:#d32f2f'>{high_speed_pct:.1f}%</span> of alerts occur at high speeds. This is within acceptable range.")
 
 
 
 
 
 
 
 
 
1715
 
1716
  # 3. Shift Pattern Analysis
1717
  if col_shift and col_shift in df.columns:
1718
  shift_counts = df[col_shift].value_counts()
1719
+ st.markdown(f"**Shift Pattern Risk**")
1720
+
 
 
1721
  for shift_val in shift_counts.index:
1722
  shift_pct = (shift_counts[shift_val] / len(df)) * 100
1723
+ # ✅ Replace st.metric with red-percentage version
1724
  st.markdown(
1725
+ f"**Shift {shift_val} Alerts**<br>"
1726
+ f"{shift_counts[shift_val]} "
1727
+ f'<span style="color:#d32f2f; font-weight:bold;">({shift_pct:.1f}% of total alerts)</span>',
 
 
 
 
 
 
 
1728
  unsafe_allow_html=True
1729
  )
1730
+
1731
  if shift_pct > 50:
1732
+ st.warning(f"Shift {shift_val} has disproportionately high alerts (<span style='color:#d32f2f'>{shift_pct:.1f}%</span>). Review shift scheduling and workload.")
1733
  else:
1734
+ st.info(f"Shift {shift_val} alert distribution is acceptable (<span style='color:#d32f2f'>{shift_pct:.1f}%</span>).")
 
 
1735
 
1736
  # 4. Operator Risk Profiling
1737
  if col_operator and col_operator in df.columns:
1738
  operator_alerts = df[col_operator].value_counts()
1739
  top_risk_operators = operator_alerts.head(5)
1740
+ st.markdown(f"**High-Risk Operator Identification**")
1741
+
1742
+ for op_name, count in top_risk_operators.items():
 
 
 
 
1743
  op_pct = (count / len(df)) * 100
 
 
 
 
 
 
1744
  st.markdown(
1745
+ f"**Operator: {op_name}**<br>"
1746
+ f"{count} alerts "
1747
+ f'<span style="color:#d32f2f; font-weight:bold;">({op_pct:.1f}% of total alerts)</span>',
1748
  unsafe_allow_html=True
1749
  )
1750
+
1751
  if op_pct > 5:
1752
+ st.warning(f"Operator {op_name} has high fatigue risk (<span style='color:#d32f2f'>{op_pct:.1f}%</span> of alerts). Consider coaching or rest plan.")
1753
  else:
1754
+ st.info(f"Operator {op_name} fatigue risk is within acceptable range (<span style='color:#d32f2f'>{op_pct:.1f}%</span>).")
1755
+
1756
  else:
1757
  st.info("Operator data not available for Operator Risk Profiling.")
1758
 
 
 
1759
  # Kolom kanan: AI Recommendations
1760
  with col_recs:
1761
  st.subheader("Recommendations")
1762
  ai_recs = []
1763
+ insights_found = []
1764
 
1765
  # Peak hour
1766
  if "hour" in df.columns and not df.empty:
1767
  peak_hour = df["hour"].value_counts().idxmax()
1768
  critical_hours = [2, 3, 4, 5]
1769
  if peak_hour in critical_hours:
1770
+ insights_found.append(f"Most fatigue risk occurs at **{peak_hour}:00** — during critical circadian low period (3-6 AM). Consider enhanced monitoring.")
1771
  else:
1772
  insights_found.append(f"Most fatigue risk occurs at **{peak_hour}:00** — likely due to circadian drop.")
1773
 
1774
  # Risk shift
1775
  if col_shift and not df.empty:
1776
  worst_shift = df[col_shift].value_counts().idxmax()
1777
+ insights_found.append(f"Highest fatigue recorded in **Shift {worst_shift}** — review scheduling & workload.")
1778
 
1779
  # Worst operator
1780
  if col_operator and not df.empty:
1781
  worst_operator = df[col_operator].value_counts().idxmax()
1782
+ insights_found.append(f"Operator at highest risk: **{worst_operator}** — suggested coaching or rest plan.")
1783
 
1784
  # Duration risk
1785
  if "duration_sec" in df.columns and not df.empty:
1786
  avg_duration = df["duration_sec"].mean()
1787
  if not pd.isna(avg_duration) and avg_duration > 10:
1788
+ insights_found.append("Long fatigue event duration suggests slow response — improve alerting training.")
1789
 
1790
+ # Recommendations based on insights
1791
  if insights_found:
 
1792
  if any("circadian low" in i.lower() for i in insights_found):
1793
  ai_recs.append({
1794
  "recommendation": "Deploy enhanced fatigue monitoring systems (e.g., EOR) specifically during 3-6 AM shifts.",
1795
+ "data_point": f"Critical Hour Alerts: {len(critical_alerts)} "
1796
+ f"<span style='color:#d32f2f;'>({critical_pct:.1f}% of total alerts)</span>",
1797
+ "reason": "High percentage of alerts during circadian low period (3–6 AM) indicates elevated risk."
1798
  })
1799
  if any("shift" in i.lower() for i in insights_found):
1800
+ pct = (df[col_shift].value_counts()[worst_shift] / len(df)) * 100
1801
  ai_recs.append({
1802
  "recommendation": "Review shift rotation schedules to minimize consecutive high-risk shifts.",
1803
+ "data_point": f"Shift {worst_shift} Alerts: {df[col_shift].value_counts()[worst_shift]} "
1804
+ f"<span style='color:#d32f2f;'>({pct:.1f}% of total alerts)</span>",
1805
+ "reason": f"Shift {worst_shift} accounts for a disproportionate share of fatigue events."
1806
  })
1807
  if any("operator" in i.lower() for i in insights_found):
1808
+ pct = (df[col_operator].value_counts()[worst_operator] / len(df)) * 100
1809
  ai_recs.append({
1810
  "recommendation": "Initiate individual coaching or mandatory rest periods for high-risk operators.",
1811
+ "data_point": f"Operator {worst_operator} Alerts: {df[col_operator].value_counts()[worst_operator]} "
1812
+ f"<span style='color:#d32f2f;'>({pct:.1f}% of total alerts)</span>",
1813
+ "reason": f"Individual intervention needed to mitigate recurrence risk."
1814
  })
1815
  if any("duration" in i.lower() for i in insights_found):
1816
  ai_recs.append({
1817
  "recommendation": "Review and improve alert response protocols and training.",
1818
  "data_point": f"Average Fatigue Event Duration: {avg_duration:.2f} seconds",
1819
+ "reason": "Long duration suggests delayed response requires procedural review."
1820
  })
1821
  if any("high-speed" in i.lower() for i in insights_found):
1822
  ai_recs.append({
1823
  "recommendation": "Implement speed management strategies in conjunction with fatigue monitoring.",
1824
+ "data_point": f"High-Speed Fatigue Events: {len(high_speed_fatigue)} "
1825
+ f"<span style='color:#d32f2f;'>({high_speed_pct:.1f}% of total alerts)</span>",
1826
+ "reason": "High-speed fatigue greatly increases collision severity risk."
1827
  })
1828
  if not ai_recs:
1829
  ai_recs.append({
1830
+ "recommendation": "Data quality is sufficient. Focus on implementing recommendations from Objectives 15.",
1831
  "data_point": "General Data Quality Check",
1832
+ "reason": "No critical anomalies detected in aggregate metrics."
1833
  })
1834
 
1835
+ # Render recommendations
1836
  for rec in ai_recs:
 
 
1837
  st.markdown(f"""
1838
  <div style="
1839
  background: #f8f9fa;
 
1844
  color: #2c3e50;
1845
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
1846
  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
 
 
 
1847
  ">
1848
  <div style="
1849
  font-weight: bold;
 
1871
 
1872
  # ================= FOOTER ===========================
1873
  st.markdown("---")
1874
+ st.markdown('<div class="footer">FatigueAnalyzer - Transforming Mining Safety with Intelligent Analytics | Contact: info@bukittechnology.com</div>', unsafe_allow_html=True)