π Web Application Service - Architecture & Design
Customer Churn Prediction by Abdelrahman-Adnan
οΏ½ What You'll Learn
This tutorial teaches you how to build a production-ready web application for machine learning predictions. By the end, you'll understand:
- π― How to create APIs for ML model serving
- π How to build interactive dashboards for business users
- π How to integrate models into web applications
- π‘οΈ How to handle errors and validate user inputs
- π How to display ML results in user-friendly formats
π€ Why Do We Need a Web Application Service?
Imagine you've built an amazing machine learning model that predicts customer churn. But how do people actually use it?
The Problem:
- Data scientists can run models in Jupyter notebooks
- But business users need simple interfaces
- Developers need API endpoints to integrate into other systems
- Models sitting on servers are useless without access points
Our Solution:
The Web Application Service acts as a bridge between your ML models and real users. Think of it like a restaurant:
- π€ The Model = The kitchen (where the magic happens)
- π The Web App = The restaurant interface (menu, waiters, tables)
- π₯ Users = Customers who want to enjoy the food
π― What This Service Does
For Business Users:
- Provides a simple dashboard where they can input customer data
- Shows churn probability in easy-to-understand formats
- Gives actionable recommendations for customer retention
For Developers:
- Offers REST API endpoints for system integration
- Provides automatic documentation
- Handles authentication and rate limiting
For the System:
- Manages model loading and caching for fast responses
- Handles multiple concurrent requests
- Logs all predictions for monitoring
ποΈ Understanding the Architecture
Let's break down how our web application is structured. Think of it like building a house:
π The Blueprint: System Architecture
π Web Application House
βββ πͺ Front Door (API Gateway)
βββ ποΈ Living Room (Streamlit Dashboard)
βββ π΄ Kitchen (FastAPI Backend)
βββ π Library (Model Storage)
βββ π§ Utility Room (Shared Components)
Detailed Architecture Explanation:
1. πͺ The Front Door (Entry Points)
- Streamlit Dashboard (Port 8501): Where business users enter
- FastAPI Backend (Port 8000): Where developers and systems connect
- Both doors lead to the same house but serve different visitors
2. ποΈ Living Room (User Interface)
- Comfortable space for business users
- Easy-to-use forms and buttons
- Visual charts and graphs
- No technical knowledge required
3. π΄ Kitchen (Processing Engine)
- Where the real work happens
- Takes raw ingredients (customer data)
- Follows recipes (ML models)
- Produces meals (predictions)
4. π Library (Knowledge Storage)
- Stores trained models
- Keeps data preprocessing tools
- Maintains configuration settings
5. π§ Utility Room (Shared Services)
- Logging and monitoring
- Error handling
- Security features
- Performance optimization
π Project File Structure (What Goes Where)
services/web-app/
βββ π― fastapi_app.py # Main kitchen (API server)
βββ π streamlit_app.py # Living room (dashboard)
βββ π³ Dockerfile # House blueprint for containers
βββ π requirements.txt # Shopping list for dependencies
βββ π utils/ # Utility room contents
β βββ π€ model_loader.py # Robot that fetches models
β βββ π§ preprocessing.py # Data cleaning tools
β βββ β
validators.py # Quality control inspectors
βββ π config/ # House rules and settings
βββ βοΈ settings.py # Master control panel
βββ π logging.conf # Communication system setup
π Tutorial: Building the FastAPI Backend
Let's build our API step by step, like learning to cook:
Step 1: Setting Up the Kitchen (Basic FastAPI Setup)
What we're doing: Creating the foundation for our API server.
# fastapi_app.py - Our main kitchen setup
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import logging
# ποΈ Create our restaurant (FastAPI app)
app = FastAPI(
title="Churn Prediction Kitchen", # Restaurant name
description="Serves fresh ML predictions", # What we do
version="1.0.0" # Our recipe book version
)
# π Setup the communication system (logging)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# π― A simple welcome message (like a restaurant sign)
@app.get("/")
async def welcome():
return {"message": "Welcome to the Churn Prediction Kitchen! π³"}
Why this works:
-
FastAPI()
creates our web server - like opening a restaurant -
title
anddescription
help people understand what we do -
logging
helps us track what's happening - like a kitchen log book - The
@app.get("/")
creates a simple welcome page
Step 2: Defining Our Menu (Data Models)
What we're doing: Defining what kind of orders (data) we accept.
# π Our menu - what customers can order
class CustomerData(BaseModel):
"""
This is like a restaurant order form.
Customers must fill out all required fields.
"""
# π₯ Customer Demographics (required info)
gender: str # "Male" or "Female"
SeniorCitizen: int # 0 or 1 (like a checkbox)
Partner: str # "Yes" or "No"
Dependents: str # "Yes" or "No"
# π Service Information
tenure: int # How many months they've been a customer
PhoneService: str # "Yes" or "No"
InternetService: str # "DSL", "Fiber optic", or "No"
# π° Billing Information
MonthlyCharges: float # How much they pay per month
TotalCharges: str # Total amount paid (comes as text)
PaymentMethod: str # How they pay
Contract: str # Contract type
# β
Built-in validation (quality control)
@validator('tenure')
def check_tenure(cls, v):
if v < 0 or v > 100:
raise ValueError('Tenure must be between 0 and 100 months')
return v
@validator('MonthlyCharges')
def check_charges(cls, v):
if v <= 0:
raise ValueError('Monthly charges must be positive')
return v
# π What we serve back (our prediction results)
class PredictionResponse(BaseModel):
"""
This is what customers get back - like a receipt with results
"""
churn_probability: float # Chance of churning (0 to 1)
risk_level: str # "Low", "Medium", or "High"
confidence: float # How sure we are (0 to 1)
recommendations: list # What to do about it
model_version: str # Which recipe we used
Why this is important:
-
BaseModel
ensures data quality - like checking IDs at a club -
@validator
functions catch bad data before it causes problems - Clear field names help users understand what to provide
- Response models guarantee consistent output format
Step 3: Loading Our Secret Recipe (Model Loading)
What we're doing: Getting our trained ML model ready to make predictions.
# π€ Our robot chef that loads and manages models
class ModelManager:
"""
Think of this as our head chef who:
- Knows where all recipes are stored
- Can quickly access any cooking tool
- Ensures everything is fresh and ready
"""
def __init__(self):
self.model = None # Our main recipe
self.scaler = None # Tool for standardizing ingredients
self.encoder = None # Tool for converting text to numbers
self.model_version = None # Which version of recipe we're using
# π Load everything when the kitchen opens
self._load_models()
def _load_models(self):
"""Load our cooking tools and recipes"""
try:
# π Try to get the latest recipe from our recipe book (MLflow)
import mlflow
# π Look for the production recipe
model_uri = "models:/churn-prediction/Production"
self.model = mlflow.sklearn.load_model(model_uri)
# π οΈ Load our cooking tools
import joblib
self.scaler = joblib.load('artifacts/scaler.pkl')
self.encoder = joblib.load('artifacts/encoder.pkl')
logger.info("β
Kitchen is ready! All tools and recipes loaded.")
except Exception as e:
logger.error(f"β Kitchen setup failed: {str(e)}")
# π¨ If something goes wrong, try backup recipes
self._load_backup_models()
def _load_backup_models(self):
"""If our main recipes fail, use backup ones"""
try:
import joblib
self.model = joblib.load('models/backup_model.pkl')
self.scaler = joblib.load('models/backup_scaler.pkl')
self.encoder = joblib.load('models/backup_encoder.pkl')
self.model_version = "backup"
logger.info("π Using backup recipes - kitchen operational!")
except Exception as e:
logger.critical(f"π₯ Complete kitchen failure: {str(e)}")
raise
# π Create our model manager (hire our head chef)
model_manager = ModelManager()
Key concepts explained:
- Model Manager: Like a head chef who manages all cooking tools
- MLflow integration: Like having a digital recipe book that updates automatically
- Backup system: Always have Plan B when technology fails
- Error handling: Graceful failure - the show must go on
Step 4: The Main Cooking Process (Prediction Endpoint)
What we're doing: Creating the actual service that makes predictions.
@app.post("/predict")
async def predict_churn(customer_data: CustomerData):
"""
This is our main cooking station where we:
1. Take a customer order (input data)
2. Prepare the ingredients (preprocess)
3. Cook the meal (run prediction)
4. Serve the result (return response)
"""
try:
# π Start timing (for performance monitoring)
import time
start_time = time.time()
# π₯ Step 1: Prepare the ingredients (preprocess data)
logger.info("π₯ Preparing customer data...")
processed_data = prepare_customer_data(customer_data)
# π³ Step 2: Cook the prediction (run model)
logger.info("π³ Making prediction...")
churn_probability = model_manager.model.predict_proba(processed_data)[0][1]
# π¨ Step 3: Determine risk level (business logic)
risk_level = determine_risk_level(churn_probability)
# π‘ Step 4: Generate recommendations (business value)
recommendations = generate_recommendations(churn_probability, customer_data)
# β±οΈ Step 5: Calculate how long it took
processing_time = time.time() - start_time
# π Step 6: Log what happened (for monitoring)
logger.info(f"β
Prediction complete: {churn_probability:.4f} in {processing_time:.3f}s")
# π½οΈ Step 7: Serve the result
return PredictionResponse(
churn_probability=round(churn_probability, 4),
risk_level=risk_level,
confidence=calculate_confidence(churn_probability),
recommendations=recommendations,
model_version=model_manager.model_version
)
except Exception as e:
# π¨ If something goes wrong, handle it gracefully
logger.error(f"β Prediction failed: {str(e)}")
raise HTTPException(status_code=500, detail=f"Prediction error: {str(e)}")
def prepare_customer_data(customer_data: CustomerData):
"""
Turn raw customer data into model-ready format
Like washing and chopping vegetables before cooking
"""
# π Convert to pandas DataFrame (model's favorite format)
import pandas as pd
data_dict = customer_data.dict()
df = pd.DataFrame([data_dict])
# π§ Fix TotalCharges (sometimes it comes as text)
df['TotalCharges'] = pd.to_numeric(df['TotalCharges'], errors='coerce')
df['TotalCharges'].fillna(0, inplace=True)
# π― Add calculated features (like making sauce from basic ingredients)
df['AvgMonthlyCharges'] = df['TotalCharges'] / (df['tenure'] + 1)
df['ChargesRatio'] = df['MonthlyCharges'] / (df['TotalCharges'] + 1)
# π·οΈ Convert categories to numbers (models speak math, not English)
categorical_columns = ['gender', 'Partner', 'Dependents', 'Contract', 'PaymentMethod']
for col in categorical_columns:
if col in df.columns:
df[col] = model_manager.encoder.transform(df[col].astype(str))
# π Scale numerical features (make all numbers play nicely together)
numerical_columns = ['tenure', 'MonthlyCharges', 'TotalCharges']
df[numerical_columns] = model_manager.scaler.transform(df[numerical_columns])
return df.values
def determine_risk_level(probability):
"""Convert probability to business-friendly risk levels"""
if probability > 0.7:
return "High Risk π΄"
elif probability > 0.4:
return "Medium Risk π‘"
else:
return "Low Risk π’"
def generate_recommendations(probability, customer_data):
"""Provide actionable business recommendations"""
recommendations = []
if probability > 0.7: # High risk customers
recommendations.extend([
"π― Contact customer immediately with retention offer",
"π° Provide significant discount or service upgrade",
"π Schedule call with customer success manager"
])
# Specific recommendations based on customer profile
if customer_data.Contract == "Month-to-month":
recommendations.append("π Offer annual contract with 20% discount")
elif probability > 0.4: # Medium risk
recommendations.extend([
"π§ Send targeted retention email campaign",
"π Offer service upgrade or add-on discount",
"π Monitor closely for changes in usage"
])
else: # Low risk
recommendations.extend([
"π Continue standard customer engagement",
"β Consider for premium service upselling"
])
return recommendations
What each part does:
-
@app.post("/predict")
: Creates an endpoint that accepts POST requests - Data preprocessing: Converts human-readable data to model format
- Prediction: Uses our trained model to calculate churn probability
- Business logic: Converts probability to actionable insights
- Error handling: Ensures the service stays running even when things go wrong
- Logging: Tracks performance and helps with debugging
π§ Configuration Management
What we're doing: Setting up a central control panel for our application.
# config/settings.py - Our master control panel
from pydantic import BaseSettings
class Settings(BaseSettings):
"""
This is like the control room of our kitchen.
All the important settings are here in one place.
"""
# π·οΈ Basic application info
APP_NAME: str = "Churn Prediction API"
APP_VERSION: str = "1.0.0"
DEBUG: bool = False # Turn on/off detailed error messages
# π Network settings (where and how to serve)
API_HOST: str = "0.0.0.0" # Listen on all network interfaces
API_PORT: int = 8000 # Which door customers use
# π Model settings (where to find our recipes)
MLFLOW_TRACKING_URI: str = "http://localhost:5000"
MODEL_NAME: str = "churn-prediction"
MODEL_STAGE: str = "Production" # Which version to use
# π File locations (where we keep our tools)
MODEL_PATH: str = "models/"
ARTIFACTS_PATH: str = "artifacts/"
# π Logging settings (how much detail to record)
LOG_LEVEL: str = "INFO"
LOG_FILE: str = "logs/api.log"
# π Security settings
CORS_ORIGINS: list = ["*"] # Which websites can use our API
API_KEY_REQUIRED: bool = False # Do users need special access?
class Config:
env_file = ".env" # Load settings from environment file
case_sensitive = True # Settings names must match exactly
# π Create our global settings object
settings = Settings()
Why configuration matters:
- Centralized control: Change settings in one place
- Environment flexibility: Different settings for development vs production
- Security: Keep sensitive information in environment variables
- Maintainability: Easy to update without changing code
This architecture tutorial shows you how to build a robust, production-ready web application for ML model serving. Each component has a specific purpose and works together to create a reliable service that business users love and developers can easily maintain.
Top comments (0)