import gradio as gr import prediction_engine as engine import data_handler as handler import analysis_utils import auth_manager # Import logika untuk log viewer from log_viewer import load_logs, filter_logs, export_logs from datetime import datetime import pandas as pd # ============================================================================== # WRAPPERS (Jembatan UI ke Logic) # Fungsi-fungsi ini menghubungkan input dari Gradio UI ke fungsi backend, # serta mengatur visibilitas komponen UI. # ============================================================================== # 1. Auth Wrapper: Menangani Login def login_wrapper(username, password): success, role, msg = auth_manager.authenticate(username, password) if not success: # Login Gagal: Tampilkan pesan error, sembunyikan semua tab aplikasi return ( gr.update(visible=True, value=msg), # status gr.update(visible=True), # login_col gr.update(visible=False), # app_col gr.update(visible=False), # tab_manual gr.update(visible=False), # tab_file gr.update(visible=False), # tab_admin gr.update(visible=False) # tab_users ) # Login Sukses: Tentukan visibilitas tab berdasarkan Role user show_manual = True # Semua user bisa akses input manual show_file = role in ['premium', 'developer'] # Fitur analisis file hanya untuk Premium & Dev show_admin = role == 'developer' # Admin panel hanya untuk Developer show_users = role == 'developer' # User management hanya untuk Developer return ( gr.update(visible=True, value=msg), # status gr.update(visible=False), # login_col (Sembunyikan form login) gr.update(visible=True), # app_col (Tampilkan aplikasi utama) gr.update(visible=show_manual), gr.update(visible=show_file), gr.update(visible=show_admin), gr.update(visible=show_users) ) def logout_wrapper(): """Fungsi Logout: Mereset UI ke kondisi awal.""" return ( gr.update(value="Logged out"), gr.update(visible=True), # Tampilkan form login gr.update(visible=False), # Sembunyikan aplikasi gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False) ) # Wrapper untuk User Management (Developer) def add_user_wrapper(u, p, r): success, msg = auth_manager.add_user(u, p, r) return gr.update(value=msg), auth_manager.list_users() def delete_user_wrapper(u): success, msg = auth_manager.delete_user(u) return gr.update(value=msg), auth_manager.list_users() def refresh_users_wrapper(): return auth_manager.list_users() # 2. Wrapper Manual Prediction def manual_predict_wrapper(assessed, town, prop, year, date): pred, debug = engine.predict_single(assessed, town, prop, year, date, return_debug=True) return pred, str(debug) def manual_suggest_wrapper(town, prop, year): val = engine.get_suggested_assessed_value(town, prop, year) gr.Info(f"Data ditemukan: ${val:,.0f}") return val def download_template_wrapper(): path = analysis_utils.generate_excel_template() return path # 3. Wrapper File Upload & Analysis def on_file_upload(file): df, err = handler.load_preview(file) if err: return gr.update(visible=True, value=pd.DataFrame()), gr.update(visible=True, value=f"❌ {err}"), gr.update(visible=False) return gr.update(visible=True, value=df), gr.update(visible=True, value="✅ File loaded. Check data below."), gr.update(visible=True) def impute_missing_values(df): """Mengisi nilai Assessed Value yang kosong/nol dengan saran AI (Mode/Median).""" if df is None or df.empty: return df, "Dataframe is empty." count = 0 df['Assessed Value'] = pd.to_numeric(df['Assessed Value'], errors='coerce').fillna(0.0) for idx, row in df.iterrows(): # Jika nilai <= 100, dianggap kosong/invalid dan perlu diimputasi if row['Assessed Value'] <= 100: try: town = row.get('Town', '') prop = row.get('Property_Residential', 'Single Family') year = row.get('List Year', '2021') suggested = engine.get_suggested_assessed_value(town, prop, year) df.at[idx, 'Assessed Value'] = suggested count += 1 except: pass return df, f"✅ Imputed {count} rows with suggested values." def generate_dashboard_wrapper(df): """Menjalankan analisis lengkap dan menghasilkan 9 grafik.""" results = handler.process_dataframe(df) if results is None: return (gr.update(visible=False), "Error processing.", "-", "-", "-", "-", None, None, None, None, None, None, None, None, None, None, None, None, None) status = results[0] if isinstance(status, str): # Jika return berupa pesan error string if status == "ERROR_COLS": msg = f"❌ {results[1]}" elif status == "ERROR_FORMAT": msg = f"❌ {results[1]}" else: msg = f"❌ Error System: {results[1]}" # Return semua None untuk komponen grafik return (gr.update(visible=False), msg, "-", "-", "-", "-", None, None, None, None, None, None, None, None, None, None, None, None, None) # Unpack hasil sukses df_res, metrics, f1, f2, f3, f4, f5, f6, f7, f8, f9, xlsx_path = results return ( gr.update(visible=True), "✅ Analisis Selesai!", metrics["total_count"], metrics["total_val"], metrics["avg_price"], metrics["avg_gain"], f1, f2, f3, f4, f5, f6, f7, f8, f9, df_res, xlsx_path ) # 4. Wrapper Admin (Log Viewer) def on_export_logs(): path = export_logs() return gr.update(value=path, visible=True) # ============================================================================== # UI LAYOUT (Gradio Blocks) # ============================================================================== custom_css = """ .stat-card { border: 1px solid #ddd; padding: 10px; border-radius: 8px; text-align: center; background-color: #f9f9f9; } .stat-value { font-size: 24px; font-weight: bold; color: #2563eb; } """ ui_towns = sorted([t.title() for t in engine.TRAIN_TOWNS]) ui_props = sorted([p.title() for p in engine.TRAIN_PROP_TYPES]) ui_years = engine.TRAIN_YEARS with gr.Blocks(theme=gr.themes.Soft(), css=custom_css, title="Real Estate AI Pro") as demo: gr.Markdown("# 🏢 Real Estate AI Valuator & Analyzer") # --- LOGIN SECTION --- with gr.Group(visible=True) as login_col: with gr.Row(): with gr.Column(scale=1): pass # Spacer Left with gr.Column(scale=1): # Centered Column gr.Markdown("### 🔐 Login System", elem_id="login_header") username_input = gr.Textbox(label="Username", placeholder="Username") password_input = gr.Textbox(label="Password", type="password", placeholder="Password") login_btn = gr.Button("Login", variant="primary") login_status = gr.Textbox(label="Status", interactive=False) # Show credentials for free and premium only gr.Markdown("Credentials: `free/free`, `premium/premium`") with gr.Column(scale=1): pass # Spacer Right # --- MAIN APP SECTION (Hidden Initially) --- with gr.Group(visible=False) as app_col: with gr.Row(): # Header dengan tombol Logout with gr.Column(scale=8): pass with gr.Column(scale=1, min_width=100): logout_btn = gr.Button("🚪 Logout", size="sm", variant="secondary") with gr.Tabs() as main_tabs: # --- TAB 1: INPUT MANUAL (Visible to All) --- with gr.TabItem("📝 Input Manual") as tab_manual: with gr.Row(): with gr.Column(): t1_town = gr.Dropdown(ui_towns, label="Town", value="Norwalk") t1_prop = gr.Dropdown(ui_props, label="Property Type", value="Single Family") t1_year = gr.Dropdown(ui_years, label="List Year", value="2021") with gr.Row(): t1_assessed = gr.Number(label="Assessed Value", value=0) t1_btn_suggest = gr.Button("🔍 Cari", size="sm") t1_date = gr.Textbox(label="Prediction Date", value=datetime.now().strftime('%Y-%m-%d')) t1_btn_run = gr.Button("Hitung", variant="primary") with gr.Column(): t1_output = gr.Number(label="Estimasi Harga ($)", precision=2) # --- TAB 2: ADVANCED ANALYSIS (Premium Only) --- with gr.TabItem("📊 Advanced Analysis", visible=False) as tab_file: gr.Markdown("Upload Excel/CSV. Sistem akan **otomatis menyesuaikan** jika nama kolom Anda berbeda.") with gr.Row(variant="panel"): with gr.Column(): t2_file = gr.File(label="1. Upload File", file_types=[".csv", ".xlsx", ".xls"]) with gr.Row(): t2_btn_template = gr.Button("⬇️ Download Template Input", size="sm") t2_template_file = gr.File(label="Template File", visible=False, show_label=False) with gr.Column(scale=0, min_width=200): t2_status = gr.Textbox(label="Status", interactive=False, value="Menunggu...") with gr.Group(visible=False) as t2_preview_group: gr.Markdown("### Preview Data & Imputasi") t2_preview_df = gr.Dataframe(interactive=True, label="Data Preview (Editable)") with gr.Row(): t2_btn_impute = gr.Button("🔍 Search / Impute Missing Values", variant="secondary") t2_btn_run = gr.Button("⚡ Generate Dashboard", variant="primary") with gr.Group(visible=False) as t2_container: gr.Markdown("---") gr.Markdown("### Hasil Analisis") with gr.Row(): kpi1 = gr.Textbox(label="Total Unit") kpi2 = gr.Textbox(label="Total Valuasi") kpi3 = gr.Textbox(label="Rata-rata Harga") kpi4 = gr.Textbox(label="Avg Growth") # Layout Grafik 1 per baris with gr.Row(): plot1 = gr.Plot(label="Prediksi vs Taksiran") with gr.Row(): plot2 = gr.Plot(label="Sebaran Harga (Box)") with gr.Row(): plot3 = gr.Plot(label="Trend (Dual Axis)") with gr.Row(): plot4 = gr.Plot(label="Price Dist (Violin)") with gr.Row(): plot5 = gr.Plot(label="Heatmap Town vs Type") with gr.Row(): plot6 = gr.Plot(label="Top Growth Towns") with gr.Row(): plot7 = gr.Plot(label="Price Segmentation") with gr.Row(): plot8 = gr.Plot(label="Outlier Detection") with gr.Row(): plot9 = gr.Plot(label="Investment Potential") t2_df = gr.Dataframe(interactive=False, label="Hasil Prediksi") t2_download = gr.File(label="Download Laporan (.xlsx)") # --- TAB 3: ADMIN PANEL (Developer Only) --- with gr.TabItem("🛠️ Admin Panel (Developer)", visible=False) as tab_admin: gr.Markdown("### Developer Log Inspector") with gr.Row(): with gr.Column(scale=1): admin_btn_refresh = gr.Button("🔄 Refresh Data") admin_btn_download = gr.Button("💾 Export to Excel") admin_download_file = gr.File(label="Download", visible=False) with gr.Column(scale=3): admin_search_town = gr.Textbox(label="Filter by Town", placeholder="e.g. Norwalk") admin_search_date = gr.Textbox(label="Filter by Date", placeholder="YYYY-MM-DD") admin_status_msg = gr.Textbox(label="DB Status", interactive=False) admin_data_table = gr.Dataframe(interactive=False, wrap=True, label="System Logs") # --- TAB 4: USERS SETTING (Developer Only) --- with gr.TabItem("👥 Users Setting", visible=False) as tab_users: gr.Markdown("### User Management") with gr.Row(): with gr.Column(scale=1): u_name = gr.Textbox(label="New Username") u_pass = gr.Textbox(label="New Password", type="password") u_role = gr.Dropdown(["free", "premium", "developer"], label="Role", value="free") u_btn_add = gr.Button("Add User", variant="primary") gr.Markdown("---") u_del_name = gr.Textbox(label="Username to Delete") u_btn_del = gr.Button("Delete User", variant="stop") with gr.Column(scale=2): u_btn_refresh = gr.Button("Refresh User List") u_status = gr.Textbox(label="Status") u_table = gr.Dataframe(interactive=False, label="Current Users") # --- EVENT WIRING (Menghubungkan Tombol dengan Fungsi) --- # Login Events login_btn.click( fn=login_wrapper, inputs=[username_input, password_input], outputs=[login_status, login_col, app_col, tab_manual, tab_file, tab_admin, tab_users] ) logout_btn.click( fn=logout_wrapper, inputs=None, outputs=[login_status, login_col, app_col, tab_manual, tab_file, tab_admin, tab_users] ) # Manual Prediction Events t1_btn_suggest.click(manual_suggest_wrapper, inputs=[t1_town, t1_prop, t1_year], outputs=t1_assessed) t1_btn_run.click(manual_predict_wrapper, inputs=[t1_assessed, t1_town, t1_prop, t1_year, t1_date], outputs=[t1_output, gr.Textbox(visible=False)]) # File Analysis Events t2_btn_template.click(fn=download_template_wrapper, inputs=None, outputs=t2_template_file) t2_template_file.change(lambda x: gr.update(visible=True), inputs=t2_template_file, outputs=t2_template_file) t2_file.upload(fn=on_file_upload, inputs=[t2_file], outputs=[t2_preview_df, t2_status, t2_preview_group]) t2_btn_impute.click(fn=impute_missing_values, inputs=[t2_preview_df], outputs=[t2_preview_df, t2_status]) t2_btn_run.click( fn=generate_dashboard_wrapper, inputs=[t2_preview_df], outputs=[t2_container, t2_status, kpi1, kpi2, kpi3, kpi4, plot1, plot2, plot3, plot4, plot5, plot6, plot7, plot8, plot9, t2_df, t2_download] ) # Admin Panel Events admin_btn_refresh.click(load_logs, inputs=None, outputs=[admin_data_table, admin_status_msg]) admin_search_town.change(filter_logs, inputs=[admin_search_town, admin_search_date], outputs=[admin_data_table, admin_status_msg]) admin_search_date.change(filter_logs, inputs=[admin_search_town, admin_search_date], outputs=[admin_data_table, admin_status_msg]) admin_btn_download.click(on_export_logs, inputs=None, outputs=admin_download_file) # User Management Events u_btn_add.click(add_user_wrapper, inputs=[u_name, u_pass, u_role], outputs=[u_status, u_table]) u_btn_del.click(delete_user_wrapper, inputs=[u_del_name], outputs=[u_status, u_table]) u_btn_refresh.click(refresh_users_wrapper, inputs=None, outputs=u_table) if __name__ == "__main__": demo.launch()