SHELLAPANDIANGANHUNGING commited on
Commit
c25a116
·
verified ·
1 Parent(s): 465c899

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +32 -34
app.py CHANGED
@@ -284,15 +284,26 @@ st.sidebar.markdown('</div>', unsafe_allow_html=True)
284
 
285
 
286
  # =================== HEADER ===================
287
- st.markdown("""
288
- <div class="main-header">
289
- <h1>PLN Audit Insight & Intelligence Dashboard</h1>
290
- <p style="text-align:center; color:#546e7a; font-size:1.1em; margin-top:8px;">
291
- Operational Risk Intelligence for Audit & Compliance
292
- </p>
293
- </div>
294
- """, unsafe_allow_html=True)
295
-
 
 
 
 
 
 
 
 
 
 
 
296
 
297
  # =================== 1. Pie Charts: Temuan/Person by Company (PG & UM) - PERBAIKAN ===================
298
  st.markdown("<h3 class='section-title'>OBJECTIVE 1 - Company Reporting Activity: Who Reports the Most?</h3>", unsafe_allow_html=True)
@@ -303,28 +314,21 @@ df_local = df_filtered.copy()
303
  # Tambah kolom bulan
304
  df_local['created_month'] = df_local['created_at'].dt.to_period('M')
305
 
306
- # --- Langsung buat Area_Type PG / UM tanpa filter ---
307
-
308
  if 'temuan_kode_distrik' in df_local.columns:
309
-
310
  df_local['Area_Type'] = df_local['temuan_kode_distrik'].apply(
311
  lambda x: 'PG' if 'PG' in str(x).upper()
312
  else 'UM' if 'UM' in str(x).upper()
313
  else 'Other'
314
  )
315
 
316
- # Otomatis bagi dataset
317
  df_pg = df_local[df_local['Area_Type'] == 'PG'].copy()
318
  df_um = df_local[df_local['Area_Type'] == 'UM'].copy()
319
 
320
- else:
321
- df_pg = pd.DataFrame()
322
- df_um = pd.DataFrame()
323
-
324
  # --- Fungsi untuk menghitung rasio perusahaan ---
325
  def calculate_avg_ratio_per_company(df_area):
326
  if df_area.empty:
327
- # Jika area tidak dipilih atau data kosong setelah filter
328
  return pd.DataFrame()
329
  # Hitung temuan per bulan per perusahaan
330
  findings_by_company_month = df_area.groupby(['created_month', 'nama_perusahaan']).size().reset_index(name='findings_count')
@@ -335,10 +339,8 @@ else:
335
  # Isi NaN dengan 0 untuk kolom yang mungkin hilang dari merge
336
  merged = merged.fillna({'findings_count': 0, 'unique_creators': 0})
337
  # Filter untuk menghindari pembagian dengan nol
338
- # Kita hanya ingin menghitung rasio jika jumlah pelapor > 0
339
  merged = merged[merged['unique_creators'] > 0]
340
  # Hitung rasio (ignore NaN)
341
- # Pembagian oleh 0 akan menghasilkan inf, jadi kita ganti inf dengan NaN
342
  merged['ratio'] = merged['findings_count'] / merged['unique_creators']
343
  merged['ratio'] = merged['ratio'].replace([np.inf, -np.inf], np.nan)
344
 
@@ -347,11 +349,9 @@ else:
347
  return pd.DataFrame()
348
 
349
  # Rata-rata bulanan per perusahaan
350
- # Group by nama_perusahaan dan ambil mean dari rasio
351
- # mean() akan mengabaikan NaN secara default
352
  avg_ratio = merged.groupby('nama_perusahaan')['ratio'].mean().reset_index(name='avg_monthly_ratio')
353
 
354
- # Jika hasil akhirnya hanya NaN (karena semua rasio perusahaan adalah NaN), kembalikan DataFrame kosong
355
  if avg_ratio['avg_monthly_ratio'].isna().all():
356
  return pd.DataFrame()
357
 
@@ -364,14 +364,12 @@ else:
364
  # Fungsi untuk menentukan warna
365
  def get_color_map(company_series):
366
  pln_color = "#FFD700" # Kuning untuk PLN
367
- # Daftar warna biru (dari gelap ke terang)
368
  blue_colors = ["#1E90FF", "#87CEEB", "#B0E0E6", "#ADD8E6", "#E0F6FF"]
369
  color_map = {}
370
  for company in company_series:
371
  if 'PLN' in str(company).upper():
372
  color_map[company] = pln_color
373
  else:
374
- # Pilih warna biru berdasarkan indeks, ulangi jika perlu
375
  idx = len([c for c in color_map.values() if c != pln_color]) % len(blue_colors)
376
  color_map[company] = blue_colors[idx]
377
  return color_map
@@ -380,14 +378,14 @@ else:
380
  col1, col2 = st.columns(2)
381
 
382
  with col1:
383
- st.markdown("<h5>Avg Monthly Finding by Company</h5>", unsafe_allow_html=True)
384
  if not avg_ratio_pg.empty:
385
  color_discrete_map_pg = get_color_map(avg_ratio_pg['nama_perusahaan'])
386
  fig_pg = px.pie(
387
  avg_ratio_pg,
388
  values='avg_monthly_ratio',
389
  names='nama_perusahaan',
390
- title='Unit Pembangkit Company',
391
  color='nama_perusahaan',
392
  color_discrete_map=color_discrete_map_pg
393
  )
@@ -395,11 +393,10 @@ else:
395
 
396
  # AI Insight untuk PG
397
  if not avg_ratio_pg.empty:
398
- # Temukan perusahaan dengan rasio tertinggi dan terendah di PG
399
  top_company_pg = avg_ratio_pg.loc[avg_ratio_pg['avg_monthly_ratio'].idxmax()]
400
  low_company_pg = avg_ratio_pg.loc[avg_ratio_pg['avg_monthly_ratio'].idxmin()]
401
 
402
- st.markdown("### Insight")
403
  insight_text = (
404
  f"<div class='ai-insight'>"
405
  f"In PG Area, <strong>{top_company_pg['nama_perusahaan']}</strong> has the highest average finding-to-person ratio "
@@ -414,14 +411,14 @@ else:
414
  st.warning("No data for PG area or all ratios are NaN.")
415
 
416
  with col2:
417
- st.markdown("<h5>Avg Monthly Finding by Company</h5>", unsafe_allow_html=True)
418
  if not avg_ratio_um.empty:
419
  color_discrete_map_um = get_color_map(avg_ratio_um['nama_perusahaan'])
420
  fig_um = px.pie(
421
  avg_ratio_um,
422
  values='avg_monthly_ratio',
423
  names='nama_perusahaan',
424
- title='Unit Maintenance',
425
  color='nama_perusahaan',
426
  color_discrete_map=color_discrete_map_um
427
  )
@@ -429,11 +426,10 @@ else:
429
 
430
  # AI Insight untuk UM
431
  if not avg_ratio_um.empty:
432
- # Temukan perusahaan dengan rasio tertinggi dan terendah di UM
433
  top_company_um = avg_ratio_um.loc[avg_ratio_um['avg_monthly_ratio'].idxmax()]
434
  low_company_um = avg_ratio_um.loc[avg_ratio_um['avg_monthly_ratio'].idxmin()]
435
 
436
- st.markdown("### Insight")
437
  insight_text = (
438
  f"<div class='ai-insight'>"
439
  f"In UM Area, <strong>{top_company_um['nama_perusahaan']}</strong> exhibits the highest average finding-to-person ratio "
@@ -445,7 +441,9 @@ else:
445
  st.markdown(insight_text, unsafe_allow_html=True)
446
  else:
447
  st.warning("No data for UM area or all ratios are NaN.")
448
-
 
 
449
 
450
  # =================== 2. Treemap: Distribusi Temuan per Area (nama_lokasi_full) - PERBAIKAN ===================
451
  st.markdown("<h3 class='section-title'>OBJECTIVE 2 - Active vs Inactive Locations: Who Leads?</h3>", unsafe_allow_html=True)
 
284
 
285
 
286
  # =================== HEADER ===================
287
+ # Gunakan kolom untuk menyusun logo dan teks secara horizontal
288
+ header_cols = st.columns([1, 3]) # Rasio lebar kolom: Logo (kecil), Teks (lebih besar)
289
+
290
+ with header_cols[0]:
291
+ # Cek apakah file logo ada, lalu tampilkan
292
+ logo_path = "pln.png" # Sesuaikan nama file jika berbeda
293
+ if os.path.exists(logo_path):
294
+ st.image(logo_path, caption="", width=100) # Atur width sesuai kebutuhan
295
+ else:
296
+ st.warning(f"Logo `{logo_path}` not found. Please place it in the same directory as this script.")
297
+
298
+ with header_cols[1]:
299
+ st.markdown("""
300
+ <div class="main-header">
301
+ <h1>PLN Audit Insight & Intelligence Dashboard</h1>
302
+ <p style="text-align:center; color:#546e7a; font-size:1.1em; margin-top:8px;">
303
+ Operational Risk Intelligence for Audit & Compliance
304
+ </p>
305
+ </div>
306
+ """, unsafe_allow_html=True)
307
 
308
  # =================== 1. Pie Charts: Temuan/Person by Company (PG & UM) - PERBAIKAN ===================
309
  st.markdown("<h3 class='section-title'>OBJECTIVE 1 - Company Reporting Activity: Who Reports the Most?</h3>", unsafe_allow_html=True)
 
314
  # Tambah kolom bulan
315
  df_local['created_month'] = df_local['created_at'].dt.to_period('M')
316
 
317
+ # --- Langsung buat Area_Type PG / UM tanpa filter manual ---
 
318
  if 'temuan_kode_distrik' in df_local.columns:
 
319
  df_local['Area_Type'] = df_local['temuan_kode_distrik'].apply(
320
  lambda x: 'PG' if 'PG' in str(x).upper()
321
  else 'UM' if 'UM' in str(x).upper()
322
  else 'Other'
323
  )
324
 
325
+ # Otomatis bagi dataset berdasarkan Area_Type
326
  df_pg = df_local[df_local['Area_Type'] == 'PG'].copy()
327
  df_um = df_local[df_local['Area_Type'] == 'UM'].copy()
328
 
 
 
 
 
329
  # --- Fungsi untuk menghitung rasio perusahaan ---
330
  def calculate_avg_ratio_per_company(df_area):
331
  if df_area.empty:
 
332
  return pd.DataFrame()
333
  # Hitung temuan per bulan per perusahaan
334
  findings_by_company_month = df_area.groupby(['created_month', 'nama_perusahaan']).size().reset_index(name='findings_count')
 
339
  # Isi NaN dengan 0 untuk kolom yang mungkin hilang dari merge
340
  merged = merged.fillna({'findings_count': 0, 'unique_creators': 0})
341
  # Filter untuk menghindari pembagian dengan nol
 
342
  merged = merged[merged['unique_creators'] > 0]
343
  # Hitung rasio (ignore NaN)
 
344
  merged['ratio'] = merged['findings_count'] / merged['unique_creators']
345
  merged['ratio'] = merged['ratio'].replace([np.inf, -np.inf], np.nan)
346
 
 
349
  return pd.DataFrame()
350
 
351
  # Rata-rata bulanan per perusahaan
 
 
352
  avg_ratio = merged.groupby('nama_perusahaan')['ratio'].mean().reset_index(name='avg_monthly_ratio')
353
 
354
+ # Jika hasil akhirnya hanya NaN, kembalikan DataFrame kosong
355
  if avg_ratio['avg_monthly_ratio'].isna().all():
356
  return pd.DataFrame()
357
 
 
364
  # Fungsi untuk menentukan warna
365
  def get_color_map(company_series):
366
  pln_color = "#FFD700" # Kuning untuk PLN
 
367
  blue_colors = ["#1E90FF", "#87CEEB", "#B0E0E6", "#ADD8E6", "#E0F6FF"]
368
  color_map = {}
369
  for company in company_series:
370
  if 'PLN' in str(company).upper():
371
  color_map[company] = pln_color
372
  else:
 
373
  idx = len([c for c in color_map.values() if c != pln_color]) % len(blue_colors)
374
  color_map[company] = blue_colors[idx]
375
  return color_map
 
378
  col1, col2 = st.columns(2)
379
 
380
  with col1:
381
+ st.markdown("<h5>Unit Pembangkit: Avg Monthly Finding/Person by Company</h5>", unsafe_allow_html=True)
382
  if not avg_ratio_pg.empty:
383
  color_discrete_map_pg = get_color_map(avg_ratio_pg['nama_perusahaan'])
384
  fig_pg = px.pie(
385
  avg_ratio_pg,
386
  values='avg_monthly_ratio',
387
  names='nama_perusahaan',
388
+ title='PG Area',
389
  color='nama_perusahaan',
390
  color_discrete_map=color_discrete_map_pg
391
  )
 
393
 
394
  # AI Insight untuk PG
395
  if not avg_ratio_pg.empty:
 
396
  top_company_pg = avg_ratio_pg.loc[avg_ratio_pg['avg_monthly_ratio'].idxmax()]
397
  low_company_pg = avg_ratio_pg.loc[avg_ratio_pg['avg_monthly_ratio'].idxmin()]
398
 
399
+ st.markdown("### Insight (PG)")
400
  insight_text = (
401
  f"<div class='ai-insight'>"
402
  f"In PG Area, <strong>{top_company_pg['nama_perusahaan']}</strong> has the highest average finding-to-person ratio "
 
411
  st.warning("No data for PG area or all ratios are NaN.")
412
 
413
  with col2:
414
+ st.markdown("<h5>Unit Maintenance: Avg Monthly Finding/Person by Company</h5>", unsafe_allow_html=True)
415
  if not avg_ratio_um.empty:
416
  color_discrete_map_um = get_color_map(avg_ratio_um['nama_perusahaan'])
417
  fig_um = px.pie(
418
  avg_ratio_um,
419
  values='avg_monthly_ratio',
420
  names='nama_perusahaan',
421
+ title='UM Area',
422
  color='nama_perusahaan',
423
  color_discrete_map=color_discrete_map_um
424
  )
 
426
 
427
  # AI Insight untuk UM
428
  if not avg_ratio_um.empty:
 
429
  top_company_um = avg_ratio_um.loc[avg_ratio_um['avg_monthly_ratio'].idxmax()]
430
  low_company_um = avg_ratio_um.loc[avg_ratio_um['avg_monthly_ratio'].idxmin()]
431
 
432
+ st.markdown("### Insight (UM)")
433
  insight_text = (
434
  f"<div class='ai-insight'>"
435
  f"In UM Area, <strong>{top_company_um['nama_perusahaan']}</strong> exhibits the highest average finding-to-person ratio "
 
441
  st.markdown(insight_text, unsafe_allow_html=True)
442
  else:
443
  st.warning("No data for UM area or all ratios are NaN.")
444
+ else:
445
+ st.error("Column 'temuan_kode_distrik' not found in the data. Cannot determine PG/UM areas.")
446
+ st.stop()
447
 
448
  # =================== 2. Treemap: Distribusi Temuan per Area (nama_lokasi_full) - PERBAIKAN ===================
449
  st.markdown("<h3 class='section-title'>OBJECTIVE 2 - Active vs Inactive Locations: Who Leads?</h3>", unsafe_allow_html=True)