SHELLAPANDIANGANHUNGING commited on
Commit
65db3aa
Β·
verified Β·
1 Parent(s): 491c8e6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +86 -82
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
- if alarm_data.empty:
727
- insight_lines = ["β€’ No alarm data available for Red or Amber analysis."]
 
 
 
 
 
 
 
728
  else:
729
- # Filter hanya Red dan Amber (case-insensitive, robust terhadap NaN)
730
- red_amber_data = alarm_data[
731
- alarm_data['Alarm Status'].str.contains(r'\b(Red|Amber)\b', case=False, na=False)
732
- ].copy()
733
 
734
  if red_amber_data.empty:
735
  insight_lines = ["β€’ No Red or Amber alarms detected in the dataset."]
736
  else:
737
- # Hitung jumlah
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
- red_amber_data['time_band'] = red_amber_data['hour'].apply(hour_to_band)
752
- band_counts = red_amber_data['time_band'].value_counts()
753
- top2 = band_counts.nlargest(2)
 
754
 
755
- # Ambil dominan & kedua dominan (jika ada)
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
- # Band 1: 12:00–18:00
781
- band1 = red_amber_data[
782
- (red_amber_data['Position'] == pos) &
783
- (red_amber_data['hour'].between(12, 17))
784
- ]
785
- # Band 2: 18:00–00:00
786
- band2 = red_amber_data[
787
- (red_amber_data['Position'] == pos) &
788
- ((red_amber_data['hour'] >= 18) | (red_amber_data['hour'] <= 5))
789
- ]
790
 
791
- # Band 1
792
  if not band1.empty:
793
- h_r, c_r = peak_hour_in(band1, 'Red')
794
- if c_r > 0:
795
- insight_lines.append(f"β€’ Position {pos}, 12:00–18:00: Peak Red alarm at {h_r:02d}:00 ({c_r} occurrences).")
796
- h_a, c_a = peak_hour_in(band1, 'Amber')
797
- if c_a > 0:
798
- insight_lines.append(f"β€’ Position {pos}, 12:00–18:00: Peak Amber alarm at {h_a:02d}:00 ({c_a} occurrences).")
799
-
800
- # Band 2
 
 
 
 
 
 
 
 
 
801
  if not band2.empty:
802
- h_r, c_r = peak_hour_in(band2, 'Red')
803
- if c_r > 0:
804
- insight_lines.append(f"β€’ Position {pos}, 18:00–00:00: Peak Red alarm at {h_r:02d}:00 ({c_r} occurrences).")
805
- h_a, c_a = peak_hour_in(band2, 'Amber')
806
- if c_a > 0:
807
- insight_lines.append(f"β€’ Position {pos}, 18:00–00:00: Peak Amber alarm at {h_a:02d}:00 ({c_a} occurrences).")
808
-
809
- # =============== DISPLAY (NEW LINE PER BULLET) ===============
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- #### OBJECTICVE 3
 
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