import streamlit as st import pandas as pd import numpy as np import plotly.express as px import docx import io from sklearn.linear_model import LinearRegression from datetime import datetime, timedelta from pyvis.network import Network import streamlit.components.v1 as components # ========================================== # 1. SETUP & SIMULASI DATA (Agar Menu Langsung Tes) # ========================================== st.set_page_config(page_title="AAI ACL SUPREME v21.0", layout="wide") if 'data' not in st.session_state: # Buat data simulasi otomatis agar menu tidak kosong saat pertama buka dates = [datetime.now() - timedelta(days=x) for x in range(100)] df_init = pd.DataFrame({ 'tanggal_transaksi': dates, 'id_vendor': np.random.choice(['VND01', 'VND02', 'VND03', 'VND04'], 100), 'nilai_transaksi': np.random.randint(100000000, 250000000, 100), 'no_rekening': np.random.choice(['111222', '333444', '555666'], 100), 'keterangan': np.random.choice(['Pembelian ATK', 'Biaya Koordinasi', 'Fee Konsultan', 'Titipan'], 100), 'no_faktur': range(1000, 1100) }) # Transformasi awal df_init['tanggal_transaksi'] = pd.to_datetime(df_init['tanggal_transaksi']) df_init['jam'] = df_init['tanggal_transaksi'].dt.hour st.session_state.data = df_init # ========================================== # 2. CORE ANALYTICS ENGINE # ========================================== class AuditEngine: @staticmethod def run_analysis(df): res = {} # Deskriptif res['stats'] = df['nilai_transaksi'].describe() # Investigatif res['split'] = df[df['nilai_transaksi'].between(185000000, 199999999)] res['ghost'] = df[(df['jam'] >= 22) | (df['jam'] <= 5)] res['weekend'] = df[df['tanggal_transaksi'].dt.dayofweek >= 5] # Prediktif (Simple) res['trend'] = "Meningkat" if df['nilai_transaksi'].tail(10).mean() > df['nilai_transaksi'].head(10).mean() else "Menurun" return res # ========================================== # 3. UI LAYOUT & NAVIGATION (FIXED ROUTING) # ========================================== # --- LOGO PERUSAHAAN DI SIDEBAR --- # Ganti path/link logo dengan lokasi file logo Anda st.sidebar.image("logo_aai.png", use_container_width=True) # Tambahkan sedikit jarak agar tidak terlalu nempel st.sidebar.markdown("
", unsafe_allow_html=True) st.sidebar.title("COMMAND CENTER") menu = st.sidebar.radio( "PILIH MODUL:", ["Dashboard", "Transformasi Data", "Pivot & Filtering Data", "Analisis Deskriptif", "Analisis Investigatif (6 Tab)", "Document Similarity Detection", "Network Link Analysis", "Diagnostik & Anomali", "Analisis Prediktif", "Relasional (COI)", "Pelaporan (LHA)"], key="nav_main" ) # 2. PINDAHKAN LOGIKA KELUAR KE PALING ATAS (Sebelum menu lain) if menu == "Keluar": st.session_state.clear() st.session_state.logged_in = False st.rerun() # 3. BARU SETELAH ITU MASUK KE MENU LAINNYA elif menu == "Dashboard": st.write("Ini Dashboard") # ... dan seterusnya # --- HEADER STATIS --- st.markdown(f"""

🛡️ AAI ACL SUPREME v21.0

Module: {menu} | Active Database: v16.db

""", unsafe_allow_html=True) # ========================================== # 4. LOGIKA ROUTING MENU (DIBALIK LAYAR) # ========================================== df = st.session_state.data if menu == "Dashboard": c1, c2, c3 = st.columns(3) c1.metric("Total Exposure", f"Rp {df['nilai_transaksi'].sum():,}") c2.metric("Total Transaksi", len(df)) c3.metric("Anomali Terdeteksi", len(df[df['nilai_transaksi'] > 195000000])) st.plotly_chart(px.line(df, x='tanggal_transaksi', y='nilai_transaksi', title="Tren Pengadaan")) elif menu == "Transformasi Data": st.subheader("🛠️ Data Cleaning & ETL") uploaded_file = st.file_uploader("Upload File Baru", type=['xlsx', 'csv']) if uploaded_file: st.session_state.data = pd.read_excel(uploaded_file) st.success("Data Berhasil Diperbarui!") st.write("Preview Data Saat Ini:", df.head(10)) elif menu == "Pivot & Filtering Data": st.subheader("Advanced Pivot & Dynamic Filtering") # Ambil data dari session state df_pivot = st.session_state.data.copy() # ========================================== # 1. MODUL DYNAMIC FILTERING (Sidebar-style inside main) # ========================================== with st.expander(" FILTER DATA (Klik untuk Membuka)", expanded=True): f1, f2, f3 = st.columns(3) # Filter berdasarkan Vendor with f1: vendors = st.multiselect("Pilih Vendor:", options=df_pivot['id_vendor'].unique()) if vendors: df_pivot = df_pivot[df_pivot['id_vendor'].isin(vendors)] # Filter berdasarkan Range Nilai with f2: min_val, max_val = int(df_pivot['nilai_transaksi'].min()), int(df_pivot['nilai_transaksi'].max()) range_nilai = st.slider("Rentang Nilai Transaksi:", min_val, max_val, (min_val, max_val)) df_pivot = df_pivot[(df_pivot['nilai_transaksi'] >= range_nilai[0]) & (df_pivot['nilai_transaksi'] <= range_nilai[1])] # Filter berdasarkan Keterangan (Keyword) with f3: search = st.text_input("Cari di Keterangan:", "") if search: df_pivot = df_pivot[df_pivot['keterangan'].str.contains(search, case=False)] st.divider() # ========================================== # 2. MODUL PIVOT TABLE ENGINE # ========================================== st.markdown("### Pivot Table Builder") p1, p2, p3, p4 = st.columns(4) with p1: rows = st.selectbox("Baris (Rows):", ["id_vendor", "keterangan", "no_rekening", "jam"]) with p2: cols = st.selectbox("Kolom (Columns):", [None, "id_vendor", "keterangan"]) with p3: vals = st.selectbox("Nilai (Values):", ["nilai_transaksi", "jam"]) with p4: func = st.selectbox("Fungsi (Agregasi):", ["sum", "count", "mean", "max", "min"]) # Eksekusi Pivot try: pivot_table = df_pivot.pivot_table( index=rows, columns=cols if cols else None, values=vals, aggfunc=func, fill_value=0 ) # Tampilan Tabel st.write(f"**Hasil Analisis Pivot ({func} of {vals}):**") st.dataframe(pivot_table, use_container_width=True) # ========================================== # 3. VISUALISASI OTOMATIS BERDASARKAN PIVOT # ========================================== st.markdown("### Visualisasi Hasil Pivot") # Jika kolom None, gunakan bar chart sederhana, jika ada kolom gunakan grouped bar if cols: fig_pivot = px.bar(pivot_table, barmode='group', title=f"Perbandingan {vals} per {rows} dan {cols}") else: fig_pivot = px.bar(pivot_table, y=vals, title=f"Total {vals} per {rows}", color=pivot_table.index) st.plotly_chart(fig_pivot, use_container_width=True) # Tombol Download Hasil Filter & Pivot csv = pivot_table.to_csv().encode('utf-8') st.download_button( label="Download Tabel Pivot (.csv)", data=csv, file_name=f"pivot_audit_{datetime.now().strftime('%Y%m%d')}.csv", mime="text/csv" ) except Exception as e: st.error(f"Sistem tidak dapat membuat pivot dengan kombinasi ini. Error: {e}") elif menu == "Analisis Deskriptif": st.subheader("📈 Ringkasan Statistik Data") st.table(df['nilai_transaksi'].describe()) st.plotly_chart(px.histogram(df, x='nilai_transaksi', title="Distribusi Nilai Transaksi")) elif menu == "Analisis Investigatif (6 Tab)": t1, t2, t3, t4, t5, t6 = st.tabs(["🏗️ Split", "🎯 Threshold", "🌙 Ghost", "📅 Weekend", "🔢 Pattern", "🔍 Keyword"]) with t1: st.write("### Potensi Pecah Kontrak (185jt - 200jt)") st.dataframe(df[df['nilai_transaksi'].between(185000000, 199999999)]) with t3: st.write("### Jam Kerja Tidak Wajar") st.dataframe(df[(df['jam'] >= 22) | (df['jam'] <= 5)]) with t6: st.write("### Scan Kata Kunci") st.dataframe(df[df['keterangan'].str.contains('Fee|Koordinasi|Titipan', case=False)]) elif menu == "Network Link Analysis": st.subheader("🕸️ Network Affiliation Map") net = Network(height='500px', width='100%', bgcolor='#222222', font_color='white') # Contoh Link Vendor - Rekening for i, row in df.head(20).iterrows(): net.add_node(row['id_vendor'], label=row['id_vendor'], color='#3b82f6') net.add_node(row['no_rekening'], label=f"Rek:{row['no_rekening']}", color='#ef4444', shape='diamond') net.add_edge(row['id_vendor'], row['no_rekening']) net.save_graph("net.html") components.html(open("net.html", 'r').read(), height=550) elif menu == "Diagnostik & Anomali": st.subheader("🧬 Gap & Duplicate Analysis") dupes = df[df.duplicated(subset=['nilai_transaksi', 'id_vendor'], keep=False)] st.error("Ditemukan Transaksi Duplikat (Nilai & Vendor Sama):") st.dataframe(dupes) elif menu == "Analisis Prediktif": st.subheader("🔮 Forecasting Risiko") st.info(f"Tren Pengadaan Bulan Depan Diprediksi: {AuditEngine.run_analysis(df)['trend']}") st.plotly_chart(px.scatter(df, x='tanggal_transaksi', y='nilai_transaksi', trendline="ols")) elif menu == "Relasional (COI)": st.subheader("🧬 Conflict of Interest (COI) Scan") st.info("Modul ini membandingkan Nomor Rekening Vendor dengan Nomor Rekening Karyawan.") file_emp = st.file_uploader("Unggah Database Karyawan (.xlsx)", type=['xlsx']) if file_emp: df_emp = pd.read_excel(file_emp) # Pastikan kolom rekening ada di kedua dataframe if 'no_rekening' in df_emp.columns and 'no_rekening' in st.session_state.data.columns: # Cari irisan (inner join) berdasarkan no_rekening temuan_coi = pd.merge( st.session_state.data, df_emp[['nama_karyawan', 'no_rekening']], on='no_rekening', how='inner' ) if not temuan_coi.empty: st.error(f"🧬 TEMUAN KRITIS: Ditemukan {len(temuan_coi)} transaksi dengan rekening yang identik antara Vendor dan Karyawan!") # Tampilkan kolom-kolom kunci saja agar jelas st.dataframe(temuan_coi[['tanggal_transaksi', 'id_vendor', 'nilai_transaksi', 'no_rekening', 'nama_karyawan']]) else: st.success("🧬 Bersih: Tidak ditemukan kecocokan nomor rekening antara Vendor dan Karyawan.") else: st.warning("Pastikan kedua file memiliki kolom 'no_rekening'.") elif menu == "Pelaporan (LHA)": st.subheader("📑 Automated LHA Report Generator") if st.button("🚀 EXPORT DATA TEMUAN KE WORD"): # Jalankan ulang analisis untuk ambil data terbaru res = AuditEngine.run_analysis(df) doc = docx.Document() # --- HEADER LAPORAN --- title = doc.add_heading('LAPORAN HASIL ANALISIS INVESTIGASI (LHA)', 0) title.alignment = 1 # Center doc.add_paragraph(f"Tanggal Cetak: {datetime.now().strftime('%d-%m-%Y %H:%M')}") doc.add_paragraph(f"Total Populasi: {len(df)} Transaksi") # --- FUNGSI OTOMATIS TAMBAH TABEL --- def create_table_in_word(dataframe, title_text): doc.add_heading(title_text, level=1) if not dataframe.empty: # Ambil 10 kolom pertama saja agar tidak kepotong di Word df_report = dataframe.head(50) table = doc.add_table(rows=1, cols=len(df_report.columns)) table.style = 'Table Grid' # Header Tabel hdr_cells = table.rows[0].cells for i, col_name in enumerate(df_report.columns): hdr_cells[i].text = str(col_name) # Isi Data for _, row in df_report.iterrows(): row_cells = table.add_row().cells for i, val in enumerate(row): row_cells[i].text = str(val) else: doc.add_paragraph("Tidak ditemukan temuan pada kategori ini.") # --- REPLIKASI TABEL TEMUAN --- # 1. Tabel Split Purchase create_table_in_word(res['split'], "I. Temuan Potensi Pecah Kontrak (Split Purchase)") # 2. Tabel Ghost Hours create_table_in_word(res['ghost'], "II. Temuan Transaksi Jam Tidak Wajar (Ghost Hours)") # 3. Tabel Weekend create_table_in_word(res['weekend'], "III. Temuan Transaksi Hari Libur (Weekend)") # 4. Tabel Keywords (Opsional jika ada) keyword_finds = df[df['keterangan'].str.contains('Fee|Koordinasi|Titipan', case=False)] create_table_in_word(keyword_finds, "IV. Temuan Kata Kunci Berisiko (Keywords)") # --- SIMPAN DAN DOWNLOAD --- buf = io.BytesIO() doc.save(buf) st.success("Laporan Berhasil Disusun!") st.download_button( label="📥 DOWNLOAD LHA LENGKAP (.docx)", data=buf.getvalue(), file_name=f"LHA_AUDIT_{datetime.now().strftime('%Y%m%d')}.docx", mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document" ) elif menu == "Logout": st.info("Sesi Berakhir. Silakan tutup tab ini.") st.stop() # --- ?? LOGIKA MENU: Document Similarity Detection --- elif menu == "Document Similarity Detection": st.subheader("Analisis Kesamaan Konten Isi Dokumen") # --- TEMPAT UPLOAD FILE --- st.markdown("Silakan Upload Dokumen Audit Anda") files = st.file_uploader( "Pilih file (PDF, DOCX, XLSX, atau CSV)", accept_multiple_files=True, type=['pdf', 'docx', 'xlsx', 'csv'], key="uploader_kesamaan" ) # --- TOMBOL EKSEKUSI --- if files: st.success(f"? {len(files)} dokumen siap dianalisis.") kol1, kol2 = st.columns(2) with kol1: if st.button("?? Jalankan Analisis"): st.info("Sedang mengekstrak teks dan membandingkan isi...") # Nanti kita isi logika skor kemiripannya di sini with kol2: if st.button("??? Bersihkan"): st.rerun() else: st.info("Silakan unggah minimal 2 dokumen untuk dibandingkan tingkat kemiripannya.") # --- Tombol Keluar Sistem (Masih pakai yang lama agar tetap jalan) --- if st.sidebar.button("Keluar Sistem"): # Hapus semua sisa session state yang masih nyangkut for key in st.session_state.keys(): del st.session_state[key] # Matikan status logged_in secara paksa st.session_state.logged_in = False # Paksa aplikasi untuk rerun dan otomatis mental ke login st.rerun() # --- FOOTER SIDEBAR (SAFE MODE) --- st.sidebar.markdown("---") # Garis pembatas st.sidebar.write("Developed by Om Bro EA") st.sidebar.caption("@Akademi Audit Indonesia - 2026")