SHELLAPANDIANGANHUNGING commited on
Commit
556e9c0
·
verified ·
1 Parent(s): 38b9940

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +91 -185
app.py CHANGED
@@ -1665,229 +1665,135 @@ st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
1665
  # Membagi tampilan menjadi dua kolom
1666
  col_insights, col_recs = st.columns(2)
1667
 
1668
- # Kolom kiri: Insights by Advanced Analytics
 
 
1669
  with col_insights:
 
1670
  st.subheader("Insights by Advanced Analytics")
1671
 
1672
- # 1. Critical Hour Analysis (2-5 AM)
1673
  critical_hours = [2, 3, 4, 5]
1674
  critical_alerts = df[df['hour'].isin(critical_hours)]
1675
  critical_pct = (len(critical_alerts) / len(df)) * 100 if len(df) > 0 else 0
1676
 
1677
  st.markdown(f"**Critical Hour Risk (3-6 AM)**")
1678
- # Use conditional formatting for background color
1679
- bg_color = "#ffcccc" if critical_pct > 50 else "#ffebcc" if critical_pct > 25 else "#ffffcc" if critical_pct > 10 else "#e6ffe6"
1680
- st.markdown(f'<div style="background-color: {bg_color}; padding: 10px; border-radius: 5px;">Critical Hour Alerts: {len(critical_alerts)} ({critical_pct:.1f}% of total alerts)</div>', unsafe_allow_html=True)
1681
- if critical_pct > 10: # If more than 10% of alerts happen in critical hours
1682
- st.warning(f"High risk: {critical_pct:.1f}% of fatigue alerts occur during critical hours (3-6 AM). This is a known circadian dip period.")
1683
- else:
1684
- st.info(f"{critical_pct:.1f}% of alerts occur during critical hours. This is within acceptable range.")
1685
-
1686
- # 2. High-Speed Fatigue Analysis (Environmental Risk)
1687
- if col_speed and col_speed in df.columns:
1688
-
1689
- # --- Threshold FIX > 20 km/h ---
1690
- high_speed_threshold = 20
1691
 
1692
- # --- Filter High-Speed Fatigue ---
1693
- high_speed_fatigue = df[df[col_speed] > high_speed_threshold]
1694
-
1695
- # --- Persentase ---
1696
- high_speed_pct = (len(high_speed_fatigue) / len(df) * 100) if len(df) > 0 else 0
 
1697
 
1698
- # --- Teks judul + hasil warna merah ---
1699
  st.markdown(
1700
  f"""
1701
- <p style="color:#d32f2f; font-size:20px; font-weight:700; margin-bottom:4px;">
1702
- High-Speed Fatigue Events: {len(high_speed_fatigue)}
1703
- </p>
1704
- <p style="color:#d32f2f; font-size:15px; font-weight:500; margin-top:-6px;">
1705
- {high_speed_pct:.1f}% of total alerts (Speed > {high_speed_threshold} km/h)
1706
- </p>
1707
  """,
1708
  unsafe_allow_html=True
1709
  )
1710
 
1711
- # --- pesan risiko ---
1712
- if high_speed_pct > 20:
1713
  st.warning(
1714
- f"High risk: {high_speed_pct:.1f}% of fatigue alerts occur at high speeds. "
1715
- f"This increases accident severity potential."
1716
  )
1717
  else:
1718
  st.info(
1719
- f"{high_speed_pct:.1f}% of alerts occur at high speeds. This is within acceptable range."
1720
  )
1721
 
1722
- else:
1723
- st.info("Speed data not available for High-Speed Fatigue Analysis.")
 
 
 
 
1724
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1725
 
1726
  # 3. Shift Pattern Analysis
1727
  if col_shift and col_shift in df.columns:
 
1728
  shift_counts = df[col_shift].value_counts()
1729
- # shift_alerts_by_hour = df.groupby([col_shift, 'hour']).size().reset_index(name='alerts') # Tidak digunakan dalam tampilan ini
1730
-
1731
- st.markdown(f"**Shift Pattern Risk**")
1732
  for shift_val in shift_counts.index:
1733
  shift_pct = (shift_counts[shift_val] / len(df)) * 100
1734
- st.metric(f"Shift {shift_val} Alerts", f"{shift_counts[shift_val]}", f"{shift_pct:.1f}% of total alerts")
1735
- if shift_pct > 50: # If one shift has more than 50% of alerts
1736
- st.warning(f"Shift {shift_val} has disproportionately high alerts ({shift_pct:.1f}%). Review shift scheduling and workload.")
 
 
 
 
 
 
 
 
1737
  else:
1738
- st.info(f"Shift {shift_val} alert distribution is acceptable ({shift_pct:.1f}%).")
 
 
1739
  else:
1740
  st.info("Shift data not available for Shift Pattern Analysis.")
1741
 
1742
- # 4. Operator Risk Profiling
1743
- # 4. Operator Risk Profiling
1744
- if col_operator and col_operator in df.columns:
1745
- operator_alerts = df[col_operator].value_counts()
1746
- top_risk_operators = operator_alerts.head(5) # Top 5 operators by alerts
1747
-
1748
- st.markdown("**High-Risk Operator Identification**")
1749
-
1750
- # Warna berdasarkan ranking 1–5
1751
- colors = ["#d32f2f", "#e57373", "#ef9a9a", "#ffcdd2", "#ffe1e4"]
1752
-
1753
- for idx, (op_name, count) in enumerate(top_risk_operators.items()):
1754
- op_pct = (count / len(df)) * 100
1755
- color = colors[idx] if idx < len(colors) else colors[-1]
1756
-
1757
- # Teks normal untuk nama dan jumlah alert
1758
- st.markdown(f"**Operator:** {op_name} \n**Alerts:** {count}")
1759
-
1760
- # Hanya 'Share' yang berwarna sesuai ranking
1761
- st.markdown(
1762
- f"<span style='font-weight:600'>Share:</span> "
1763
- f"<span style='color:{color}; font-weight:700'>{op_pct:.1f}% of total alerts</span>",
1764
- unsafe_allow_html=True
1765
- )
1766
 
1767
- # Risk message (tetap gunakan component Streamlit agar konsisten)
1768
- if op_pct > 5:
1769
- st.warning(f"Operator {op_name} has high fatigue risk ({op_pct:.1f}%). Consider coaching or rest plan.")
1770
- else:
1771
- st.info(f"Operator {op_name} fatigue risk is within acceptable range ({op_pct:.1f}%).")
1772
 
1773
- else:
1774
- st.info("Operator data not available for Operator Risk Profiling.")
1775
 
 
1776
 
 
 
 
1777
 
1778
- # Kolom kanan: AI Recommendations
1779
- with col_recs:
1780
- st.subheader("Recommendations")
1781
- ai_recs = []
1782
- insights_found = [] # Untuk menyimpan insight yang ditemukan
1783
 
1784
- # Peak hour
1785
- if "hour" in df.columns and not df.empty:
1786
- peak_hour = df["hour"].value_counts().idxmax()
1787
- critical_hours = [2, 3, 4, 5]
1788
- if peak_hour in critical_hours:
1789
- insights_found.append(f" Most fatigue risk occurs at **{peak_hour}:00** — during critical circadian low period (3-6 AM). Consider enhanced monitoring.")
1790
- else:
1791
- insights_found.append(f"Most fatigue risk occurs at **{peak_hour}:00** — likely due to circadian drop.")
1792
-
1793
- # Risk shift
1794
- if col_shift and not df.empty:
1795
- worst_shift = df[col_shift].value_counts().idxmax()
1796
- insights_found.append(f" Highest fatigue recorded in **Shift {worst_shift}** — review scheduling & workload.")
1797
-
1798
- # Worst operator
1799
- if col_operator and not df.empty:
1800
- worst_operator = df[col_operator].value_counts().idxmax()
1801
- insights_found.append(f" Operator at highest risk: **{worst_operator}** — suggested coaching or rest plan.")
1802
-
1803
- # Duration risk
1804
- if "duration_sec" in df.columns and not df.empty:
1805
- avg_duration = df["duration_sec"].mean()
1806
- if not pd.isna(avg_duration) and avg_duration > 10:
1807
- insights_found.append(" Long fatigue event duration suggests slow response — improve alerting training.")
1808
-
1809
- # Generate recommendations based on found insights
1810
- if insights_found:
1811
- # Contoh rekomendasi berdasarkan insight
1812
- if any("circadian low" in i.lower() for i in insights_found):
1813
- ai_recs.append({
1814
- "recommendation": "Deploy enhanced fatigue monitoring systems (e.g., EOR) specifically during 3-6 AM shifts.",
1815
- "data_point": f"Critical Hour Alerts: {len(critical_alerts)} ({critical_pct:.1f}% of total alerts)",
1816
- "reason": "High percentage of alerts occurring during the known circadian low period (3-6 AM) indicates increased risk during these hours."
1817
- })
1818
- if any("shift" in i.lower() for i in insights_found):
1819
- ai_recs.append({
1820
- "recommendation": "Review shift rotation schedules to minimize consecutive high-risk shifts.",
1821
- "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)",
1822
- "reason": f"The identified high-risk shift ({worst_shift}) has the highest number of fatigue alerts, suggesting scheduling or workload issues."
1823
- })
1824
- if any("operator" in i.lower() for i in insights_found):
1825
- ai_recs.append({
1826
- "recommendation": "Initiate individual coaching or mandatory rest periods for high-risk operators.",
1827
- "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)",
1828
- "reason": f"The identified high-risk operator ({worst_operator}) has the highest number of fatigue alerts, indicating a need for targeted intervention."
1829
- })
1830
- if any("duration" in i.lower() for i in insights_found):
1831
- ai_recs.append({
1832
- "recommendation": "Review and improve alert response protocols and training.",
1833
- "data_point": f"Average Fatigue Event Duration: {avg_duration:.2f} seconds",
1834
- "reason": "Long average duration suggests potential delays in response time or alert acknowledgment, requiring protocol review."
1835
- })
1836
- if any("high-speed" in i.lower() for i in insights_found):
1837
- ai_recs.append({
1838
- "recommendation": "Implement speed management strategies in conjunction with fatigue monitoring.",
1839
- "data_point": f"High-Speed Fatigue Events: {len(high_speed_fatigue)} ({high_speed_pct:.1f}% of total alerts)",
1840
- "reason": "A significant percentage of alerts occur at high speeds, increasing accident severity risk. Speed control is crucial."
1841
- })
1842
- if not ai_recs:
1843
- ai_recs.append({
1844
- "recommendation": "Data quality is sufficient. Focus on implementing recommendations from Objectives 1-5.",
1845
- "data_point": "General Data Quality Check",
1846
- "reason": "No specific high-impact insights were automatically identified from the aggregated data in this section."
1847
- })
1848
 
1849
- # Menampilkan rekomendasi dalam format kotak yang sesuai dengan permintaan
1850
- for rec in ai_recs:
1851
- # Gunakan div dengan class khusus untuk membuat kotak rekomendasi di kolom kanan
1852
- # Gaya diambil dari .insight-box untuk konsistensi dan menghindari warna ungu
1853
- st.markdown(f"""
1854
- <div style="
1855
- background: #f8f9fa;
1856
- border: 1px solid #dee2e6;
1857
- border-radius: 8px;
1858
- padding: 15px;
1859
- margin: 10px 0;
1860
- color: #2c3e50;
1861
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
1862
- box-shadow: 0 2px 8px rgba(0,0,0,0.05);
1863
- display: flex;
1864
- flex-direction: column;
1865
- justify-content: space-between;
1866
- ">
1867
- <div style="
1868
- font-weight: bold;
1869
- color: #2c3e50;
1870
- margin-bottom: 8px;
1871
- font-size: 14px;
1872
- background: #e9ecef;
1873
- padding: 8px;
1874
- border-radius: 5px;
1875
- border-left: 4px solid #495057;
1876
- ">AI Recommendation</div>
1877
- <div style="padding-top: 8px; font-size: 14px; margin-bottom: 10px;">
1878
- <strong>Action:</strong> {rec['recommendation']}
1879
- </div>
1880
- <div style="font-size: 12px; padding: 8px; background: #e9ecef; border-radius: 5px; margin-top: 5px;">
1881
- <strong>Data Point:</strong> {rec['data_point']}
1882
- </div>
1883
- <div style="font-size: 12px; padding: 8px; background: #f1f1f1; border-radius: 5px; margin-top: 5px;">
1884
- <strong>AI Reasoning:</strong> {rec['reason']}
1885
- </div>
1886
- </div>
1887
- """, unsafe_allow_html=True)
1888
  else:
1889
- st.info("No specific data points available for AI recommendations. Ensure relevant columns (hour, shift, operator, duration, speed) are present and populated.")
1890
-
1891
- # ================= FOOTER ===========================
1892
- st.markdown("---")
1893
- st.markdown('<div class="footer">FatigueAnalyzer - Transforming Mining Safety with Intelligent Analytics | Contact: info@bukittechnology.com</div>', unsafe_allow_html=True)
 
1665
  # Membagi tampilan menjadi dua kolom
1666
  col_insights, col_recs = st.columns(2)
1667
 
1668
+ # =========================================
1669
+ # LEFT COLUMN — INSIGHTS
1670
+ # =========================================
1671
  with col_insights:
1672
+
1673
  st.subheader("Insights by Advanced Analytics")
1674
 
1675
+ # 1. Critical Hour Analysis (3–6 AM)
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(f"**Critical Hour Risk (3-6 AM)**")
 
 
 
 
 
 
 
 
 
 
 
 
 
1681
 
1682
+ bg_color = (
1683
+ "#ffcccc" if critical_pct > 50 else
1684
+ "#ffebcc" if critical_pct > 25 else
1685
+ "#ffffcc" if critical_pct > 10 else
1686
+ "#e6ffe6"
1687
+ )
1688
 
 
1689
  st.markdown(
1690
  f"""
1691
+ <div style="background-color:{bg_color}; padding:10px; border-radius:5px;">
1692
+ Critical Hour Alerts: {len(critical_alerts)} ({critical_pct:.1f}% of total alerts)
1693
+ </div>
 
 
 
1694
  """,
1695
  unsafe_allow_html=True
1696
  )
1697
 
1698
+ if critical_pct > 10:
 
1699
  st.warning(
1700
+ f"High risk: {critical_pct:.1f}% of fatigue alerts occur during critical hours (3-6 AM)."
 
1701
  )
1702
  else:
1703
  st.info(
1704
+ f"{critical_pct:.1f}% of alerts occur during critical hours. This is within acceptable range."
1705
  )
1706
 
1707
+ # 2. High-Speed Fatigue Analysis
1708
+ if col_speed and col_speed in df.columns:
1709
+
1710
+ high_speed_threshold = 20
1711
+ high_speed_fatigue = df[df[col_speed] > high_speed_threshold]
1712
+ high_speed_pct = (len(high_speed_fatigue) / len(df)) * 100 if len(df) else 0
1713
 
1714
+ # Title
1715
+ st.markdown("**High-Speed Fatigue Risk (Speed > 20 km/h)**")
1716
+
1717
+ # Red text (no box)
1718
+ st.markdown(
1719
+ f"""
1720
+ <p style="color:#d32f2f; font-size:20px; font-weight:700; margin-bottom:4px;">
1721
+ High-Speed Fatigue Events: {len(high_speed_fatigue)}
1722
+ </p>
1723
+ <p style="color:#d32f2f; font-size:15px; font-weight:500; margin-top:-6px;">
1724
+ {high_speed_pct:.1f}% of total alerts (Speed > {high_speed_threshold} km/h)
1725
+ </p>
1726
+ """,
1727
+ unsafe_allow_html=True
1728
+ )
1729
+
1730
+ if high_speed_pct > 20:
1731
+ st.warning(
1732
+ f"High risk: {high_speed_pct:.1f}% fatigue alerts occur at high speeds."
1733
+ )
1734
+ else:
1735
+ st.info(
1736
+ f"{high_speed_pct:.1f}% of alerts occur at high speeds. Within acceptable limits."
1737
+ )
1738
+ else:
1739
+ st.info("Speed data not available for High-Speed Fatigue Analysis.")
1740
 
1741
  # 3. Shift Pattern Analysis
1742
  if col_shift and col_shift in df.columns:
1743
+
1744
  shift_counts = df[col_shift].value_counts()
1745
+
1746
+ st.markdown("**Shift Pattern Risk**")
1747
+
1748
  for shift_val in shift_counts.index:
1749
  shift_pct = (shift_counts[shift_val] / len(df)) * 100
1750
+
1751
+ st.metric(
1752
+ f"Shift {shift_val} Alerts",
1753
+ f"{shift_counts[shift_val]}",
1754
+ f"{shift_pct:.1f}% of total alerts"
1755
+ )
1756
+
1757
+ if shift_pct > 50:
1758
+ st.warning(
1759
+ f"Shift {shift_val} has disproportionately high alerts ({shift_pct:.1f}%)."
1760
+ )
1761
  else:
1762
+ st.info(
1763
+ f"Shift {shift_val} alert distribution is acceptable ({shift_pct:.1f}%)."
1764
+ )
1765
  else:
1766
  st.info("Shift data not available for Shift Pattern Analysis.")
1767
 
1768
+ # 4. Operator Risk Profiling
1769
+ if col_operator and col_operator in df.columns:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1770
 
1771
+ operator_alerts = df[col_operator].value_counts()
1772
+ top_risk_operators = operator_alerts.head(5)
 
 
 
1773
 
1774
+ st.markdown("**High-Risk Operator Identification**")
 
1775
 
1776
+ colors = ["#d32f2f", "#e57373", "#ef9a9a", "#ffcdd2", "#ffe1e4"]
1777
 
1778
+ for idx, (op_name, count) in enumerate(top_risk_operators.items()):
1779
+ op_pct = (count / len(df)) * 100
1780
+ color = colors[idx] if idx < len(colors) else colors[-1]
1781
 
1782
+ st.markdown(f"**Operator:** {op_name} \n**Alerts:** {count}")
 
 
 
 
1783
 
1784
+ st.markdown(
1785
+ f"<span style='font-weight:600'>Share:</span> "
1786
+ f"<span style='color:{color}; font-weight:700'>{op_pct:.1f}% of total alerts</span>",
1787
+ unsafe_allow_html=True
1788
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1789
 
1790
+ if op_pct > 5:
1791
+ st.warning(
1792
+ f"Operator {op_name} has high fatigue risk ({op_pct:.1f}%)."
1793
+ )
1794
+ else:
1795
+ st.info(
1796
+ f"Operator {op_name} risk is acceptable ({op_pct:.1f}%)."
1797
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1798
  else:
1799
+ st.info("Operator data not available for Operator Risk Profiling.")