AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

Capstone: Building a Complete ML Pipeline End-to-End

📚 Applied ML⏱️ 21 min read🎓 Grade 10
✍️ AI Computer Institute Editorial Team Updated: September 2026 CBSE-aligned · Peer-reviewed · 21 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

It is 8:40 pm and you have just placed an order for two plates of momos on a food delivery app. Before the restaurant has even started cooking, the app already shows you a number: "Delivering in 32 mins." Over the next half hour you watch that number tick down through 31, then 29, then 24 minutes, as a small scooter icon crawls across a map of your neighbourhood. Have you ever wondered how the app produced 32 in the first place, seconds after you tapped "Place Order," before a rider had even been assigned?

That number is not a fixed rule like "add five minutes for every kilometre." It is the output of a trained machine learning model. But the model is only the last visible step of something much bigger: a complete ML pipeline, an ordered chain of stages that starts long before you place your order, with someone deciding what data to collect, and ends the instant a prediction reaches your screen. This chapter builds one such pipeline from the very first stage to the last, using ideas already in your toolkit (features, models, training, evaluation) and puts all of them together into one working system.

A Pipeline Is a Chain of Stages

A common misconception is that "doing machine learning" means picking an algorithm and calling .fit() on it. Training a model is one stage among many, and it is usually not even the hardest one. Think about how an IRCTC train ticket gets booked: you search for trains, pick one, enter passenger details, wait for availability to be checked, then pay, and only after all of that does a PNR appear. Skip a stage, or make a mistake in an early one (enter the wrong station code, say), and no amount of care at the payment step will fix it. A machine learning pipeline works the same way: each stage feeds the next, and a flaw introduced early quietly poisons everything that follows it.

For a delivery-time predictor, the full chain looks like this:

  • Problem framing: decide precisely what you are predicting (a number of minutes) and how you will judge success (for example, "average error under 4 minutes").
  • Data collection: gather records of past orders, including distance, restaurant preparation time, traffic conditions, weather, and the delivery time that was actually observed.
  • Data cleaning: remove or fix broken rows, such as a delivery logged as 0 minutes, a distance recorded as negative, or duplicate rows from a retried request.
  • Exploratory data analysis: look at the cleaned data before modelling it, checking which columns correlate with delivery time and flagging outliers such as a two-hour delivery during a flood.
  • Feature engineering: turn raw columns into signals a model can use, such as converting a text category like "traffic" into numbers.
  • Train/test split: set aside a portion of the data the model never sees during training, so you can later check whether it actually learned a pattern or just memorised the training rows.
  • Model training: fit an algorithm to the training data so it learns the relationship between the features and the target (the value you are trying to predict).
  • Evaluation: measure error on the held-out test data using a metric such as mean absolute error.
  • Iteration: based on the evaluation, adjust features, try a different algorithm, or tune a setting, then retrain and re-evaluate.
  • Deployment: wrap the final trained model in code the app can call the instant a customer places an order.
  • Monitoring: keep checking the deployed model's real-world accuracy, because traffic patterns, new restaurants, and monsoon season all change the relationship the model originally learned.

Training the model is stage seven of eleven. Everything before it decides how good the model can possibly be; everything after it decides whether that quality ever reaches an actual customer. The rest of this chapter builds each of these stages in order, starting small enough to compute by hand.

Worked Example: Predicting Delivery Time From One Feature

Suppose you have gathered five past orders and, for now, recorded only one feature: distance from restaurant to customer, in kilometres. This is a small, hand-sized version of the first three stages already done: the problem is framed (predict minutes), the data is collected, and it is already clean.

  • Order A: distance = 1 km, actual delivery time = 12 min
  • Order B: distance = 2 km, actual delivery time = 20 min
  • Order C: distance = 3 km, actual delivery time = 24 min
  • Order D: distance = 4 km, actual delivery time = 33 min
  • Order E: distance = 5 km, actual delivery time = 41 min

A simple linear regression models this relationship as a straight line, time = b0 + b1 * distance, where b1 is the slope (extra minutes per extra kilometre) and b0 is the intercept (a baseline time even at zero distance, covering things like handing the order to the rider). Least squares finds the line that minimises total squared error. It works out the slope as the sum, across every order, of (that order's distance minus the average distance) multiplied by (that order's time minus the average time), divided by the sum of (that order's distance minus the average distance) squared. Once the slope is known, the intercept is simply the average time minus the slope multiplied by the average distance.

Start with the averages. The five distances sum to 15, so the mean distance is 15 divided by 5, which is 3. The five times sum to 130 (12 + 20 + 24 + 33 + 41), so the mean time is 130 divided by 5, which is 26.

Next, for each order, subtract the mean from its distance and from its time, and multiply the two differences together:

  • Order A: (1 − 3) = −2; (12 − 26) = −14; product = 28
  • Order B: (2 − 3) = −1; (20 − 26) = −6; product = 6
  • Order C: (3 − 3) = 0; (24 − 26) = −2; product = 0
  • Order D: (4 − 3) = 1; (33 − 26) = 7; product = 7
  • Order E: (5 − 3) = 2; (41 − 26) = 15; product = 30

Summing the products gives 28 + 6 + 0 + 7 + 30 = 71. Summing the squared distance-differences gives 4 + 1 + 0 + 1 + 4 = 10. So the slope is 71 divided by 10, which is 7.1, and the intercept is 26 minus 7.1 times 3, which is 26 minus 21.3, or 4.7. The fitted line is predicted_time = 4.7 + 7.1 * distance. Every extra kilometre adds about 7.1 minutes, and even a delivery right next door carries a baseline of 4.7 minutes.

Before trusting this by-hand arithmetic, confirm it in code. This is exactly what LinearRegression in scikit-learn does internally, just automated and generalised to any number of features:

from sklearn.linear_model import LinearRegression
import numpy as np

distance = np.array([[1], [2], [3], [4], [5]])   # scikit-learn expects a 2D array of features
time = np.array([12, 20, 24, 33, 41])

model = LinearRegression()
model.fit(distance, time)

print("slope:", model.coef_[0])        # 7.1
print("intercept:", model.intercept_)  # 4.7

Now check how good this line actually is by predicting each training order and comparing it to the real value:

  • Order A: predicted = 4.7 + 7.1(1) = 11.8; actual = 12; error = 0.2
  • Order B: predicted = 4.7 + 7.1(2) = 18.9; actual = 20; error = 1.1
  • Order C: predicted = 4.7 + 7.1(3) = 26.0; actual = 24; error = −2.0
  • Order D: predicted = 4.7 + 7.1(4) = 33.1; actual = 33; error = −0.1
  • Order E: predicted = 4.7 + 7.1(5) = 40.2; actual = 41; error = 0.8

Mean absolute error (MAE) averages the absolute value of these errors: (0.2 + 1.1 + 2.0 + 0.1 + 0.8) divided by 5, which is 4.2 divided by 5, or 0.84 minutes. Root mean squared error (RMSE) squares each error first, averages those squares, then takes the square root: the squared errors are 0.04, 1.21, 4.00, 0.01, and 0.64, which average to 1.18, and the square root of 1.18 is approximately 1.09 minutes. RMSE comes out larger than MAE here for a specific reason: squaring punishes Order C's 2.0-minute miss far more than it punishes Order D's 0.1-minute miss, so a metric built on squares is more sensitive to a single bad prediction than one built on absolute values. Neither number is "the" correct one to report; MAE tells a team "predictions are typically off by about a minute," while RMSE warns them "a few orders miss by noticeably more than that."

The entire point of fitting this line is to predict delivery time for a distance the model has never seen. Reusing the model object already trained above:

print("prediction for 7 km:", model.predict([[7]])[0])  # 54.4

By hand, this is the same calculation: 4.7 + 7.1 × 7 = 4.7 + 49.7 = 54.4 minutes. Nobody in the training data placed an order from exactly 7 km away. The model generalised from five nearby examples to a new situation, which is the entire difference between machine learning and simply looking up a past answer.

Scaling Up: A Real Pipeline With Multiple Features

Real delivery data is never just one column. Distance matters, but so does how long the kitchen takes to cook, whether traffic is heavy, and whether it is raining. Here is a small but more realistic dataset with five features, built as a pandas DataFrame:

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
from sklearn.pipeline import Pipeline
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import mean_absolute_error

orders = pd.DataFrame({
    "distance_km":       [1.2, 2.5, 0.8, 4.1, 3.3, 5.0, 1.8, 2.2, 6.1, 3.7, 0.5, 4.8, 2.9, 5.5],
    "prep_time_min":     [10, 15, 8, 20, 12, 18, 10, 14, 22, 16, 6, 19, 13, 21],
    "traffic":           ["Low", "Medium", "Low", "High", "Medium", "Medium", "Low", "Medium", "High", "Medium", "Low", "High", "Medium", "High"],
    "weather":           ["Clear", "Clear", "Clear", "Rainy", "Clear", "Rainy", "Clear", "Clear", "Rainy", "Clear", "Clear", "Clear", "Clear", "Rainy"],
    "is_weekend":        [0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1],
    "delivery_time_min": [18, 29, 13, 52, 31, 48, 20, 27, 64, 37, 10, 49, 29, 60]
})

X = orders.drop(columns=["delivery_time_min"])
y = orders["delivery_time_min"]

This is stages two and three written down: orders is the collected data, already clean enough to use (no missing values, no impossible negative distances). Splitting it into X (the features) and y (the target, delivery time) is the standard first move before doing anything else.

Two of these columns, traffic and weather, are text categories, and most model implementations only accept numbers. One-hot encoding is the feature-engineering fix: it replaces a category with one 0/1 column per possible value. traffic becomes three columns ("is this Low?", "is this Medium?", "is this High?"), and for any given row, exactly one of them is 1 and the rest are 0. A ColumnTransformer applies this only to the categorical columns while leaving the numeric ones untouched:

categorical_features = ["traffic", "weather"]

preprocessor = ColumnTransformer(
    transformers=[("category", OneHotEncoder(handle_unknown="ignore"), categorical_features)],
    remainder="passthrough"
)

remainder="passthrough" tells the transformer to keep distance_km, prep_time_min, and is_weekend exactly as they are, since they are already numeric. handle_unknown="ignore" is a small but important piece of real-world defensiveness: if a category the encoder never saw during training (a new value like "Foggy") shows up later, it is encoded as all zeros instead of crashing the whole pipeline. Chaining this preprocessing step together with a model gives a single object that performs the whole feature-engineering-plus-training stage in one call, and that object is literally called a Pipeline in scikit-learn, the same word used for the whole end-to-end system:

pipeline = Pipeline(steps=[
    ("preprocessor", preprocessor),
    ("model", DecisionTreeRegressor(max_depth=3, random_state=42))
])

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42
)

pipeline.fit(X_train, y_train)

test_predictions = pipeline.predict(X_test)
print("Test MAE:", mean_absolute_error(y_test, test_predictions))

train_test_split sets roughly 30 percent of the fourteen orders aside as a test set before any training happens, matching the train/test split stage from the earlier list. Calling pipeline.fit(X_train, y_train) runs both the one-hot encoding and the decision tree's training in sequence, using only the training rows. pipeline.predict(X_test) then produces a delivery-time guess for each order in the test set, and mean_absolute_error compares those guesses to the real recorded times. This is the same metric computed by hand a moment ago, now applied automatically to data the model never trained on.

Once trained, the pipeline can score a brand-new order the same way you traced a prediction by hand earlier. Just pass it in with the same column names used during training:

new_order = pd.DataFrame({
    "distance_km": [3.0],
    "prep_time_min": [15],
    "traffic": ["Medium"],
    "weather": ["Clear"],
    "is_weekend": [0]
})

predicted_minutes = pipeline.predict(new_order)[0]
print("Predicted delivery time:", predicted_minutes, "minutes")

Is the Model Actually Good? Overfitting, Underfitting, and Iteration

A single test-set score does not, by itself, tell you whether a model is well built; you have to compare it against the training-set error too:

train_predictions = pipeline.predict(X_train)
print("Train MAE:", mean_absolute_error(y_train, train_predictions))
print("Test MAE:", mean_absolute_error(y_test, test_predictions))

Three patterns can show up here, and each demands a different fix. If both the train and test error are large and roughly equal, the model is underfitting: it is too simple to capture the real pattern. A DecisionTreeRegressor(max_depth=1) can only split the data once, so it ends up predicting one of just two possible averages no matter how far away the customer actually lives. The fix is a more expressive model or better features, not more data. If the train error is close to zero but the test error is much higher, the model is overfitting: given enough depth, a decision tree can grow a separate leaf for every single training order and "predict" each one perfectly, which is not learning a pattern but memorising answers, and it fails the moment it meets an order it has not already memorised. The fix is to simplify the model with a smaller max_depth, gather more training examples, or drop features that are really just noise. If train and test error are both low and reasonably close together, the pipeline has struck a sensible balance and is ready to move toward deployment.

This comparison is also why the five-order example earlier, despite being computed entirely by hand, was already doing real machine learning: the line was fitted using all five points, but the honest test of whether it had learned something useful was predicting the unseen 7 km case, not re-checking the same five orders it trained on. Iteration in a real pipeline means adjusting a hyperparameter like max_depth, trying LinearRegression as an alternative to compare against the decision tree, or engineering a new feature (extracting the hour of day from an order timestamp, say), and then repeating the fit-and-evaluate cycle until the test error stops improving.

Shipping It: From Notebook to Live Prediction

A trained pipeline sitting in a notebook has not helped a single customer yet. Deployment means wrapping it in a plain function that the delivery app's backend can call the moment an order is placed:

def predict_delivery_time(order):
    order_df = pd.DataFrame([order])
    return float(pipeline.predict(order_df)[0])

eta = predict_delivery_time({
    "distance_km": 2.4,
    "prep_time_min": 12,
    "traffic": "Medium",
    "weather": "Clear",
    "is_weekend": 0
})

In a production system, this function typically sits behind a web API that the mobile app calls, returning a number within a fraction of a second. Nobody is retraining a model live while you wait for your momos to be confirmed. What happens live is only the .predict() step; everything from data collection through evaluation already happened earlier, offline, long before your order existed.

Deployment also opens a new loop rather than closing the pipeline down. A model trained on last year's traffic patterns will quietly get worse as new flyovers open, new restaurants join the platform, or monsoon season changes how long every trip takes. Monitoring means comparing predicted times against actual times for real, live orders on an ongoing basis, and retraining the model periodically on fresh data when that gap grows too wide. The pipeline you have built is not a script you run once. It is a system with a heartbeat.

Back to the 32-Minute Promise

The next time an app tells you "Delivering in 32 mins" before a rider has even been assigned, you can trace exactly where that number came from. Someone framed the problem as predicting minutes and picked a metric for success. Historical orders were collected and cleaned. Distance, traffic, weather, and other signals were engineered into features a model could actually read. The data was split so the model could be tested honestly instead of graded on questions it had already seen the answers to. A model (perhaps a decision tree not unlike the one built in this chapter) was trained, evaluated with MAE, checked for overfitting, and tuned. The final pipeline was wrapped in a function, deployed behind the app, and is still being watched today, ready to be retrained the moment its predictions start drifting from reality.

That is the real skill this capstone is testing: not whether you can call .fit() on a dataset, but whether you can build, trace, and reason about every stage of the chain that turns raw historical data into a number someone trusts enough to plan their evening around.

Practice Exercises

Now it is time to practice! Complete these challenges to solidify your understanding:

  • Exercise 1: Write a short program that demonstrates the core concept from this chapter. Test it with at least 3 different inputs.
  • Exercise 2: Find a real-world example where capstone: building a complete ml pipeline end-to-end is used in an Indian company (like TCS, Infosys, Flipkart, or ISRO). Write a paragraph explaining the connection.
  • Exercise 3: Create a mind-map connecting capstone: building a complete ml pipeline end-to-end to at least 3 other topics you have studied.

Key Takeaways — Summary and Recap

Let us recap what we covered: the core ideas behind capstone: building a complete ml pipeline end-to-end, how they connect to real-world applications, and why they matter for your journey in computer science. Remember these key points as you move forward. For competitive exam preparation (CBSE, JEE, BITSAT), focus on understanding the WHY behind each concept, not just the WHAT.

← NumPy and Pandas Mastery: The Data Scientist's Essential ToolsNaive Bayes for Text Classification: Spam, Sentiment, and Language Detection →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn