SHELLAPANDIANGANHUNGING commited on
Commit
8fa9c78
·
verified ·
1 Parent(s): c868721

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +481 -147
app.py CHANGED
@@ -724,169 +724,502 @@ st.markdown(f"""
724
  # </div>
725
  # """, unsafe_allow_html=True)
726
  st.markdown("""
727
- <h3 class="objective-title">OBJECTIVE 3: Alarm Frequency Analysis — When, Where, and Which Tyres Matter Most?</h3>
728
- <small>*Showing all alarm types: Normal (Green), Amber (Yellow), Red (Red)</small>
729
  """, unsafe_allow_html=True)
730
 
731
  # Filter semua data (termasuk alarm normal)
732
  alarm_data = dff.copy()
733
 
734
- col_b, col_a = st.columns(2)
735
-
736
- # =============== COL B: Donut Charts (Distribusi Alarm per Position - Semua Jenis) ===============
737
- with col_b:
738
- st.markdown('<h5 style="text-align:center; margin-top: 0;">Alarm Distribution by Position</h5>', unsafe_allow_html=True)
739
 
740
- if alarm_data.empty:
741
- st.warning("No data to display.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
742
  else:
743
- # Group alarm status sesuai permintaan Anda
744
- alarm_data['Alarm_Category'] = alarm_data['Alarm Status'].apply(
745
- lambda x: 'Normal' if x == 'No Alarm'
746
- else 'Red Alarm' if x == 'Red High Pressure'
747
- else 'Amber Alarm'
748
- )
749
-
750
- # Buat 4 donut chart
751
- fig_donut = make_subplots(
752
- rows=2, cols=2,
753
- specs=[[{'type':'domain'}, {'type':'domain'}],
754
- [{'type':'domain'}, {'type':'domain'}]],
755
- subplot_titles=['Position 1', 'Position 2', 'Position 3', 'Position 4']
756
- )
757
-
758
- for i, pos in enumerate([1, 2, 3, 4], 1):
759
- pos_data = alarm_data[alarm_data['Position'] == pos]
760
- if not pos_data.empty:
761
- counts = pos_data['Alarm_Category'].value_counts()
762
- labels = counts.index.tolist()
763
- values = counts.values.tolist()
764
-
765
- # Warna: Hijau (Normal), Kuning (Amber), Merah (Red)
766
- color_map = {
767
- 'Normal': '#2E7D32', # Hijau
768
- 'Amber Alarm': '#FFC107', # Kuning
769
- 'Red Alarm': '#D32F2F' # Merah
770
- }
771
- colors = [color_map[label] for label in labels]
772
-
773
- fig_donut.add_trace(
774
- go.Pie(
775
- labels=labels,
776
- values=values,
777
- name=f'Position {pos}',
778
- hole=0.4,
779
- marker_colors=colors
 
 
 
 
 
 
 
 
 
 
 
 
 
 
780
  ),
781
- row=(i - 1) // 2 + 1,
782
- col=(i - 1) % 2 + 1
783
- )
784
-
785
- fig_donut.update_layout(
786
- height=500,
787
- showlegend=True,
788
- margin=dict(t=40, b=20, l=20, r=20),
789
- legend=dict(
790
- orientation="h",
791
- yanchor="bottom",
792
- y=1.02,
793
- xanchor="right",
794
- x=1
795
  )
796
- )
797
- st.plotly_chart(fig_donut, use_container_width=True)
798
-
799
- # =============== COL A: Radial Charts (Count Alarm per Jam - Stacked by Status) ===============
800
- with col_a:
801
- st.markdown('<h5 style="text-align:center; margin-top: 0;">Alarm Count by Hour (Radial - Stacked)</h5>', unsafe_allow_html=True)
802
-
803
- if alarm_data.empty:
804
- st.warning("No data to display.")
805
  else:
806
- # Buat 4 radial chart
807
- fig_radial = make_subplots(
808
- rows=2, cols=2,
809
- specs=[[{'type': 'polar'}, {'type': 'polar'}],
810
- [{'type': 'polar'}, {'type': 'polar'}]],
811
- subplot_titles=['Position 1', 'Position 2', 'Position 3', 'Position 4']
812
- )
813
-
814
- for i, pos in enumerate([1, 2, 3, 4], 1):
815
- pos_data = alarm_data[alarm_data['Position'] == pos].copy()
816
- if not pos_data.empty:
817
- # Kelompokkan jam dan status
818
- hourly_status_counts = pos_data.groupby(['hour', 'Alarm Status']).size().unstack(fill_value=0)
819
-
820
- # Ambil masing-masing kategori
821
- hourly_normal = hourly_status_counts.get('No Alarm', pd.Series(0, index=range(24)))
822
- hourly_amber = hourly_status_counts.get('Amber High Pressure', pd.Series(0, index=range(24))) # Ganti sesuai nama kolom Anda
823
- hourly_red = hourly_status_counts.get('Red High Pressure', pd.Series(0, index=range(24)))
824
-
825
- # Total per jam
826
- total_per_hour = hourly_normal + hourly_amber + hourly_red
827
-
828
- # Sudut: jam 3 → 0° (atas), jam 12 → 90° (kanan), jam 9 → 180° (bawah), jam 6 → 270° (kiri)
829
- theta = [(h - 3) * 15 % 360 for h in range(24)] # Jam 3 pagi = 0°
830
-
831
- # Tambahkan bar polar untuk Normal (hijau)
832
- fig_radial.add_trace(
833
- go.Barpolar(
834
- r=hourly_normal.values,
835
- theta=theta,
836
- name='Normal',
837
- marker_color='#2E7D32', # Hijau
838
- opacity=0.8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
839
  ),
840
- row=(i - 1) // 2 + 1,
841
- col=(i - 1) % 2 + 1
842
- )
843
-
844
- # Tambahkan bar polar untuk Amber (kuning) — ditumpuk di atas Normal
845
- fig_radial.add_trace(
846
- go.Barpolar(
847
- r=hourly_amber.values,
848
- theta=theta,
849
- name='Amber',
850
- marker_color='#FFC107', # Kuning
851
- opacity=0.8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
852
  ),
853
- row=(i - 1) // 2 + 1,
854
- col=(i - 1) % 2 + 1
855
- )
 
 
 
 
 
 
 
 
856
 
857
- # Tambahkan bar polar untuk Red (merah) ditumpuk di atas Amber
858
- fig_radial.add_trace(
859
- go.Barpolar(
860
- r=hourly_red.values,
861
- theta=theta,
862
- name='Red',
863
- marker_color='#D32F2F', # Merah
864
- opacity=0.8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
865
  ),
866
- row=(i - 1) // 2 + 1,
867
- col=(i - 1) % 2 + 1
868
- )
869
-
870
- fig_radial.update_layout(
871
- height=600,
872
- showlegend=False,
873
- margin=dict(t=60, b=20, l=20, r=20),
874
- polar=dict(
875
- angularaxis=dict(
876
- direction="clockwise",
877
- period=24,
878
- rotation=0, # Jam 3 pagi di atas
879
- tickvals=[0, 90, 180, 270],
880
- ticktext=["03:00", "12:00", "21:00", "18:00"],
881
- tickfont=dict(size=12)
882
  ),
883
- radialaxis=dict(
884
- visible=True,
885
- range=[0, max(total_per_hour.max() * 1.1, 1)]
886
- )
887
  )
888
- )
889
- st.plotly_chart(fig_radial, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
890
 
891
  # =============== INSIGHT 3 ===============
892
  if alarm_data.empty:
@@ -932,6 +1265,7 @@ st.markdown(f"""
932
  </div>
933
  </div>
934
  """, unsafe_allow_html=True)
 
935
  # ================= OBJECTIVE 3 =================
936
  st.markdown('<h3 class="objective-title">OBJECTIVE 4: Correlation — How Does Heat Influence Pressure and Which Tyres Trigger Red Alarms?</h3>', unsafe_allow_html=True)
937
 
 
724
  # </div>
725
  # """, unsafe_allow_html=True)
726
  st.markdown("""
727
+ <h3 class="objective-title">OBJECTIVE 3: Alarm Frequency Analysis — Shift-Based Radial Charts</h3>
728
+ <small>*Showing alarm types by shift: Normal (Green), Amber (Yellow), Red (Red)</small>
729
  """, unsafe_allow_html=True)
730
 
731
  # Filter semua data (termasuk alarm normal)
732
  alarm_data = dff.copy()
733
 
734
+ col1, col2 = st.columns(2)
 
 
 
 
735
 
736
+ # =============== COL 1: Position 1 & 3 (Pagi & Sore) ===============
737
+ with col1:
738
+ # Position 1 Pagi
739
+ st.markdown('<h6 style="text-align:center; margin-top: 0;">Position 1 (06:00–18:00)</h6>', unsafe_allow_html=True)
740
+ if not alarm_data[alarm_data['Position'] == 1].empty:
741
+ pos1_data = alarm_data[alarm_data['Position'] == 1].copy()
742
+ pos1_data = pos1_data[pos1_data['hour'].between(6, 17, inclusive='both')]
743
+
744
+ if not pos1_data.empty:
745
+ hourly_status_counts = pos1_data.groupby(['hour', 'Alarm Status']).size().unstack(fill_value=0)
746
+
747
+ hourly_normal = hourly_status_counts.get('No Alarm', pd.Series(0, index=range(6, 18))).reindex(range(6, 18), fill_value=0)
748
+ hourly_amber = hourly_status_counts.get('Amber High Pressure', pd.Series(0, index=range(6, 18))).reindex(range(6, 18), fill_value=0)
749
+ hourly_red = hourly_status_counts.get('Red High Pressure', pd.Series(0, index=range(6, 18))).reindex(range(6, 18), fill_value=0)
750
+
751
+ theta = [(h - 6) * 30 for h in range(6, 18)] # 12 jam * 30° = 360°
752
+
753
+ fig1 = go.Figure()
754
+ fig1.add_trace(go.Barpolar(
755
+ r=hourly_normal.values,
756
+ theta=theta,
757
+ name='Normal',
758
+ marker_color='#2E7D32',
759
+ opacity=0.8
760
+ ))
761
+ fig1.add_trace(go.Barpolar(
762
+ r=hourly_amber.values,
763
+ theta=theta,
764
+ name='Amber',
765
+ marker_color='#FFC107',
766
+ opacity=0.8
767
+ ))
768
+ fig1.add_trace(go.Barpolar(
769
+ r=hourly_red.values,
770
+ theta=theta,
771
+ name='Red',
772
+ marker_color='#D32F2F',
773
+ opacity=0.8
774
+ ))
775
+ fig1.update_layout(
776
+ polar=dict(
777
+ angularaxis=dict(
778
+ direction="clockwise",
779
+ period=12,
780
+ rotation=0,
781
+ tickvals=[0, 90, 180, 270],
782
+ ticktext=["06:00", "12:00", "18:00", "24:00"],
783
+ tickfont=dict(size=10)
784
+ ),
785
+ radialaxis=dict(visible=True)
786
+ ),
787
+ showlegend=False,
788
+ margin=dict(t=30, b=20, l=20, r=20),
789
+ height=250
790
+ )
791
+ st.plotly_chart(fig1, use_container_width=True)
792
+ else:
793
+ st.warning("No data for Position 1 (06:00–18:00)")
794
  else:
795
+ st.warning("No data for Position 1")
796
+
797
+ # Position 1 Sore
798
+ st.markdown('<h6 style="text-align:center; margin-top: 0;">Position 1 (18:00–06:00)</h6>', unsafe_allow_html=True)
799
+ if not alarm_data[alarm_data['Position'] == 1].empty:
800
+ pos1_data = alarm_data[alarm_data['Position'] == 1].copy()
801
+ pos1_data = pos1_data[~pos1_data['hour'].between(6, 17, inclusive='both')]
802
+
803
+ if not pos1_data.empty:
804
+ hourly_status_counts = pos1_data.groupby(['hour', 'Alarm Status']).size().unstack(fill_value=0)
805
+
806
+ hourly_normal = pd.concat([hourly_status_counts.get('No Alarm', pd.Series(0, index=range(18, 24))).reindex(range(18, 24), fill_value=0),
807
+ hourly_status_counts.get('No Alarm', pd.Series(0, index=range(0, 6))).reindex(range(0, 6), fill_value=0)]).values
808
+ hourly_amber = pd.concat([hourly_status_counts.get('Amber High Pressure', pd.Series(0, index=range(18, 24))).reindex(range(18, 24), fill_value=0),
809
+ hourly_status_counts.get('Amber High Pressure', pd.Series(0, index=range(0, 6))).reindex(range(0, 6), fill_value=0)]).values
810
+ hourly_red = pd.concat([hourly_status_counts.get('Red High Pressure', pd.Series(0, index=range(18, 24))).reindex(range(18, 24), fill_value=0),
811
+ hourly_status_counts.get('Red High Pressure', pd.Series(0, index=range(0, 6))).reindex(range(0, 6), fill_value=0)]).values
812
+
813
+ theta = [(h - 18) * 30 for h in range(18, 24)] + [(h + 6) * 30 for h in range(0, 6)] # 12 jam * 30° = 360°
814
+
815
+ fig2 = go.Figure()
816
+ fig2.add_trace(go.Barpolar(
817
+ r=hourly_normal,
818
+ theta=theta,
819
+ name='Normal',
820
+ marker_color='#2E7D32',
821
+ opacity=0.8
822
+ ))
823
+ fig2.add_trace(go.Barpolar(
824
+ r=hourly_amber,
825
+ theta=theta,
826
+ name='Amber',
827
+ marker_color='#FFC107',
828
+ opacity=0.8
829
+ ))
830
+ fig2.add_trace(go.Barpolar(
831
+ r=hourly_red,
832
+ theta=theta,
833
+ name='Red',
834
+ marker_color='#D32F2F',
835
+ opacity=0.8
836
+ ))
837
+ fig2.update_layout(
838
+ polar=dict(
839
+ angularaxis=dict(
840
+ direction="clockwise",
841
+ period=12,
842
+ rotation=0,
843
+ tickvals=[0, 90, 180, 270],
844
+ ticktext=["18:00", "24:00", "06:00", "12:00"],
845
+ tickfont=dict(size=10)
846
  ),
847
+ radialaxis=dict(visible=True)
848
+ ),
849
+ showlegend=False,
850
+ margin=dict(t=30, b=20, l=20, r=20),
851
+ height=250
 
 
 
 
 
 
 
 
 
852
  )
853
+ st.plotly_chart(fig2, use_container_width=True)
854
+ else:
855
+ st.warning("No data for Position 1 (18:00–06:00)")
 
 
 
 
 
 
856
  else:
857
+ st.warning("No data for Position 1")
858
+
859
+ # Position 3 Pagi
860
+ st.markdown('<h6 style="text-align:center; margin-top: 0;">Position 3 (06:00–18:00)</h6>', unsafe_allow_html=True)
861
+ if not alarm_data[alarm_data['Position'] == 3].empty:
862
+ pos3_data = alarm_data[alarm_data['Position'] == 3].copy()
863
+ pos3_data = pos3_data[pos3_data['hour'].between(6, 17, inclusive='both')]
864
+
865
+ if not pos3_data.empty:
866
+ hourly_status_counts = pos3_data.groupby(['hour', 'Alarm Status']).size().unstack(fill_value=0)
867
+
868
+ hourly_normal = hourly_status_counts.get('No Alarm', pd.Series(0, index=range(6, 18))).reindex(range(6, 18), fill_value=0)
869
+ hourly_amber = hourly_status_counts.get('Amber High Pressure', pd.Series(0, index=range(6, 18))).reindex(range(6, 18), fill_value=0)
870
+ hourly_red = hourly_status_counts.get('Red High Pressure', pd.Series(0, index=range(6, 18))).reindex(range(6, 18), fill_value=0)
871
+
872
+ theta = [(h - 6) * 30 for h in range(6, 18)] # 12 jam * 30° = 360°
873
+
874
+ fig3 = go.Figure()
875
+ fig3.add_trace(go.Barpolar(
876
+ r=hourly_normal.values,
877
+ theta=theta,
878
+ name='Normal',
879
+ marker_color='#2E7D32',
880
+ opacity=0.8
881
+ ))
882
+ fig3.add_trace(go.Barpolar(
883
+ r=hourly_amber.values,
884
+ theta=theta,
885
+ name='Amber',
886
+ marker_color='#FFC107',
887
+ opacity=0.8
888
+ ))
889
+ fig3.add_trace(go.Barpolar(
890
+ r=hourly_red.values,
891
+ theta=theta,
892
+ name='Red',
893
+ marker_color='#D32F2F',
894
+ opacity=0.8
895
+ ))
896
+ fig3.update_layout(
897
+ polar=dict(
898
+ angularaxis=dict(
899
+ direction="clockwise",
900
+ period=12,
901
+ rotation=0,
902
+ tickvals=[0, 90, 180, 270],
903
+ ticktext=["06:00", "12:00", "18:00", "24:00"],
904
+ tickfont=dict(size=10)
905
  ),
906
+ radialaxis=dict(visible=True)
907
+ ),
908
+ showlegend=False,
909
+ margin=dict(t=30, b=20, l=20, r=20),
910
+ height=250
911
+ )
912
+ st.plotly_chart(fig3, use_container_width=True)
913
+ else:
914
+ st.warning("No data for Position 3 (06:00–18:00)")
915
+ else:
916
+ st.warning("No data for Position 3")
917
+
918
+ # Position 3 Sore
919
+ st.markdown('<h6 style="text-align:center; margin-top: 0;">Position 3 (18:00–06:00)</h6>', unsafe_allow_html=True)
920
+ if not alarm_data[alarm_data['Position'] == 3].empty:
921
+ pos3_data = alarm_data[alarm_data['Position'] == 3].copy()
922
+ pos3_data = pos3_data[~pos3_data['hour'].between(6, 17, inclusive='both')]
923
+
924
+ if not pos3_data.empty:
925
+ hourly_status_counts = pos3_data.groupby(['hour', 'Alarm Status']).size().unstack(fill_value=0)
926
+
927
+ hourly_normal = pd.concat([hourly_status_counts.get('No Alarm', pd.Series(0, index=range(18, 24))).reindex(range(18, 24), fill_value=0),
928
+ hourly_status_counts.get('No Alarm', pd.Series(0, index=range(0, 6))).reindex(range(0, 6), fill_value=0)]).values
929
+ hourly_amber = pd.concat([hourly_status_counts.get('Amber High Pressure', pd.Series(0, index=range(18, 24))).reindex(range(18, 24), fill_value=0),
930
+ hourly_status_counts.get('Amber High Pressure', pd.Series(0, index=range(0, 6))).reindex(range(0, 6), fill_value=0)]).values
931
+ hourly_red = pd.concat([hourly_status_counts.get('Red High Pressure', pd.Series(0, index=range(18, 24))).reindex(range(18, 24), fill_value=0),
932
+ hourly_status_counts.get('Red High Pressure', pd.Series(0, index=range(0, 6))).reindex(range(0, 6), fill_value=0)]).values
933
+
934
+ theta = [(h - 18) * 30 for h in range(18, 24)] + [(h + 6) * 30 for h in range(0, 6)] # 12 jam * 30° = 360°
935
+
936
+ fig4 = go.Figure()
937
+ fig4.add_trace(go.Barpolar(
938
+ r=hourly_normal,
939
+ theta=theta,
940
+ name='Normal',
941
+ marker_color='#2E7D32',
942
+ opacity=0.8
943
+ ))
944
+ fig4.add_trace(go.Barpolar(
945
+ r=hourly_amber,
946
+ theta=theta,
947
+ name='Amber',
948
+ marker_color='#FFC107',
949
+ opacity=0.8
950
+ ))
951
+ fig4.add_trace(go.Barpolar(
952
+ r=hourly_red,
953
+ theta=theta,
954
+ name='Red',
955
+ marker_color='#D32F2F',
956
+ opacity=0.8
957
+ ))
958
+ fig4.update_layout(
959
+ polar=dict(
960
+ angularaxis=dict(
961
+ direction="clockwise",
962
+ period=12,
963
+ rotation=0,
964
+ tickvals=[0, 90, 180, 270],
965
+ ticktext=["18:00", "24:00", "06:00", "12:00"],
966
+ tickfont=dict(size=10)
967
  ),
968
+ radialaxis=dict(visible=True)
969
+ ),
970
+ showlegend=False,
971
+ margin=dict(t=30, b=20, l=20, r=20),
972
+ height=250
973
+ )
974
+ st.plotly_chart(fig4, use_container_width=True)
975
+ else:
976
+ st.warning("No data for Position 3 (18:00–06:00)")
977
+ else:
978
+ st.warning("No data for Position 3")
979
 
980
+ # =============== COL 2: Position 2 & 4 (Pagi & Sore) ===============
981
+ with col2:
982
+ # Position 2 Pagi
983
+ st.markdown('<h6 style="text-align:center; margin-top: 0;">Position 2 (06:00–18:00)</h6>', unsafe_allow_html=True)
984
+ if not alarm_data[alarm_data['Position'] == 2].empty:
985
+ pos2_data = alarm_data[alarm_data['Position'] == 2].copy()
986
+ pos2_data = pos2_data[pos2_data['hour'].between(6, 17, inclusive='both')]
987
+
988
+ if not pos2_data.empty:
989
+ hourly_status_counts = pos2_data.groupby(['hour', 'Alarm Status']).size().unstack(fill_value=0)
990
+
991
+ hourly_normal = hourly_status_counts.get('No Alarm', pd.Series(0, index=range(6, 18))).reindex(range(6, 18), fill_value=0)
992
+ hourly_amber = hourly_status_counts.get('Amber High Pressure', pd.Series(0, index=range(6, 18))).reindex(range(6, 18), fill_value=0)
993
+ hourly_red = hourly_status_counts.get('Red High Pressure', pd.Series(0, index=range(6, 18))).reindex(range(6, 18), fill_value=0)
994
+
995
+ theta = [(h - 6) * 30 for h in range(6, 18)] # 12 jam * 30° = 360°
996
+
997
+ fig5 = go.Figure()
998
+ fig5.add_trace(go.Barpolar(
999
+ r=hourly_normal.values,
1000
+ theta=theta,
1001
+ name='Normal',
1002
+ marker_color='#2E7D32',
1003
+ opacity=0.8
1004
+ ))
1005
+ fig5.add_trace(go.Barpolar(
1006
+ r=hourly_amber.values,
1007
+ theta=theta,
1008
+ name='Amber',
1009
+ marker_color='#FFC107',
1010
+ opacity=0.8
1011
+ ))
1012
+ fig5.add_trace(go.Barpolar(
1013
+ r=hourly_red.values,
1014
+ theta=theta,
1015
+ name='Red',
1016
+ marker_color='#D32F2F',
1017
+ opacity=0.8
1018
+ ))
1019
+ fig5.update_layout(
1020
+ polar=dict(
1021
+ angularaxis=dict(
1022
+ direction="clockwise",
1023
+ period=12,
1024
+ rotation=0,
1025
+ tickvals=[0, 90, 180, 270],
1026
+ ticktext=["06:00", "12:00", "18:00", "24:00"],
1027
+ tickfont=dict(size=10)
1028
  ),
1029
+ radialaxis=dict(visible=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1030
  ),
1031
+ showlegend=False,
1032
+ margin=dict(t=30, b=20, l=20, r=20),
1033
+ height=250
 
1034
  )
1035
+ st.plotly_chart(fig5, use_container_width=True)
1036
+ else:
1037
+ st.warning("No data for Position 2 (06:00–18:00)")
1038
+ else:
1039
+ st.warning("No data for Position 2")
1040
+
1041
+ # Position 2 Sore
1042
+ st.markdown('<h6 style="text-align:center; margin-top: 0;">Position 2 (18:00–06:00)</h6>', unsafe_allow_html=True)
1043
+ if not alarm_data[alarm_data['Position'] == 2].empty:
1044
+ pos2_data = alarm_data[alarm_data['Position'] == 2].copy()
1045
+ pos2_data = pos2_data[~pos2_data['hour'].between(6, 17, inclusive='both')]
1046
+
1047
+ if not pos2_data.empty:
1048
+ hourly_status_counts = pos2_data.groupby(['hour', 'Alarm Status']).size().unstack(fill_value=0)
1049
+
1050
+ hourly_normal = pd.concat([hourly_status_counts.get('No Alarm', pd.Series(0, index=range(18, 24))).reindex(range(18, 24), fill_value=0),
1051
+ hourly_status_counts.get('No Alarm', pd.Series(0, index=range(0, 6))).reindex(range(0, 6), fill_value=0)]).values
1052
+ hourly_amber = pd.concat([hourly_status_counts.get('Amber High Pressure', pd.Series(0, index=range(18, 24))).reindex(range(18, 24), fill_value=0),
1053
+ hourly_status_counts.get('Amber High Pressure', pd.Series(0, index=range(0, 6))).reindex(range(0, 6), fill_value=0)]).values
1054
+ hourly_red = pd.concat([hourly_status_counts.get('Red High Pressure', pd.Series(0, index=range(18, 24))).reindex(range(18, 24), fill_value=0),
1055
+ hourly_status_counts.get('Red High Pressure', pd.Series(0, index=range(0, 6))).reindex(range(0, 6), fill_value=0)]).values
1056
+
1057
+ theta = [(h - 18) * 30 for h in range(18, 24)] + [(h + 6) * 30 for h in range(0, 6)] # 12 jam * 30° = 360°
1058
+
1059
+ fig6 = go.Figure()
1060
+ fig6.add_trace(go.Barpolar(
1061
+ r=hourly_normal,
1062
+ theta=theta,
1063
+ name='Normal',
1064
+ marker_color='#2E7D32',
1065
+ opacity=0.8
1066
+ ))
1067
+ fig6.add_trace(go.Barpolar(
1068
+ r=hourly_amber,
1069
+ theta=theta,
1070
+ name='Amber',
1071
+ marker_color='#FFC107',
1072
+ opacity=0.8
1073
+ ))
1074
+ fig6.add_trace(go.Barpolar(
1075
+ r=hourly_red,
1076
+ theta=theta,
1077
+ name='Red',
1078
+ marker_color='#D32F2F',
1079
+ opacity=0.8
1080
+ ))
1081
+ fig6.update_layout(
1082
+ polar=dict(
1083
+ angularaxis=dict(
1084
+ direction="clockwise",
1085
+ period=12,
1086
+ rotation=0,
1087
+ tickvals=[0, 90, 180, 270],
1088
+ ticktext=["18:00", "24:00", "06:00", "12:00"],
1089
+ tickfont=dict(size=10)
1090
+ ),
1091
+ radialaxis=dict(visible=True)
1092
+ ),
1093
+ showlegend=False,
1094
+ margin=dict(t=30, b=20, l=20, r=20),
1095
+ height=250
1096
+ )
1097
+ st.plotly_chart(fig6, use_container_width=True)
1098
+ else:
1099
+ st.warning("No data for Position 2 (18:00–06:00)")
1100
+ else:
1101
+ st.warning("No data for Position 2")
1102
+
1103
+ # Position 4 Pagi
1104
+ st.markdown('<h6 style="text-align:center; margin-top: 0;">Position 4 (06:00–18:00)</h6>', unsafe_allow_html=True)
1105
+ if not alarm_data[alarm_data['Position'] == 4].empty:
1106
+ pos4_data = alarm_data[alarm_data['Position'] == 4].copy()
1107
+ pos4_data = pos4_data[pos4_data['hour'].between(6, 17, inclusive='both')]
1108
+
1109
+ if not pos4_data.empty:
1110
+ hourly_status_counts = pos4_data.groupby(['hour', 'Alarm Status']).size().unstack(fill_value=0)
1111
+
1112
+ hourly_normal = hourly_status_counts.get('No Alarm', pd.Series(0, index=range(6, 18))).reindex(range(6, 18), fill_value=0)
1113
+ hourly_amber = hourly_status_counts.get('Amber High Pressure', pd.Series(0, index=range(6, 18))).reindex(range(6, 18), fill_value=0)
1114
+ hourly_red = hourly_status_counts.get('Red High Pressure', pd.Series(0, index=range(6, 18))).reindex(range(6, 18), fill_value=0)
1115
+
1116
+ theta = [(h - 6) * 30 for h in range(6, 18)] # 12 jam * 30° = 360°
1117
+
1118
+ fig7 = go.Figure()
1119
+ fig7.add_trace(go.Barpolar(
1120
+ r=hourly_normal.values,
1121
+ theta=theta,
1122
+ name='Normal',
1123
+ marker_color='#2E7D32',
1124
+ opacity=0.8
1125
+ ))
1126
+ fig7.add_trace(go.Barpolar(
1127
+ r=hourly_amber.values,
1128
+ theta=theta,
1129
+ name='Amber',
1130
+ marker_color='#FFC107',
1131
+ opacity=0.8
1132
+ ))
1133
+ fig7.add_trace(go.Barpolar(
1134
+ r=hourly_red.values,
1135
+ theta=theta,
1136
+ name='Red',
1137
+ marker_color='#D32F2F',
1138
+ opacity=0.8
1139
+ ))
1140
+ fig7.update_layout(
1141
+ polar=dict(
1142
+ angularaxis=dict(
1143
+ direction="clockwise",
1144
+ period=12,
1145
+ rotation=0,
1146
+ tickvals=[0, 90, 180, 270],
1147
+ ticktext=["06:00", "12:00", "18:00", "24:00"],
1148
+ tickfont=dict(size=10)
1149
+ ),
1150
+ radialaxis=dict(visible=True)
1151
+ ),
1152
+ showlegend=False,
1153
+ margin=dict(t=30, b=20, l=20, r=20),
1154
+ height=250
1155
+ )
1156
+ st.plotly_chart(fig7, use_container_width=True)
1157
+ else:
1158
+ st.warning("No data for Position 4 (06:00–18:00)")
1159
+ else:
1160
+ st.warning("No data for Position 4")
1161
+
1162
+ # Position 4 Sore
1163
+ st.markdown('<h6 style="text-align:center; margin-top: 0;">Position 4 (18:00–06:00)</h6>', unsafe_allow_html=True)
1164
+ if not alarm_data[alarm_data['Position'] == 4].empty:
1165
+ pos4_data = alarm_data[alarm_data['Position'] == 4].copy()
1166
+ pos4_data = pos4_data[~pos4_data['hour'].between(6, 17, inclusive='both')]
1167
+
1168
+ if not pos4_data.empty:
1169
+ hourly_status_counts = pos4_data.groupby(['hour', 'Alarm Status']).size().unstack(fill_value=0)
1170
+
1171
+ hourly_normal = pd.concat([hourly_status_counts.get('No Alarm', pd.Series(0, index=range(18, 24))).reindex(range(18, 24), fill_value=0),
1172
+ hourly_status_counts.get('No Alarm', pd.Series(0, index=range(0, 6))).reindex(range(0, 6), fill_value=0)]).values
1173
+ hourly_amber = pd.concat([hourly_status_counts.get('Amber High Pressure', pd.Series(0, index=range(18, 24))).reindex(range(18, 24), fill_value=0),
1174
+ hourly_status_counts.get('Amber High Pressure', pd.Series(0, index=range(0, 6))).reindex(range(0, 6), fill_value=0)]).values
1175
+ hourly_red = pd.concat([hourly_status_counts.get('Red High Pressure', pd.Series(0, index=range(18, 24))).reindex(range(18, 24), fill_value=0),
1176
+ hourly_status_counts.get('Red High Pressure', pd.Series(0, index=range(0, 6))).reindex(range(0, 6), fill_value=0)]).values
1177
+
1178
+ theta = [(h - 18) * 30 for h in range(18, 24)] + [(h + 6) * 30 for h in range(0, 6)] # 12 jam * 30° = 360°
1179
+
1180
+ fig8 = go.Figure()
1181
+ fig8.add_trace(go.Barpolar(
1182
+ r=hourly_normal,
1183
+ theta=theta,
1184
+ name='Normal',
1185
+ marker_color='#2E7D32',
1186
+ opacity=0.8
1187
+ ))
1188
+ fig8.add_trace(go.Barpolar(
1189
+ r=hourly_amber,
1190
+ theta=theta,
1191
+ name='Amber',
1192
+ marker_color='#FFC107',
1193
+ opacity=0.8
1194
+ ))
1195
+ fig8.add_trace(go.Barpolar(
1196
+ r=hourly_red,
1197
+ theta=theta,
1198
+ name='Red',
1199
+ marker_color='#D32F2F',
1200
+ opacity=0.8
1201
+ ))
1202
+ fig8.update_layout(
1203
+ polar=dict(
1204
+ angularaxis=dict(
1205
+ direction="clockwise",
1206
+ period=12,
1207
+ rotation=0,
1208
+ tickvals=[0, 90, 180, 270],
1209
+ ticktext=["18:00", "24:00", "06:00", "12:00"],
1210
+ tickfont=dict(size=10)
1211
+ ),
1212
+ radialaxis=dict(visible=True)
1213
+ ),
1214
+ showlegend=False,
1215
+ margin=dict(t=30, b=20, l=20, r=20),
1216
+ height=250
1217
+ )
1218
+ st.plotly_chart(fig8, use_container_width=True)
1219
+ else:
1220
+ st.warning("No data for Position 4 (18:00–06:00)")
1221
+ else:
1222
+ st.warning("No data for Position 4")
1223
 
1224
  # =============== INSIGHT 3 ===============
1225
  if alarm_data.empty:
 
1265
  </div>
1266
  </div>
1267
  """, unsafe_allow_html=True)
1268
+
1269
  # ================= OBJECTIVE 3 =================
1270
  st.markdown('<h3 class="objective-title">OBJECTIVE 4: Correlation — How Does Heat Influence Pressure and Which Tyres Trigger Red Alarms?</h3>', unsafe_allow_html=True)
1271