SHELLAPANDIANGANHUNGING commited on
Commit
9f5c1b5
·
verified ·
1 Parent(s): 90b1a02

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +194 -188
app.py CHANGED
@@ -1122,7 +1122,7 @@ try:
1122
  # ================================
1123
  # 12. DISPLAY TABLE
1124
  # ================================
1125
- st.subheader("Operator Risk Summary Table (8 Weeks Observed)")
1126
  table_display = (
1127
  risk_matrix[[
1128
  "Operator Name",
@@ -1546,7 +1546,7 @@ else:
1546
  for insight in ob_insights:
1547
  st.markdown(f"""
1548
  <div class="ai-insight-box">
1549
- <div class="ai-insight-title">Risk Summary</div>
1550
  <p>{insight}</p>
1551
  </div>
1552
  """, unsafe_allow_html=True)
@@ -1573,7 +1573,7 @@ else:
1573
  for insight in coal_insights:
1574
  st.markdown(f"""
1575
  <div class="ai-insight-box">
1576
- <div class="ai-insight-title">Risk Summary</div>
1577
  <p>{insight}</p>
1578
  </div>
1579
  """, unsafe_allow_html=True)
@@ -1661,213 +1661,219 @@ else:
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 1–5.",
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;
1840
- border: 1px solid #dee2e6;
1841
- border-radius: 8px;
1842
- padding: 15px;
1843
- margin: 10px 0;
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;
 
 
 
 
1850
  color: #2c3e50;
1851
- margin-bottom: 8px;
1852
- font-size: 14px;
1853
- background: #e9ecef;
1854
- padding: 8px;
1855
- border-radius: 5px;
1856
- border-left: 4px solid #495057;
1857
- ">AI Recommendation</div>
1858
- <div style="padding-top: 8px; font-size: 14px; margin-bottom: 10px;">
1859
- <strong>Action:</strong> {rec['recommendation']}
1860
- </div>
1861
- <div style="font-size: 12px; padding: 8px; background: #e9ecef; border-radius: 5px; margin-top: 5px;">
1862
- <strong>Data Point:</strong> {rec['data_point']}
1863
- </div>
1864
- <div style="font-size: 12px; padding: 8px; background: #f1f1f1; border-radius: 5px; margin-top: 5px;">
1865
- <strong>AI Reasoning:</strong> {rec['reason']}
 
 
 
 
 
 
 
1866
  </div>
1867
- </div>
1868
- """, unsafe_allow_html=True)
1869
- else:
1870
- st.info("No specific data points available for AI recommendations. Ensure relevant columns (hour, shift, operator, duration, speed) are present and populated.")
 
 
 
1871
 
1872
  # ================= FOOTER ===========================
1873
  st.markdown("---")
 
1122
  # ================================
1123
  # 12. DISPLAY TABLE
1124
  # ================================
1125
+ st.subheader("Operator Hazard Summary Table (8 Weeks Observed)")
1126
  table_display = (
1127
  risk_matrix[[
1128
  "Operator Name",
 
1546
  for insight in ob_insights:
1547
  st.markdown(f"""
1548
  <div class="ai-insight-box">
1549
+ <div class="ai-insight-title">Hazard Summary</div>
1550
  <p>{insight}</p>
1551
  </div>
1552
  """, unsafe_allow_html=True)
 
1573
  for insight in coal_insights:
1574
  st.markdown(f"""
1575
  <div class="ai-insight-box">
1576
+ <div class="ai-insight-title">Hazard Summary</div>
1577
  <p>{insight}</p>
1578
  </div>
1579
  """, unsafe_allow_html=True)
 
1661
  # =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
1662
  st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
1663
 
1664
+ try:
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
 
1681
+ # ✅ Highlight percentage in red using markdown
1682
  st.markdown(
1683
+ f'<div style="background-color: {bg_color}; padding: 10px; border-radius: 5px;">'
1684
+ f'Critical Hour Alerts: {len(critical_alerts)} '
1685
+ f'<span style="color:#d32f2f; font-weight:bold;">({critical_pct:.1f}% of total alerts)</span>'
1686
+ '</div>',
1687
  unsafe_allow_html=True
1688
  )
1689
 
1690
+ # Gunakan st.markdown untuk menampilkan peringatan/info dengan HTML
1691
+ if critical_pct > 10:
1692
+ 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)
1693
  else:
1694
+ 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)
1695
 
1696
+ # 2. High-Speed Fatigue Analysis
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)**")
1703
+
1704
+ # ✅ st.metric doesn't support HTML, so replace with markdown
 
 
 
 
 
 
 
 
 
 
1705
  st.markdown(
1706
+ f"<div style='font-size:1.1em; margin:8px 0;'>"
1707
+ f"High-Speed Fatigue Events: <b>{len(high_speed_fatigue)}</b> "
1708
+ f"<span style='color:#d32f2f; font-weight:bold;'>({high_speed_pct:.1f}% of total alerts)</span>"
1709
+ "</div>",
1710
  unsafe_allow_html=True
1711
  )
1712
 
1713
+ if high_speed_pct > 20:
1714
+ 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)
1715
  else:
1716
+ 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)
1717
 
1718
+ # 3. Shift Pattern Analysis
1719
+ if col_shift and col_shift in df.columns:
1720
+ shift_counts = df[col_shift].value_counts()
1721
+ st.markdown(f"**Shift Pattern Risk**")
1722
+
1723
+ for shift_val in shift_counts.index:
1724
+ shift_pct = (shift_counts[shift_val] / len(df)) * 100
1725
+ # ✅ Replace st.metric with red-percentage version
1726
+ st.markdown(
1727
+ f"**Shift {shift_val} Alerts**<br>"
1728
+ f"{shift_counts[shift_val]} "
1729
+ f'<span style="color:#d32f2f; font-weight:bold;">({shift_pct:.1f}% of total alerts)</span>',
1730
+ unsafe_allow_html=True
1731
+ )
1732
+
1733
+ if shift_pct > 50:
1734
+ 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)
1735
+ else:
1736
+ 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)
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
+ st.markdown(f"**High-Risk Operator Identification**")
1743
+
1744
+ for op_name, count in top_risk_operators.items():
1745
+ op_pct = (count / len(df)) * 100
1746
+ st.markdown(
1747
+ f"**Operator: {op_name}**<br>"
1748
+ f"{count} alerts "
1749
+ f'<span style="color:#d32f2f; font-weight:bold;">({op_pct:.1f}% of total alerts)</span>',
1750
+ unsafe_allow_html=True
1751
+ )
1752
+
1753
+ if op_pct > 5:
1754
+ 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)
1755
+ else:
1756
+ 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)
1757
 
 
 
 
 
 
 
1758
  else:
1759
+ st.info("Operator data not available for Operator Risk Profiling.")
1760
+
1761
+ # Kolom kanan: AI Recommendations
1762
+ with col_recs:
1763
+ st.subheader("Recommendations")
1764
+ ai_recs = []
1765
+ insights_found = []
1766
+
1767
+ # Peak hour
1768
+ if "hour" in df.columns and not df.empty:
1769
+ peak_hour = df["hour"].value_counts().idxmax()
1770
+ critical_hours = [2, 3, 4, 5]
1771
+ if peak_hour in critical_hours:
1772
+ insights_found.append(f"Most fatigue risk occurs at **{peak_hour}:00** — during critical circadian low period (3-6 AM). Consider enhanced monitoring.")
1773
+ else:
1774
+ insights_found.append(f"Most fatigue risk occurs at **{peak_hour}:00** — likely due to circadian drop.")
1775
+
1776
+ # Risk shift
1777
+ if col_shift and not df.empty:
1778
+ worst_shift = df[col_shift].value_counts().idxmax()
1779
+ insights_found.append(f"Highest fatigue recorded in **Shift {worst_shift}** review scheduling & workload.")
1780
+
1781
+ # Worst operator
1782
+ if col_operator and not df.empty:
1783
+ worst_operator = df[col_operator].value_counts().idxmax()
1784
+ insights_found.append(f"Operator at highest risk: **{worst_operator}** suggested coaching or rest plan.")
1785
+
1786
+ # Duration risk
1787
+ if "duration_sec" in df.columns and not df.empty:
1788
+ avg_duration = df["duration_sec"].mean()
1789
+ if not pd.isna(avg_duration) and avg_duration > 10:
1790
+ insights_found.append("Long fatigue event duration suggests slow response — improve alerting training.")
1791
+
1792
+ # Recommendations based on insights
1793
+ if insights_found:
1794
+ if any("circadian low" in i.lower() for i in insights_found):
1795
+ ai_recs.append({
1796
+ "recommendation": "Deploy enhanced fatigue monitoring systems (e.g., EOR) specifically during 3-6 AM shifts.",
1797
+ "data_point": f"Critical Hour Alerts: {len(critical_alerts)} "
1798
+ f"<span style='color:#d32f2f;'>({critical_pct:.1f}% of total alerts)</span>",
1799
+ "reason": "High percentage of alerts during circadian low period (3–6 AM) indicates elevated risk."
1800
+ })
1801
+ if any("shift" in i.lower() for i in insights_found):
1802
+ pct = (df[col_shift].value_counts()[worst_shift] / len(df)) * 100
1803
+ ai_recs.append({
1804
+ "recommendation": "Review shift rotation schedules to minimize consecutive high-risk shifts.",
1805
+ "data_point": f"Shift {worst_shift} Alerts: {df[col_shift].value_counts()[worst_shift]} "
1806
+ f"<span style='color:#d32f2f;'>({pct:.1f}% of total alerts)</span>",
1807
+ "reason": f"Shift {worst_shift} accounts for a disproportionate share of fatigue events."
1808
+ })
1809
+ if any("operator" in i.lower() for i in insights_found):
1810
+ pct = (df[col_operator].value_counts()[worst_operator] / len(df)) * 100
1811
+ ai_recs.append({
1812
+ "recommendation": "Initiate individual coaching or mandatory rest periods for high-risk operators.",
1813
+ "data_point": f"Operator {worst_operator} Alerts: {df[col_operator].value_counts()[worst_operator]} "
1814
+ f"<span style='color:#d32f2f;'>({pct:.1f}% of total alerts)</span>",
1815
+ "reason": f"Individual intervention needed to mitigate recurrence risk."
1816
+ })
1817
+ if any("duration" in i.lower() for i in insights_found):
1818
+ ai_recs.append({
1819
+ "recommendation": "Review and improve alert response protocols and training.",
1820
+ "data_point": f"Average Fatigue Event Duration: {avg_duration:.2f} seconds",
1821
+ "reason": "Long duration suggests delayed response — requires procedural review."
1822
+ })
1823
+ if any("high-speed" in i.lower() for i in insights_found):
1824
+ ai_recs.append({
1825
+ "recommendation": "Implement speed management strategies in conjunction with fatigue monitoring.",
1826
+ "data_point": f"High-Speed Fatigue Events: {len(high_speed_fatigue)} "
1827
+ f"<span style='color:#d32f2f;'>({high_speed_pct:.1f}% of total alerts)</span>",
1828
+ "reason": "High-speed fatigue greatly increases collision severity risk."
1829
+ })
1830
+ if not ai_recs:
1831
+ ai_recs.append({
1832
+ "recommendation": "Data quality is sufficient. Focus on implementing recommendations from Objectives 1–5.",
1833
+ "data_point": "General Data Quality Check",
1834
+ "reason": "No critical anomalies detected in aggregate metrics."
1835
+ })
1836
 
1837
+ # Render recommendations
1838
+ for rec in ai_recs:
1839
+ st.markdown(f"""
 
 
 
 
 
 
 
 
 
 
1840
  <div style="
1841
+ background: #f8f9fa;
1842
+ border: 1px solid #dee2e6;
1843
+ border-radius: 8px;
1844
+ padding: 15px;
1845
+ margin: 10px 0;
1846
  color: #2c3e50;
1847
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
1848
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
1849
+ ">
1850
+ <div style="
1851
+ font-weight: bold;
1852
+ color: #2c3e50;
1853
+ margin-bottom: 8px;
1854
+ font-size: 14px;
1855
+ background: #e9ecef;
1856
+ padding: 8px;
1857
+ border-radius: 5px;
1858
+ border-left: 4px solid #495057;
1859
+ ">AI Recommendation</div>
1860
+ <div style="padding-top: 8px; font-size: 14px; margin-bottom: 10px;">
1861
+ <strong>Action:</strong> {rec['recommendation']}
1862
+ </div>
1863
+ <div style="font-size: 12px; padding: 8px; background: #e9ecef; border-radius: 5px; margin-top: 5px;">
1864
+ <strong>Data Point:</strong> {rec['data_point']}
1865
+ </div>
1866
+ <div style="font-size: 12px; padding: 8px; background: #f1f1f1; border-radius: 5px; margin-top: 5px;">
1867
+ <strong>AI Reasoning:</strong> {rec['reason']}
1868
+ </div>
1869
  </div>
1870
+ """, unsafe_allow_html=True)
1871
+ else:
1872
+ st.info("No specific data points available for AI recommendations. Ensure relevant columns (hour, shift, operator, duration, speed) are present and populated.")
1873
+
1874
+ except Exception as e:
1875
+ st.error(f"Error in Objective 6: {e}")
1876
+ st.exception(e) # Untuk debugging lebih lanjut
1877
 
1878
  # ================= FOOTER ===========================
1879
  st.markdown("---")