Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1451,128 +1451,279 @@ else:
|
|
| 1451 |
</div>
|
| 1452 |
""", unsafe_allow_html=True)
|
| 1453 |
|
| 1454 |
-
|
| 1455 |
-
|
| 1456 |
-
|
| 1457 |
-
|
| 1458 |
-
|
| 1459 |
-
|
| 1460 |
-
|
| 1461 |
-
|
| 1462 |
-
|
| 1463 |
-
|
| 1464 |
-
|
| 1465 |
-
|
| 1466 |
-
|
| 1467 |
-
|
| 1468 |
-
|
| 1469 |
-
|
| 1470 |
-
|
| 1471 |
-
|
| 1472 |
-
|
| 1473 |
-
|
| 1474 |
-
|
| 1475 |
-
|
| 1476 |
-
|
| 1477 |
-
|
| 1478 |
-
|
| 1479 |
-
|
| 1480 |
-
|
| 1481 |
-
|
| 1482 |
-
|
| 1483 |
-
|
| 1484 |
-
|
| 1485 |
-
|
| 1486 |
-
|
| 1487 |
-
|
| 1488 |
-
|
| 1489 |
-
|
| 1490 |
-
|
| 1491 |
-
|
| 1492 |
-
|
| 1493 |
-
|
| 1494 |
-
|
| 1495 |
-
|
| 1496 |
-
|
| 1497 |
-
|
| 1498 |
-
|
| 1499 |
-
|
| 1500 |
-
|
| 1501 |
-
|
| 1502 |
-
|
| 1503 |
-
|
| 1504 |
-
|
| 1505 |
-
|
| 1506 |
-
|
| 1507 |
-
|
| 1508 |
-
|
| 1509 |
-
|
| 1510 |
-
|
| 1511 |
-
|
| 1512 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1513 |
)
|
|
|
|
|
|
|
| 1514 |
|
| 1515 |
-
|
| 1516 |
-
|
| 1517 |
-
|
| 1518 |
-
|
| 1519 |
-
|
| 1520 |
-
|
| 1521 |
-
|
| 1522 |
-
|
| 1523 |
-
|
| 1524 |
-
|
| 1525 |
-
|
| 1526 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1527 |
)
|
| 1528 |
-
|
| 1529 |
-
|
| 1530 |
-
|
| 1531 |
-
|
| 1532 |
-
|
| 1533 |
-
|
| 1534 |
-
|
| 1535 |
-
|
| 1536 |
-
|
| 1537 |
-
|
| 1538 |
-
|
| 1539 |
-
|
| 1540 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1541 |
f"{ob_worsening} out of 10 top hazard operators are showing <span class='trend-up'>worsening</span> trends.",
|
| 1542 |
f"{ob_one_time} operators are classified as <b>One Time Event</b> (single-week activity).",
|
| 1543 |
f"Average hazard: {ob_avg_risk:.2f} events/week (max: {ob_max_risk:.2f})."
|
| 1544 |
]
|
| 1545 |
-
|
| 1546 |
-
st.markdown(
|
| 1547 |
-
|
| 1548 |
-
<div class="ai-insight-
|
| 1549 |
-
|
| 1550 |
-
|
| 1551 |
-
|
|
|
|
|
|
|
|
|
|
| 1552 |
else:
|
| 1553 |
st.info("No OB HAULER data for analysis.")
|
| 1554 |
-
|
| 1555 |
-
|
| 1556 |
-
|
| 1557 |
-
|
| 1558 |
-
|
| 1559 |
-
|
| 1560 |
-
|
| 1561 |
-
|
| 1562 |
-
|
| 1563 |
-
|
| 1564 |
-
|
| 1565 |
-
|
| 1566 |
-
|
| 1567 |
-
|
| 1568 |
-
|
| 1569 |
-
|
| 1570 |
-
|
| 1571 |
-
|
| 1572 |
-
|
| 1573 |
-
|
| 1574 |
-
|
| 1575 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1576 |
# ===============================================================
|
| 1577 |
col_rec1, col_rec2 = st.columns(2)
|
| 1578 |
|
|
|
|
| 1451 |
</div>
|
| 1452 |
""", unsafe_allow_html=True)
|
| 1453 |
|
| 1454 |
+
# ===============================================================
|
| 1455 |
+
# PLOT FUNCTION — UPDATED: color for slope=0 is now #FFD700
|
| 1456 |
+
# ===============================================================
|
| 1457 |
+
def plot_chart(data, title):
|
| 1458 |
+
if data.empty:
|
| 1459 |
+
fig = go.Figure()
|
| 1460 |
+
fig.add_annotation(
|
| 1461 |
+
text="No Data",
|
| 1462 |
+
x=0.5, y=0.5,
|
| 1463 |
+
showarrow=False,
|
| 1464 |
+
font_size=16
|
| 1465 |
+
)
|
| 1466 |
+
fig.update_layout(height=350, title=dict(text=title, x=0.5))
|
| 1467 |
+
return fig
|
| 1468 |
+
|
| 1469 |
+
data_sorted = data.sort_values('weekly_avg', ascending=False)
|
| 1470 |
+
|
| 1471 |
+
def get_color(slope):
|
| 1472 |
+
if slope == 0:
|
| 1473 |
+
return "#FFD700" # ✅ Kuning untuk One Time Event
|
| 1474 |
+
elif slope > 0:
|
| 1475 |
+
if slope < 0.5:
|
| 1476 |
+
return "#ffcdd2"
|
| 1477 |
+
elif slope < 1.0:
|
| 1478 |
+
return "#ef9a9a"
|
| 1479 |
+
elif slope < 1.5:
|
| 1480 |
+
return "#e57373"
|
| 1481 |
+
else:
|
| 1482 |
+
return "#d32f2f"
|
| 1483 |
+
else: # slope < 0
|
| 1484 |
+
if slope > -0.5:
|
| 1485 |
+
return "#c8e6c9"
|
| 1486 |
+
elif slope > -1.0:
|
| 1487 |
+
return "#a5d6a7"
|
| 1488 |
+
elif slope > -1.5:
|
| 1489 |
+
return "#81c784"
|
| 1490 |
+
else:
|
| 1491 |
+
return "#388e3c"
|
| 1492 |
+
|
| 1493 |
+
colors = [get_color(s) for s in data_sorted["slope"]]
|
| 1494 |
+
|
| 1495 |
+
bar_trace = go.Bar(
|
| 1496 |
+
x=data_sorted[col_operator].astype(str),
|
| 1497 |
+
y=data_sorted["weekly_avg"],
|
| 1498 |
+
marker=dict(
|
| 1499 |
+
color=colors,
|
| 1500 |
+
line=dict(width=2, color="rgba(0,0,0,0.2)")
|
| 1501 |
+
),
|
| 1502 |
+
text=[f"{v:.1f}" for v in data_sorted["weekly_avg"]],
|
| 1503 |
+
textposition="outside",
|
| 1504 |
+
hovertemplate=(
|
| 1505 |
+
"<b>%{x}</b><br>" +
|
| 1506 |
+
"Weekly Avg: %{y:.2f}<br>" +
|
| 1507 |
+
"Trend Slope: %{customdata[0]:+.3f}<br>" +
|
| 1508 |
+
"Total Events: %{customdata[1]}<br>" +
|
| 1509 |
+
"Weeks Active: %{customdata[2]}<br>" +
|
| 1510 |
+
"<extra></extra>"
|
| 1511 |
+
),
|
| 1512 |
+
customdata=np.stack([data_sorted["slope"], data_sorted["total_events"], data_sorted["n_weeks"]], axis=-1)
|
| 1513 |
+
)
|
| 1514 |
+
|
| 1515 |
+
fig = go.Figure(bar_trace)
|
| 1516 |
+
fig.update_layout(
|
| 1517 |
+
title=dict(text=f"<b>{title}</b>", x=0.5),
|
| 1518 |
+
height=450,
|
| 1519 |
+
margin=dict(l=50, r=20, t=60, b=120),
|
| 1520 |
+
xaxis_title="<b>Operator Name</b>",
|
| 1521 |
+
yaxis_title="<b>Weekly Avg Events</b>",
|
| 1522 |
+
font=dict(family="Segoe UI", size=12),
|
| 1523 |
+
bargap=0.3,
|
| 1524 |
+
plot_bgcolor="rgba(0,0,0,0)",
|
| 1525 |
+
paper_bgcolor="rgba(0,0,0,0)",
|
| 1526 |
+
xaxis=dict(tickangle=45)
|
| 1527 |
+
)
|
| 1528 |
+
return fig
|
| 1529 |
+
|
| 1530 |
+
|
| 1531 |
+
# ==========================
|
| 1532 |
+
# INSIGHT DISPLAY SECTION
|
| 1533 |
+
# ==========================
|
| 1534 |
+
col_insight1, col_insight2 = st.columns(2)
|
| 1535 |
+
|
| 1536 |
+
with col_insight1:
|
| 1537 |
+
if not top_ob.empty:
|
| 1538 |
+
st.markdown("### OB HAULER Hazard Analysis")
|
| 1539 |
+
ob_worsening = len(top_ob[top_ob['slope'] > 0])
|
| 1540 |
+
ob_improving = len(top_ob[top_ob['slope'] < 0])
|
| 1541 |
+
ob_one_time = len(top_ob[top_ob['slope'] == 0])
|
| 1542 |
+
ob_avg_risk = top_ob['weekly_avg'].mean()
|
| 1543 |
+
ob_max_risk = top_ob['weekly_avg'].max()
|
| 1544 |
+
|
| 1545 |
+
# Always show 3 insights
|
| 1546 |
+
ob_insights = [
|
| 1547 |
+
f"{ob_worsening} out of 10 top hazard operators are showing <span class='trend-up'>worsening</span> trends.",
|
| 1548 |
+
f"{ob_one_time} operators are classified as <b>One Time Event</b> (single-week activity).",
|
| 1549 |
+
f"Average hazard: {ob_avg_risk:.2f} events/week (max: {ob_max_risk:.2f})."
|
| 1550 |
+
]
|
| 1551 |
+
for insight in ob_insights:
|
| 1552 |
+
st.markdown(
|
| 1553 |
+
f"""
|
| 1554 |
+
<div class="ai-insight-box">
|
| 1555 |
+
<div class="ai-insight-title">Hazard Summary</div>
|
| 1556 |
+
<p>{insight}</p>
|
| 1557 |
+
</div>
|
| 1558 |
+
""",
|
| 1559 |
+
unsafe_allow_html=True
|
| 1560 |
)
|
| 1561 |
+
else:
|
| 1562 |
+
st.info("No OB HAULER data for analysis.")
|
| 1563 |
|
| 1564 |
+
with col_insight2:
|
| 1565 |
+
if not top_coal.empty:
|
| 1566 |
+
st.markdown("### HAULING COAL Hazard Analysis")
|
| 1567 |
+
coal_worsening = len(top_coal[top_coal['slope'] > 0])
|
| 1568 |
+
coal_improving = len(top_coal[top_coal['slope'] < 0])
|
| 1569 |
+
coal_one_time = len(top_coal[top_coal['slope'] == 0])
|
| 1570 |
+
coal_avg_risk = top_coal['weekly_avg'].mean()
|
| 1571 |
+
coal_max_risk = top_coal['weekly_avg'].max()
|
| 1572 |
+
|
| 1573 |
+
coal_insights = [
|
| 1574 |
+
f"{coal_worsening} out of 10 top hazard operators are showing <span class='trend-up'>worsening</span> trends.",
|
| 1575 |
+
f"{coal_one_time} operators are classified as <b>One Time Event</b> (single-week activity).",
|
| 1576 |
+
f"Average hazard: {coal_avg_risk:.2f} events/week (max: {coal_max_risk:.2f})."
|
| 1577 |
+
]
|
| 1578 |
+
for insight in coal_insights:
|
| 1579 |
+
st.markdown(
|
| 1580 |
+
f"""
|
| 1581 |
+
<div class="ai-insight-box">
|
| 1582 |
+
<div class="ai-insight-title">Hazard Summary</div>
|
| 1583 |
+
<p>{insight}</p>
|
| 1584 |
+
</div>
|
| 1585 |
+
""",
|
| 1586 |
+
unsafe_allow_html=True
|
| 1587 |
)
|
| 1588 |
+
else:
|
| 1589 |
+
st.info("No HAULING COAL data for analysis.")
|
| 1590 |
+
# ===============================================================
|
| 1591 |
+
# RECOMMENDATIONS# ===============================================================
|
| 1592 |
+
# PLOT FUNCTION — UPDATED: color for slope=0 is now #FFD700
|
| 1593 |
+
# ===============================================================
|
| 1594 |
+
def plot_chart(data, title):
|
| 1595 |
+
if data.empty:
|
| 1596 |
+
fig = go.Figure()
|
| 1597 |
+
fig.add_annotation(
|
| 1598 |
+
text="No Data",
|
| 1599 |
+
x=0.5, y=0.5,
|
| 1600 |
+
showarrow=False,
|
| 1601 |
+
font_size=16
|
| 1602 |
+
)
|
| 1603 |
+
fig.update_layout(height=350, title=dict(text=title, x=0.5))
|
| 1604 |
+
return fig
|
| 1605 |
+
|
| 1606 |
+
data_sorted = data.sort_values('weekly_avg', ascending=False)
|
| 1607 |
+
|
| 1608 |
+
def get_color(slope):
|
| 1609 |
+
if slope == 0:
|
| 1610 |
+
return "#FFD700" # ✅ Kuning untuk One Time Event
|
| 1611 |
+
elif slope > 0:
|
| 1612 |
+
if slope < 0.5:
|
| 1613 |
+
return "#ffcdd2"
|
| 1614 |
+
elif slope < 1.0:
|
| 1615 |
+
return "#ef9a9a"
|
| 1616 |
+
elif slope < 1.5:
|
| 1617 |
+
return "#e57373"
|
| 1618 |
+
else:
|
| 1619 |
+
return "#d32f2f"
|
| 1620 |
+
else: # slope < 0
|
| 1621 |
+
if slope > -0.5:
|
| 1622 |
+
return "#c8e6c9"
|
| 1623 |
+
elif slope > -1.0:
|
| 1624 |
+
return "#a5d6a7"
|
| 1625 |
+
elif slope > -1.5:
|
| 1626 |
+
return "#81c784"
|
| 1627 |
+
else:
|
| 1628 |
+
return "#388e3c"
|
| 1629 |
+
|
| 1630 |
+
colors = [get_color(s) for s in data_sorted["slope"]]
|
| 1631 |
+
|
| 1632 |
+
bar_trace = go.Bar(
|
| 1633 |
+
x=data_sorted[col_operator].astype(str),
|
| 1634 |
+
y=data_sorted["weekly_avg"],
|
| 1635 |
+
marker=dict(
|
| 1636 |
+
color=colors,
|
| 1637 |
+
line=dict(width=2, color="rgba(0,0,0,0.2)")
|
| 1638 |
+
),
|
| 1639 |
+
text=[f"{v:.1f}" for v in data_sorted["weekly_avg"]],
|
| 1640 |
+
textposition="outside",
|
| 1641 |
+
hovertemplate=(
|
| 1642 |
+
"<b>%{x}</b><br>" +
|
| 1643 |
+
"Weekly Avg: %{y:.2f}<br>" +
|
| 1644 |
+
"Trend Slope: %{customdata[0]:+.3f}<br>" +
|
| 1645 |
+
"Total Events: %{customdata[1]}<br>" +
|
| 1646 |
+
"Weeks Active: %{customdata[2]}<br>" +
|
| 1647 |
+
"<extra></extra>"
|
| 1648 |
+
),
|
| 1649 |
+
customdata=np.stack([data_sorted["slope"], data_sorted["total_events"], data_sorted["n_weeks"]], axis=-1)
|
| 1650 |
+
)
|
| 1651 |
+
|
| 1652 |
+
fig = go.Figure(bar_trace)
|
| 1653 |
+
fig.update_layout(
|
| 1654 |
+
title=dict(text=f"<b>{title}</b>", x=0.5),
|
| 1655 |
+
height=450,
|
| 1656 |
+
margin=dict(l=50, r=20, t=60, b=120),
|
| 1657 |
+
xaxis_title="<b>Operator Name</b>",
|
| 1658 |
+
yaxis_title="<b>Weekly Avg Events</b>",
|
| 1659 |
+
font=dict(family="Segoe UI", size=12),
|
| 1660 |
+
bargap=0.3,
|
| 1661 |
+
plot_bgcolor="rgba(0,0,0,0)",
|
| 1662 |
+
paper_bgcolor="rgba(0,0,0,0)",
|
| 1663 |
+
xaxis=dict(tickangle=45)
|
| 1664 |
+
)
|
| 1665 |
+
return fig
|
| 1666 |
+
|
| 1667 |
+
|
| 1668 |
+
# ==========================
|
| 1669 |
+
# INSIGHT DISPLAY SECTION
|
| 1670 |
+
# ==========================
|
| 1671 |
+
col_insight1, col_insight2 = st.columns(2)
|
| 1672 |
+
|
| 1673 |
+
with col_insight1:
|
| 1674 |
+
if not top_ob.empty:
|
| 1675 |
+
st.markdown("### OB HAULER Hazard Analysis")
|
| 1676 |
+
ob_worsening = len(top_ob[top_ob['slope'] > 0])
|
| 1677 |
+
ob_improving = len(top_ob[top_ob['slope'] < 0])
|
| 1678 |
+
ob_one_time = len(top_ob[top_ob['slope'] == 0])
|
| 1679 |
+
ob_avg_risk = top_ob['weekly_avg'].mean()
|
| 1680 |
+
ob_max_risk = top_ob['weekly_avg'].max()
|
| 1681 |
+
|
| 1682 |
+
# Always show 3 insights
|
| 1683 |
+
ob_insights = [
|
| 1684 |
f"{ob_worsening} out of 10 top hazard operators are showing <span class='trend-up'>worsening</span> trends.",
|
| 1685 |
f"{ob_one_time} operators are classified as <b>One Time Event</b> (single-week activity).",
|
| 1686 |
f"Average hazard: {ob_avg_risk:.2f} events/week (max: {ob_max_risk:.2f})."
|
| 1687 |
]
|
| 1688 |
+
for insight in ob_insights:
|
| 1689 |
+
st.markdown(
|
| 1690 |
+
f"""
|
| 1691 |
+
<div class="ai-insight-box">
|
| 1692 |
+
<div class="ai-insight-title">Hazard Summary</div>
|
| 1693 |
+
<p>{insight}</p>
|
| 1694 |
+
</div>
|
| 1695 |
+
""",
|
| 1696 |
+
unsafe_allow_html=True
|
| 1697 |
+
)
|
| 1698 |
else:
|
| 1699 |
st.info("No OB HAULER data for analysis.")
|
| 1700 |
+
|
| 1701 |
+
with col_insight2:
|
| 1702 |
+
if not top_coal.empty:
|
| 1703 |
+
st.markdown("### HAULING COAL Hazard Analysis")
|
| 1704 |
+
coal_worsening = len(top_coal[top_coal['slope'] > 0])
|
| 1705 |
+
coal_improving = len(top_coal[top_coal['slope'] < 0])
|
| 1706 |
+
coal_one_time = len(top_coal[top_coal['slope'] == 0])
|
| 1707 |
+
coal_avg_risk = top_coal['weekly_avg'].mean()
|
| 1708 |
+
coal_max_risk = top_coal['weekly_avg'].max()
|
| 1709 |
+
|
| 1710 |
+
coal_insights = [
|
| 1711 |
+
f"{coal_worsening} out of 10 top hazard operators are showing <span class='trend-up'>worsening</span> trends.",
|
| 1712 |
+
f"{coal_one_time} operators are classified as <b>One Time Event</b> (single-week activity).",
|
| 1713 |
+
f"Average hazard: {coal_avg_risk:.2f} events/week (max: {coal_max_risk:.2f})."
|
| 1714 |
+
]
|
| 1715 |
+
for insight in coal_insights:
|
| 1716 |
+
st.markdown(
|
| 1717 |
+
f"""
|
| 1718 |
+
<div class="ai-insight-box">
|
| 1719 |
+
<div class="ai-insight-title">Hazard Summary</div>
|
| 1720 |
+
<p>{insight}</p>
|
| 1721 |
+
</div>
|
| 1722 |
+
""",
|
| 1723 |
+
unsafe_allow_html=True
|
| 1724 |
+
)
|
| 1725 |
+
else:
|
| 1726 |
+
st.info("No HAULING COAL data for analysis.")
|
| 1727 |
# ===============================================================
|
| 1728 |
col_rec1, col_rec2 = st.columns(2)
|
| 1729 |
|