From Research to Production: How I Built a Customer Churn Prediction API That Actually Works

Introduction
Ever wondered how to bridge the gap between your ML experiments and real-world applications? I used to spend days, perfecting machine learning models, only to face the harsh reality that production deployment is a completely different beas…


This content originally appeared on DEV Community and was authored by Apoorv Tripathi

Introduction
Ever wondered how to bridge the gap between your ML experiments and real-world applications? I used to spend days, perfecting machine learning models, only to face the harsh reality that production deployment is a completely different beast.

I recently completed a customer churn prediction project that demonstrates the full ML lifecycle - from initial data exploration in Jupyter notebooks to a production-ready FastAPI service that can handle real customer data efficiently. This journey taught me that your ML model is only as good as the infrastructure that serves it.

The Challenge: From Notebook to Production

The typical ML workflow looks something like this:

  1. Research Phase: Data exploration, feature engineering, model training in Jupyter
  2. Validation Phase: Cross-validation, hyperparameter tuning, model selection
  3. Production Gap: ??? (This is where most of my projects used to fail)

The missing piece is the production infrastructure - the API layer, data validation, error handling, and scalability considerations that make your model actually usable in the real world.

What Makes This Project Special?

πŸ”§ Custom Pipeline Architecture

The foundation of any production ML system is a robust, reproducible pipeline. I built a scikit-learn pipeline with custom transformers that encapsulate domain-specific feature engineering:


    from sklearn.base import BaseEstimator, TransformerMixin
    from sklearn.compose import ColumnTransformer
    from sklearn.pipeline import Pipeline
    from sklearn.preprocessing import OneHotEncoder

    class durationTransform(BaseEstimator, TransformerMixin):
        def fit(self, x, y=None):
            return self

        def transform(self, x):
             #Handle both DataFrame and numpy array inputs
            if isinstance(x, pd.DataFrame):
                db = x.copy()
            else:
                db = pd.DataFrame(x, columns=["transactiondate", "membershipexpiredate"])

             #Calculate subscription duration in days
            db["transactiondate"] = pd.todatetime(db["transactiondate"])
            db["membershipexpiredate"] = pd.todatetime(db["membershipexpiredate"])

            result = (db["membershipexpiredate"] - db["transactiondate"]).dt.days
            return result.values.reshape(-1, 1)

     #Build the complete pipeline
    genencoding = ColumnTransformer([
        ("gender", OneHotEncoder(), [1])
    ], remainder='passthrough')

    substime = ColumnTransformer([
        ("durationindays", durationTransform(), [8, 9])
    ], remainder='passthrough')

    pipe = Pipeline([
        ('genencoding', genencoding),
        ('substime', substime)
    ])


Why This Matters: Custom transformers ensure that the same feature engineering logic is applied consistently during training and inference, preventing data leakage and ensuring reproducibility.

πŸ“Š Handling Imbalanced Data

Customer churn datasets are notoriously imbalanced - you typically have 10-15% churners vs 85-90% loyal customers. This imbalance can severely impact model performance:


    from sklearn.utils import resample

     Original data distribution
    zeros = dbtrain[dbtrain['ischurn'] == 0]   9,354 non-churners
    ones = dbtrain[dbtrain['ischurn'] == 1]    646 churners

     #Undersampling to balance the dataset
    zerosundersampled = resample(zeros, 
                                 replace=False, 
                                 nsamples=len(ones), 
                                 randomstate=42)

     #Combine and shuffle
    dbtrain = pd.concat([zerosundersampled, ones])
    dbtrain = dbtrain.sample(frac=1, randomstate=42).resetindex(drop=True)

The Result: Balanced dataset with 646 churners vs 646 non-churners, leading to more reliable model performance metrics.

πŸš€ Production API with FastAPI

The API layer is where most ML projects fail. I built a comprehensive FastAPI service with multiple endpoints:

from fastapi import FastAPI, HTTPException
    from pydantic import BaseModel
    from typing import Optional
    from datetime import date

    app = FastAPI(title="Customer Churn Prediction", 
                  description="Production-ready ML API for customer churn prediction",
                  version='1.0.0')

     #Pydantic model for data validation
    class dataval(BaseModel):
        userid: Optional[int] = None
        city: int
        gender: str
        registeredvia: int
        paymentmethodid: int
        paymentplandays: int
        actualamountpaid: int
        isautorenew: int
        transactiondate: date
        membershipexpiredate: date

    @app.post("/predict")
    def predict(
        city: int,
        gender: str,
        registeredvia: int,
        paymentmethodid: int,
        paymentplandays: int,
        actualamountpaid: int,
        isautorenew: int,
        transactiondate: date,
        membershipexpiredate: date,
        userid: Optional[int] = None
    ):
        # Validate input data
        data = dataval(
            userid=userid,
            city=city,
            gender=gender,
            registeredvia=registeredvia,
            paymentmethodid=paymentmethodid,
            paymentplandays=paymentplandays,
            actualamountpaid=actualamountpaid,
            isautorenew=isautorenew,
            transactiondate=transactiondate,
            membershipexpiredate=membershipexpiredate
        )

         # Generate user ID if not provided
        user = validuser(data.userid)

         Transform data through pipeline
        pipedata = [[
            data.city, data.gender, data.registeredvia,
            data.paymentmethodid, data.paymentplandays,
            data.actualamountpaid, data.isautorenew,
            data.transactiondate, data.membershipexpiredate
        ]]

        try:
            transformed = pipe.transform(pipedata)
            dftransformed = pd.DataFrame(transformed, 
                columns=["durationofsubscription", "female", "male", "city", 
                        "registeredvia", "paymentmethodid", "paymentplandays", 
                        "actualamountpaid", "isautorenew"])

            # Make prediction
            prediction = model.predict(dftransformed)
            result = {user: dftransformed.iloc[0].todict()}
            result[user]["prediction"] = int(prediction[0])

            # Store result
            saveprediction(result)

            return result

        except Exception as e:
            raise HTTPException(statuscode=500, 
                              detail=f"Prediction failed: {str(e)}")

Key Features:

Automatic API Documentation: FastAPI generates interactive docs at /docs
Type Validation: Pydantic ensures data integrity
Error Handling: Graceful degradation with informative error messages
User Management: Automatic ID generation and data persistence

πŸ’Ύ Persistent Storage with User Management

Production systems need to track predictions and user data:

import json
    import os

    def validuser(user: int):
        """Generate or validate user IDs with persistent storage"""
        if pd.isna(user):
            with open("data/users.json", "r") as f:
                data = json.load(f)
                maxuser = max(data)
                user = maxuser + 1
                data.append(int(user))
                with open("data/users.json", "w") as f:
                    json.dump(data, f, indent=2)
            return user
        else:
            with open("data/users.json", "r") as f:
                data = json.load(f)
                if user not in data:
                    data.append(int(user))
                    with open("data/users.json", "w") as f:
                        json.dump(data, f, indent=2)
            return user

    def saveprediction(result: dict):
        """Persist prediction results with user data"""
        jsonpath = "data/userdata.json"

        if os.path.exists(jsonpath):
            with open(jsonpath, "r") as f:
                jsonfile = json.load(f)
        else:
            jsonfile = {}

        jsonfile.update(result)

        with open(jsonpath, "w") as f:
            json.dump(jsonfile, f, indent=2)

πŸ”„ Model Serialization with Cloudpickle

Traditional pickle often fails with complex ML pipelines. Cloudpickle handles custom transformers and complex objects:

import cloudpickle

     Save the trained model and pipeline
    with open("model/model.pickle", "wb") as f:
        cloudpickle.dump(adaboostmodel, f)

    with open("model/pipe.pickle", "wb") as f:
        cloudpickle.dump(pipe, f)

     Load in production
    with open("model/model.pickle", "rb") as f:
        model = cloudpickle.load(f)

    with open("model/pipe.pickle", "rb") as f:
        pipe = cloudpickle.load(f)

The Complete System Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Jupyter       β”‚    β”‚   FastAPI       β”‚    β”‚   Production    β”‚
β”‚   Notebook      │───▢│   Service       │───▢│   Deployment    β”‚
β”‚                 β”‚    β”‚                 β”‚    β”‚                 β”‚
β”‚ β€’ Data EDA      β”‚    β”‚ β€’ REST API      β”‚    β”‚ β€’ Load Balancer β”‚
β”‚ β€’ Model Trainingβ”‚    β”‚ β€’ Validation    β”‚    β”‚ β€’ Auto-scaling  β”‚
β”‚ β€’ Pipeline Dev  β”‚    β”‚ β€’ Error Handlingβ”‚    β”‚ β€’ Monitoring    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                       β”‚                       β”‚
         β–Ό                       β–Ό                       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Custom        β”‚    β”‚   Pydantic      β”‚    β”‚   JSON Storage  β”‚
β”‚   Transformers  β”‚    β”‚   Models        β”‚    β”‚   & User Mgmt   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Performance Results

The production system achieved impressive results:

Model Accuracy Production Status
AdaBoost 89.08% βœ… Production
Random Forest 87.39% βœ… Backup
Decision Tree 88.24% βœ… Interpretable
Voting Classifier 82.35% βœ… Ensemble

Key Lessons Learned

1. Infrastructure Matters More Than You Think

Your ML model is only as good as the infrastructure that serves it. A 95% accurate model is worthless if it crashes in production.

2. Data Validation is Non-Negotiable

Pydantic models saved me countless hours of debugging by catching data issues early.

3. Custom Transformers Are Game-Changers

They encapsulate domain knowledge and ensure consistency between training and inference.

4. User Management is Critical

Production systems need to track predictions, manage user data, and handle GDPR compliance.

5. Error Handling Makes the Difference

Graceful degradation and informative error messages are essential for production reliability.

Technical Stack

Backend: FastAPI, Python 3.8+
ML: scikit-learn, pandas, numpy
Validation: Pydantic
Serialization: cloudpickle
Storage: JSON files (can be upgraded to database)
Development: Jupyter Notebooks

Conclusion

Building a production-ready ML API requires more than just a good model. It requires:

  1. Robust Infrastructure: Proper API design, error handling, and scalability
  2. Data Integrity: Validation, transformation, and persistence
  3. User Experience: Clear documentation, helpful error messages, and efficient processing
  4. Business Logic: User management, audit trails, and compliance considerations

The key insight? Your ML model is only as good as the infrastructure that serves it. By combining proper data preprocessing, model serialization, and RESTful API design, I created a system that can handle real customer data efficiently and reliably.

This project demonstrates that the gap between research and production isn't insurmountable - it just requires thinking beyond the model and building a complete system that serves real users.

What's your experience with taking ML models from research to production? Share your challenges and solutions in the comments below!


This content originally appeared on DEV Community and was authored by Apoorv Tripathi


Print Share Comment Cite Upload Translate Updates
APA

Apoorv Tripathi | Sciencx (2025-07-09T02:23:36+00:00) From Research to Production: How I Built a Customer Churn Prediction API That Actually Works. Retrieved from https://www.scien.cx/2025/07/09/from-research-to-production-how-i-built-a-customer-churn-prediction-api-that-actually-works/

MLA
" » From Research to Production: How I Built a Customer Churn Prediction API That Actually Works." Apoorv Tripathi | Sciencx - Wednesday July 9, 2025, https://www.scien.cx/2025/07/09/from-research-to-production-how-i-built-a-customer-churn-prediction-api-that-actually-works/
HARVARD
Apoorv Tripathi | Sciencx Wednesday July 9, 2025 » From Research to Production: How I Built a Customer Churn Prediction API That Actually Works., viewed ,<https://www.scien.cx/2025/07/09/from-research-to-production-how-i-built-a-customer-churn-prediction-api-that-actually-works/>
VANCOUVER
Apoorv Tripathi | Sciencx - » From Research to Production: How I Built a Customer Churn Prediction API That Actually Works. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/07/09/from-research-to-production-how-i-built-a-customer-churn-prediction-api-that-actually-works/
CHICAGO
" » From Research to Production: How I Built a Customer Churn Prediction API That Actually Works." Apoorv Tripathi | Sciencx - Accessed . https://www.scien.cx/2025/07/09/from-research-to-production-how-i-built-a-customer-churn-prediction-api-that-actually-works/
IEEE
" » From Research to Production: How I Built a Customer Churn Prediction API That Actually Works." Apoorv Tripathi | Sciencx [Online]. Available: https://www.scien.cx/2025/07/09/from-research-to-production-how-i-built-a-customer-churn-prediction-api-that-actually-works/. [Accessed: ]
rf:citation
» From Research to Production: How I Built a Customer Churn Prediction API That Actually Works | Apoorv Tripathi | Sciencx | https://www.scien.cx/2025/07/09/from-research-to-production-how-i-built-a-customer-churn-prediction-api-that-actually-works/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.