Brian045's picture
Update app.py
d2faca5 verified
Raw History Blame Contribute Delete
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()