Tourism-Package / app.py
sathishaiuse's picture
Update app.py
ae80e55 verified
# 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
# -------------------------
@st.cache_resource(show_spinner=False)
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.")