Spaces:
Sleeping
Sleeping
Download app.py from Brian045/RealEstatePricePrediction: direct link, hf CLI and curl.
- Browser
- Download file 16.1 kB
-
https://huggingface.co/spaces/Brian045/RealEstatePricePrediction/resolve/main/app.py
- Command line
-
hf download hf://spaces/Brian045/RealEstatePricePrediction/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Brian045/RealEstatePricePrediction/resolve/main/app.py
16.1 kB
| 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() |