Spaces:
Sleeping
Sleeping
| # app.py - Gradio app to serve a saved sklearn pipeline (loan_pipeline.pkl) | |
| # Robust, commented, and production-friendly. | |
| import joblib | |
| import pandas as pd | |
| import gradio as gr | |
| import traceback | |
| from typing import Tuple, Dict, Any | |
| MODEL_PATH = "loan_pipeline.pkl" # file must be in the same repo root | |
| # -------------------------- | |
| # Load model safely (with helpful error message) | |
| # -------------------------- | |
| try: | |
| model = joblib.load(MODEL_PATH) | |
| except Exception as e: | |
| # If the model fails to load (common: sklearn version mismatch), | |
| # store the exception so the UI can display an informative error. | |
| model = None | |
| load_error = traceback.format_exc() | |
| else: | |
| load_error = None | |
| # -------------------------- | |
| # Utility: safe conversion helper | |
| # -------------------------- | |
| def safe_cast(value: Any, to_type, default=None): | |
| """ | |
| Try to cast `value` to `to_type`. If fails, return `default`. | |
| Keeps app from crashing when users type bad input. | |
| """ | |
| try: | |
| return to_type(value) | |
| except Exception: | |
| return default | |
| # -------------------------- | |
| # Prediction function | |
| # -------------------------- | |
| def predict_loan(age, income, employment, credit_score, dependents, | |
| loan_amount, purpose, existing_debt, marital_status, | |
| education_level, default_history) -> Tuple[str, Dict[str, float]]: | |
| """ | |
| Build one-row DataFrame from inputs, run model.predict & predict_proba, | |
| then return (decision_text, probabilities_map). | |
| Return value shape matches Gradio outputs: (Textbox, Label). | |
| """ | |
| # If model failed to load, return error message | |
| if model is None: | |
| # Return error text + empty label mapping | |
| return ("ERROR: Model load failed. See server logs.", {"ERROR": 1.0}) | |
| # Coerce numeric inputs safely | |
| age_i = safe_cast(age, int, None) | |
| income_f = safe_cast(income, float, None) | |
| credit_score_i = safe_cast(credit_score, int, None) | |
| dependents_i = safe_cast(dependents, int, None) | |
| loan_amount_f = safe_cast(loan_amount, float, None) | |
| existing_debt_f = safe_cast(existing_debt, float, None) | |
| default_history_i = safe_cast(default_history, int, None) | |
| # Basic validation: ensure required numeric fields are present | |
| missing_inputs = [] | |
| for name, val in [ | |
| ("age", age_i), ("income", income_f), ("credit_score", credit_score_i), | |
| ("dependents", dependents_i), ("loan_amount", loan_amount_f), | |
| ("existing_debt", existing_debt_f), ("default_history", default_history_i) | |
| ]: | |
| if val is None: | |
| missing_inputs.append(name) | |
| if missing_inputs: | |
| message = f"Invalid or missing numeric inputs: {', '.join(missing_inputs)}" | |
| # Return a user-friendly error message plus a label map so Gradio doesn't crash. | |
| return (f"INPUT ERROR: {message}", {"ERROR": 1.0}) | |
| # Build the row dict exactly matching training column names | |
| row = { | |
| 'age': age_i, | |
| 'income': income_f, | |
| 'employment': employment, | |
| 'credit_score': credit_score_i, | |
| 'dependents': dependents_i, | |
| 'loan_amount': loan_amount_f, | |
| 'purpose': purpose, | |
| 'existing_debt': existing_debt_f, | |
| 'marital_status': marital_status, | |
| 'education_level': education_level, | |
| 'default_history': default_history_i | |
| } | |
| # Create DataFrame (single-row) - pipeline expects DataFrame with same columns | |
| try: | |
| df = pd.DataFrame([row]) | |
| except Exception as e: | |
| return (f"ERROR building DataFrame: {e}", {"ERROR": 1.0}) | |
| # Run model prediction inside try/except to catch runtime issues | |
| try: | |
| pred = int(model.predict(df)[0]) # 0 or 1 | |
| proba = model.predict_proba(df)[0] # [prob_no, prob_yes] | |
| except Exception as e: | |
| # If predict fails, return readable error | |
| tb = traceback.format_exc() | |
| return (f"PREDICTION ERROR: {str(e)}", {"ERROR": 1.0}) | |
| # Create human-friendly outputs | |
| decision = "YES" if pred == 1 else "NO" | |
| probs_map = {"NO": round(float(proba[0]), 4), "YES": round(float(proba[1]), 4)} | |
| return (f"Decision: {decision}", probs_map) | |
| # -------------------------- | |
| # Build Gradio UI | |
| # -------------------------- | |
| title = "Loan Approval Demo" | |
| description = ( | |
| "Enter applicant details and get a prediction from the saved pipeline.\n\n" | |
| "If the model fails to load or predict, a helpful error message will appear here." | |
| ) | |
| # Input widgets. Keep values and types aligned with training data. | |
| inputs = [ | |
| gr.Number(label="age", value=30, precision=0), | |
| gr.Number(label="income", value=50000.0), | |
| gr.Dropdown(label="employment", choices=["employed","self-employed","unemployed","contract"], value="employed"), | |
| gr.Number(label="credit_score", value=650, precision=0), | |
| gr.Number(label="dependents", value=0, precision=0), | |
| gr.Number(label="loan_amount", value=100000.0), | |
| gr.Dropdown(label="purpose", choices=["home","car","education","personal","business"], value="personal"), | |
| gr.Number(label="existing_debt", value=0.0), | |
| gr.Dropdown(label="marital_status", choices=["single","married","divorced","widowed"], value="single"), | |
| gr.Dropdown(label="education_level", choices=["other","highschool","bachelor","master","phd"], value="bachelor"), | |
| gr.Dropdown(label="default_history", choices=[0,1], value=0) | |
| ] | |
| # Outputs: Textbox for readable decision + Label for probabilities | |
| outputs = [ | |
| gr.Textbox(label="Decision"), | |
| gr.Label(num_top_classes=2, label="Probabilities") | |
| ] | |
| iface = gr.Interface( | |
| fn=predict_loan, | |
| inputs=inputs, | |
| outputs=outputs, | |
| title=title, | |
| description=description, | |
| ) | |
| # -------------------------- | |
| # Launch the app (only when run as script) | |
| # -------------------------- | |
| if __name__ == "__main__": | |
| iface.launch() | |