Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1159,10 +1159,9 @@ import plotly.graph_objects as go
|
|
| 1159 |
|
| 1160 |
# =================== OBJECTIVE 5: Operator Fatigue Risk Gradient Dashboard =====================
|
| 1161 |
# ... (kode sebelumnya tetap sama) ...
|
| 1162 |
-
|
| 1163 |
st.subheader("OBJECTIVE 5: See your team’s fatigue Fatigue Hazard Profile!")
|
| 1164 |
|
| 1165 |
-
#
|
| 1166 |
st.markdown("""
|
| 1167 |
<style>
|
| 1168 |
.big-title {
|
|
@@ -1263,30 +1262,20 @@ st.markdown("""
|
|
| 1263 |
.recommendation-title {
|
| 1264 |
font-weight: bold;
|
| 1265 |
color: white;
|
| 1266 |
-
margin-bottom:
|
| 1267 |
font-size: 14px;
|
| 1268 |
background: rgba(255,255,255,0.2);
|
| 1269 |
padding: 8px;
|
| 1270 |
border-radius: 5px;
|
| 1271 |
border-left: 4px solid white;
|
| 1272 |
}
|
| 1273 |
-
.recommendation-box ul {
|
| 1274 |
-
padding-left: 24px;
|
| 1275 |
-
margin: 12px 0;
|
| 1276 |
-
line-height: 1.6;
|
| 1277 |
-
font-size: 14px;
|
| 1278 |
-
}
|
| 1279 |
-
.recommendation-box ul li {
|
| 1280 |
-
margin-bottom: 8px;
|
| 1281 |
-
}
|
| 1282 |
.recommendation-reason {
|
| 1283 |
font-size: 12px;
|
| 1284 |
-
margin-top:
|
| 1285 |
padding: 8px;
|
| 1286 |
-
background: rgba(255,255,255,0.
|
| 1287 |
border-radius: 5px;
|
| 1288 |
-
border-left: 3px solid rgba(255,255,255,0.
|
| 1289 |
-
font-style: italic;
|
| 1290 |
}
|
| 1291 |
</style>
|
| 1292 |
""", unsafe_allow_html=True)
|
|
@@ -1459,9 +1448,7 @@ else:
|
|
| 1459 |
</div>
|
| 1460 |
""", unsafe_allow_html=True)
|
| 1461 |
|
| 1462 |
-
#
|
| 1463 |
-
# PLOT FUNCTION — UPDATED: color for slope=0 is now #FFD700
|
| 1464 |
-
# ===============================================================
|
| 1465 |
def plot_chart(data, title):
|
| 1466 |
if data.empty:
|
| 1467 |
fig = go.Figure()
|
|
@@ -1545,7 +1532,7 @@ else:
|
|
| 1545 |
st.plotly_chart(plot_chart(top_coal, "HAULING COAL Operators (Hazard Gradient)"), use_container_width=True)
|
| 1546 |
|
| 1547 |
# ===============================================================
|
| 1548 |
-
# AI INSIGHTS —
|
| 1549 |
# ===============================================================
|
| 1550 |
col_insight1, col_insight2 = st.columns(2)
|
| 1551 |
|
|
@@ -1557,6 +1544,7 @@ else:
|
|
| 1557 |
ob_one_time = len(top_ob[top_ob['slope'] == 0])
|
| 1558 |
ob_avg_risk = top_ob['weekly_avg'].mean()
|
| 1559 |
ob_max_risk = top_ob['weekly_avg'].max()
|
|
|
|
| 1560 |
ob_insights = []
|
| 1561 |
if ob_worsening > ob_improving:
|
| 1562 |
ob_insights.append(f"{ob_worsening} out of 10 top risk operators are showing <span class='trend-up'>worsening</span> trends.")
|
|
@@ -1564,15 +1552,20 @@ else:
|
|
| 1564 |
ob_insights.append(f"{ob_improving} out of 10 top risk operators are showing <span class='trend-down'>improvement</span>.")
|
| 1565 |
if ob_one_time > 0:
|
| 1566 |
ob_insights.append(f"{ob_one_time} operators are classified as <b>One Time Event</b> (single-week activity).")
|
|
|
|
|
|
|
| 1567 |
ob_insights.append(f"Average risk: {ob_avg_risk:.2f} events/week (max: {ob_max_risk:.2f}).")
|
| 1568 |
|
| 1569 |
-
|
| 1570 |
-
|
| 1571 |
-
<div class="ai-insight-
|
| 1572 |
-
|
| 1573 |
-
<
|
| 1574 |
-
|
| 1575 |
-
|
|
|
|
|
|
|
|
|
|
| 1576 |
else:
|
| 1577 |
st.info("No OB HAULER data for analysis.")
|
| 1578 |
|
|
@@ -1584,6 +1577,7 @@ else:
|
|
| 1584 |
coal_one_time = len(top_coal[top_coal['slope'] == 0])
|
| 1585 |
coal_avg_risk = top_coal['weekly_avg'].mean()
|
| 1586 |
coal_max_risk = top_coal['weekly_avg'].max()
|
|
|
|
| 1587 |
coal_insights = []
|
| 1588 |
if coal_worsening > coal_improving:
|
| 1589 |
coal_insights.append(f"{coal_worsening} out of 10 top risk operators are showing <span class='trend-up'>worsening</span> trends.")
|
|
@@ -1591,116 +1585,92 @@ else:
|
|
| 1591 |
coal_insights.append(f"{coal_improving} out of 10 top risk operators are showing <span class='trend-down'>improvement</span>.")
|
| 1592 |
if coal_one_time > 0:
|
| 1593 |
coal_insights.append(f"{coal_one_time} operators are classified as <b>One Time Event</b> (single-week activity).")
|
|
|
|
|
|
|
| 1594 |
coal_insights.append(f"Average risk: {coal_avg_risk:.2f} events/week (max: {coal_max_risk:.2f}).")
|
| 1595 |
|
| 1596 |
-
|
| 1597 |
-
|
| 1598 |
-
<div class="ai-insight-
|
| 1599 |
-
|
| 1600 |
-
<
|
| 1601 |
-
|
| 1602 |
-
|
|
|
|
|
|
|
|
|
|
| 1603 |
else:
|
| 1604 |
st.info("No HAULING COAL data for analysis.")
|
| 1605 |
|
| 1606 |
# ===============================================================
|
| 1607 |
-
# RECOMMENDATIONS —
|
| 1608 |
# ===============================================================
|
| 1609 |
col_rec1, col_rec2 = st.columns(2)
|
| 1610 |
|
| 1611 |
-
|
| 1612 |
-
|
| 1613 |
if not top_ob.empty:
|
| 1614 |
w = len(top_ob[top_ob['slope'] > 0])
|
| 1615 |
ot = len(top_ob[top_ob['slope'] == 0])
|
| 1616 |
avg = top_ob['weekly_avg'].mean()
|
| 1617 |
-
|
| 1618 |
-
rec_list_ob = []
|
| 1619 |
-
|
| 1620 |
-
# Point 1: Trend-driven action
|
| 1621 |
if w > 5:
|
| 1622 |
-
|
|
|
|
| 1623 |
elif ot > 4:
|
| 1624 |
-
|
|
|
|
| 1625 |
elif avg > 8:
|
| 1626 |
-
|
|
|
|
| 1627 |
else:
|
| 1628 |
-
|
|
|
|
|
|
|
|
|
|
| 1629 |
|
| 1630 |
-
|
| 1631 |
-
|
| 1632 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1633 |
else:
|
| 1634 |
-
|
| 1635 |
-
|
| 1636 |
-
|
| 1637 |
-
|
|
|
|
| 1638 |
|
| 1639 |
-
|
| 1640 |
-
while len(rec_list_ob) < 3:
|
| 1641 |
-
rec_list_ob.append("—")
|
| 1642 |
|
|
|
|
|
|
|
| 1643 |
st.markdown("### OB HAULER Recommendations")
|
| 1644 |
st.markdown(f"""
|
| 1645 |
<div class="recommendation-box">
|
| 1646 |
<div class="recommendation-title">Action Plan</div>
|
| 1647 |
-
<
|
| 1648 |
-
|
| 1649 |
-
<li>{rec_list_ob[1]}</li>
|
| 1650 |
-
<li>{rec_list_ob[2]}</li>
|
| 1651 |
-
</ul>
|
| 1652 |
-
<div class="recommendation-reason">
|
| 1653 |
-
Based on trend slope, activity duration, and cohort event frequency.
|
| 1654 |
-
</div>
|
| 1655 |
</div>
|
| 1656 |
""", unsafe_allow_html=True)
|
| 1657 |
else:
|
| 1658 |
st.info("No OB HAULER recommendations.")
|
| 1659 |
|
| 1660 |
-
# === HAULING COAL RECOMMENDATIONS (3-point list) ===
|
| 1661 |
with col_rec2:
|
| 1662 |
-
if
|
| 1663 |
-
w = len(top_coal[top_coal['slope'] > 0])
|
| 1664 |
-
ot = len(top_coal[top_coal['slope'] == 0])
|
| 1665 |
-
avg = top_coal['weekly_avg'].mean()
|
| 1666 |
-
|
| 1667 |
-
rec_list_coal = []
|
| 1668 |
-
|
| 1669 |
-
# Point 1: Trend-driven action
|
| 1670 |
-
if w > 5:
|
| 1671 |
-
rec_list_coal.append("Conduct targeted fatigue risk assessments for operators with worsening trends (slope > 0).")
|
| 1672 |
-
elif ot > 4:
|
| 1673 |
-
rec_list_coal.append("Investigate data completeness — high count of One Time Events may reflect inconsistent reporting.")
|
| 1674 |
-
elif avg > 8:
|
| 1675 |
-
rec_list_coal.append("Review shift scheduling and rest-break compliance to reduce event frequency.")
|
| 1676 |
-
else:
|
| 1677 |
-
rec_list_coal.append("Continue current protocols with routine monitoring of top-risk operators.")
|
| 1678 |
-
|
| 1679 |
-
# Point 2: One-Time Event follow-up
|
| 1680 |
-
if ot > 0:
|
| 1681 |
-
rec_list_coal.append(f"Re-engage {ot} operators flagged as <b>One Time Event</b> to verify activity continuity.")
|
| 1682 |
-
else:
|
| 1683 |
-
rec_list_coal.append("No One Time Event operators identified — trend analysis remains reliable.")
|
| 1684 |
-
|
| 1685 |
-
# Point 3: Benchmarking
|
| 1686 |
-
rec_list_coal.append(f"Use cohort average ({avg:.2f} events/week) as baseline for monthly fatigue KPI reviews.")
|
| 1687 |
-
|
| 1688 |
-
# Ensure exactly 3 items
|
| 1689 |
-
while len(rec_list_coal) < 3:
|
| 1690 |
-
rec_list_coal.append("—")
|
| 1691 |
-
|
| 1692 |
st.markdown("### HAULING COAL Recommendations")
|
| 1693 |
st.markdown(f"""
|
| 1694 |
<div class="recommendation-box">
|
| 1695 |
<div class="recommendation-title">Action Plan</div>
|
| 1696 |
-
<
|
| 1697 |
-
|
| 1698 |
-
<li>{rec_list_coal[1]}</li>
|
| 1699 |
-
<li>{rec_list_coal[2]}</li>
|
| 1700 |
-
</ul>
|
| 1701 |
-
<div class="recommendation-reason">
|
| 1702 |
-
Based on trend slope, activity duration, and cohort event frequency.
|
| 1703 |
-
</div>
|
| 1704 |
</div>
|
| 1705 |
""", unsafe_allow_html=True)
|
| 1706 |
else:
|
|
@@ -1708,8 +1678,8 @@ else:
|
|
| 1708 |
|
| 1709 |
except Exception as e:
|
| 1710 |
st.error(f"Error in Top 10 Operator analysis: {str(e)}")
|
| 1711 |
-
|
| 1712 |
-
# =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
|
| 1713 |
st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
|
| 1714 |
|
| 1715 |
# Membagi tampilan menjadi dua kolom
|
|
|
|
| 1159 |
|
| 1160 |
# =================== OBJECTIVE 5: Operator Fatigue Risk Gradient Dashboard =====================
|
| 1161 |
# ... (kode sebelumnya tetap sama) ...
|
|
|
|
| 1162 |
st.subheader("OBJECTIVE 5: See your team’s fatigue Fatigue Hazard Profile!")
|
| 1163 |
|
| 1164 |
+
# Custom CSS — tetap seperti sebelumnya (sudah sesuai preferensi)
|
| 1165 |
st.markdown("""
|
| 1166 |
<style>
|
| 1167 |
.big-title {
|
|
|
|
| 1262 |
.recommendation-title {
|
| 1263 |
font-weight: bold;
|
| 1264 |
color: white;
|
| 1265 |
+
margin-bottom: 8px;
|
| 1266 |
font-size: 14px;
|
| 1267 |
background: rgba(255,255,255,0.2);
|
| 1268 |
padding: 8px;
|
| 1269 |
border-radius: 5px;
|
| 1270 |
border-left: 4px solid white;
|
| 1271 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1272 |
.recommendation-reason {
|
| 1273 |
font-size: 12px;
|
| 1274 |
+
margin-top: 10px;
|
| 1275 |
padding: 8px;
|
| 1276 |
+
background: rgba(255,255,255,0.1);
|
| 1277 |
border-radius: 5px;
|
| 1278 |
+
border-left: 3px solid rgba(255,255,255,0.3);
|
|
|
|
| 1279 |
}
|
| 1280 |
</style>
|
| 1281 |
""", unsafe_allow_html=True)
|
|
|
|
| 1448 |
</div>
|
| 1449 |
""", unsafe_allow_html=True)
|
| 1450 |
|
| 1451 |
+
# ✅ Function definition at correct indentation level
|
|
|
|
|
|
|
| 1452 |
def plot_chart(data, title):
|
| 1453 |
if data.empty:
|
| 1454 |
fig = go.Figure()
|
|
|
|
| 1532 |
st.plotly_chart(plot_chart(top_coal, "HAULING COAL Operators (Hazard Gradient)"), use_container_width=True)
|
| 1533 |
|
| 1534 |
# ===============================================================
|
| 1535 |
+
# AI INSIGHTS — DIPERBAIKI: Risk Summary jadi 1 box + 3 list
|
| 1536 |
# ===============================================================
|
| 1537 |
col_insight1, col_insight2 = st.columns(2)
|
| 1538 |
|
|
|
|
| 1544 |
ob_one_time = len(top_ob[top_ob['slope'] == 0])
|
| 1545 |
ob_avg_risk = top_ob['weekly_avg'].mean()
|
| 1546 |
ob_max_risk = top_ob['weekly_avg'].max()
|
| 1547 |
+
|
| 1548 |
ob_insights = []
|
| 1549 |
if ob_worsening > ob_improving:
|
| 1550 |
ob_insights.append(f"{ob_worsening} out of 10 top risk operators are showing <span class='trend-up'>worsening</span> trends.")
|
|
|
|
| 1552 |
ob_insights.append(f"{ob_improving} out of 10 top risk operators are showing <span class='trend-down'>improvement</span>.")
|
| 1553 |
if ob_one_time > 0:
|
| 1554 |
ob_insights.append(f"{ob_one_time} operators are classified as <b>One Time Event</b> (single-week activity).")
|
| 1555 |
+
else:
|
| 1556 |
+
ob_insights.append("No operators classified as <b>One Time Event</b>.")
|
| 1557 |
ob_insights.append(f"Average risk: {ob_avg_risk:.2f} events/week (max: {ob_max_risk:.2f}).")
|
| 1558 |
|
| 1559 |
+
st.markdown(f"""
|
| 1560 |
+
<div class="ai-insight-box">
|
| 1561 |
+
<div class="ai-insight-title">Risk Summary</div>
|
| 1562 |
+
<ul style="padding-left: 20px; margin: 8px 0; line-height: 1.5;">
|
| 1563 |
+
<li>{ob_insights[0]}</li>
|
| 1564 |
+
<li>{ob_insights[1]}</li>
|
| 1565 |
+
<li>{ob_insights[2]}</li>
|
| 1566 |
+
</ul>
|
| 1567 |
+
</div>
|
| 1568 |
+
""", unsafe_allow_html=True)
|
| 1569 |
else:
|
| 1570 |
st.info("No OB HAULER data for analysis.")
|
| 1571 |
|
|
|
|
| 1577 |
coal_one_time = len(top_coal[top_coal['slope'] == 0])
|
| 1578 |
coal_avg_risk = top_coal['weekly_avg'].mean()
|
| 1579 |
coal_max_risk = top_coal['weekly_avg'].max()
|
| 1580 |
+
|
| 1581 |
coal_insights = []
|
| 1582 |
if coal_worsening > coal_improving:
|
| 1583 |
coal_insights.append(f"{coal_worsening} out of 10 top risk operators are showing <span class='trend-up'>worsening</span> trends.")
|
|
|
|
| 1585 |
coal_insights.append(f"{coal_improving} out of 10 top risk operators are showing <span class='trend-down'>improvement</span>.")
|
| 1586 |
if coal_one_time > 0:
|
| 1587 |
coal_insights.append(f"{coal_one_time} operators are classified as <b>One Time Event</b> (single-week activity).")
|
| 1588 |
+
else:
|
| 1589 |
+
coal_insights.append("No operators classified as <b>One Time Event</b>.")
|
| 1590 |
coal_insights.append(f"Average risk: {coal_avg_risk:.2f} events/week (max: {coal_max_risk:.2f}).")
|
| 1591 |
|
| 1592 |
+
st.markdown(f"""
|
| 1593 |
+
<div class="ai-insight-box">
|
| 1594 |
+
<div class="ai-insight-title">Risk Summary</div>
|
| 1595 |
+
<ul style="padding-left: 20px; margin: 8px 0; line-height: 1.5;">
|
| 1596 |
+
<li>{coal_insights[0]}</li>
|
| 1597 |
+
<li>{coal_insights[1]}</li>
|
| 1598 |
+
<li>{coal_insights[2]}</li>
|
| 1599 |
+
</ul>
|
| 1600 |
+
</div>
|
| 1601 |
+
""", unsafe_allow_html=True)
|
| 1602 |
else:
|
| 1603 |
st.info("No HAULING COAL data for analysis.")
|
| 1604 |
|
| 1605 |
# ===============================================================
|
| 1606 |
+
# RECOMMENDATIONS — TIDAK DIUBAH SAMA SEKALI
|
| 1607 |
# ===============================================================
|
| 1608 |
col_rec1, col_rec2 = st.columns(2)
|
| 1609 |
|
| 1610 |
+
def generate_recommendations(top_ob, top_coal):
|
| 1611 |
+
rec = {}
|
| 1612 |
if not top_ob.empty:
|
| 1613 |
w = len(top_ob[top_ob['slope'] > 0])
|
| 1614 |
ot = len(top_ob[top_ob['slope'] == 0])
|
| 1615 |
avg = top_ob['weekly_avg'].mean()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1616 |
if w > 5:
|
| 1617 |
+
r = "Prioritize fatigue intervention for operators with worsening trends."
|
| 1618 |
+
reason = "High proportion of deteriorating operators signals emerging fatigue risks."
|
| 1619 |
elif ot > 4:
|
| 1620 |
+
r = "Validate data completeness — high One Time Event count may indicate reporting gaps."
|
| 1621 |
+
reason = "Operators with single-week data cannot yield reliable trend analysis."
|
| 1622 |
elif avg > 8:
|
| 1623 |
+
r = "Review scheduling and rest protocols to reduce event frequency."
|
| 1624 |
+
reason = "Elevated average event rate increases cumulative fatigue exposure."
|
| 1625 |
else:
|
| 1626 |
+
r = "Maintain current protocols with targeted monitoring."
|
| 1627 |
+
reason = "Risk profile is stable; focus on sustaining safe practices."
|
| 1628 |
+
rec['ob'] = r
|
| 1629 |
+
rec['ob_reason'] = reason
|
| 1630 |
|
| 1631 |
+
if not top_coal.empty:
|
| 1632 |
+
w = len(top_coal[top_coal['slope'] > 0])
|
| 1633 |
+
ot = len(top_coal[top_coal['slope'] == 0])
|
| 1634 |
+
avg = top_coal['weekly_avg'].mean()
|
| 1635 |
+
if w > 5:
|
| 1636 |
+
r = "Prioritize fatigue intervention for operators with worsening trends."
|
| 1637 |
+
reason = "High proportion of deteriorating operators signals emerging fatigue risks."
|
| 1638 |
+
elif ot > 4:
|
| 1639 |
+
r = "Validate data completeness — high One Time Event count may indicate reporting gaps."
|
| 1640 |
+
reason = "Operators with single-week data cannot yield reliable trend analysis."
|
| 1641 |
+
elif avg > 8:
|
| 1642 |
+
r = "Review scheduling and rest protocols to reduce event frequency."
|
| 1643 |
+
reason = "Elevated average event rate increases cumulative fatigue exposure."
|
| 1644 |
else:
|
| 1645 |
+
r = "Maintain current protocols with targeted monitoring."
|
| 1646 |
+
reason = "Risk profile is stable; focus on sustaining safe practices."
|
| 1647 |
+
rec['coal'] = r
|
| 1648 |
+
rec['coal_reason'] = reason
|
| 1649 |
+
return rec
|
| 1650 |
|
| 1651 |
+
ai_rec = generate_recommendations(top_ob, top_coal)
|
|
|
|
|
|
|
| 1652 |
|
| 1653 |
+
with col_rec1:
|
| 1654 |
+
if 'ob' in ai_rec:
|
| 1655 |
st.markdown("### OB HAULER Recommendations")
|
| 1656 |
st.markdown(f"""
|
| 1657 |
<div class="recommendation-box">
|
| 1658 |
<div class="recommendation-title">Action Plan</div>
|
| 1659 |
+
<div>{ai_rec['ob']}</div>
|
| 1660 |
+
<div class="recommendation-reason">AI Reasoning: {ai_rec['ob_reason']}</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1661 |
</div>
|
| 1662 |
""", unsafe_allow_html=True)
|
| 1663 |
else:
|
| 1664 |
st.info("No OB HAULER recommendations.")
|
| 1665 |
|
|
|
|
| 1666 |
with col_rec2:
|
| 1667 |
+
if 'coal' in ai_rec:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1668 |
st.markdown("### HAULING COAL Recommendations")
|
| 1669 |
st.markdown(f"""
|
| 1670 |
<div class="recommendation-box">
|
| 1671 |
<div class="recommendation-title">Action Plan</div>
|
| 1672 |
+
<div>{ai_rec['coal']}</div>
|
| 1673 |
+
<div class="recommendation-reason">AI Reasoning: {ai_rec['coal_reason']}</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1674 |
</div>
|
| 1675 |
""", unsafe_allow_html=True)
|
| 1676 |
else:
|
|
|
|
| 1678 |
|
| 1679 |
except Exception as e:
|
| 1680 |
st.error(f"Error in Top 10 Operator analysis: {str(e)}")
|
| 1681 |
+
st.exception(e)
|
| 1682 |
+
# =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
|
| 1683 |
st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
|
| 1684 |
|
| 1685 |
# Membagi tampilan menjadi dua kolom
|