Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1155,8 +1155,17 @@ except Exception as e:
|
|
| 1155 |
|
| 1156 |
# ... (kode sebelumnya tetap sama) ...
|
| 1157 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1158 |
# =================== OBJECTIVE 5: Operator Fatigue Risk Gradient Dashboard =====================
|
| 1159 |
-
st.subheader("OBJECTIVE 5: See your team
|
|
|
|
| 1160 |
# Custom CSS untuk tampilan ala market saham yang sangat fancy dan profesional
|
| 1161 |
st.markdown("""
|
| 1162 |
<style>
|
|
@@ -1294,12 +1303,9 @@ else:
|
|
| 1294 |
st.info("No operator data after filtering.")
|
| 1295 |
st.stop()
|
| 1296 |
|
| 1297 |
-
#
|
| 1298 |
-
|
| 1299 |
-
|
| 1300 |
-
st.stop()
|
| 1301 |
-
|
| 1302 |
-
df_op["year_week"] = df_op["start"].dt.strftime("%Y-W%U")
|
| 1303 |
|
| 1304 |
# Fuzzy match fleet names
|
| 1305 |
fleet_clean = df_op[col_fleet_type].str.strip().str.upper()
|
|
@@ -1309,52 +1315,60 @@ else:
|
|
| 1309 |
ob_data = df_op[df_op["is_ob"]]
|
| 1310 |
coal_data = df_op[df_op["is_coal"]]
|
| 1311 |
|
| 1312 |
-
# Fungsi hitung top 10 (untuk bar chart) - berdasarkan
|
| 1313 |
def get_top10_with_slope(data):
|
| 1314 |
if data.empty:
|
| 1315 |
st.warning("Data is empty in get_top10_with_slope.")
|
| 1316 |
return pd.DataFrame()
|
|
|
|
| 1317 |
# Pastikan col_operator tidak None dan ada di data
|
| 1318 |
if col_operator is None or col_operator not in data.columns:
|
| 1319 |
st.error(f"Operator column '{col_operator}' not found in data subset for get_top10.")
|
| 1320 |
return pd.DataFrame()
|
| 1321 |
|
| 1322 |
-
|
|
|
|
|
|
|
| 1323 |
metrics = []
|
| 1324 |
try:
|
| 1325 |
-
for nik, grp in
|
| 1326 |
# Lewati jika nik adalah None
|
| 1327 |
if pd.isna(nik):
|
| 1328 |
continue
|
| 1329 |
-
grp = grp.sort_values("
|
| 1330 |
-
counts = grp["
|
| 1331 |
-
|
| 1332 |
-
|
| 1333 |
total_events = counts.sum()
|
| 1334 |
-
|
| 1335 |
-
|
| 1336 |
-
|
|
|
|
| 1337 |
y_mean = counts.mean()
|
| 1338 |
-
numerator = np.sum((
|
| 1339 |
-
denominator = np.sum((
|
| 1340 |
slope = numerator / denominator if denominator != 0 else 0.0
|
| 1341 |
else:
|
| 1342 |
slope = 0.0
|
|
|
|
| 1343 |
metrics.append({
|
| 1344 |
col_operator: nik,
|
| 1345 |
-
"
|
| 1346 |
"slope": slope,
|
| 1347 |
"total_events": total_events,
|
| 1348 |
-
"
|
| 1349 |
})
|
| 1350 |
except KeyError as e:
|
| 1351 |
st.error(f"KeyError in get_top10_with_slope: {e}. This might happen if the operator column contains invalid data types or unexpected values.")
|
| 1352 |
return pd.DataFrame()
|
| 1353 |
-
|
|
|
|
| 1354 |
if not metrics:
|
| 1355 |
st.warning("No valid operator data found for slope calculation in get_top10.")
|
| 1356 |
return pd.DataFrame()
|
| 1357 |
-
|
|
|
|
|
|
|
| 1358 |
|
| 1359 |
top_ob = get_top10_with_slope(ob_data)
|
| 1360 |
top_coal = get_top10_with_slope(coal_data)
|
|
@@ -1364,45 +1378,52 @@ else:
|
|
| 1364 |
if data.empty:
|
| 1365 |
st.warning("Data is empty in get_all_operators_with_slope.")
|
| 1366 |
return pd.DataFrame()
|
|
|
|
| 1367 |
# Pastikan col_operator tidak None dan ada di data
|
| 1368 |
if col_operator is None or col_operator not in data.columns:
|
| 1369 |
st.error(f"Operator column '{col_operator}' not found in data subset for get_all.")
|
| 1370 |
return pd.DataFrame()
|
| 1371 |
|
| 1372 |
-
|
|
|
|
|
|
|
| 1373 |
metrics = []
|
| 1374 |
try:
|
| 1375 |
-
for nik, grp in
|
| 1376 |
# Lewati jika nik adalah None
|
| 1377 |
if pd.isna(nik):
|
| 1378 |
continue
|
| 1379 |
-
grp = grp.sort_values("
|
| 1380 |
-
counts = grp["
|
| 1381 |
-
|
| 1382 |
-
|
| 1383 |
total_events = counts.sum()
|
| 1384 |
-
|
| 1385 |
-
|
| 1386 |
-
|
|
|
|
| 1387 |
y_mean = counts.mean()
|
| 1388 |
-
numerator = np.sum((
|
| 1389 |
-
denominator = np.sum((
|
| 1390 |
slope = numerator / denominator if denominator != 0 else 0.0
|
| 1391 |
else:
|
| 1392 |
slope = 0.0
|
|
|
|
| 1393 |
metrics.append({
|
| 1394 |
col_operator: nik,
|
| 1395 |
-
"
|
| 1396 |
"slope": slope,
|
| 1397 |
"total_events": total_events,
|
| 1398 |
-
"
|
| 1399 |
})
|
| 1400 |
except KeyError as e:
|
| 1401 |
st.error(f"KeyError in get_all_operators_with_slope: {e}. This might happen if the operator column contains invalid data types or unexpected values.")
|
| 1402 |
return pd.DataFrame()
|
|
|
|
| 1403 |
if not metrics:
|
| 1404 |
st.warning("No valid operator data found for slope calculation in get_all.")
|
| 1405 |
return pd.DataFrame()
|
|
|
|
| 1406 |
return pd.DataFrame(metrics)
|
| 1407 |
|
| 1408 |
all_ob = get_all_operators_with_slope(ob_data)
|
|
@@ -1459,7 +1480,7 @@ else:
|
|
| 1459 |
<span>Stable (0)</span>
|
| 1460 |
</div>
|
| 1461 |
<br>
|
| 1462 |
-
<i>Note: Only appears when operator data shows consistent behavior within a single
|
| 1463 |
</div>
|
| 1464 |
</div>
|
| 1465 |
""", unsafe_allow_html=True)
|
|
@@ -1480,8 +1501,8 @@ else:
|
|
| 1480 |
fig.update_layout(height=350, title=title)
|
| 1481 |
return fig
|
| 1482 |
|
| 1483 |
-
# Urutkan data berdasarkan
|
| 1484 |
-
data_sorted = data.sort_values('
|
| 1485 |
|
| 1486 |
# Kategorisasi warna berdasarkan slope dengan gradasi yang berbeda
|
| 1487 |
def get_color(slope):
|
|
@@ -1513,22 +1534,22 @@ else:
|
|
| 1513 |
# Buat trace bar, TANPA argumen 'title'
|
| 1514 |
bar_trace = go.Bar(
|
| 1515 |
x=data_sorted[col_operator].astype(str),
|
| 1516 |
-
y=data_sorted["
|
| 1517 |
marker=dict(
|
| 1518 |
color=colors,
|
| 1519 |
line=dict(width=2, color="rgba(0,0,0,0.2)")
|
| 1520 |
),
|
| 1521 |
-
text=[f"{v:.1f}" for v in data_sorted["
|
| 1522 |
textposition="outside",
|
| 1523 |
hovertemplate=(
|
| 1524 |
"<b>%{x}</b><br>" +
|
| 1525 |
-
"
|
| 1526 |
"Trend Slope: %{customdata[0]:+.3f}<br>" +
|
| 1527 |
"Total Events: %{customdata[1]}<br>" +
|
| 1528 |
-
"
|
| 1529 |
"<extra></extra>"
|
| 1530 |
),
|
| 1531 |
-
customdata=np.stack([data_sorted["slope"], data_sorted["total_events"], data_sorted["
|
| 1532 |
)
|
| 1533 |
|
| 1534 |
# Buat figure dan tambahkan trace
|
|
@@ -1541,7 +1562,7 @@ else:
|
|
| 1541 |
height=450,
|
| 1542 |
margin=dict(l=50, r=20, t=60, b=120),
|
| 1543 |
xaxis_title="<b>Operator ID</b>",
|
| 1544 |
-
yaxis_title="<b>
|
| 1545 |
font=dict(family="Segoe UI", size=12),
|
| 1546 |
bargap=0.3,
|
| 1547 |
plot_bgcolor="rgba(0,0,0,0)",
|
|
@@ -1573,14 +1594,14 @@ else:
|
|
| 1573 |
st.markdown("### OB HAULER Analysis")
|
| 1574 |
ob_worsening = len(top_ob[top_ob['slope'] > 0])
|
| 1575 |
ob_improving = len(top_ob[top_ob['slope'] < 0])
|
| 1576 |
-
ob_avg_risk = top_ob['
|
| 1577 |
-
ob_max_risk = top_ob['
|
| 1578 |
ob_insights = []
|
| 1579 |
if ob_worsening > ob_improving:
|
| 1580 |
ob_insights.append(f"{ob_worsening} out of 10 top risk operators are showing <span class='trend-up'>worsening</span> trends, indicating potential fatigue issues in this fleet type.")
|
| 1581 |
else:
|
| 1582 |
ob_insights.append(f"{ob_improving} out of 10 top risk operators are showing <span class='trend-down'>improvement</span>, suggesting effective fatigue management strategies.")
|
| 1583 |
-
ob_insights.append(f"Average risk level among top 10 operators is {ob_avg_risk:.2f} events per
|
| 1584 |
|
| 1585 |
for insight in ob_insights:
|
| 1586 |
st.markdown(f"""
|
|
@@ -1598,14 +1619,14 @@ else:
|
|
| 1598 |
st.markdown("### HAULING COAL Analysis")
|
| 1599 |
coal_worsening = len(top_coal[top_coal['slope'] > 0])
|
| 1600 |
coal_improving = len(top_coal[top_coal['slope'] < 0])
|
| 1601 |
-
coal_avg_risk = top_coal['
|
| 1602 |
-
coal_max_risk = top_coal['
|
| 1603 |
coal_insights = []
|
| 1604 |
if coal_worsening > coal_improving:
|
| 1605 |
coal_insights.append(f"{coal_worsening} out of 10 top risk operators are showing <span class='trend-up'>worsening</span> trends, requiring immediate attention.")
|
| 1606 |
else:
|
| 1607 |
coal_insights.append(f"{coal_improving} out of 10 top risk operators are showing <span class='trend-down'>improvement</span>, indicating positive trends in safety management.")
|
| 1608 |
-
coal_insights.append(f"Average risk level among top 10 operators is {coal_avg_risk:.2f} events per
|
| 1609 |
|
| 1610 |
for insight in coal_insights:
|
| 1611 |
st.markdown(f"""
|
|
@@ -1632,7 +1653,7 @@ else:
|
|
| 1632 |
recommendations = {}
|
| 1633 |
if not top_ob.empty:
|
| 1634 |
ob_worsening = len(top_ob[top_ob['slope'] > 0])
|
| 1635 |
-
ob_avg_risk = top_ob['
|
| 1636 |
if ob_worsening > 5: # Lebih dari setengah
|
| 1637 |
recommendations['ob'] = "Implement immediate fatigue monitoring protocols for operators showing worsening trends."
|
| 1638 |
reason_ob = "High percentage of operators showing increasing risk trends indicates potential systemic fatigue issues requiring immediate intervention."
|
|
@@ -1646,7 +1667,7 @@ else:
|
|
| 1646 |
|
| 1647 |
if not top_coal.empty:
|
| 1648 |
coal_worsening = len(top_coal[top_coal['slope'] > 0])
|
| 1649 |
-
coal_avg_risk = top_coal['
|
| 1650 |
if coal_worsening > 5: # Lebih dari setengah
|
| 1651 |
recommendations['coal'] = "Implement immediate fatigue monitoring protocols for operators showing worsening trends."
|
| 1652 |
reason_coal = "High percentage of operators showing increasing risk trends indicates potential systemic fatigue issues requiring immediate intervention."
|
|
@@ -1693,9 +1714,7 @@ else:
|
|
| 1693 |
except Exception as e:
|
| 1694 |
st.error(f"Error in Top 10 Operator analysis: {str(e)}")
|
| 1695 |
st.code(f"Error: {e}", language="python")
|
| 1696 |
-
|
| 1697 |
-
|
| 1698 |
-
|
| 1699 |
|
| 1700 |
# =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
|
| 1701 |
st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
|
|
|
|
| 1155 |
|
| 1156 |
# ... (kode sebelumnya tetap sama) ...
|
| 1157 |
|
| 1158 |
+
|
| 1159 |
+
|
| 1160 |
+
```python
|
| 1161 |
+
import streamlit as st
|
| 1162 |
+
import pandas as pd
|
| 1163 |
+
import plotly.graph_objects as go
|
| 1164 |
+
import numpy as np
|
| 1165 |
+
|
| 1166 |
# =================== OBJECTIVE 5: Operator Fatigue Risk Gradient Dashboard =====================
|
| 1167 |
+
st.subheader("OBJECTIVE 5: See your team's fatigue risk gradient at a glance!")
|
| 1168 |
+
|
| 1169 |
# Custom CSS untuk tampilan ala market saham yang sangat fancy dan profesional
|
| 1170 |
st.markdown("""
|
| 1171 |
<style>
|
|
|
|
| 1303 |
st.info("No operator data after filtering.")
|
| 1304 |
st.stop()
|
| 1305 |
|
| 1306 |
+
# Buat kolom date dan daily_count
|
| 1307 |
+
df_op["date"] = df_op["start"].dt.date
|
| 1308 |
+
daily = df_op.groupby([col_operator, "date"]).size().reset_index(name="daily_count")
|
|
|
|
|
|
|
|
|
|
| 1309 |
|
| 1310 |
# Fuzzy match fleet names
|
| 1311 |
fleet_clean = df_op[col_fleet_type].str.strip().str.upper()
|
|
|
|
| 1315 |
ob_data = df_op[df_op["is_ob"]]
|
| 1316 |
coal_data = df_op[df_op["is_coal"]]
|
| 1317 |
|
| 1318 |
+
# Fungsi hitung top 10 (untuk bar chart) - berdasarkan daily avg events tertinggi
|
| 1319 |
def get_top10_with_slope(data):
|
| 1320 |
if data.empty:
|
| 1321 |
st.warning("Data is empty in get_top10_with_slope.")
|
| 1322 |
return pd.DataFrame()
|
| 1323 |
+
|
| 1324 |
# Pastikan col_operator tidak None dan ada di data
|
| 1325 |
if col_operator is None or col_operator not in data.columns:
|
| 1326 |
st.error(f"Operator column '{col_operator}' not found in data subset for get_top10.")
|
| 1327 |
return pd.DataFrame()
|
| 1328 |
|
| 1329 |
+
# Hitung daily count untuk masing-masing operator dan tanggal
|
| 1330 |
+
daily_data = data.groupby([col_operator, "date"]).size().reset_index(name="daily_count")
|
| 1331 |
+
|
| 1332 |
metrics = []
|
| 1333 |
try:
|
| 1334 |
+
for nik, grp in daily_data.groupby(col_operator):
|
| 1335 |
# Lewati jika nik adalah None
|
| 1336 |
if pd.isna(nik):
|
| 1337 |
continue
|
| 1338 |
+
grp = grp.sort_values("date")
|
| 1339 |
+
counts = grp["daily_count"].values
|
| 1340 |
+
dates = np.arange(len(counts))
|
| 1341 |
+
daily_avg = counts.mean()
|
| 1342 |
total_events = counts.sum()
|
| 1343 |
+
n_days = len(counts)
|
| 1344 |
+
|
| 1345 |
+
if n_days >= 2:
|
| 1346 |
+
x_mean = dates.mean()
|
| 1347 |
y_mean = counts.mean()
|
| 1348 |
+
numerator = np.sum((dates - x_mean) * (counts - y_mean))
|
| 1349 |
+
denominator = np.sum((dates - x_mean) ** 2)
|
| 1350 |
slope = numerator / denominator if denominator != 0 else 0.0
|
| 1351 |
else:
|
| 1352 |
slope = 0.0
|
| 1353 |
+
|
| 1354 |
metrics.append({
|
| 1355 |
col_operator: nik,
|
| 1356 |
+
"daily_avg": daily_avg,
|
| 1357 |
"slope": slope,
|
| 1358 |
"total_events": total_events,
|
| 1359 |
+
"n_days": n_days
|
| 1360 |
})
|
| 1361 |
except KeyError as e:
|
| 1362 |
st.error(f"KeyError in get_top10_with_slope: {e}. This might happen if the operator column contains invalid data types or unexpected values.")
|
| 1363 |
return pd.DataFrame()
|
| 1364 |
+
|
| 1365 |
+
# Ambil top 10 berdasarkan daily_avg (descending order)
|
| 1366 |
if not metrics:
|
| 1367 |
st.warning("No valid operator data found for slope calculation in get_top10.")
|
| 1368 |
return pd.DataFrame()
|
| 1369 |
+
|
| 1370 |
+
df_metrics = pd.DataFrame(metrics)
|
| 1371 |
+
return df_metrics.nlargest(10, "daily_avg")
|
| 1372 |
|
| 1373 |
top_ob = get_top10_with_slope(ob_data)
|
| 1374 |
top_coal = get_top10_with_slope(coal_data)
|
|
|
|
| 1378 |
if data.empty:
|
| 1379 |
st.warning("Data is empty in get_all_operators_with_slope.")
|
| 1380 |
return pd.DataFrame()
|
| 1381 |
+
|
| 1382 |
# Pastikan col_operator tidak None dan ada di data
|
| 1383 |
if col_operator is None or col_operator not in data.columns:
|
| 1384 |
st.error(f"Operator column '{col_operator}' not found in data subset for get_all.")
|
| 1385 |
return pd.DataFrame()
|
| 1386 |
|
| 1387 |
+
# Hitung daily count untuk masing-masing operator dan tanggal
|
| 1388 |
+
daily_data = data.groupby([col_operator, "date"]).size().reset_index(name="daily_count")
|
| 1389 |
+
|
| 1390 |
metrics = []
|
| 1391 |
try:
|
| 1392 |
+
for nik, grp in daily_data.groupby(col_operator):
|
| 1393 |
# Lewati jika nik adalah None
|
| 1394 |
if pd.isna(nik):
|
| 1395 |
continue
|
| 1396 |
+
grp = grp.sort_values("date")
|
| 1397 |
+
counts = grp["daily_count"].values
|
| 1398 |
+
dates = np.arange(len(counts))
|
| 1399 |
+
daily_avg = counts.mean()
|
| 1400 |
total_events = counts.sum()
|
| 1401 |
+
n_days = len(counts)
|
| 1402 |
+
|
| 1403 |
+
if n_days >= 2:
|
| 1404 |
+
x_mean = dates.mean()
|
| 1405 |
y_mean = counts.mean()
|
| 1406 |
+
numerator = np.sum((dates - x_mean) * (counts - y_mean))
|
| 1407 |
+
denominator = np.sum((dates - x_mean) ** 2)
|
| 1408 |
slope = numerator / denominator if denominator != 0 else 0.0
|
| 1409 |
else:
|
| 1410 |
slope = 0.0
|
| 1411 |
+
|
| 1412 |
metrics.append({
|
| 1413 |
col_operator: nik,
|
| 1414 |
+
"daily_avg": daily_avg,
|
| 1415 |
"slope": slope,
|
| 1416 |
"total_events": total_events,
|
| 1417 |
+
"n_days": n_days
|
| 1418 |
})
|
| 1419 |
except KeyError as e:
|
| 1420 |
st.error(f"KeyError in get_all_operators_with_slope: {e}. This might happen if the operator column contains invalid data types or unexpected values.")
|
| 1421 |
return pd.DataFrame()
|
| 1422 |
+
|
| 1423 |
if not metrics:
|
| 1424 |
st.warning("No valid operator data found for slope calculation in get_all.")
|
| 1425 |
return pd.DataFrame()
|
| 1426 |
+
|
| 1427 |
return pd.DataFrame(metrics)
|
| 1428 |
|
| 1429 |
all_ob = get_all_operators_with_slope(ob_data)
|
|
|
|
| 1480 |
<span>Stable (0)</span>
|
| 1481 |
</div>
|
| 1482 |
<br>
|
| 1483 |
+
<i>Note: Only appears when operator data shows consistent behavior within a single day observation period.</i>
|
| 1484 |
</div>
|
| 1485 |
</div>
|
| 1486 |
""", unsafe_allow_html=True)
|
|
|
|
| 1501 |
fig.update_layout(height=350, title=title)
|
| 1502 |
return fig
|
| 1503 |
|
| 1504 |
+
# Urutkan data berdasarkan daily_avg dari besar ke kecil
|
| 1505 |
+
data_sorted = data.sort_values('daily_avg', ascending=False)
|
| 1506 |
|
| 1507 |
# Kategorisasi warna berdasarkan slope dengan gradasi yang berbeda
|
| 1508 |
def get_color(slope):
|
|
|
|
| 1534 |
# Buat trace bar, TANPA argumen 'title'
|
| 1535 |
bar_trace = go.Bar(
|
| 1536 |
x=data_sorted[col_operator].astype(str),
|
| 1537 |
+
y=data_sorted["daily_avg"],
|
| 1538 |
marker=dict(
|
| 1539 |
color=colors,
|
| 1540 |
line=dict(width=2, color="rgba(0,0,0,0.2)")
|
| 1541 |
),
|
| 1542 |
+
text=[f"{v:.1f}" for v in data_sorted["daily_avg"]],
|
| 1543 |
textposition="outside",
|
| 1544 |
hovertemplate=(
|
| 1545 |
"<b>%{x}</b><br>" +
|
| 1546 |
+
"Daily Avg: %{y:.2f}<br>" +
|
| 1547 |
"Trend Slope: %{customdata[0]:+.3f}<br>" +
|
| 1548 |
"Total Events: %{customdata[1]}<br>" +
|
| 1549 |
+
"Days Active: %{customdata[2]}<br>" +
|
| 1550 |
"<extra></extra>"
|
| 1551 |
),
|
| 1552 |
+
customdata=np.stack([data_sorted["slope"], data_sorted["total_events"], data_sorted["n_days"]], axis=-1)
|
| 1553 |
)
|
| 1554 |
|
| 1555 |
# Buat figure dan tambahkan trace
|
|
|
|
| 1562 |
height=450,
|
| 1563 |
margin=dict(l=50, r=20, t=60, b=120),
|
| 1564 |
xaxis_title="<b>Operator ID</b>",
|
| 1565 |
+
yaxis_title="<b>Daily Avg Events</b>",
|
| 1566 |
font=dict(family="Segoe UI", size=12),
|
| 1567 |
bargap=0.3,
|
| 1568 |
plot_bgcolor="rgba(0,0,0,0)",
|
|
|
|
| 1594 |
st.markdown("### OB HAULER Analysis")
|
| 1595 |
ob_worsening = len(top_ob[top_ob['slope'] > 0])
|
| 1596 |
ob_improving = len(top_ob[top_ob['slope'] < 0])
|
| 1597 |
+
ob_avg_risk = top_ob['daily_avg'].mean()
|
| 1598 |
+
ob_max_risk = top_ob['daily_avg'].max()
|
| 1599 |
ob_insights = []
|
| 1600 |
if ob_worsening > ob_improving:
|
| 1601 |
ob_insights.append(f"{ob_worsening} out of 10 top risk operators are showing <span class='trend-up'>worsening</span> trends, indicating potential fatigue issues in this fleet type.")
|
| 1602 |
else:
|
| 1603 |
ob_insights.append(f"{ob_improving} out of 10 top risk operators are showing <span class='trend-down'>improvement</span>, suggesting effective fatigue management strategies.")
|
| 1604 |
+
ob_insights.append(f"Average risk level among top 10 operators is {ob_avg_risk:.2f} events per day with maximum {ob_max_risk:.2f}.")
|
| 1605 |
|
| 1606 |
for insight in ob_insights:
|
| 1607 |
st.markdown(f"""
|
|
|
|
| 1619 |
st.markdown("### HAULING COAL Analysis")
|
| 1620 |
coal_worsening = len(top_coal[top_coal['slope'] > 0])
|
| 1621 |
coal_improving = len(top_coal[top_coal['slope'] < 0])
|
| 1622 |
+
coal_avg_risk = top_coal['daily_avg'].mean()
|
| 1623 |
+
coal_max_risk = top_coal['daily_avg'].max()
|
| 1624 |
coal_insights = []
|
| 1625 |
if coal_worsening > coal_improving:
|
| 1626 |
coal_insights.append(f"{coal_worsening} out of 10 top risk operators are showing <span class='trend-up'>worsening</span> trends, requiring immediate attention.")
|
| 1627 |
else:
|
| 1628 |
coal_insights.append(f"{coal_improving} out of 10 top risk operators are showing <span class='trend-down'>improvement</span>, indicating positive trends in safety management.")
|
| 1629 |
+
coal_insights.append(f"Average risk level among top 10 operators is {coal_avg_risk:.2f} events per day with maximum {coal_max_risk:.2f}.")
|
| 1630 |
|
| 1631 |
for insight in coal_insights:
|
| 1632 |
st.markdown(f"""
|
|
|
|
| 1653 |
recommendations = {}
|
| 1654 |
if not top_ob.empty:
|
| 1655 |
ob_worsening = len(top_ob[top_ob['slope'] > 0])
|
| 1656 |
+
ob_avg_risk = top_ob['daily_avg'].mean()
|
| 1657 |
if ob_worsening > 5: # Lebih dari setengah
|
| 1658 |
recommendations['ob'] = "Implement immediate fatigue monitoring protocols for operators showing worsening trends."
|
| 1659 |
reason_ob = "High percentage of operators showing increasing risk trends indicates potential systemic fatigue issues requiring immediate intervention."
|
|
|
|
| 1667 |
|
| 1668 |
if not top_coal.empty:
|
| 1669 |
coal_worsening = len(top_coal[top_coal['slope'] > 0])
|
| 1670 |
+
coal_avg_risk = top_coal['daily_avg'].mean()
|
| 1671 |
if coal_worsening > 5: # Lebih dari setengah
|
| 1672 |
recommendations['coal'] = "Implement immediate fatigue monitoring protocols for operators showing worsening trends."
|
| 1673 |
reason_coal = "High percentage of operators showing increasing risk trends indicates potential systemic fatigue issues requiring immediate intervention."
|
|
|
|
| 1714 |
except Exception as e:
|
| 1715 |
st.error(f"Error in Top 10 Operator analysis: {str(e)}")
|
| 1716 |
st.code(f"Error: {e}", language="python")
|
| 1717 |
+
```
|
|
|
|
|
|
|
| 1718 |
|
| 1719 |
# =================== OBJECTIVE 6: Automated Insights & AI Recommendations =====================
|
| 1720 |
st.subheader("OBJECTIVE 6: Instant Insights & Recommendations")
|