SHELLAPANDIANGANHUNGING commited on
Commit
57aeead
·
verified ·
1 Parent(s): 67f58b4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -129
app.py CHANGED
@@ -299,9 +299,20 @@ st.markdown("""
299
  border: 1px solid #e0e0e0;
300
  border-radius: 12px;
301
  margin-bottom: 25px;
 
 
 
302
  }
303
  .header-title {
304
  text-align: center;
 
 
 
 
 
 
 
 
305
  }
306
  </style>
307
  """, unsafe_allow_html=True)
@@ -326,8 +337,22 @@ with st.container():
326
  </div>
327
  """, unsafe_allow_html=True)
328
 
 
 
 
 
 
 
 
 
 
 
329
 
330
 
 
 
 
 
331
 
332
  import streamlit as st
333
  import plotly.graph_objects as go
@@ -422,16 +447,19 @@ if 'temuan_kode_distrik' in df_local.columns:
422
  ]
423
  pln_color = "#FFD700" # Kuning PLN
424
 
425
- def assign_colors(df):
426
  colors = []
 
427
  blue_idx = 0
428
  for company in df['nama_perusahaan']:
429
  if 'PLN' in str(company).upper():
430
  colors.append(pln_color)
 
431
  else:
432
  colors.append(pastel_blues[blue_idx % len(pastel_blues)])
 
433
  blue_idx += 1
434
- return colors
435
 
436
  # Fungsi untuk membuat polar bar chart
437
  def create_polar_bar_chart(df, area_name):
@@ -442,7 +470,7 @@ if 'temuan_kode_distrik' in df_local.columns:
442
  df = df.sort_values('avg_monthly_ratio', ascending=True)
443
  companies = df['nama_perusahaan'].tolist()
444
  ratios = df['avg_monthly_ratio'].tolist()
445
- colors = assign_colors(df)
446
 
447
  # Hitung total temuan untuk menghitung sudut (proporsi)
448
  total_findings = df_local[df_local['Area_Type'] == area_name].groupby('nama_perusahaan').size()
@@ -461,17 +489,22 @@ if 'temuan_kode_distrik' in df_local.columns:
461
  current_angle += a
462
 
463
  fig = go.Figure()
464
- fig.add_trace(go.Barpolar(
465
- r=ratios,
466
- theta=mid_angles,
467
- width=angles,
468
- marker_color=colors,
469
- marker_line_color="white",
470
- marker_line_width=1.2,
471
- opacity=0.9,
472
- hovertemplate="<b>%{text}</b><br>Avg Ratio: %{r:.2f}<extra></extra>",
473
- text=companies,
474
- ))
 
 
 
 
 
475
 
476
  fig.update_layout(
477
  title=f'{area_name} Area',
@@ -490,7 +523,15 @@ if 'temuan_kode_distrik' in df_local.columns:
490
  showline=False
491
  ),
492
  ),
493
- showlegend=False,
 
 
 
 
 
 
 
 
494
  margin=dict(t=40, b=20, l=20, r=20),
495
  height=400,
496
  paper_bgcolor="rgba(0,0,0,0)",
@@ -653,111 +694,6 @@ if not avg_ratio_per_location.empty:
653
  st.markdown(insight_text, unsafe_allow_html=True)
654
  else:
655
  st.warning("No data available for location ratio calculation or all ratios are NaN.")
656
- # =================== OBJECTIVE 2 — Active vs Inactive Locations (Treemap with Color Gradient) ===================
657
- st.markdown(
658
- """
659
- <style>
660
- .section-title {
661
- text-align: center;
662
- font-size: 1.5rem;
663
- font-weight: 600;
664
- color: #2c3e50;
665
- margin-bottom: 1.2rem;
666
- }
667
- .ai-insight {
668
- background-color: #f8f9fa;
669
- padding: 12px;
670
- border-left: 4px solid #27ae60;
671
- border-radius: 0 4px 4px 0;
672
- font-size: 0.95rem;
673
- line-height: 1.5;
674
- margin-top: 1rem;
675
- }
676
- </style>
677
- <h3 class='section-title'>OBJECTIVE 3 — Active vs Inactive Division: Who Leads?</h3>
678
- """,
679
- unsafe_allow_html=True
680
- )
681
-
682
- df_local = df_filtered.copy()
683
- if df_local.empty:
684
- st.warning("No data available after filtering.")
685
- st.stop()
686
-
687
- df_local['created_month'] = df_local['created_at'].dt.to_period('M')
688
-
689
- # Hitung temuan per bulan per lokasi
690
- findings_by_location_month = df_local.groupby(['created_month', 'nama']).size().reset_index(name='findings_count')
691
- # Hitung jumlah orang unik per bulan per lokasi
692
- creators_by_location_month = df_local.groupby(['created_month', 'nama'])['creator_nid'].nunique().reset_index(name='unique_creators')
693
- # Gabung
694
- merged_loc = findings_by_location_month.merge(creators_by_location_month, on=['created_month', 'nama'], how='outer')
695
- # Isi NaN dengan 0 untuk kolom yang mungkin hilang dari merge
696
- merged_loc = merged_loc.fillna({'findings_count': 0, 'unique_creators': 0})
697
- # Filter untuk menghindari pembagian dengan nol
698
- merged_loc = merged_loc[merged_loc['unique_creators'] > 0]
699
- # Hitung rasio (ignore NaN)
700
- merged_loc['ratio'] = merged_loc['findings_count'] / merged_loc['unique_creators']
701
- merged_loc['ratio'] = merged_loc['ratio'].replace([np.inf, -np.inf], np.nan)
702
-
703
- # Rata-rata bulanan per lokasi
704
- avg_ratio_per_location = merged_loc.groupby('nama')['ratio'].mean().reset_index(name='avg_monthly_ratio')
705
-
706
- # Filter hasil akhir untuk menghindari NaN
707
- avg_ratio_per_location = avg_ratio_per_location.dropna(subset=['avg_monthly_ratio'])
708
-
709
- # Plot Treemap dengan gradasi warna
710
- if not avg_ratio_per_location.empty:
711
- # Gunakan color_continuous_scale untuk gradasi warna: merah → kuning → hijau
712
- fig_treemap = px.treemap(
713
- avg_ratio_per_location,
714
- path=['nama'], # Path untuk hierarki (hanya satu level di sini)
715
- values='avg_monthly_ratio', # Nilai yang menentukan ukuran area
716
- title='Avg Monthly Finding by Division',
717
- labels={'avg_monthly_ratio': 'Avg Monthly Finding/Person Ratio', 'nama': 'Location'},
718
- color='avg_monthly_ratio', # Warna berdasarkan nilai rasio (bukan kategori)
719
- color_continuous_scale=[
720
- [0.0, '#D32F2F'], # Merah untuk rendah
721
- [0.5, '#FFB300'], # Kuning untuk sedang
722
- [1.0, '#4CAF50'] # Hijau untuk tinggi
723
- ]
724
- )
725
- # Format hover
726
- fig_treemap.update_traces(
727
- hovertemplate="<b>%{label}</b><br>Avg Ratio: %{value:.2f}<extra></extra>"
728
- )
729
- fig_treemap.update_layout(height=600)
730
- st.plotly_chart(fig_treemap, use_container_width=True)
731
-
732
- # AI Insight untuk Treemap Lokasi (Business-focused)
733
- if not avg_ratio_per_location.empty:
734
- # Temukan lokasi dengan rasio tertinggi dan terendah
735
- top_location = avg_ratio_per_location.loc[avg_ratio_per_location['avg_monthly_ratio'].idxmax()]
736
- low_location = avg_ratio_per_location.loc[avg_ratio_per_location['avg_monthly_ratio'].idxmin()]
737
-
738
- st.markdown("### Insight")
739
- insight_text = (
740
- f"<div class='ai-insight'>"
741
- f"The treemap visualizes the average finding-to-person ratio per location using a <strong>color gradient</strong>, indicating reporting activity levels. "
742
- f"Locations with <span style='color:#4CAF50; font-weight:bold;'>green</span> color have a high ratio, indicating high reporting activity or exposure. "
743
- f"Those with <span style='color:#FFB300; font-weight:bold;'>yellow</span> color have a medium ratio, indicating moderate reporting. "
744
- f"Locations with <span style='color:#D32F2F; font-weight:bold;'>red</span> color have a low ratio, indicating lower activity levels or potentially under-reporting. "
745
- f"<strong>{top_location['nama']}</strong> shows the highest activity level "
746
- f"(<strong>{top_location['avg_monthly_ratio']:.2f}</strong>). "
747
- f"<strong>{low_location['nama']}</strong> shows the lowest activity level "
748
- f"(<strong>{low_location['avg_monthly_ratio']:.2f}</strong>). "
749
- f"Areas with high activity (green) warrant investigation into the underlying causes of frequent findings. "
750
- f"Areas with low activity (red) should be reviewed to ensure reporting completeness and identify any hidden risks."
751
- f"</div>"
752
- )
753
- st.markdown(insight_text, unsafe_allow_html=True)
754
- else:
755
- st.warning("No data available for location ratio calculation or all ratios are NaN.")
756
-
757
- import streamlit as st
758
- import plotly.express as px
759
- import numpy as np
760
- import pandas as pd
761
 
762
  # =================== OBJECTIVE 3 - Frequency & Response Time ===================
763
  st.markdown(
@@ -780,7 +716,7 @@ st.markdown(
780
  margin-top: 1rem;
781
  }
782
  </style>
783
- <h3 class='section-title'>OBJECTIVE 4 — Frequency & Response Time: Who Reports Well? Who Executes Well?</h3>
784
  """,
785
  unsafe_allow_html=True
786
  )
@@ -869,11 +805,11 @@ avg_leadtime_per_executor = compute_executor_leadtime_by_pic(df_local)
869
  col_3a, col_3c = st.columns(2)
870
 
871
  with col_3a:
872
- st.markdown("<h5>4a. Average Finding by Division (Reporter)</h5>", unsafe_allow_html=True)
873
  if avg_ratio_per_nama.empty:
874
  st.warning("No data for reporter analysis by division.")
875
  else:
876
- sort_option_3a = st.selectbox("Show 4a:", ["Top 10", "Bottom 10"], key='sort_3a')
877
 
878
  # Urutkan data dari tertinggi ke terendah
879
  sorted_data_all = avg_ratio_per_nama.sort_values('avg_monthly_ratio', ascending=False)
@@ -932,11 +868,11 @@ with col_3a:
932
  st.markdown(insight_text, unsafe_allow_html=True)
933
 
934
  with col_3c:
935
- st.markdown("<h5>4b. Average Finding Rate per Reporter (Name)</h5>", unsafe_allow_html=True)
936
  if avg_rate_per_creator.empty:
937
  st.warning("No data for reporter analysis by creator_name.")
938
  else:
939
- sort_option_3c = st.selectbox("Show 4b:", ["Top 10", "Bottom 10"], key='sort_3c')
940
 
941
  # Urutkan data dari tertinggi ke terendah
942
  sorted_data_all = avg_rate_per_creator.sort_values('avg_monthly_rate', ascending=False)
@@ -999,11 +935,11 @@ with col_3c:
999
  col_3b, col_3d = st.columns(2)
1000
 
1001
  with col_3b:
1002
- st.markdown("<h5>4c. Average Lead Time by Division (Executor)</h5>", unsafe_allow_html=True)
1003
  if avg_leadtime_nama.empty:
1004
  st.warning("No data for executor analysis by division.")
1005
  else:
1006
- sort_option_3b = st.selectbox("Show 4c:", ["Top 10", "Bottom 10"], key='sort_3b')
1007
 
1008
  # Urutkan data dari tertinggi ke terendah
1009
  sorted_data_all = avg_leadtime_nama.sort_values('avg_monthly_leadtime', ascending=False)
@@ -1060,11 +996,11 @@ with col_3b:
1060
  st.markdown(insight_text, unsafe_allow_html=True)
1061
 
1062
  with col_3d:
1063
- st.markdown("<h5>4d. Average Lead Time by Executor (Name)</h5>", unsafe_allow_html=True)
1064
  if avg_leadtime_per_executor.empty:
1065
  st.warning("No data for executor analysis by nama_pic.")
1066
  else:
1067
- sort_option_3d = st.selectbox("Show 4d:", ["Top 10", "Bottom 10"], key='sort_3d')
1068
 
1069
  # Urutkan data dari tertinggi ke terendah
1070
  sorted_data_all = avg_leadtime_per_executor.sort_values('avg_monthly_leadtime', ascending=False)
 
299
  border: 1px solid #e0e0e0;
300
  border-radius: 12px;
301
  margin-bottom: 25px;
302
+ display: flex;
303
+ align-items: center;
304
+ justify-content: space-between;
305
  }
306
  .header-title {
307
  text-align: center;
308
+ flex: 1;
309
+ }
310
+ .logo-container {
311
+ display: flex;
312
+ align-items: center;
313
+ justify-content: flex-end;
314
+ width: 100px; /* Sesuaikan ukuran logo */
315
+ height: auto;
316
  }
317
  </style>
318
  """, unsafe_allow_html=True)
 
337
  </div>
338
  """, unsafe_allow_html=True)
339
 
340
+ # RIGHT — LOGO
341
+ with col3:
342
+ try:
343
+ st.image("pln.png", width=80) # Sesuaikan width sesuai kebutuhan
344
+ except:
345
+ st.write("") # Jika logo tidak ditemukan, kosongkan
346
+
347
+ st.markdown('</div>', unsafe_allow_html=True)
348
+
349
+
350
 
351
 
352
+ import streamlit as st
353
+ import plotly.graph_objects as go
354
+ import numpy as np
355
+ import pandas as pd
356
 
357
  import streamlit as st
358
  import plotly.graph_objects as go
 
447
  ]
448
  pln_color = "#FFD700" # Kuning PLN
449
 
450
+ def assign_colors_and_create_legend(df):
451
  colors = []
452
+ names = []
453
  blue_idx = 0
454
  for company in df['nama_perusahaan']:
455
  if 'PLN' in str(company).upper():
456
  colors.append(pln_color)
457
+ names.append(company)
458
  else:
459
  colors.append(pastel_blues[blue_idx % len(pastel_blues)])
460
+ names.append(company)
461
  blue_idx += 1
462
+ return colors, names
463
 
464
  # Fungsi untuk membuat polar bar chart
465
  def create_polar_bar_chart(df, area_name):
 
470
  df = df.sort_values('avg_monthly_ratio', ascending=True)
471
  companies = df['nama_perusahaan'].tolist()
472
  ratios = df['avg_monthly_ratio'].tolist()
473
+ colors, legend_names = assign_colors_and_create_legend(df)
474
 
475
  # Hitung total temuan untuk menghitung sudut (proporsi)
476
  total_findings = df_local[df_local['Area_Type'] == area_name].groupby('nama_perusahaan').size()
 
489
  current_angle += a
490
 
491
  fig = go.Figure()
492
+
493
+ # Tambahkan satu trace untuk setiap perusahaan agar muncul di legend
494
+ for i, (comp, ratio, color, angle) in enumerate(zip(companies, ratios, colors, angles)):
495
+ fig.add_trace(go.Barpolar(
496
+ r=[ratio],
497
+ theta=[mid_angles[i]],
498
+ width=[angle],
499
+ marker_color=[color],
500
+ marker_line_color="white",
501
+ marker_line_width=1.2,
502
+ opacity=0.9,
503
+ hovertemplate="<b>%{text}</b><br>Avg Ratio: %{r[0]:.2f}<extra></extra>",
504
+ text=[comp],
505
+ name=comp, # 🔥 Nama perusahaan untuk legend
506
+ showlegend=True # 🔥 Tampilkan di legend
507
+ ))
508
 
509
  fig.update_layout(
510
  title=f'{area_name} Area',
 
523
  showline=False
524
  ),
525
  ),
526
+ showlegend=True, # 🔥 Aktifkan legend
527
+ legend=dict(
528
+ orientation="v",
529
+ yanchor="top",
530
+ y=1,
531
+ xanchor="left",
532
+ x=1.02,
533
+ font=dict(size=10)
534
+ ),
535
  margin=dict(t=40, b=20, l=20, r=20),
536
  height=400,
537
  paper_bgcolor="rgba(0,0,0,0)",
 
694
  st.markdown(insight_text, unsafe_allow_html=True)
695
  else:
696
  st.warning("No data available for location ratio calculation or all ratios are NaN.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
697
 
698
  # =================== OBJECTIVE 3 - Frequency & Response Time ===================
699
  st.markdown(
 
716
  margin-top: 1rem;
717
  }
718
  </style>
719
+ <h3 class='section-title'>OBJECTIVE 3 — Frequency & Response Time: Who Reports Well? Who Executes Well?</h3>
720
  """,
721
  unsafe_allow_html=True
722
  )
 
805
  col_3a, col_3c = st.columns(2)
806
 
807
  with col_3a:
808
+ st.markdown("<h5>3a. Average Finding by Division (Reporter)</h5>", unsafe_allow_html=True)
809
  if avg_ratio_per_nama.empty:
810
  st.warning("No data for reporter analysis by division.")
811
  else:
812
+ sort_option_3a = st.selectbox("Show 3a:", ["Top 10", "Bottom 10"], key='sort_3a')
813
 
814
  # Urutkan data dari tertinggi ke terendah
815
  sorted_data_all = avg_ratio_per_nama.sort_values('avg_monthly_ratio', ascending=False)
 
868
  st.markdown(insight_text, unsafe_allow_html=True)
869
 
870
  with col_3c:
871
+ st.markdown("<h5>3b. Average Finding Rate per Reporter (Name)</h5>", unsafe_allow_html=True)
872
  if avg_rate_per_creator.empty:
873
  st.warning("No data for reporter analysis by creator_name.")
874
  else:
875
+ sort_option_3c = st.selectbox("Show 3b:", ["Top 10", "Bottom 10"], key='sort_3c')
876
 
877
  # Urutkan data dari tertinggi ke terendah
878
  sorted_data_all = avg_rate_per_creator.sort_values('avg_monthly_rate', ascending=False)
 
935
  col_3b, col_3d = st.columns(2)
936
 
937
  with col_3b:
938
+ st.markdown("<h5>3c. Average Lead Time by Division (Executor)</h5>", unsafe_allow_html=True)
939
  if avg_leadtime_nama.empty:
940
  st.warning("No data for executor analysis by division.")
941
  else:
942
+ sort_option_3b = st.selectbox("Show 3c:", ["Top 10", "Bottom 10"], key='sort_3b')
943
 
944
  # Urutkan data dari tertinggi ke terendah
945
  sorted_data_all = avg_leadtime_nama.sort_values('avg_monthly_leadtime', ascending=False)
 
996
  st.markdown(insight_text, unsafe_allow_html=True)
997
 
998
  with col_3d:
999
+ st.markdown("<h5>3d. Average Lead Time by Executor (Name)</h5>", unsafe_allow_html=True)
1000
  if avg_leadtime_per_executor.empty:
1001
  st.warning("No data for executor analysis by nama_pic.")
1002
  else:
1003
+ sort_option_3d = st.selectbox("Show 3d:", ["Top 10", "Bottom 10"], key='sort_3d')
1004
 
1005
  # Urutkan data dari tertinggi ke terendah
1006
  sorted_data_all = avg_leadtime_per_executor.sort_values('avg_monthly_leadtime', ascending=False)