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