Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1663,106 +1663,122 @@ else:
|
|
| 1663 |
# =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
|
| 1664 |
st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
|
| 1665 |
|
| 1666 |
-
# Membagi
|
| 1667 |
col_insights, col_recs = st.columns(2)
|
| 1668 |
|
| 1669 |
-
#
|
|
|
|
|
|
|
| 1670 |
with col_insights:
|
| 1671 |
st.subheader("Insights by Advanced Analytics")
|
| 1672 |
|
| 1673 |
-
# 1. Critical Hour Analysis
|
| 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(
|
| 1679 |
-
|
| 1680 |
-
|
| 1681 |
-
|
| 1682 |
-
|
| 1683 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1684 |
else:
|
| 1685 |
-
st.info(f"{critical_pct:.1f}% of alerts occur during critical hours.
|
| 1686 |
|
| 1687 |
-
# 2. High-Speed Fatigue Analysis
|
| 1688 |
if col_speed and col_speed in df.columns:
|
| 1689 |
-
high_speed_threshold = df[col_speed].quantile(0.75)
|
| 1690 |
-
high_speed_fatigue = df[df[col_speed] >= high_speed_threshold]
|
| 1691 |
-
high_speed_pct = (len(high_speed_fatigue) / len(df)) * 100
|
| 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 |
-
|
| 1696 |
-
|
|
|
|
| 1697 |
else:
|
| 1698 |
-
st.info(f"{high_speed_pct:.1f}%
|
| 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 |
-
st.markdown("**Shift Pattern Risk**")
|
| 1706 |
-
|
| 1707 |
-
shift_color = "#d32f2f" # merah gelap
|
| 1708 |
-
|
| 1709 |
-
for shift_val in shift_counts.index:
|
| 1710 |
-
shift_pct = (shift_counts[shift_val] / len(df)) * 100
|
| 1711 |
-
|
| 1712 |
-
st.markdown(
|
| 1713 |
-
f"""
|
| 1714 |
-
<div style="padding:12px 16px; border-radius:10px; background-color:#fff; border:1px solid #eee; margin-bottom:10px;">
|
| 1715 |
-
<div style="font-size:16px; font-weight:600; color:inherit;">Shift {shift_val} Alerts</div>
|
| 1716 |
-
<div style="font-size:28px; font-weight:700; color:inherit;">{shift_counts[shift_val]}</div>
|
| 1717 |
-
<div style="font-size:14px; font-weight:700;">
|
| 1718 |
-
<span style="color:{shift_color};">{shift_pct:.1f}% of total alerts</span>
|
| 1719 |
-
</div>
|
| 1720 |
-
</div>
|
| 1721 |
-
""",
|
| 1722 |
-
unsafe_allow_html=True
|
| 1723 |
-
)
|
| 1724 |
|
| 1725 |
-
|
| 1726 |
-
|
| 1727 |
-
|
| 1728 |
-
|
| 1729 |
-
|
| 1730 |
-
st.info("Shift data not available for Shift Pattern Analysis.")
|
| 1731 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1732 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1733 |
|
| 1734 |
-
# 4. Operator Risk Profiling
|
| 1735 |
-
if col_operator and col_operator in df.columns:
|
| 1736 |
-
|
| 1737 |
-
|
| 1738 |
|
| 1739 |
-
|
| 1740 |
|
| 1741 |
-
|
| 1742 |
-
|
| 1743 |
|
| 1744 |
-
|
| 1745 |
-
|
| 1746 |
-
|
| 1747 |
|
| 1748 |
-
|
| 1749 |
-
|
| 1750 |
|
| 1751 |
-
|
| 1752 |
-
|
| 1753 |
-
|
| 1754 |
-
|
| 1755 |
-
|
| 1756 |
-
)
|
| 1757 |
|
| 1758 |
-
|
| 1759 |
-
|
| 1760 |
-
|
| 1761 |
-
|
| 1762 |
-
|
|
|
|
| 1763 |
|
| 1764 |
-
else:
|
| 1765 |
-
st.info("Operator data not available for Operator Risk Profiling.")
|
| 1766 |
|
| 1767 |
|
| 1768 |
# Kolom kanan: AI Recommendations
|
|
|
|
| 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 (3–6 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
|