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

Update app.py

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