Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -723,90 +723,100 @@ def generate_objective2_insights(df: pd.DataFrame) -> str:
|
|
| 723 |
|
| 724 |
# === RENDER INSIGHT BOX ===
|
| 725 |
# =============== INSIGHT 2 (Ringkas & Fokus ke Red & Amber) ===============
|
| 726 |
-
|
| 727 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 728 |
else:
|
| 729 |
-
# Filter
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
].copy()
|
| 733 |
|
| 734 |
if red_amber_data.empty:
|
| 735 |
insight_lines = ["β’ No Red or Amber alarms detected in the dataset."]
|
| 736 |
else:
|
| 737 |
-
|
| 738 |
-
red_alarms = red_amber_data['Alarm Status'].str.contains('Red', case=False).sum()
|
| 739 |
-
amber_alarms = red_amber_data['Alarm Status'].str.contains('Amber', case=False).sum()
|
| 740 |
-
total_red_amber = len(red_amber_data)
|
| 741 |
-
|
| 742 |
-
# Kelompok waktu dominan: hanya 2 band utama (sesuai preferensi)
|
| 743 |
-
def hour_to_band(h):
|
| 744 |
-
if 12 <= h < 18:
|
| 745 |
-
return "12:00β18:00 (Afternoon)"
|
| 746 |
-
elif (18 <= h <= 23) or (0 <= h < 6):
|
| 747 |
-
return "18:00β00:00 (Evening/Night)"
|
| 748 |
-
else:
|
| 749 |
-
return "06:00β12:00 (Morning)" # fallback, jarang muncul
|
| 750 |
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
|
|
|
| 754 |
|
| 755 |
-
#
|
| 756 |
-
dominant_band = top2.index[0] if len(top2) > 0 else "β"
|
| 757 |
-
second_band = top2.index[1] if len(top2) > 1 else "β"
|
| 758 |
-
dom_pct = (top2.iloc[0] / total_red_amber * 100) if len(top2) > 0 else 0
|
| 759 |
-
sec_pct = (top2.iloc[1] / total_red_amber * 100) if len(top2) > 1 else 0
|
| 760 |
-
|
| 761 |
-
# Insight utama
|
| 762 |
-
insight_lines = [
|
| 763 |
-
# f"β’ Total Red alarms: {red_alarms}, Amber alarms: {amber_alarms} (combined: {total_red_amber})",
|
| 764 |
-
# f"β’ Dominant time band: {dominant_band} ({dom_pct:.1f}%)",
|
| 765 |
-
# f"β’ Second-dominant time band: {second_band} ({sec_pct:.1f}%)"
|
| 766 |
-
]
|
| 767 |
-
|
| 768 |
-
# Helper: peak hour (Red/Amber) dalam subset
|
| 769 |
-
def peak_hour_in(data, alarm_type):
|
| 770 |
-
subset = data[data['Alarm Status'].str.contains(alarm_type, case=False, na=False)]
|
| 771 |
-
if not subset.empty:
|
| 772 |
-
counts = subset['hour'].value_counts()
|
| 773 |
-
h = int(counts.idxmax())
|
| 774 |
-
c = int(counts.max())
|
| 775 |
-
return h, c
|
| 776 |
-
return None, 0
|
| 777 |
-
|
| 778 |
-
# Loop Position 1β4 (sesuai preferensi: breakdown per position)
|
| 779 |
for pos in [1, 2, 3, 4]:
|
| 780 |
-
#
|
| 781 |
-
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
# Band
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
]
|
| 790 |
|
| 791 |
-
# Band 1
|
| 792 |
if not band1.empty:
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 801 |
if not band2.empty:
|
| 802 |
-
|
| 803 |
-
if
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
insight_lines.append(f"β’ Position {pos}, 18:00β00:00: Peak
|
| 808 |
-
|
| 809 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 810 |
insight_text = "\n".join(insight_lines)
|
| 811 |
|
| 812 |
st.markdown(f"""
|
|
@@ -816,20 +826,14 @@ st.markdown(f"""
|
|
| 816 |
white-space: pre-line;
|
| 817 |
font-family: 'Segoe UI', sans-serif;
|
| 818 |
line-height: 1.6;
|
|
|
|
| 819 |
">
|
| 820 |
{insight_text}
|
| 821 |
</div>
|
| 822 |
-
<div style="
|
| 823 |
-
font-size: 0.85em;
|
| 824 |
-
color: #6C757D;
|
| 825 |
-
margin-top: 16px;
|
| 826 |
-
text-align: right;
|
| 827 |
-
font-weight: 500;
|
| 828 |
-
">
|
| 829 |
-
</div>
|
| 830 |
</div>
|
| 831 |
""", unsafe_allow_html=True)
|
| 832 |
-
|
|
|
|
| 833 |
# ================= OBJECTIVE 3 =================
|
| 834 |
st.markdown('<h3 class="objective-title">OBJECTIVE 3: Correlation β How Does Heat Influence Pressure and Which Tyres Trigger Red Alarms?</h3>', unsafe_allow_html=True)
|
| 835 |
|
|
|
|
| 723 |
|
| 724 |
# === RENDER INSIGHT BOX ===
|
| 725 |
# =============== INSIGHT 2 (Ringkas & Fokus ke Red & Amber) ===============
|
| 726 |
+
# =============== INSIGHT 3 (Objective 3: Red & Amber Alarm Patterns by Time & Position) ===============
|
| 727 |
+
|
| 728 |
+
# Pastikan alarm_data ada dan punya kolom wajib
|
| 729 |
+
required_cols = {'Alarm Status', 'hour', 'Position'}
|
| 730 |
+
if not required_cols.issubset(alarm_data.columns) or alarm_data.empty:
|
| 731 |
+
insight_lines = [
|
| 732 |
+
"β’ No alarm data available for Red or Amber analysis.",
|
| 733 |
+
"β’ Ensure dataset includes 'Alarm Status', 'hour', and 'Position' columns."
|
| 734 |
+
]
|
| 735 |
else:
|
| 736 |
+
# Filter Red & Amber β case-insensitive, aman terhadap NaN
|
| 737 |
+
mask_red = alarm_data['Alarm Status'].str.contains(r'\bRed\b', case=False, na=False)
|
| 738 |
+
mask_amber = alarm_data['Alarm Status'].str.contains(r'\bAmber\b', case=False, na=False)
|
| 739 |
+
red_amber_data = alarm_data[mask_red | mask_amber].copy()
|
| 740 |
|
| 741 |
if red_amber_data.empty:
|
| 742 |
insight_lines = ["β’ No Red or Amber alarms detected in the dataset."]
|
| 743 |
else:
|
| 744 |
+
insight_lines = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 745 |
|
| 746 |
+
# Helper: count alarms per (position, hour, type)
|
| 747 |
+
# β tanpa def, langsung vectorized
|
| 748 |
+
red_amber_data['is_red'] = mask_red.loc[red_amber_data.index]
|
| 749 |
+
red_amber_data['is_amber'] = mask_amber.loc[red_amber_data.index]
|
| 750 |
|
| 751 |
+
# Loop position 1β4
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 752 |
for pos in [1, 2, 3, 4]:
|
| 753 |
+
# Filter by position
|
| 754 |
+
pos_data = red_amber_data[red_amber_data['Position'] == pos]
|
| 755 |
+
if pos_data.empty:
|
| 756 |
+
continue
|
| 757 |
+
|
| 758 |
+
# Band 1: 12:00β18:00 (12 β€ hour β€ 17)
|
| 759 |
+
band1 = pos_data[(pos_data['hour'] >= 12) & (pos_data['hour'] <= 17)]
|
| 760 |
+
# Band 2: 18:00β00:00 (18β23 atau 0β5)
|
| 761 |
+
band2 = pos_data[(pos_data['hour'] >= 18) | (pos_data['hour'] <= 5)]
|
|
|
|
| 762 |
|
| 763 |
+
# βββββ Band 1: 12:00β18:00 βββββ
|
| 764 |
if not band1.empty:
|
| 765 |
+
# Red peak hour in band1
|
| 766 |
+
red_band1 = band1[band1['is_red']]
|
| 767 |
+
if not red_band1.empty:
|
| 768 |
+
hour_counts = red_band1['hour'].value_counts()
|
| 769 |
+
peak_h = int(hour_counts.idxmax())
|
| 770 |
+
count = int(hour_counts.max())
|
| 771 |
+
insight_lines.append(f"β’ Position {pos}, 12:00β18:00: Peak Red alarm at {peak_h:02d}:00 ({count} occurrences).")
|
| 772 |
+
|
| 773 |
+
# Amber peak hour in band1
|
| 774 |
+
amber_band1 = band1[band1['is_amber']]
|
| 775 |
+
if not amber_band1.empty:
|
| 776 |
+
hour_counts = amber_band1['hour'].value_counts()
|
| 777 |
+
peak_h = int(hour_counts.idxmax())
|
| 778 |
+
count = int(hour_counts.max())
|
| 779 |
+
insight_lines.append(f"β’ Position {pos}, 12:00β18:00: Peak Amber alarm at {peak_h:02d}:00 ({count} occurrences).")
|
| 780 |
+
|
| 781 |
+
# βββββ Band 2: 18:00β00:00 βββββ
|
| 782 |
if not band2.empty:
|
| 783 |
+
red_band2 = band2[band2['is_red']]
|
| 784 |
+
if not red_band2.empty:
|
| 785 |
+
hour_counts = red_band2['hour'].value_counts()
|
| 786 |
+
peak_h = int(hour_counts.idxmax())
|
| 787 |
+
count = int(hour_counts.max())
|
| 788 |
+
insight_lines.append(f"β’ Position {pos}, 18:00β00:00: Peak Red alarm at {peak_h:02d}:00 ({count} occurrences).")
|
| 789 |
+
|
| 790 |
+
amber_band2 = band2[band2['is_amber']]
|
| 791 |
+
if not amber_band2.empty:
|
| 792 |
+
hour_counts = amber_band2['hour'].value_counts()
|
| 793 |
+
peak_h = int(hour_counts.idxmax())
|
| 794 |
+
count = int(hour_counts.max())
|
| 795 |
+
insight_lines.append(f"β’ Position {pos}, 18:00β00:00: Peak Amber alarm at {peak_h:02d}:00 ({count} occurrences).")
|
| 796 |
+
|
| 797 |
+
# Jika insight_lines masih kosong (misal: semua alarm di pos 5+), tambahkan fallback
|
| 798 |
+
if not insight_lines:
|
| 799 |
+
insight_lines = ["β’ Red and Amber alarms occur outside standard wheel positions (1β4)."]
|
| 800 |
+
|
| 801 |
+
# =============== TAMBAHKAN INSIGHT NARATIF (SESUAI YANG ANDA TULISKAN) ===============
|
| 802 |
+
# Catatan: Ini bukan hasil komputasi, tapi insight profesional berdasarkan pola umum.
|
| 803 |
+
# Disesuaikan dengan preferensi: business-ready, actionable, tanpa spekulasi root cause.
|
| 804 |
+
|
| 805 |
+
# Cek apakah ada front tyre (pos 1/2) dan rear tyre (pos 3/4) Red/Amber
|
| 806 |
+
has_front_red = not red_amber_data[(red_amber_data['Position'].isin([1, 2])) & (red_amber_data['is_red'])].empty
|
| 807 |
+
has_rear_amber = not red_amber_data[(red_amber_data['Position'].isin([3, 4])) & (red_amber_data['is_amber'])].empty
|
| 808 |
+
|
| 809 |
+
if has_front_red or has_rear_amber:
|
| 810 |
+
# Tambahkan insight berbasis pola operasional (hard-coded, validasi berdasarkan pengalaman lapangan)
|
| 811 |
+
insight_lines.append("")
|
| 812 |
+
insight_lines.append("Front tyres:")
|
| 813 |
+
insight_lines.append("1. The front tyre pressure is high, close to threshold, with a small number of Red alarms. However, as operating time increases, the number of Red alarms rises significantly. Furthermore, during 04:00β06:00, operational slowdown likely occurs β leading to a decrease in Red alarm notifications.")
|
| 814 |
+
|
| 815 |
+
insight_lines.append("")
|
| 816 |
+
insight_lines.append("Rear tyres:")
|
| 817 |
+
insight_lines.append("2. When the unit begins operation, notifications often indicate pressure below the minimum threshold due to initially low rear tyre pressure. As a result, Amber alarms do not occur during operation since pressure remains within the defined threshold range.")
|
| 818 |
+
|
| 819 |
+
# =============== DISPLAY ===============
|
| 820 |
insight_text = "\n".join(insight_lines)
|
| 821 |
|
| 822 |
st.markdown(f"""
|
|
|
|
| 826 |
white-space: pre-line;
|
| 827 |
font-family: 'Segoe UI', sans-serif;
|
| 828 |
line-height: 1.6;
|
| 829 |
+
font-size: 0.95em;
|
| 830 |
">
|
| 831 |
{insight_text}
|
| 832 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 833 |
</div>
|
| 834 |
""", unsafe_allow_html=True)
|
| 835 |
+
|
| 836 |
+
### OBJECTICVE 3
|
| 837 |
# ================= OBJECTIVE 3 =================
|
| 838 |
st.markdown('<h3 class="objective-title">OBJECTIVE 3: Correlation β How Does Heat Influence Pressure and Which Tyres Trigger Red Alarms?</h3>', unsafe_allow_html=True)
|
| 839 |
|