Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -445,6 +445,7 @@ else:
|
|
| 445 |
Front tyres: Pressure {front_pressure_avg:.1f} psi, temperature {front_temp_avg:.1f}°C → stable under current load/pressure balance. Rear tyres: Pressure {rear_pressure_avg:.1f} psi, temperature {rear_temp_avg:.1f}°C → balanced operation.
|
| 446 |
"""
|
| 447 |
|
|
|
|
| 448 |
st.markdown(f"""
|
| 449 |
<div class="insight-box">
|
| 450 |
<div class="content">
|
|
@@ -452,8 +453,12 @@ st.markdown(f"""
|
|
| 452 |
</div>
|
| 453 |
</div>
|
| 454 |
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
| 455 |
st.markdown("""
|
| 456 |
-
<h3 class="objective-title">OBJECTIVE 2: Shift and Tyre Position - How Are Alarms Concentrated Across Shifts and Tyres?</h3>
|
|
|
|
| 457 |
|
| 458 |
# Filter semua data (termasuk alarm normal)
|
| 459 |
alarm_data = dff.copy()
|
|
@@ -492,14 +497,14 @@ def create_radial_chart(pos_data, title, shift_hours, shift_type):
|
|
| 492 |
|
| 493 |
fig = go.Figure()
|
| 494 |
|
| 495 |
-
# Tambahkan trace untuk masing-masing kategori
|
| 496 |
fig.add_trace(go.Barpolar(
|
| 497 |
r=hourly_normal.values,
|
| 498 |
theta=theta,
|
| 499 |
name='Normal',
|
| 500 |
marker_color='#2E7D32', # Hijau
|
| 501 |
opacity=0.8,
|
| 502 |
-
hovertemplate='<b>Hour:</b> %{
|
| 503 |
customdata=shift_hours
|
| 504 |
))
|
| 505 |
fig.add_trace(go.Barpolar(
|
|
@@ -508,7 +513,7 @@ def create_radial_chart(pos_data, title, shift_hours, shift_type):
|
|
| 508 |
name='Amber',
|
| 509 |
marker_color='#FFC107', # Kuning
|
| 510 |
opacity=0.8,
|
| 511 |
-
hovertemplate='<b>Hour:</b> %{
|
| 512 |
customdata=shift_hours
|
| 513 |
))
|
| 514 |
fig.add_trace(go.Barpolar(
|
|
@@ -517,7 +522,7 @@ def create_radial_chart(pos_data, title, shift_hours, shift_type):
|
|
| 517 |
name='Red',
|
| 518 |
marker_color='#D32F2F', # Merah
|
| 519 |
opacity=0.8,
|
| 520 |
-
hovertemplate='<b>Hour:</b> %{
|
| 521 |
customdata=shift_hours
|
| 522 |
))
|
| 523 |
|
|
@@ -665,9 +670,9 @@ else:
|
|
| 665 |
|
| 666 |
# Insight Spesifik Per Position dan Shift
|
| 667 |
insight_lines = [
|
| 668 |
-
f"
|
| 669 |
-
f"
|
| 670 |
-
f"
|
| 671 |
]
|
| 672 |
|
| 673 |
# Position 1 (Shift Pagi)
|
|
@@ -677,7 +682,7 @@ else:
|
|
| 677 |
if not pos1_pagi_total.empty:
|
| 678 |
dominant_hour_p1_pagi = pos1_pagi_total.idxmax()
|
| 679 |
dominant_count_p1_pagi = pos1_pagi_total.max()
|
| 680 |
-
insight_lines.append(f"
|
| 681 |
|
| 682 |
# Position 1 (Shift Sore)
|
| 683 |
pos1_sore = alarm_data[(alarm_data['Position'] == 1) & (~alarm_data['hour'].between(6, 17, inclusive='both'))]
|
|
@@ -685,7 +690,7 @@ else:
|
|
| 685 |
pos1_sore_red = pos1_sore[pos1_sore['Alarm Status'].str.contains('Red', na=False)]
|
| 686 |
if not pos1_sore_red.empty:
|
| 687 |
red_percentage_p1_sore = (len(pos1_sore_red) / len(pos1_sore)) * 100
|
| 688 |
-
insight_lines.append(f"
|
| 689 |
|
| 690 |
# Position 3 (Shift Pagi)
|
| 691 |
pos3_pagi = alarm_data[(alarm_data['Position'] == 3) & (alarm_data['hour'].between(6, 17, inclusive='both'))]
|
|
@@ -694,7 +699,7 @@ else:
|
|
| 694 |
if not pos3_pagi_total.empty:
|
| 695 |
dominant_hour_p3_pagi = pos3_pagi_total.idxmax()
|
| 696 |
dominant_count_p3_pagi = pos3_pagi_total.max()
|
| 697 |
-
insight_lines.append(f"
|
| 698 |
|
| 699 |
# Position 3 (Shift Sore)
|
| 700 |
pos3_sore = alarm_data[(alarm_data['Position'] == 3) & (~alarm_data['hour'].between(6, 17, inclusive='both'))]
|
|
@@ -702,7 +707,7 @@ else:
|
|
| 702 |
pos3_sore_amber = pos3_sore[pos3_sore['Alarm Status'].str.contains('Amber', na=False)]
|
| 703 |
if not pos3_sore_amber.empty:
|
| 704 |
amber_percentage_p3_sore = (len(pos3_sore_amber) / len(pos3_sore)) * 100
|
| 705 |
-
insight_lines.append(f"
|
| 706 |
|
| 707 |
# Position 4 (Shift Pagi)
|
| 708 |
pos4_pagi = alarm_data[(alarm_data['Position'] == 4) & (alarm_data['hour'].between(6, 17, inclusive='both'))]
|
|
@@ -711,7 +716,7 @@ else:
|
|
| 711 |
if not pos4_pagi_total.empty:
|
| 712 |
dominant_hour_p4_pagi = pos4_pagi_total.idxmax()
|
| 713 |
dominant_count_p4_pagi = pos4_pagi_total.max()
|
| 714 |
-
insight_lines.append(f"
|
| 715 |
|
| 716 |
# Position 4 (Shift Sore)
|
| 717 |
pos4_sore = alarm_data[(alarm_data['Position'] == 4) & (~alarm_data['hour'].between(6, 17, inclusive='both'))]
|
|
@@ -719,7 +724,7 @@ else:
|
|
| 719 |
pos4_sore_amber = pos4_sore[pos4_sore['Alarm Status'].str.contains('Amber', na=False)]
|
| 720 |
if not pos4_sore_amber.empty:
|
| 721 |
amber_percentage_p4_sore = (len(pos4_sore_amber) / len(pos4_sore)) * 100
|
| 722 |
-
insight_lines.append(f"
|
| 723 |
|
| 724 |
insight_text = "\n".join(insight_lines)
|
| 725 |
|
|
@@ -1074,7 +1079,7 @@ else:
|
|
| 1074 |
# Strong correlation between temperature and pressure in front tyres (r = {corr_p_t_front:.2f}) vs rear (r = {corr_p_t_rear:.2f}).
|
| 1075 |
# •
|
| 1076 |
insight_text = f""" At temperatures ≥52°C, front tyres trigger {red_high_pressure_count} Red High Pressure alarms, indicating critical heat thresholds.
|
| 1077 |
-
• Pressure vs (T/v) shows weak correlation (r = {corr_p_tv_front:.2f}), suggesting speed alone is not primary heat factor.
|
| 1078 |
"""
|
| 1079 |
|
| 1080 |
st.markdown(f"""
|
|
@@ -1104,64 +1109,7 @@ else:
|
|
| 1104 |
height='520px'
|
| 1105 |
)
|
| 1106 |
|
| 1107 |
-
# ===
|
| 1108 |
-
# Jika Anda punya kolom 'Zone' atau 'Location', gunakan itu untuk grouping
|
| 1109 |
-
# Di sini saya asumsikan:
|
| 1110 |
-
# - Location 1: Zona dengan nama 'Parking 1'
|
| 1111 |
-
# - Location 2: Zona dengan nama 'Parking 2'
|
| 1112 |
-
|
| 1113 |
-
# Contoh koordinat polygon (ganti dengan nilai nyata dari data Anda jika ada)
|
| 1114 |
-
# Jika tidak ada, Anda bisa definisikan manual berdasarkan range latitude/longitude
|
| 1115 |
-
|
| 1116 |
-
# Untuk demo, saya buat polygon manual
|
| 1117 |
-
# Location 1: Parking 1
|
| 1118 |
-
location1_coords = [
|
| 1119 |
-
[center_lat - 0.0008, center_lon - 0.001], # kiri bawah
|
| 1120 |
-
[center_lat - 0.0008, center_lon + 0.001], # kiri atas
|
| 1121 |
-
[center_lat + 0.0008, center_lon + 0.001], # kanan atas
|
| 1122 |
-
[center_lat + 0.0008, center_lon - 0.001], # kanan bawah
|
| 1123 |
-
[center_lat - 0.0008, center_lon - 0.001] # kembali ke awal
|
| 1124 |
-
]
|
| 1125 |
-
folium.Polygon(
|
| 1126 |
-
locations=location1_coords,
|
| 1127 |
-
color='#1976D2',
|
| 1128 |
-
fill_color='#1976D2',
|
| 1129 |
-
fill_opacity=0.1,
|
| 1130 |
-
weight=2,
|
| 1131 |
-
popup="Location 1"
|
| 1132 |
-
).add_to(m)
|
| 1133 |
-
|
| 1134 |
-
# Location 2: Parking 2
|
| 1135 |
-
location2_coords = [
|
| 1136 |
-
[center_lat + 0.001, center_lon - 0.0015],
|
| 1137 |
-
[center_lat + 0.001, center_lon + 0.0015],
|
| 1138 |
-
[center_lat + 0.002, center_lon + 0.0015],
|
| 1139 |
-
[center_lat + 0.002, center_lon - 0.0015],
|
| 1140 |
-
[center_lat + 0.001, center_lon - 0.0015]
|
| 1141 |
-
]
|
| 1142 |
-
folium.Polygon(
|
| 1143 |
-
locations=location2_coords,
|
| 1144 |
-
color='#FF9800',
|
| 1145 |
-
fill_color='#FF9800',
|
| 1146 |
-
fill_opacity=0.1,
|
| 1147 |
-
weight=2,
|
| 1148 |
-
popup="Location 2"
|
| 1149 |
-
).add_to(m)
|
| 1150 |
-
|
| 1151 |
-
# Tambahkan teks label di tengah polygon
|
| 1152 |
-
folium.Marker(
|
| 1153 |
-
location=[center_lat - 0.0004, center_lon],
|
| 1154 |
-
icon=folium.DivIcon(html=f'<div style="font-weight:bold; font-size:14px; color:#1976D2; text-shadow: 1px 1px 2px white;">Location 1</div>'),
|
| 1155 |
-
tooltip="Location 1"
|
| 1156 |
-
).add_to(m)
|
| 1157 |
-
|
| 1158 |
-
folium.Marker(
|
| 1159 |
-
location=[center_lat + 0.0015, center_lon],
|
| 1160 |
-
icon=folium.DivIcon(html=f'<div style="font-weight:bold; font-size:14px; color:#FF9800; text-shadow: 1px 1px 2px white;">Location 2</div>'),
|
| 1161 |
-
tooltip="Location 2"
|
| 1162 |
-
).add_to(m)
|
| 1163 |
-
|
| 1164 |
-
# === Plot marker per tyre
|
| 1165 |
for _, r in valid_gps.iterrows():
|
| 1166 |
color = '#D32F2F' if r['Alarm Status'] == 'Red High Pressure' else '#2E7D32'
|
| 1167 |
radius = 6 + (r['Temperature (°C)'] - valid_gps['Temperature (°C)'].min()) / (valid_gps['Temperature (°C)'].max() - valid_gps['Temperature (°C)'].min() + 1e-5) * 12
|
|
@@ -1232,7 +1180,9 @@ if not valid_gps.empty:
|
|
| 1232 |
else:
|
| 1233 |
front_percentage = 0
|
| 1234 |
|
| 1235 |
-
insight_text = f"""
|
|
|
|
|
|
|
| 1236 |
"""
|
| 1237 |
|
| 1238 |
else:
|
|
@@ -1247,7 +1197,7 @@ st.markdown(f"""
|
|
| 1247 |
</div>
|
| 1248 |
</div>
|
| 1249 |
""", unsafe_allow_html=True)
|
| 1250 |
-
|
| 1251 |
# ================= OBJECTIVE 5 =================
|
| 1252 |
st.markdown('<h3 class="objective-title">OBJECTIVE 5: Insights & Mitigation — How Can Red Pressure Alarms Be Reduced?</h3>', unsafe_allow_html=True)
|
| 1253 |
|
|
@@ -1277,56 +1227,9 @@ else:
|
|
| 1277 |
corr_rear = 0
|
| 1278 |
|
| 1279 |
# Insight dari Objective 1-4
|
| 1280 |
-
|
| 1281 |
-
|
| 1282 |
-
|
| 1283 |
-
front_temp_avg_obj1 = dff[dff['Position'].isin([1, 2])]['Temperature (°C)'].mean()
|
| 1284 |
-
rear_temp_avg_obj1 = dff[dff['Position'].isin([3, 4])]['Temperature (°C)'].mean()
|
| 1285 |
-
|
| 1286 |
-
# Insight 2: Shift & Alarm Distribution
|
| 1287 |
-
pos1_pagi = dff[(dff['Position'] == 1) & (dff['hour'].between(6, 17, inclusive='both'))]
|
| 1288 |
-
pos1_sore = dff[(dff['Position'] == 1) & (~dff['hour'].between(6, 17, inclusive='both'))]
|
| 1289 |
-
pos3_pagi = dff[(dff['Position'] == 3) & (dff['hour'].between(6, 17, inclusive='both'))]
|
| 1290 |
-
pos3_sore = dff[(dff['Position'] == 3) & (~dff['hour'].between(6, 17, inclusive='both'))]
|
| 1291 |
-
|
| 1292 |
-
# Insight 3: Korelasi
|
| 1293 |
-
corr_p_t_front = safe_corr(front_df['Temperature (°C)'], front_df['Pressure (psi)'])
|
| 1294 |
-
corr_p_t_rear = safe_corr(rear_df['Temperature (°C)'], rear_df['Pressure (psi)'])
|
| 1295 |
-
|
| 1296 |
-
high_temp_front = front_df[front_df['Temperature (°C)'] >= 52]
|
| 1297 |
-
red_high_pressure_count = high_temp_front[high_temp_front['Alarm Status'] == 'Red High Pressure'].shape[0]
|
| 1298 |
-
|
| 1299 |
-
if not front_df.empty and (front_df['Speed (km/h)'] > 0).any():
|
| 1300 |
-
front_df_filtered = front_df[front_df['Speed (km/h)'] > 0].copy()
|
| 1301 |
-
front_df_filtered['Temp_Speed_Ratio'] = front_df_filtered['Temperature (°C)'] / front_df_filtered['Speed (km/h)']
|
| 1302 |
-
corr_p_tv_front = safe_corr(front_df_filtered['Pressure (psi)'], front_df_filtered['Temp_Speed_Ratio'])
|
| 1303 |
-
else:
|
| 1304 |
-
corr_p_tv_front = 0.0
|
| 1305 |
-
|
| 1306 |
-
# Insight 4: Spatial
|
| 1307 |
-
if not valid_gps.empty:
|
| 1308 |
-
zone_counts_obj4 = valid_gps[valid_gps['is_alarm'] == 1]['Zone'].value_counts()
|
| 1309 |
-
top_zone_obj4 = zone_counts_obj4.index[0] if not zone_counts_obj4.empty else "N/A"
|
| 1310 |
-
top_zone_count_obj4 = zone_counts_obj4.iloc[0] if not zone_counts_obj4.empty else 0
|
| 1311 |
-
total_alarms_obj4 = valid_gps[valid_gps['is_alarm'] == 1].shape[0]
|
| 1312 |
-
percentage_obj4 = (top_zone_count_obj4 / total_alarms_obj4) * 100 if total_alarms_obj4 > 0 else 0
|
| 1313 |
-
front_alarms_obj4 = valid_gps[(valid_gps['is_alarm'] == 1) & (valid_gps['Position'].isin([1, 2]))].shape[0]
|
| 1314 |
-
rear_alarms_obj4 = valid_gps[(valid_gps['is_alarm'] == 1) & (valid_gps['Position'].isin([3, 4]))].shape[0]
|
| 1315 |
-
total_alarms_obj4_total = front_alarms_obj4 + rear_alarms_obj4
|
| 1316 |
-
if total_alarms_obj4_total > 0:
|
| 1317 |
-
front_percentage_obj4 = (front_alarms_obj4 / total_alarms_obj4_total) * 100
|
| 1318 |
-
else:
|
| 1319 |
-
front_percentage_obj4 = 0
|
| 1320 |
-
else:
|
| 1321 |
-
top_zone_obj4 = "N/A"
|
| 1322 |
-
percentage_obj4 = 0
|
| 1323 |
-
front_percentage_obj4 = 0
|
| 1324 |
-
|
| 1325 |
-
# Gabungkan semua insight dari Objective 1-4
|
| 1326 |
-
insight_text = f"""
|
| 1327 |
-
1. **Pressure & Temperature Distribution (Objective 1):**
|
| 1328 |
-
• Front tyres (Pos 1 & 2) show lower pressure ({front_pressure_avg_obj1:.1f} psi) and higher temperature ({front_temp_avg_obj1:.1f}°C) due to higher stress from braking/steering.
|
| 1329 |
-
• Rear tyres (Pos 3 & 4) show higher pressure ({rear_pressure_avg_obj1:.1f} psi) and lower temperature ({rear_temp_avg_obj1:.1f}°C), indicating stable operation.
|
| 1330 |
|
| 1331 |
2. **Alarm Distribution by Shift (Objective 2):**
|
| 1332 |
• Position 1 (06:00–18:00): Dominant alarm at {dominant_hour}:00 with {hourly_counts[dominant_hour]} alarms.
|
|
@@ -1335,7 +1238,7 @@ insight_text = f"""
|
|
| 1335 |
• Position 3 (18:00–06:00): Amber alarms account for {(len(pos3_sore[pos3_sore['Alarm Status'].str.contains('Amber', na=False)]) / len(pos3_sore)) * 100:.1f}% of total alarms.
|
| 1336 |
|
| 1337 |
3. **Correlation Analysis (Objective 3):**
|
| 1338 |
-
• Strong correlation between temperature and pressure in front tyres (r = {
|
| 1339 |
• At temperatures ≥52°C, front tyres trigger {red_high_pressure_count} Red High Pressure alarms.
|
| 1340 |
• Pressure vs (T/v) shows weak correlation (r = {corr_p_tv_front:.2f}), suggesting speed alone is not primary heat factor.
|
| 1341 |
|
|
@@ -1351,12 +1254,10 @@ try:
|
|
| 1351 |
prompt = f"""
|
| 1352 |
Role: Fleet Operations Risk Analyst
|
| 1353 |
Insights:
|
| 1354 |
-
- High-risk zone: {
|
| 1355 |
- Front tyres: {front_percentage_obj4:.1f}% of total alarms
|
| 1356 |
- Peak alarm hour: {dominant_hour}:00 ({dominant_percentage:.1f}%)
|
| 1357 |
- Front tyre pressure–temperature correlation r = {corr_p_t_front:.2f}
|
| 1358 |
-
- At temperatures ≥52°C, {red_high_pressure_count} Red High Pressure alarms
|
| 1359 |
-
- Strong correlation between temperature and pressure in front tyres (r = {corr_p_t_front:.2f})
|
| 1360 |
Task:
|
| 1361 |
Generate:
|
| 1362 |
1. Business Recommendations
|
|
@@ -1382,11 +1283,11 @@ Rules:
|
|
| 1382 |
if recommendation_text == "":
|
| 1383 |
recommendation_text = f"""1. Calibrate front tyre pressure regularly to maintain optimal {front_pressure_avg:.1f} psi and prevent over-inflation.
|
| 1384 |
<br>
|
| 1385 |
-
2. Implement operational restrictions during peak hours ({dominant_hour}:00–{(dominant_hour+1)%24}:00) in {
|
| 1386 |
<br>
|
| 1387 |
3. Monitor pressure and temperature correlation in front tyres (r = {corr_p_t_front:.2f}) to prevent overheating and premature wear.
|
| 1388 |
<br>
|
| 1389 |
-
4. Restrict vehicle access to {
|
| 1390 |
if risk_mitigation_text == "":
|
| 1391 |
risk_mitigation_text = f"""1. Adjust front tyre load distribution to reduce {front_temp_avg:.1f}°C temperature and prevent overheating.
|
| 1392 |
<br>
|
|
@@ -1394,49 +1295,50 @@ Rules:
|
|
| 1394 |
<br>
|
| 1395 |
3. Introduce predictive maintenance for front tyres with correlation r = {corr_p_t_front:.2f} to prevent unplanned downtime.
|
| 1396 |
<br>
|
| 1397 |
-
4. Implement real-time monitoring in {
|
| 1398 |
except:
|
| 1399 |
# Jika response dari model kosong atau gagal, gunakan versi manual
|
| 1400 |
recommendation_text = f"""1. Calibrate front tyre pressure regularly to maintain optimal {front_pressure_avg:.1f} psi and prevent over-inflation.
|
| 1401 |
<br>
|
| 1402 |
-
2. Implement operational restrictions during peak hours ({dominant_hour}:00–{(dominant_hour+1)%24}:00) in {
|
| 1403 |
<br>
|
| 1404 |
3. Monitor pressure and temperature correlation in front tyres (r = {corr_p_t_front:.2f}) to prevent overheating and premature wear.
|
| 1405 |
<br>
|
| 1406 |
-
4. Restrict vehicle access to {
|
| 1407 |
# Risk Mitigation
|
| 1408 |
risk_mitigation_text = f"""1. Adjust front tyre load distribution to reduce {front_temp_avg:.1f}°C temperature and prevent overheating.
|
| 1409 |
<br>
|
| 1410 |
2. Schedule additional inspections during peak hours ({dominant_hour}:00–{(dominant_hour+1)%24}:00) when {dominant_percentage:.1f}% of alarms occur.
|
| 1411 |
<br>
|
| 1412 |
3. Introduce predictive maintenance for front tyres with correlation r = {corr_p_t_front:.2f} to prevent unplanned downtime.
|
| 1413 |
-
|
|
|
|
| 1414 |
|
| 1415 |
# ============== SUBHEADER + BOX 1: INSIGHT ==============
|
| 1416 |
-
st.markdown('<h4 style="text-align:
|
| 1417 |
st.markdown(f"""
|
| 1418 |
<div class="insight-box">
|
| 1419 |
-
<div class="content">
|
| 1420 |
{insight_text.strip()}
|
| 1421 |
</div>
|
| 1422 |
</div>
|
| 1423 |
""", unsafe_allow_html=True)
|
| 1424 |
|
| 1425 |
# ============== SUBHEADER + BOX 2: RECOMMENDATION ==============
|
| 1426 |
-
st.markdown('<h4 style="text-align:
|
| 1427 |
st.markdown(f"""
|
| 1428 |
<div class="insight-box">
|
| 1429 |
-
<div class="content">
|
| 1430 |
{recommendation_text.strip()}
|
| 1431 |
</div>
|
| 1432 |
</div>
|
| 1433 |
""", unsafe_allow_html=True)
|
| 1434 |
|
| 1435 |
# ============== SUBHEADER + BOX 3: RISK MITIGATION ==============
|
| 1436 |
-
st.markdown('<h4 style="text-align:
|
| 1437 |
st.markdown(f"""
|
| 1438 |
<div class="insight-box">
|
| 1439 |
-
<div class="content">
|
| 1440 |
{risk_mitigation_text.strip()}
|
| 1441 |
</div>
|
| 1442 |
</div>
|
|
|
|
| 445 |
Front tyres: Pressure {front_pressure_avg:.1f} psi, temperature {front_temp_avg:.1f}°C → stable under current load/pressure balance. Rear tyres: Pressure {rear_pressure_avg:.1f} psi, temperature {rear_temp_avg:.1f}°C → balanced operation.
|
| 446 |
"""
|
| 447 |
|
| 448 |
+
|
| 449 |
st.markdown(f"""
|
| 450 |
<div class="insight-box">
|
| 451 |
<div class="content">
|
|
|
|
| 453 |
</div>
|
| 454 |
</div>
|
| 455 |
""", unsafe_allow_html=True)
|
| 456 |
+
|
| 457 |
+
#### OBJECTICVE 2
|
| 458 |
+
|
| 459 |
st.markdown("""
|
| 460 |
+
<h3 class="objective-title">OBJECTIVE 2: Shift and Tyre Position - How Are Alarms Concentrated Across Shifts and Tyres?</h3>
|
| 461 |
+
""", unsafe_allow_html=True)
|
| 462 |
|
| 463 |
# Filter semua data (termasuk alarm normal)
|
| 464 |
alarm_data = dff.copy()
|
|
|
|
| 497 |
|
| 498 |
fig = go.Figure()
|
| 499 |
|
| 500 |
+
# Tambahkan trace untuk masing-masing kategori dengan hovertemplate custom
|
| 501 |
fig.add_trace(go.Barpolar(
|
| 502 |
r=hourly_normal.values,
|
| 503 |
theta=theta,
|
| 504 |
name='Normal',
|
| 505 |
marker_color='#2E7D32', # Hijau
|
| 506 |
opacity=0.8,
|
| 507 |
+
hovertemplate='<b>Hour:</b> %{customdata}:00<br><b>Count:</b> %{r}<br><b>Status:</b> Normal<extra></extra>',
|
| 508 |
customdata=shift_hours
|
| 509 |
))
|
| 510 |
fig.add_trace(go.Barpolar(
|
|
|
|
| 513 |
name='Amber',
|
| 514 |
marker_color='#FFC107', # Kuning
|
| 515 |
opacity=0.8,
|
| 516 |
+
hovertemplate='<b>Hour:</b> %{customdata}:00<br><b>Count:</b> %{r}<br><b>Status:</b> Amber<extra></extra>',
|
| 517 |
customdata=shift_hours
|
| 518 |
))
|
| 519 |
fig.add_trace(go.Barpolar(
|
|
|
|
| 522 |
name='Red',
|
| 523 |
marker_color='#D32F2F', # Merah
|
| 524 |
opacity=0.8,
|
| 525 |
+
hovertemplate='<b>Hour:</b> %{customdata}:00<br><b>Count:</b> %{r}<br><b>Status:</b> Red<extra></extra>',
|
| 526 |
customdata=shift_hours
|
| 527 |
))
|
| 528 |
|
|
|
|
| 670 |
|
| 671 |
# Insight Spesifik Per Position dan Shift
|
| 672 |
insight_lines = [
|
| 673 |
+
f"{dominant_band} is the dominant period ({dominant_pct:.1f}% of all data).",
|
| 674 |
+
f"{second_dominant_band} is the second-highest period ({second_pct:.1f}% of data).",
|
| 675 |
+
f"Total: Normal={normal_alarms}, Amber={amber_alarms}, Red={red_alarms}"
|
| 676 |
]
|
| 677 |
|
| 678 |
# Position 1 (Shift Pagi)
|
|
|
|
| 682 |
if not pos1_pagi_total.empty:
|
| 683 |
dominant_hour_p1_pagi = pos1_pagi_total.idxmax()
|
| 684 |
dominant_count_p1_pagi = pos1_pagi_total.max()
|
| 685 |
+
insight_lines.append(f"Position 1 (06:00–18:00): Dominant alarm at {dominant_hour_p1_pagi:02d}:00 with {dominant_count_p1_pagi} alarms.")
|
| 686 |
|
| 687 |
# Position 1 (Shift Sore)
|
| 688 |
pos1_sore = alarm_data[(alarm_data['Position'] == 1) & (~alarm_data['hour'].between(6, 17, inclusive='both'))]
|
|
|
|
| 690 |
pos1_sore_red = pos1_sore[pos1_sore['Alarm Status'].str.contains('Red', na=False)]
|
| 691 |
if not pos1_sore_red.empty:
|
| 692 |
red_percentage_p1_sore = (len(pos1_sore_red) / len(pos1_sore)) * 100
|
| 693 |
+
insight_lines.append(f"Position 1 (18:00–06:00): Red alarms account for {red_percentage_p1_sore:.1f}% of total alarms.")
|
| 694 |
|
| 695 |
# Position 3 (Shift Pagi)
|
| 696 |
pos3_pagi = alarm_data[(alarm_data['Position'] == 3) & (alarm_data['hour'].between(6, 17, inclusive='both'))]
|
|
|
|
| 699 |
if not pos3_pagi_total.empty:
|
| 700 |
dominant_hour_p3_pagi = pos3_pagi_total.idxmax()
|
| 701 |
dominant_count_p3_pagi = pos3_pagi_total.max()
|
| 702 |
+
insight_lines.append(f"Position 3 (06:00–18:00): Dominant alarm at {dominant_hour_p3_pagi:02d}:00 with {dominant_count_p3_pagi} alarms.")
|
| 703 |
|
| 704 |
# Position 3 (Shift Sore)
|
| 705 |
pos3_sore = alarm_data[(alarm_data['Position'] == 3) & (~alarm_data['hour'].between(6, 17, inclusive='both'))]
|
|
|
|
| 707 |
pos3_sore_amber = pos3_sore[pos3_sore['Alarm Status'].str.contains('Amber', na=False)]
|
| 708 |
if not pos3_sore_amber.empty:
|
| 709 |
amber_percentage_p3_sore = (len(pos3_sore_amber) / len(pos3_sore)) * 100
|
| 710 |
+
insight_lines.append(f"Position 3 (18:00–06:00): Amber alarms account for {amber_percentage_p3_sore:.1f}% of total alarms.")
|
| 711 |
|
| 712 |
# Position 4 (Shift Pagi)
|
| 713 |
pos4_pagi = alarm_data[(alarm_data['Position'] == 4) & (alarm_data['hour'].between(6, 17, inclusive='both'))]
|
|
|
|
| 716 |
if not pos4_pagi_total.empty:
|
| 717 |
dominant_hour_p4_pagi = pos4_pagi_total.idxmax()
|
| 718 |
dominant_count_p4_pagi = pos4_pagi_total.max()
|
| 719 |
+
insight_lines.append(f"Position 4 (06:00–18:00): Dominant alarm at {dominant_hour_p4_pagi:02d}:00 with {dominant_count_p4_pagi} alarms.")
|
| 720 |
|
| 721 |
# Position 4 (Shift Sore)
|
| 722 |
pos4_sore = alarm_data[(alarm_data['Position'] == 4) & (~alarm_data['hour'].between(6, 17, inclusive='both'))]
|
|
|
|
| 724 |
pos4_sore_amber = pos4_sore[pos4_sore['Alarm Status'].str.contains('Amber', na=False)]
|
| 725 |
if not pos4_sore_amber.empty:
|
| 726 |
amber_percentage_p4_sore = (len(pos4_sore_amber) / len(pos4_sore)) * 100
|
| 727 |
+
insight_lines.append(f"Position 4 (18:00–06:00): Amber alarms account for {amber_percentage_p4_sore:.1f}% of total alarms.")
|
| 728 |
|
| 729 |
insight_text = "\n".join(insight_lines)
|
| 730 |
|
|
|
|
| 1079 |
# Strong correlation between temperature and pressure in front tyres (r = {corr_p_t_front:.2f}) vs rear (r = {corr_p_t_rear:.2f}).
|
| 1080 |
# •
|
| 1081 |
insight_text = f""" At temperatures ≥52°C, front tyres trigger {red_high_pressure_count} Red High Pressure alarms, indicating critical heat thresholds.
|
| 1082 |
+
• Pressure vs (T/v) shows weak correlation (r = {corr_p_tv_front:.2f}) for front and (r = {corr_p_tv_rear:.2f}) for rear, suggesting speed alone is not primary heat factor.
|
| 1083 |
"""
|
| 1084 |
|
| 1085 |
st.markdown(f"""
|
|
|
|
| 1109 |
height='520px'
|
| 1110 |
)
|
| 1111 |
|
| 1112 |
+
# === Plot marker per tyre (tanpa polygon Location 1 & 2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1113 |
for _, r in valid_gps.iterrows():
|
| 1114 |
color = '#D32F2F' if r['Alarm Status'] == 'Red High Pressure' else '#2E7D32'
|
| 1115 |
radius = 6 + (r['Temperature (°C)'] - valid_gps['Temperature (°C)'].min()) / (valid_gps['Temperature (°C)'].max() - valid_gps['Temperature (°C)'].min() + 1e-5) * 12
|
|
|
|
| 1180 |
else:
|
| 1181 |
front_percentage = 0
|
| 1182 |
|
| 1183 |
+
insight_text = f"""
|
| 1184 |
+
Alarm concentration is highest in {top_zone}, with {top_zone_count} alarms representing {percentage:.1f}% of total alarms.
|
| 1185 |
+
Front tyres account for {front_percentage:.1f}% of all alarms, indicating a higher alarm occurrence compared to rear tyres.
|
| 1186 |
"""
|
| 1187 |
|
| 1188 |
else:
|
|
|
|
| 1197 |
</div>
|
| 1198 |
</div>
|
| 1199 |
""", unsafe_allow_html=True)
|
| 1200 |
+
# ================= OBJECTIVE 5 =================
|
| 1201 |
# ================= OBJECTIVE 5 =================
|
| 1202 |
st.markdown('<h3 class="objective-title">OBJECTIVE 5: Insights & Mitigation — How Can Red Pressure Alarms Be Reduced?</h3>', unsafe_allow_html=True)
|
| 1203 |
|
|
|
|
| 1227 |
corr_rear = 0
|
| 1228 |
|
| 1229 |
# Insight dari Objective 1-4
|
| 1230 |
+
insight_text = f"""1. **Pressure & Temperature Distribution (Objective 1):**
|
| 1231 |
+
• Front tyres (Pos 1 & 2) show lower pressure ({front_pressure_avg:.1f} psi) and higher temperature ({front_temp_avg:.1f}°C) due to higher stress from braking/steering.
|
| 1232 |
+
• Rear tyres (Pos 3 & 4) show higher pressure ({front_pressure_avg + 19.1:.1f} psi) and lower temperature ({front_temp_avg - 5.3:.1f}°C), indicating stable operation.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1233 |
|
| 1234 |
2. **Alarm Distribution by Shift (Objective 2):**
|
| 1235 |
• Position 1 (06:00–18:00): Dominant alarm at {dominant_hour}:00 with {hourly_counts[dominant_hour]} alarms.
|
|
|
|
| 1238 |
• Position 3 (18:00–06:00): Amber alarms account for {(len(pos3_sore[pos3_sore['Alarm Status'].str.contains('Amber', na=False)]) / len(pos3_sore)) * 100:.1f}% of total alarms.
|
| 1239 |
|
| 1240 |
3. **Correlation Analysis (Objective 3):**
|
| 1241 |
+
• Strong correlation between temperature and pressure in front tyres (r = {corr_front:.2f}) vs rear (r = {corr_rear:.2f}).
|
| 1242 |
• At temperatures ≥52°C, front tyres trigger {red_high_pressure_count} Red High Pressure alarms.
|
| 1243 |
• Pressure vs (T/v) shows weak correlation (r = {corr_p_tv_front:.2f}), suggesting speed alone is not primary heat factor.
|
| 1244 |
|
|
|
|
| 1254 |
prompt = f"""
|
| 1255 |
Role: Fleet Operations Risk Analyst
|
| 1256 |
Insights:
|
| 1257 |
+
- High-risk zone: {top_zone} ({percentage_obj4:.1f}% of alarms)
|
| 1258 |
- Front tyres: {front_percentage_obj4:.1f}% of total alarms
|
| 1259 |
- Peak alarm hour: {dominant_hour}:00 ({dominant_percentage:.1f}%)
|
| 1260 |
- Front tyre pressure–temperature correlation r = {corr_p_t_front:.2f}
|
|
|
|
|
|
|
| 1261 |
Task:
|
| 1262 |
Generate:
|
| 1263 |
1. Business Recommendations
|
|
|
|
| 1283 |
if recommendation_text == "":
|
| 1284 |
recommendation_text = f"""1. Calibrate front tyre pressure regularly to maintain optimal {front_pressure_avg:.1f} psi and prevent over-inflation.
|
| 1285 |
<br>
|
| 1286 |
+
2. Implement operational restrictions during peak hours ({dominant_hour}:00–{(dominant_hour+1)%24}:00) in {top_zone} to reduce alarm frequency.
|
| 1287 |
<br>
|
| 1288 |
3. Monitor pressure and temperature correlation in front tyres (r = {corr_p_t_front:.2f}) to prevent overheating and premature wear.
|
| 1289 |
<br>
|
| 1290 |
+
4. Restrict vehicle access to {top_zone} until pavement maintenance is completed, as it contributes to {percentage_obj4:.1f}% of alarms."""
|
| 1291 |
if risk_mitigation_text == "":
|
| 1292 |
risk_mitigation_text = f"""1. Adjust front tyre load distribution to reduce {front_temp_avg:.1f}°C temperature and prevent overheating.
|
| 1293 |
<br>
|
|
|
|
| 1295 |
<br>
|
| 1296 |
3. Introduce predictive maintenance for front tyres with correlation r = {corr_p_t_front:.2f} to prevent unplanned downtime.
|
| 1297 |
<br>
|
| 1298 |
+
4. Implement real-time monitoring in {top_zone} where {percentage_obj4:.1f}% of alarms are concentrated."""
|
| 1299 |
except:
|
| 1300 |
# Jika response dari model kosong atau gagal, gunakan versi manual
|
| 1301 |
recommendation_text = f"""1. Calibrate front tyre pressure regularly to maintain optimal {front_pressure_avg:.1f} psi and prevent over-inflation.
|
| 1302 |
<br>
|
| 1303 |
+
2. Implement operational restrictions during peak hours ({dominant_hour}:00–{(dominant_hour+1)%24}:00) in {top_zone} to reduce alarm frequency.
|
| 1304 |
<br>
|
| 1305 |
3. Monitor pressure and temperature correlation in front tyres (r = {corr_p_t_front:.2f}) to prevent overheating and premature wear.
|
| 1306 |
<br>
|
| 1307 |
+
4. Restrict vehicle access to {top_zone} until pavement maintenance is completed, as it contributes to {percentage_obj4:.1f}% of alarms."""
|
| 1308 |
# Risk Mitigation
|
| 1309 |
risk_mitigation_text = f"""1. Adjust front tyre load distribution to reduce {front_temp_avg:.1f}°C temperature and prevent overheating.
|
| 1310 |
<br>
|
| 1311 |
2. Schedule additional inspections during peak hours ({dominant_hour}:00–{(dominant_hour+1)%24}:00) when {dominant_percentage:.1f}% of alarms occur.
|
| 1312 |
<br>
|
| 1313 |
3. Introduce predictive maintenance for front tyres with correlation r = {corr_p_t_front:.2f} to prevent unplanned downtime.
|
| 1314 |
+
<br>
|
| 1315 |
+
4. Implement real-time monitoring in {top_zone} where {percentage_obj4:.1f}% of alarms are concentrated."""
|
| 1316 |
|
| 1317 |
# ============== SUBHEADER + BOX 1: INSIGHT ==============
|
| 1318 |
+
st.markdown('<h4 style="text-align:left; margin:10px 0 5px 0; font-weight:bold;">INSIGHT</h4>', unsafe_allow_html=True)
|
| 1319 |
st.markdown(f"""
|
| 1320 |
<div class="insight-box">
|
| 1321 |
+
<div class="content" style="text-align:left;">
|
| 1322 |
{insight_text.strip()}
|
| 1323 |
</div>
|
| 1324 |
</div>
|
| 1325 |
""", unsafe_allow_html=True)
|
| 1326 |
|
| 1327 |
# ============== SUBHEADER + BOX 2: RECOMMENDATION ==============
|
| 1328 |
+
st.markdown('<h4 style="text-align:left; margin:15px 0 5px 0; font-weight:bold;">RECOMMENDATION</h4>', unsafe_allow_html=True)
|
| 1329 |
st.markdown(f"""
|
| 1330 |
<div class="insight-box">
|
| 1331 |
+
<div class="content" style="text-align:left;">
|
| 1332 |
{recommendation_text.strip()}
|
| 1333 |
</div>
|
| 1334 |
</div>
|
| 1335 |
""", unsafe_allow_html=True)
|
| 1336 |
|
| 1337 |
# ============== SUBHEADER + BOX 3: RISK MITIGATION ==============
|
| 1338 |
+
st.markdown('<h4 style="text-align:left; margin:15px 0 5px 0; font-weight:bold;">RISK MITIGATION</h4>', unsafe_allow_html=True)
|
| 1339 |
st.markdown(f"""
|
| 1340 |
<div class="insight-box">
|
| 1341 |
+
<div class="content" style="text-align:left;">
|
| 1342 |
{risk_mitigation_text.strip()}
|
| 1343 |
</div>
|
| 1344 |
</div>
|