SHELLAPANDIANGANHUNGING commited on
Commit
51366a9
·
verified ·
1 Parent(s): 2c58dcb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +202 -200
app.py CHANGED
@@ -1661,222 +1661,224 @@ else:
1661
 
1662
 
1663
  # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
1664
- st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
1665
-
1666
- try:
1667
- # Membagi tampilan menjadi dua kolom
1668
- col_insights, col_recs = st.columns(2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1669
 
1670
- # Kolom kiri: Insights by Advanced Analytics
1671
- with col_insights:
1672
- st.subheader("Insights by Advanced Analytics")
 
 
1673
 
1674
- # 1. Critical Hour Analysis (2-5 AM)
1675
- critical_hours = [2, 3, 4, 5]
1676
- critical_alerts = df[df['hour'].isin(critical_hours)]
1677
- critical_pct = (len(critical_alerts) / len(df)) * 100 if len(df) > 0 else 0
1678
-
1679
- st.markdown(f"**Critical Hour Risk (3-6 AM)**")
1680
- # Use conditional formatting for background color
1681
- bg_color = "#ffcccc" if critical_pct > 50 else "#ffebcc" if critical_pct > 25 else "#ffffcc" if critical_pct > 10 else "#e6ffe6"
1682
-
1683
- # ✅ Highlight percentage in red using markdown
1684
  st.markdown(
1685
- f'<div style="background-color: {bg_color}; padding: 10px; border-radius: 5px;">'
1686
- f'Critical Hour Alerts: {len(critical_alerts)} '
1687
- f'<span style="color:#d32f2f; font-weight:bold;">({critical_pct:.1f}% of total alerts)</span>'
1688
- '</div>',
1689
  unsafe_allow_html=True
1690
  )
1691
-
1692
- # Gunakan st.markdown untuk menampilkan peringatan/info dengan HTML
1693
- if critical_pct > 10:
1694
- st.markdown(f"⚠️ <span style='color:#d32f2f; font-weight:bold;'>High risk: {critical_pct:.1f}%</span> of fatigue alerts occur during critical hours (3-6 AM). This is a known circadian dip period.", unsafe_allow_html=True)
1695
  else:
1696
- st.markdown(f"<span style='color:#d32f2f; font-weight:bold;'>{critical_pct:.1f}%</span> of alerts occur during critical hours. This is within acceptable range.", unsafe_allow_html=True)
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) if not df[col_speed].dropna().empty else 0
1701
- high_speed_fatigue = df[df[col_speed] >= high_speed_threshold] if high_speed_threshold > 0 else pd.DataFrame()
1702
- high_speed_pct = (len(high_speed_fatigue) / len(df)) * 100 if len(df) > 0 else 0
1703
-
1704
- st.markdown(f"**High-Speed Fatigue Risk (Speed > {high_speed_threshold:.0f} km/h)**")
1705
-
1706
- # st.metric doesn't support HTML, so replace with markdown
1707
  st.markdown(
1708
- f"<div style='font-size:1.1em; margin:8px 0;'>"
1709
- f"High-Speed Fatigue Events: <b>{len(high_speed_fatigue)}</b> "
1710
- f"<span style='color:#d32f2f; font-weight:bold;'>({high_speed_pct:.1f}% of total alerts)</span>"
1711
- "</div>",
1712
  unsafe_allow_html=True
1713
  )
1714
-
1715
- if high_speed_pct > 20:
1716
- st.markdown(f"High risk: <span style='color:#d32f2f; font-weight:bold;'>{high_speed_pct:.1f}%</span> of fatigue alerts occur at high speeds. This increases accident severity potential.", unsafe_allow_html=True)
1717
  else:
1718
- st.markdown(f"<span style='color:#d32f2f; font-weight:bold;'>{high_speed_pct:.1f}%</span> of alerts occur at high speeds. This is within acceptable range.", unsafe_allow_html=True)
1719
-
1720
- # 3. Shift Pattern Analysis
1721
- if col_shift and col_shift in df.columns:
1722
- shift_counts = df[col_shift].value_counts()
1723
- st.markdown(f"**Shift Pattern Risk**")
1724
-
1725
- for shift_val in shift_counts.index:
1726
- shift_pct = (shift_counts[shift_val] / len(df)) * 100
1727
- # ✅ Replace st.metric with red-percentage version
1728
- st.markdown(
1729
- f"**Shift {shift_val} Alerts**<br>"
1730
- f"{shift_counts[shift_val]} "
1731
- f'<span style="color:#d32f2f; font-weight:bold;">({shift_pct:.1f}% of total alerts)</span>',
1732
- unsafe_allow_html=True
1733
- )
1734
-
1735
- if shift_pct > 50:
1736
- st.markdown(f"Shift {shift_val} has disproportionately high alerts (<span style='color:#d32f2f; font-weight:bold;'>{shift_pct:.1f}%</span>). Review shift scheduling and workload.", unsafe_allow_html=True)
1737
- else:
1738
- st.markdown(f"Shift {shift_val} alert distribution is acceptable (<span style='color:#d32f2f; font-weight:bold;'>{shift_pct:.1f}%</span>).", unsafe_allow_html=True)
1739
-
1740
- # 4. Operator Risk Profiling
1741
- if col_operator and col_operator in df.columns:
1742
- operator_alerts = df[col_operator].value_counts()
1743
- top_risk_operators = operator_alerts.head(5)
1744
- st.markdown(f"**High-Risk Operator Identification**")
1745
-
1746
- for op_name, count in top_risk_operators.items():
1747
- op_pct = (count / len(df)) * 100
1748
- st.markdown(
1749
- f"**Operator: {op_name}**<br>"
1750
- f"{count} alerts "
1751
- f'<span style="color:#d32f2f; font-weight:bold;">({op_pct:.1f}% of total alerts)</span>',
1752
- unsafe_allow_html=True
1753
- )
1754
-
1755
- if op_pct > 5:
1756
- st.markdown(f"Operator {op_name} has high fatigue risk (<span style='color:#d32f2f; font-weight:bold;'>{op_pct:.1f}%</span> of alerts). Consider coaching or rest plan.", unsafe_allow_html=True)
1757
- else:
1758
- st.markdown(f"Operator {op_name} fatigue risk is within acceptable range (<span style='color:#d32f2f; font-weight:bold;'>{op_pct:.1f}%</span>).", unsafe_allow_html=True)
1759
 
 
 
 
 
 
 
1760
  else:
1761
- st.info("Operator data not available for Operator Risk Profiling.")
1762
-
1763
- # Kolom kanan: AI Recommendations
1764
- with col_recs:
1765
- st.subheader("Recommendations")
1766
- ai_recs = []
1767
- insights_found = []
1768
-
1769
- # Peak hour
1770
- if "hour" in df.columns and not df.empty:
1771
- peak_hour = df["hour"].value_counts().idxmax()
1772
- critical_hours = [2, 3, 4, 5]
1773
- if peak_hour in critical_hours:
1774
- insights_found.append(f"Most fatigue risk occurs at **{peak_hour}:00** — during critical circadian low period (3-6 AM). Consider enhanced monitoring.")
1775
- else:
1776
- insights_found.append(f"Most fatigue risk occurs at **{peak_hour}:00** — likely due to circadian drop.")
1777
-
1778
- # Risk shift
1779
- if col_shift and not df.empty:
1780
- worst_shift = df[col_shift].value_counts().idxmax()
1781
- insights_found.append(f"Highest fatigue recorded in **Shift {worst_shift}** review scheduling & workload.")
1782
-
1783
- # Worst operator
1784
- if col_operator and not df.empty:
1785
- worst_operator = df[col_operator].value_counts().idxmax()
1786
- insights_found.append(f"Operator at highest risk: **{worst_operator}** suggested coaching or rest plan.")
1787
-
1788
- # Duration risk
1789
- if "duration_sec" in df.columns and not df.empty:
1790
- avg_duration = df["duration_sec"].mean()
1791
- if not pd.isna(avg_duration) and avg_duration > 10:
1792
- insights_found.append("Long fatigue event duration suggests slow response — improve alerting training.")
1793
-
1794
- # Recommendations based on insights
1795
- if insights_found:
1796
- if any("circadian low" in i.lower() for i in insights_found):
1797
- ai_recs.append({
1798
- "recommendation": "Deploy enhanced fatigue monitoring systems (e.g., EOR) specifically during 3-6 AM shifts.",
1799
- "data_point": f"Critical Hour Alerts: {len(critical_alerts)} "
1800
- f"<span style='color:#d32f2f;'>({critical_pct:.1f}% of total alerts)</span>",
1801
- "reason": "High percentage of alerts during circadian low period (3–6 AM) indicates elevated risk."
1802
- })
1803
- if any("shift" in i.lower() for i in insights_found):
1804
- pct = (df[col_shift].value_counts()[worst_shift] / len(df)) * 100
1805
- ai_recs.append({
1806
- "recommendation": "Review shift rotation schedules to minimize consecutive high-risk shifts.",
1807
- "data_point": f"Shift {worst_shift} Alerts: {df[col_shift].value_counts()[worst_shift]} "
1808
- f"<span style='color:#d32f2f;'>({pct:.1f}% of total alerts)</span>",
1809
- "reason": f"Shift {worst_shift} accounts for a disproportionate share of fatigue events."
1810
- })
1811
- if any("operator" in i.lower() for i in insights_found):
1812
- pct = (df[col_operator].value_counts()[worst_operator] / len(df)) * 100
1813
- ai_recs.append({
1814
- "recommendation": "Initiate individual coaching or mandatory rest periods for high-risk operators.",
1815
- "data_point": f"Operator {worst_operator} Alerts: {df[col_operator].value_counts()[worst_operator]} "
1816
- f"<span style='color:#d32f2f;'>({pct:.1f}% of total alerts)</span>",
1817
- "reason": f"Individual intervention needed to mitigate recurrence risk."
1818
- })
1819
- if any("duration" in i.lower() for i in insights_found):
1820
- ai_recs.append({
1821
- "recommendation": "Review and improve alert response protocols and training.",
1822
- "data_point": f"Average Fatigue Event Duration: {avg_duration:.2f} seconds",
1823
- "reason": "Long duration suggests delayed response — requires procedural review."
1824
- })
1825
- if any("high-speed" in i.lower() for i in insights_found):
1826
- ai_recs.append({
1827
- "recommendation": "Implement speed management strategies in conjunction with fatigue monitoring.",
1828
- "data_point": f"High-Speed Fatigue Events: {len(high_speed_fatigue)} "
1829
- f"<span style='color:#d32f2f;'>({high_speed_pct:.1f}% of total alerts)</span>",
1830
- "reason": "High-speed fatigue greatly increases collision severity risk."
1831
- })
1832
- if not ai_recs:
1833
- ai_recs.append({
1834
- "recommendation": "Data quality is sufficient. Focus on implementing recommendations from Objectives 1–5.",
1835
- "data_point": "General Data Quality Check",
1836
- "reason": "No critical anomalies detected in aggregate metrics."
1837
- })
1838
 
1839
- # Render recommendations
1840
- for rec in ai_recs:
1841
- st.markdown(f"""
 
 
 
 
 
 
 
 
 
 
1842
  <div style="
1843
- background: #f8f9fa;
1844
- border: 1px solid #dee2e6;
1845
- border-radius: 8px;
1846
- padding: 15px;
1847
- margin: 10px 0;
1848
  color: #2c3e50;
1849
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
1850
- box-shadow: 0 2px 8px rgba(0,0,0,0.05);
1851
- ">
1852
- <div style="
1853
- font-weight: bold;
1854
- color: #2c3e50;
1855
- margin-bottom: 8px;
1856
- font-size: 14px;
1857
- background: #e9ecef;
1858
- padding: 8px;
1859
- border-radius: 5px;
1860
- border-left: 4px solid #495057;
1861
- ">AI Recommendation</div>
1862
- <div style="padding-top: 8px; font-size: 14px; margin-bottom: 10px;">
1863
- <strong>Action:</strong> {rec['recommendation']}
1864
- </div>
1865
- <div style="font-size: 12px; padding: 8px; background: #e9ecef; border-radius: 5px; margin-top: 5px;">
1866
- <strong>Data Point:</strong> {rec['data_point']}
1867
- </div>
1868
- <div style="font-size: 12px; padding: 8px; background: #f1f1f1; border-radius: 5px; margin-top: 5px;">
1869
- <strong>AI Reasoning:</strong> {rec['reason']}
1870
- </div>
1871
  </div>
1872
- """, unsafe_allow_html=True)
1873
- else:
1874
- st.info("No specific data points available for AI recommendations. Ensure relevant columns (hour, shift, operator, duration, speed) are present and populated.")
1875
-
1876
- except Exception as e:
1877
- st.error(f"Error in Objective 6: {e}")
1878
- st.exception(e) # Untuk debugging lebih lanjut
 
 
 
1879
 
1880
  # ================= FOOTER ===========================
1881
  st.markdown("---")
1882
- st.markdown('<div class="footer">FatigueAnalyzer - Transforming Mining Safety with Intelligent Analytics | Contact: info@bukittechnology.com</div>', unsafe_allow_html=True)
 
 
 
 
 
 
1661
 
1662
 
1663
  # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
1664
+ st.markdown('<div style="text-align: center; font-size: 1.5em; font-weight: bold; margin-bottom: 20px;">OBJECTIVE 6: Instant Insights & Recommendations</div>', unsafe_allow_html=True)
1665
+
1666
+ # Membagi tampilan menjadi dua kolom
1667
+ col_insights, col_recs = st.columns(2)
1668
+
1669
+ # Helper function for red % formatting
1670
+ def format_red_pct(value):
1671
+ return f'<span style="color: #d32f2f; font-weight: bold;">{value:.1f}%</span>'
1672
+
1673
+ # Kolom kiri: Insights by Advanced Analytics
1674
+ with col_insights:
1675
+ st.markdown('<div style="text-align: center; font-size: 1.3em; font-weight: bold; margin-bottom: 15px;">Insights by Advanced Analytics</div>', unsafe_allow_html=True)
1676
+
1677
+ # 1. Critical Hour Analysis (2-5 AM)
1678
+ critical_hours = [2, 3, 4, 5]
1679
+ critical_alerts = df[df['hour'].isin(critical_hours)]
1680
+ critical_pct = (len(critical_alerts) / len(df)) * 100 if len(df) > 0 else 0
1681
+
1682
+ st.markdown(f"**Critical Hour Risk (3-6 AM)**", unsafe_allow_html=True)
1683
+ # Use conditional formatting for background color
1684
+ bg_color = "#ffcccc" if critical_pct > 50 else "#ffebcc" if critical_pct > 25 else "#ffffcc" if critical_pct > 10 else "#e6ffe6"
1685
+ st.markdown(
1686
+ f'<div style="background-color: {bg_color}; padding: 10px; border-radius: 5px; margin-bottom: 10px;">'
1687
+ f'Critical Hour Alerts: <b>{len(critical_alerts)}</b> ({format_red_pct(critical_pct)} of total alerts)'
1688
+ f'</div>',
1689
+ unsafe_allow_html=True
1690
+ )
1691
+ if critical_pct > 10:
1692
+ st.warning(f"High risk: {format_red_pct(critical_pct)} of fatigue alerts occur during critical hours (3-6 AM). This is a known circadian dip period.")
1693
+ else:
1694
+ st.info(f"{format_red_pct(critical_pct)} of alerts occur during critical hours. This is within acceptable range.")
1695
 
1696
+ # 2. High-Speed Fatigue Analysis (Environmental Risk)
1697
+ if col_speed and col_speed in df.columns:
1698
+ high_speed_threshold = df[col_speed].quantile(0.75) if not df[col_speed].dropna().empty else 0
1699
+ high_speed_fatigue = df[df[col_speed] >= high_speed_threshold] if high_speed_threshold > 0 else pd.DataFrame()
1700
+ high_speed_pct = (len(high_speed_fatigue) / len(df)) * 100 if len(df) > 0 else 0
1701
 
1702
+ st.markdown(f"**High-Speed Fatigue Risk (Speed > {high_speed_threshold:.0f} km/h)**", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
1703
  st.markdown(
1704
+ f'<div style="background-color: #f8f9fa; padding: 10px; border-radius: 5px; border: 1px solid #dee2e6; margin-bottom: 10px;">'
1705
+ f'<b>High-Speed Fatigue Events:</b> <span style="color: #1a73e8; font-weight: bold;">{len(high_speed_fatigue)}</span>'
1706
+ f' ({format_red_pct(high_speed_pct)} of total alerts)'
1707
+ f'</div>',
1708
  unsafe_allow_html=True
1709
  )
1710
+ if high_speed_pct > 20:
1711
+ st.warning(f"High risk: {format_red_pct(high_speed_pct)} of fatigue alerts occur at high speeds. This increases accident severity potential.")
 
 
1712
  else:
1713
+ st.info(f"{format_red_pct(high_speed_pct)} of alerts occur at high speeds. This is within acceptable range.")
1714
+ else:
1715
+ st.info("Speed data not available for High-Speed Fatigue Analysis.")
1716
+
1717
+ # 3. Shift Pattern Analysis
1718
+ if col_shift and col_shift in df.columns:
1719
+ shift_counts = df[col_shift].value_counts()
1720
+ st.markdown(f"**Shift Pattern Risk**", unsafe_allow_html=True)
1721
+ for shift_val in shift_counts.index:
1722
+ count = shift_counts[shift_val]
1723
+ shift_pct = (count / len(df)) * 100
1724
  st.markdown(
1725
+ f'<div style="background-color: #f8f9fa; padding: 8px; border-radius: 5px; border: 1px solid #e9ecef; margin: 5px 0;">'
1726
+ f'<b>Shift {shift_val} Alerts:</b> <span style="color: #1a73e8; font-weight: bold;">{count}</span>'
1727
+ f' ({format_red_pct(shift_pct)} of total alerts)'
1728
+ f'</div>',
1729
  unsafe_allow_html=True
1730
  )
1731
+ if shift_pct > 50:
1732
+ st.warning(f"Shift {shift_val} has disproportionately high alerts ({format_red_pct(shift_pct)}). Review shift scheduling and workload.")
 
1733
  else:
1734
+ st.info(f"Shift {shift_val} alert distribution is acceptable ({format_red_pct(shift_pct)}).")
1735
+ else:
1736
+ st.info("Shift data not available for Shift Pattern Analysis.")
1737
+
1738
+ # 4. Operator Risk Profiling
1739
+ if col_operator and col_operator in df.columns:
1740
+ operator_alerts = df[col_operator].value_counts()
1741
+ top_risk_operators = operator_alerts.head(5)
1742
+
1743
+ st.markdown(f"**High-Risk Operator Identification**", unsafe_allow_html=True)
1744
+ for op_name, count in top_risk_operators.items():
1745
+ op_pct = (count / len(df)) * 100
1746
+ # Anonymize name: first part only
1747
+ display_name = op_name.split()[0] if isinstance(op_name, str) else str(op_name)
1748
+ st.markdown(
1749
+ f'<div style="background-color: #f8f9fa; padding: 8px; border-radius: 5px; border: 1px solid #e9ecef; margin: 5px 0;">'
1750
+ f'<b>Operator: {display_name}</b> — <span style="color: #1a73e8; font-weight: bold;">{count}</span> alerts'
1751
+ f' ({format_red_pct(op_pct)} of total alerts)'
1752
+ f'</div>',
1753
+ unsafe_allow_html=True
1754
+ )
1755
+ if op_pct > 5:
1756
+ st.warning(f"Operator {display_name} has high fatigue risk ({format_red_pct(op_pct)} of alerts). Consider coaching or rest plan.")
1757
+ else:
1758
+ st.info(f"Operator {display_name} fatigue risk is within acceptable range ({format_red_pct(op_pct)}).")
1759
+ else:
1760
+ st.info("Operator data not available for Operator Risk Profiling.")
1761
+
1762
+
1763
+ # Kolom kanan: AI Recommendations
1764
+ with col_recs:
1765
+ st.markdown('<div style="text-align: center; font-size: 1.3em; font-weight: bold; margin-bottom: 15px;">Recommendations</div>', unsafe_allow_html=True)
1766
+ ai_recs = []
1767
+ insights_found = []
 
 
 
 
 
 
 
1768
 
1769
+ # Peak hour
1770
+ if "hour" in df.columns and not df.empty:
1771
+ peak_hour = df["hour"].value_counts().idxmax()
1772
+ critical_hours = [2, 3, 4, 5]
1773
+ if peak_hour in critical_hours:
1774
+ insights_found.append(f"Most fatigue risk occurs at **{peak_hour}:00** — during critical circadian low period (3-6 AM). Consider enhanced monitoring.")
1775
  else:
1776
+ insights_found.append(f"Most fatigue risk occurs at **{peak_hour}:00** likely due to circadian drop.")
1777
+
1778
+ # Risk shift
1779
+ if col_shift and not df.empty:
1780
+ worst_shift = df[col_shift].value_counts().idxmax()
1781
+ insights_found.append(f"Highest fatigue recorded in **Shift {worst_shift}** — review scheduling & workload.")
1782
+
1783
+ # Worst operator
1784
+ if col_operator and not df.empty:
1785
+ worst_operator = df[col_operator].value_counts().idxmax()
1786
+ display_name = worst_operator.split()[0] if isinstance(worst_operator, str) else str(worst_operator)
1787
+ insights_found.append(f"Operator at highest risk: **{display_name}** — suggested coaching or rest plan.")
1788
+
1789
+ # Duration risk
1790
+ if "duration_sec" in df.columns and not df.empty:
1791
+ avg_duration = df["duration_sec"].mean()
1792
+ if not pd.isna(avg_duration) and avg_duration > 10:
1793
+ insights_found.append("Long fatigue event duration suggests slow response — improve alerting training.")
1794
+
1795
+ # High-speed insight (add conditionally)
1796
+ if col_speed and col_speed in df.columns and high_speed_pct > 20:
1797
+ insights_found.append("High-speed fatigue events exceed threshold — integrate with speed management.")
1798
+
1799
+ # Generate recommendations based on found insights
1800
+ if insights_found:
1801
+ if any("circadian low" in i.lower() for i in insights_found):
1802
+ ai_recs.append({
1803
+ "recommendation": "Deploy enhanced fatigue monitoring systems (e.g., EOR) specifically during 3-6 AM shifts.",
1804
+ "data_point": f"Critical Hour Alerts: {len(critical_alerts)} ({format_red_pct(critical_pct)} of total alerts)",
1805
+ "reason": "High percentage of alerts occurring during the known circadian low period (3-6 AM) indicates increased risk during these hours."
1806
+ })
1807
+ if any("shift" in i.lower() for i in insights_found):
1808
+ shift_pct_val = (df[col_shift].value_counts()[worst_shift] / len(df)) * 100
1809
+ ai_recs.append({
1810
+ "recommendation": "Review shift rotation schedules to minimize consecutive high-risk shifts.",
1811
+ "data_point": f"Shift {worst_shift} Alerts: {df[col_shift].value_counts()[worst_shift]} ({format_red_pct(shift_pct_val)} of total alerts)",
1812
+ "reason": f"The identified high-risk shift ({worst_shift}) has the highest number of fatigue alerts, suggesting scheduling or workload issues."
1813
+ })
1814
+ if any("operator" in i.lower() for i in insights_found):
1815
+ op_pct_val = (df[col_operator].value_counts()[worst_operator] / len(df)) * 100
1816
+ ai_recs.append({
1817
+ "recommendation": "Initiate individual coaching or mandatory rest periods for high-risk operators.",
1818
+ "data_point": f"Operator {display_name} Alerts: {df[col_operator].value_counts()[worst_operator]} ({format_red_pct(op_pct_val)} of total alerts)",
1819
+ "reason": f"The identified high-risk operator ({display_name}) has the highest number of fatigue alerts, indicating a need for targeted intervention."
1820
+ })
1821
+ if any("duration" in i.lower() for i in insights_found):
1822
+ ai_recs.append({
1823
+ "recommendation": "Review and improve alert response protocols and training.",
1824
+ "data_point": f"Average Fatigue Event Duration: {avg_duration:.2f} seconds",
1825
+ "reason": "Long average duration suggests potential delays in response time or alert acknowledgment, requiring protocol review."
1826
+ })
1827
+ if any("high-speed" in i.lower() for i in insights_found):
1828
+ ai_recs.append({
1829
+ "recommendation": "Implement speed management strategies in conjunction with fatigue monitoring.",
1830
+ "data_point": f"High-Speed Fatigue Events: {len(high_speed_fatigue)} ({format_red_pct(high_speed_pct)} of total alerts)",
1831
+ "reason": "A significant percentage of alerts occur at high speeds, increasing accident severity risk. Speed control is crucial."
1832
+ })
1833
+ if not ai_recs:
1834
+ ai_recs.append({
1835
+ "recommendation": "Data quality is sufficient. Focus on implementing recommendations from Objectives 1-5.",
1836
+ "data_point": "General Data Quality Check",
1837
+ "reason": "No specific high-impact insights were automatically identified from the aggregated data in this section."
1838
+ })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1839
 
1840
+ # Display recommendations with consistent styling
1841
+ for rec in ai_recs:
1842
+ st.markdown(f"""
1843
+ <div style="
1844
+ background: #f8f9fa;
1845
+ border: 1px solid #dee2e6;
1846
+ border-radius: 8px;
1847
+ padding: 15px;
1848
+ margin: 10px 0;
1849
+ color: #2c3e50;
1850
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
1851
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
1852
+ ">
1853
  <div style="
1854
+ font-weight: bold;
 
 
 
 
1855
  color: #2c3e50;
1856
+ margin-bottom: 8px;
1857
+ font-size: 14px;
1858
+ background: #e9ecef;
1859
+ padding: 8px;
1860
+ border-radius: 5px;
1861
+ border-left: 4px solid #495057;
1862
+ ">AI Recommendation</div>
1863
+ <div style="padding-top: 8px; font-size: 14px; margin-bottom: 10px;">
1864
+ <strong>Action:</strong> {rec['recommendation']}
 
 
 
 
 
 
 
 
 
 
 
 
 
1865
  </div>
1866
+ <div style="font-size: 12px; padding: 8px; background: #e9ecef; border-radius: 5px; margin-top: 5px;">
1867
+ <strong>Data Point:</strong> {rec['data_point']}
1868
+ </div>
1869
+ <div style="font-size: 12px; padding: 8px; background: #f1f1f1; border-radius: 5px; margin-top: 5px;">
1870
+ <strong>AI Reasoning:</strong> {rec['reason']}
1871
+ </div>
1872
+ </div>
1873
+ """, unsafe_allow_html=True)
1874
+ else:
1875
+ st.info("No specific data points available for AI recommendations. Ensure relevant columns (hour, shift, operator, duration, speed) are present and populated.")
1876
 
1877
  # ================= FOOTER ===========================
1878
  st.markdown("---")
1879
+ st.markdown(
1880
+ '<div style="text-align: center; color: #6c757d; font-size: 0.9em; margin-top: 20px;">'
1881
+ 'FatigueAnalyzer - Transforming Mining Safety with Intelligent Analytics | Contact: info@bukittechnology.com'
1882
+ '</div>',
1883
+ unsafe_allow_html=True
1884
+ )