Spaces:
Sleeping
Sleeping
| # app.py - UI mapped to your Business Context & Data Dictionary | |
| import os | |
| import json | |
| import streamlit as st | |
| import logging | |
| from predict_utils import load_model, predict | |
| # ------------------------- | |
| # Logging | |
| # ------------------------- | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger("wellness_app") | |
| # ------------------------- | |
| # Page config | |
| # ------------------------- | |
| st.set_page_config(page_title="Visit with Us — Wellness Package Predictor", layout="centered") | |
| st.title("Visit with Us — Wellness Package Purchase Predictor") | |
| st.write( | |
| "Fill customer & interaction details below. Click **Predict** to estimate whether this customer is likely to purchase the Wellness Tourism Package." | |
| ) | |
| # ------------------------- | |
| # Feature order used when calling the model | |
| # If your training pipeline used different names/order, modify this list to match it exactly. | |
| # Exclude CustomerID and ProdTaken (target). | |
| FEATURE_ORDER = [ | |
| "Age", | |
| "TypeofContact", | |
| "CityTier", | |
| "Occupation", | |
| "Gender", | |
| "NumberOfPersonVisiting", | |
| "PreferredPropertyStar", | |
| "MaritalStatus", | |
| "NumberOfTrips", | |
| "Passport", | |
| "OwnCar", | |
| "NumberOfChildrenVisiting", | |
| "Designation", | |
| "MonthlyIncome", | |
| "PitchSatisfactionScore", | |
| "ProductPitched", | |
| "NumberOfFollowups", | |
| "DurationOfPitch" | |
| ] | |
| # ------------------------- | |
| # Helper conversions | |
| # ------------------------- | |
| def to_int_or_none(v): | |
| try: | |
| if v is None or str(v).strip() == "": | |
| return None | |
| return int(v) | |
| except: | |
| try: | |
| return int(float(v)) | |
| except: | |
| return None | |
| def to_float_or_none(v): | |
| try: | |
| if v is None or str(v).strip() == "": | |
| return None | |
| return float(v) | |
| except: | |
| return None | |
| def to_bool_int(v): | |
| # expects 0/1 or True/False or 'Yes'/'No' | |
| if v in (0, "0", "False", "false", False): | |
| return 0 | |
| if v in (1, "1", "True", "true", True): | |
| return 1 | |
| if isinstance(v, str): | |
| s = v.strip().lower() | |
| if s in ("yes", "y", "true", "t", "1"): | |
| return 1 | |
| if s in ("no", "n", "false", "f", "0"): | |
| return 0 | |
| return None | |
| # ------------------------- | |
| # Debug / env info (collapsible) | |
| # ------------------------- | |
| with st.expander("Environment & debug info", expanded=False): | |
| st.text(f"HF_MODEL_REPO = {os.getenv('HF_MODEL_REPO')}") | |
| st.text(f"HF_MODEL_FILENAME = {os.getenv('HF_MODEL_FILENAME')}") | |
| st.text(f"HF_TOKEN present? {bool(os.getenv('HF_TOKEN'))}") | |
| st.text(f"Working dir = {os.getcwd()}") | |
| try: | |
| st.write("Files in working directory:", sorted(os.listdir("."))) | |
| except Exception as e: | |
| st.write("Could not list directory:", e) | |
| # ------------------------- | |
| # Load model once | |
| # ------------------------- | |
| def _load(): | |
| return load_model() | |
| model = _load() | |
| if model is None: | |
| st.error("Model not loaded. Check server logs for details. The UI remains available for testing.") | |
| st.stop() | |
| st.success("Model loaded successfully ✅") | |
| # ------------------------- | |
| # Input form (matching Data Dictionary) | |
| # ------------------------- | |
| st.header("Customer details") | |
| with st.form(key="customer_form"): | |
| # CustomerID intentionally optional (not used by model) | |
| customer_id = st.text_input("CustomerID (optional)", value="") | |
| age = st.number_input("Age", min_value=0, max_value=120, value=35, step=1, help="Age in years") | |
| typeof_contact = st.selectbox("Type of Contact", options=["Company Invited", "Self Inquiry"], index=0) | |
| city_tier = st.selectbox("City Tier", options=[1, 2, 3], index=1, help="1 = Tier 1 (most developed)") | |
| occupation = st.selectbox("Occupation", options=[ | |
| "Salaried", "Freelancer", "Business", "Student", "Retired", "Other" | |
| ], index=0) | |
| gender = st.selectbox("Gender", options=["Male", "Female", "Other"], index=0) | |
| num_persons = st.number_input("Number Of Persons Visiting", min_value=1, max_value=20, value=1, step=1) | |
| preferred_property_star = st.selectbox("Preferred Property Star", options=[1, 2, 3, 4, 5], index=3) | |
| marital_status = st.selectbox("Marital Status", options=["Single", "Married", "Divorced", "Widowed"], index=1) | |
| number_of_trips = st.number_input("Average Number Of Trips per Year", min_value=0, max_value=100, value=2, step=1) | |
| passport = st.selectbox("Has Passport?", options=["No", "Yes"], index=1) | |
| own_car = st.selectbox("Owns Car?", options=["No", "Yes"], index=0) | |
| num_children = st.number_input("Number Of Children Visiting (age <5)", min_value=0, max_value=10, value=0, step=1) | |
| designation = st.text_input("Designation (job title)", value="") | |
| monthly_income = st.number_input("Monthly Income (gross)", min_value=0.0, value=30000.0, step=1000.0, format="%.2f") | |
| st.markdown("---") | |
| st.header("Interaction / Pitch details") | |
| pitch_score = st.slider("Pitch Satisfaction Score (1-10)", min_value=0.0, max_value=10.0, value=7.0, step=0.1) | |
| product_pitched = st.selectbox("Product Pitched", options=[ | |
| "Wellness Package", "Adventure Package", "Luxury Package", "Budget Package", "Family Package", "Other" | |
| ], index=0) | |
| number_of_followups = st.number_input("Number Of Followups", min_value=0, max_value=100, value=1, step=1) | |
| duration_of_pitch = st.number_input("Duration Of Pitch (minutes)", min_value=0.0, max_value=1000.0, value=10.0, step=0.5) | |
| submit = st.form_submit_button("Predict") | |
| # ------------------------- | |
| # Prepare payload & predict | |
| # ------------------------- | |
| if submit: | |
| # prepare a mapping preserving FEATURE_ORDER | |
| payload = { | |
| "Age": to_int_or_none(age), | |
| "TypeofContact": typeof_contact, | |
| "CityTier": to_int_or_none(city_tier), | |
| "Occupation": occupation, | |
| "Gender": gender, | |
| "NumberOfPersonVisiting": to_int_or_none(num_persons), | |
| "PreferredPropertyStar": to_int_or_none(preferred_property_star), | |
| "MaritalStatus": marital_status, | |
| "NumberOfTrips": to_int_or_none(number_of_trips), | |
| "Passport": to_bool_int(passport), | |
| "OwnCar": to_bool_int(own_car), | |
| "NumberOfChildrenVisiting": to_int_or_none(num_children), | |
| "Designation": designation, | |
| "MonthlyIncome": to_float_or_none(monthly_income), | |
| "PitchSatisfactionScore": to_float_or_none(pitch_score), | |
| "ProductPitched": product_pitched, | |
| "NumberOfFollowups": to_int_or_none(number_of_followups), | |
| "DurationOfPitch": to_float_or_none(duration_of_pitch) | |
| } | |
| # Ensure final dict follows FEATURE_ORDER | |
| ordered_payload = {k: payload.get(k) for k in FEATURE_ORDER} | |
| st.markdown("### Payload sent to model (ordered)") | |
| st.json(ordered_payload) | |
| # call predict from predict_utils | |
| with st.spinner("Running prediction..."): | |
| result = predict(model, ordered_payload) | |
| if result is None: | |
| st.error("Prediction returned nothing. Check server logs.") | |
| elif "error" in result: | |
| st.error(f"Prediction error: {result['error']}") | |
| else: | |
| pred = result.get("prediction") | |
| prob = result.get("probability") | |
| st.success("Prediction complete") | |
| st.write("**Will Purchase?**", "Yes" if int(pred) == 1 else "No") | |
| if prob is not None: | |
| st.write("**Predicted probability:**", round(float(prob), 4)) | |
| # helpful suggestion: | |
| if int(pred) == 1: | |
| st.info("Suggested action: prioritize outreach and personalize offer.") | |
| else: | |
| st.info("Suggested action: low priority; consider nurturing campaign.") | |
| # ------------------------- | |
| # Quick utilities & debug | |
| # ------------------------- | |
| st.markdown("---") | |
| with st.expander("Quick test & debug", expanded=False): | |
| if st.button("Show model type & capabilities"): | |
| try: | |
| st.write("Model type:", type(model)) | |
| st.write("Has predict:", hasattr(model, "predict")) | |
| st.write("Has predict_proba:", hasattr(model, "predict_proba")) | |
| except Exception as e: | |
| st.error(str(e)) | |
| if st.button("Generate sample payload (example)"): | |
| sample = { | |
| "Age": 30, | |
| "TypeofContact": "Company Invited", | |
| "CityTier": 1, | |
| "Occupation": "Salaried", | |
| "Gender": "Female", | |
| "NumberOfPersonVisiting": 2, | |
| "PreferredPropertyStar": 4, | |
| "MaritalStatus": "Married", | |
| "NumberOfTrips": 3, | |
| "Passport": 1, | |
| "OwnCar": 0, | |
| "NumberOfChildrenVisiting": 1, | |
| "Designation": "Manager", | |
| "MonthlyIncome": 65000.0, | |
| "PitchSatisfactionScore": 8.5, | |
| "ProductPitched": "Wellness Package", | |
| "NumberOfFollowups": 2, | |
| "DurationOfPitch": 15.0 | |
| } | |
| st.json(sample) | |
| st.write("You can paste this JSON into the 'Paste JSON dict' mode if present in earlier versions.") | |
| st.caption("If your saved pipeline expects a different feature order or column names, update FEATURE_ORDER at the top of this file to match the training pipeline exactly.") | |