Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -308,15 +308,16 @@ df = load_data()
|
|
| 308 |
|
| 309 |
# ================= HEADER =================
|
| 310 |
st.markdown("""
|
| 311 |
-
<div style="text-align:center; font-family:Arial, sans-serif;">
|
| 312 |
-
<h1 style="color:#154D9C; font-weight:bold; margin
|
| 313 |
-
|
|
|
|
|
|
|
| 314 |
Daily trend insights derived from 13–16 December 2023 data
|
| 315 |
</p>
|
| 316 |
-
<p style="font-size:10px; color:#7d7d7d; margin
|
| 317 |
-
© 2025 Bukit Technology. All rights reserved.
|
| 318 |
</p>
|
| 319 |
-
<hr style="border:0; height:1px; background-color:#e0e0e0; margin-top:8px; margin-bottom:8px;">
|
| 320 |
</div>
|
| 321 |
""", unsafe_allow_html=True)
|
| 322 |
|
|
@@ -1456,33 +1457,33 @@ else:
|
|
| 1456 |
# ================= OBJECTIVE 6 =================
|
| 1457 |
st.markdown('<h3 class="objective-title">OBJECTIVE 6: Insights & Mitigation — How Can Red Pressure Alarms Be Reduced?</h3>', unsafe_allow_html=True)
|
| 1458 |
|
| 1459 |
-
# --- DATA PREP (
|
| 1460 |
# Front tyre stats — fallback ke "—" jika NaN
|
| 1461 |
front_pressure_avg = dff[dff['Position'].isin([1, 2])]['Pressure (psi)'].mean()
|
| 1462 |
front_temp_avg = dff[dff['Position'].isin([1, 2])]['Temperature (°C)'].mean()
|
| 1463 |
front_pressure_avg_str = f"{front_pressure_avg:.1f}" if pd.notna(front_pressure_avg) else "—"
|
| 1464 |
front_temp_avg_str = f"{front_temp_avg:.1f}" if pd.notna(front_temp_avg) else "—"
|
| 1465 |
|
| 1466 |
-
# Hourly alarm stats
|
| 1467 |
-
|
|
|
|
| 1468 |
total_alarms = hourly_counts.sum()
|
|
|
|
|
|
|
|
|
|
| 1469 |
if total_alarms > 0 and not hourly_counts.empty:
|
| 1470 |
dominant_hour = int(hourly_counts.idxmax())
|
| 1471 |
dominant_percentage = (hourly_counts[dominant_hour] / total_alarms) * 100
|
| 1472 |
-
else:
|
| 1473 |
-
dominant_hour = None
|
| 1474 |
-
dominant_percentage = 0.0
|
| 1475 |
|
| 1476 |
-
# Zone alarm stats
|
| 1477 |
-
zone_counts =
|
|
|
|
|
|
|
| 1478 |
if not zone_counts.empty and total_alarms > 0:
|
| 1479 |
top_zone = str(zone_counts.index[0])
|
| 1480 |
top_zone_percentage = (zone_counts.iloc[0] / total_alarms) * 100
|
| 1481 |
-
else:
|
| 1482 |
-
top_zone = "—"
|
| 1483 |
-
top_zone_percentage = 0.0
|
| 1484 |
|
| 1485 |
-
# Correlation —
|
| 1486 |
def safe_corr(x, y):
|
| 1487 |
valid = x.notna() & y.notna()
|
| 1488 |
if valid.sum() < 2:
|
|
@@ -1493,69 +1494,68 @@ def safe_corr(x, y):
|
|
| 1493 |
front_df = dff[dff['Position'].isin([1, 2])]
|
| 1494 |
rear_df = dff[dff['Position'].isin([3, 4])]
|
| 1495 |
|
| 1496 |
-
|
| 1497 |
-
|
| 1498 |
|
| 1499 |
-
# Format korelasi:
|
| 1500 |
-
|
| 1501 |
-
|
| 1502 |
|
| 1503 |
-
#
|
|
|
|
|
|
|
|
|
|
| 1504 |
insight_lines = []
|
| 1505 |
|
| 1506 |
-
line1 = f"1. Front tyres (Pos 1 & 2)
|
| 1507 |
-
if pd.notna(front_pressure_avg) and front_pressure_avg >
|
| 1508 |
-
line1 += "
|
| 1509 |
insight_lines.append(line1)
|
| 1510 |
|
| 1511 |
if dominant_hour is not None:
|
| 1512 |
insight_lines.append(
|
| 1513 |
-
f"2. Peak alarm
|
| 1514 |
)
|
| 1515 |
else:
|
| 1516 |
-
insight_lines.append("2. No
|
| 1517 |
|
| 1518 |
insight_lines.append(
|
| 1519 |
-
f"3. Front
|
| 1520 |
-
f"
|
| 1521 |
)
|
| 1522 |
|
| 1523 |
-
if top_zone != "—":
|
| 1524 |
-
insight_lines.append(
|
|
|
|
|
|
|
| 1525 |
else:
|
| 1526 |
-
insight_lines.append("4.
|
| 1527 |
|
| 1528 |
-
|
| 1529 |
-
|
| 1530 |
-
|
| 1531 |
-
|
| 1532 |
|
| 1533 |
-
|
| 1534 |
-
if pd.notna(front_pressure_avg):
|
| 1535 |
-
# Jika pressure jauh dari ideal (misal 100-110 psi), tambahkan warning
|
| 1536 |
-
ideal_low = 100
|
| 1537 |
-
ideal_high = 110
|
| 1538 |
-
if front_pressure_avg < ideal_low or front_pressure_avg > ideal_high:
|
| 1539 |
-
action_lines.append(f"1. Calibrate front tyre pressure: current {front_pressure_avg_str} psi deviates from optimal range ({ideal_low}–{ideal_high} psi). Front temperature {front_temp_avg_str}°C suggests heat buildup; correlate with load/terrain.")
|
| 1540 |
-
else:
|
| 1541 |
-
action_lines.append(f"• Monitor front tyre pressure: current {front_pressure_avg_str} psi is within operational range.")
|
| 1542 |
-
# 2. Peak alarm di Position 1 — fokus mekanikal
|
| 1543 |
-
action_lines.append(
|
| 1544 |
-
f"2. Position 1 triggers 238 alarms — inspect for uneven load distribution, misalignment, or brake drag."
|
| 1545 |
-
)
|
| 1546 |
|
| 1547 |
-
#
|
| 1548 |
-
|
| 1549 |
-
|
| 1550 |
-
|
| 1551 |
-
|
| 1552 |
-
f"4. Low rear speed–temp correlation (r = {corr_rear_str}) indicates rear tyres operate under stable conditions. {top_zone_percentage:.1f}% of alarms in Parking 2 — inspect road surface, debris, or operational practices in this zone."
|
| 1553 |
-
)
|
| 1554 |
|
| 1555 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1556 |
|
| 1557 |
-
#
|
| 1558 |
-
st.markdown('<h4 style="text-align:center; margin:10px 0 5px 0; font-weight:bold;">
|
| 1559 |
st.markdown(f"""
|
| 1560 |
<div class="insight-box">
|
| 1561 |
<div class="content" style="text-align:left;">
|
|
@@ -1564,11 +1564,20 @@ st.markdown(f"""
|
|
| 1564 |
</div>
|
| 1565 |
""", unsafe_allow_html=True)
|
| 1566 |
|
| 1567 |
-
st.markdown('<h4 style="text-align:center; margin:15px 0 5px 0; font-weight:bold;">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1568 |
st.markdown(f"""
|
| 1569 |
<div class="insight-box">
|
| 1570 |
<div class="content" style="text-align:left;">
|
| 1571 |
-
{
|
| 1572 |
</div>
|
| 1573 |
</div>
|
| 1574 |
""", unsafe_allow_html=True)
|
|
|
|
| 308 |
|
| 309 |
# ================= HEADER =================
|
| 310 |
st.markdown("""
|
| 311 |
+
<div style="text-align:center; font-family:Arial, sans-serif; margin-bottom:16px;">
|
| 312 |
+
<h1 style="color:#154D9C; font-weight:bold; margin:0;">
|
| 313 |
+
Tyre Pressure Monitoring System (TPMS) Analytics for Mining Equipments
|
| 314 |
+
</h1>
|
| 315 |
+
<p style="font-size:12px; color:#7d7d7d; margin:4px 0 2px 0;">
|
| 316 |
Daily trend insights derived from 13–16 December 2023 data
|
| 317 |
</p>
|
| 318 |
+
<p style="font-size:10px; color:#7d7d7d; margin:2px 0 0 0;">
|
| 319 |
+
© 2025 Bukit Technology Digital. All rights reserved.
|
| 320 |
</p>
|
|
|
|
| 321 |
</div>
|
| 322 |
""", unsafe_allow_html=True)
|
| 323 |
|
|
|
|
| 1457 |
# ================= OBJECTIVE 6 =================
|
| 1458 |
st.markdown('<h3 class="objective-title">OBJECTIVE 6: Insights & Mitigation — How Can Red Pressure Alarms Be Reduced?</h3>', unsafe_allow_html=True)
|
| 1459 |
|
| 1460 |
+
# --- DATA PREP (aman terhadap NaN, empty, atau division-by-zero) ---
|
| 1461 |
# Front tyre stats — fallback ke "—" jika NaN
|
| 1462 |
front_pressure_avg = dff[dff['Position'].isin([1, 2])]['Pressure (psi)'].mean()
|
| 1463 |
front_temp_avg = dff[dff['Position'].isin([1, 2])]['Temperature (°C)'].mean()
|
| 1464 |
front_pressure_avg_str = f"{front_pressure_avg:.1f}" if pd.notna(front_pressure_avg) else "—"
|
| 1465 |
front_temp_avg_str = f"{front_temp_avg:.1f}" if pd.notna(front_temp_avg) else "—"
|
| 1466 |
|
| 1467 |
+
# Hourly alarm stats — hanya Red & Amber (ignore No Alarm)
|
| 1468 |
+
alarm_df = dff[dff['Alarm Type'].isin(['Red', 'Amber'])]
|
| 1469 |
+
hourly_counts = alarm_df['hour'].value_counts().reindex(range(24), fill_value=0)
|
| 1470 |
total_alarms = hourly_counts.sum()
|
| 1471 |
+
|
| 1472 |
+
dominant_hour = None
|
| 1473 |
+
dominant_percentage = 0.0
|
| 1474 |
if total_alarms > 0 and not hourly_counts.empty:
|
| 1475 |
dominant_hour = int(hourly_counts.idxmax())
|
| 1476 |
dominant_percentage = (hourly_counts[dominant_hour] / total_alarms) * 100
|
|
|
|
|
|
|
|
|
|
| 1477 |
|
| 1478 |
+
# Zone alarm stats — hanya Red & Amber
|
| 1479 |
+
zone_counts = alarm_df['Zone'].value_counts()
|
| 1480 |
+
top_zone = "—"
|
| 1481 |
+
top_zone_percentage = 0.0
|
| 1482 |
if not zone_counts.empty and total_alarms > 0:
|
| 1483 |
top_zone = str(zone_counts.index[0])
|
| 1484 |
top_zone_percentage = (zone_counts.iloc[0] / total_alarms) * 100
|
|
|
|
|
|
|
|
|
|
| 1485 |
|
| 1486 |
+
# Correlation — aman terhadap NaN
|
| 1487 |
def safe_corr(x, y):
|
| 1488 |
valid = x.notna() & y.notna()
|
| 1489 |
if valid.sum() < 2:
|
|
|
|
| 1494 |
front_df = dff[dff['Position'].isin([1, 2])]
|
| 1495 |
rear_df = dff[dff['Position'].isin([3, 4])]
|
| 1496 |
|
| 1497 |
+
corr_pressure_temp_front = safe_corr(front_df['Pressure (psi)'], front_df['Temperature (°C)'])
|
| 1498 |
+
corr_speed_temp_rear = safe_corr(rear_df['Speed (km/h)'], rear_df['Temperature (°C)'])
|
| 1499 |
|
| 1500 |
+
# Format korelasi: 2 desimal, hindari -0.00
|
| 1501 |
+
corr_pressure_temp_front_str = f"{corr_pressure_temp_front:+.2f}".replace("-0.00", "0.00")
|
| 1502 |
+
corr_speed_temp_rear_str = f"{corr_speed_temp_rear:+.2f}".replace("-0.00", "0.00")
|
| 1503 |
|
| 1504 |
+
# Position 1 alarm count (Red + Amber only)
|
| 1505 |
+
pos1_alarm_count = alarm_df[alarm_df['Position'] == 1].shape[0]
|
| 1506 |
+
|
| 1507 |
+
# ================= INSIGHTS (dynamic, fact-based) =================
|
| 1508 |
insight_lines = []
|
| 1509 |
|
| 1510 |
+
line1 = f"1. Front tyres (Pos 1 & 2): avg pressure {front_pressure_avg_str} psi, avg temperature {front_temp_avg_str}°C."
|
| 1511 |
+
if pd.notna(front_pressure_avg) and front_pressure_avg > 115:
|
| 1512 |
+
line1 += " Exceeds ideal inflation range (100–110 psi), indicating over-pressure risk."
|
| 1513 |
insight_lines.append(line1)
|
| 1514 |
|
| 1515 |
if dominant_hour is not None:
|
| 1516 |
insight_lines.append(
|
| 1517 |
+
f"2. Peak Red/Amber alarm concentration at {dominant_hour:02d}:00 ({dominant_percentage:.1f}% of total)."
|
| 1518 |
)
|
| 1519 |
else:
|
| 1520 |
+
insight_lines.append("2. No statistically dominant hourly alarm peak.")
|
| 1521 |
|
| 1522 |
insight_lines.append(
|
| 1523 |
+
f"3. Front pressure–temperature correlation: r = {corr_pressure_temp_front_str}; "
|
| 1524 |
+
f"Rear speed–temperature correlation: r = {corr_speed_temp_rear_str}."
|
| 1525 |
)
|
| 1526 |
|
| 1527 |
+
if top_zone != "—" and top_zone_percentage >= 10.0:
|
| 1528 |
+
insight_lines.append(
|
| 1529 |
+
f"4. Zone {top_zone} accounts for {top_zone_percentage:.1f}% of alarms — highest-risk location."
|
| 1530 |
+
)
|
| 1531 |
else:
|
| 1532 |
+
insight_lines.append("4. Alarm distribution appears relatively uniform across zones.")
|
| 1533 |
|
| 1534 |
+
if pos1_alarm_count > 0:
|
| 1535 |
+
insight_lines.append(
|
| 1536 |
+
f"5. Position 1 recorded {pos1_alarm_count} Red/Amber alarms — highest of any wheel position."
|
| 1537 |
+
)
|
| 1538 |
|
| 1539 |
+
insight_text = "<br>".join(insight_lines)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1540 |
|
| 1541 |
+
# ================= SUMMARY (HARD-CODED — EXACTLY AS PROVIDED) =================
|
| 1542 |
+
summary_text = """
|
| 1543 |
+
1. Front tyres are predominantly exposed to over-pressure risk, while rear tyres are prone to under-pressure events linked to ambient temperature variation, particularly during morning and evening operations.<br>
|
| 1544 |
+
2. Overall, over-pressure alarms significantly exceed under-pressure alarms, and pressure behaviour is not correlated with vehicle speed, suggesting that temperature rise is driven by operational and environmental factors rather than driving behaviour.
|
| 1545 |
+
"""
|
|
|
|
|
|
|
| 1546 |
|
| 1547 |
+
# ================= RECOMMENDATION (HARD-CODED — EXACTLY AS PROVIDED) =================
|
| 1548 |
+
recommendation_text = """
|
| 1549 |
+
1. Adjust cold inflation pressures based on actual axle loads, particularly for front tyres.<br>
|
| 1550 |
+
2. Apply shift-based pressure management to account for ambient temperature changes affecting rear tyres.<br>
|
| 1551 |
+
3. Strengthen leak detection and valve inspections for tyres with recurring under-pressure events.<br>
|
| 1552 |
+
4. Optimise TPMS alarm thresholds to reduce nuisance alarms and prevent alarm fatigue.<br>
|
| 1553 |
+
5. Address non-speed temperature drivers through haul road improvement, payload control, and reduced idle under load.<br>
|
| 1554 |
+
6. Use TPMS trends to enable proactive tyre health monitoring and maintenance planning.
|
| 1555 |
+
"""
|
| 1556 |
|
| 1557 |
+
# ================= RENDER =================
|
| 1558 |
+
st.markdown('<h4 style="text-align:center; margin:10px 0 5px 0; font-weight:bold;">INSIGHTS</h4>', unsafe_allow_html=True)
|
| 1559 |
st.markdown(f"""
|
| 1560 |
<div class="insight-box">
|
| 1561 |
<div class="content" style="text-align:left;">
|
|
|
|
| 1564 |
</div>
|
| 1565 |
""", unsafe_allow_html=True)
|
| 1566 |
|
| 1567 |
+
st.markdown('<h4 style="text-align:center; margin:15px 0 5px 0; font-weight:bold;">SUMMARY</h4>', unsafe_allow_html=True)
|
| 1568 |
+
st.markdown(f"""
|
| 1569 |
+
<div class="insight-box">
|
| 1570 |
+
<div class="content" style="text-align:left;">
|
| 1571 |
+
{summary_text}
|
| 1572 |
+
</div>
|
| 1573 |
+
</div>
|
| 1574 |
+
""", unsafe_allow_html=True)
|
| 1575 |
+
|
| 1576 |
+
st.markdown('<h4 style="text-align:center; margin:15px 0 5px 0; font-weight:bold;">RECOMMENDATION & RISK MITIGATION</h4>', unsafe_allow_html=True)
|
| 1577 |
st.markdown(f"""
|
| 1578 |
<div class="insight-box">
|
| 1579 |
<div class="content" style="text-align:left;">
|
| 1580 |
+
{recommendation_text}
|
| 1581 |
</div>
|
| 1582 |
</div>
|
| 1583 |
""", unsafe_allow_html=True)
|