The foundations are in place: we know what MLOps is, what a model's lifecycle looks like, and what maturity level CineClick is at (level 0). This lesson presents the project that will run through the entire course: the business context of churn, the fictional dataset we'll work with, and — the centerpiece — the data scientist's initial notebook, complete and annotated, with an honest diagnosis of its problems. We'll finish with the tool stack we'll adopt along the way and the roadmap for the remaining modules. It's important that you understand this starting point well: everything we build later is the answer to the problems we're about to lay on the table today.
Contents
- The business context: why predicting churn matters
- The dataset:
churn_customers.csv - The initial notebook, complete and explained
- Diagnosis: what's wrong with this notebook
- The course stack: stage → tool → module
- Roadmap for the rest of the course
The business context: why predicting churn matters
CineClick is a subscription streaming platform with monthly plans (basic, standard, and premium). Like every subscription business, its health depends on a simple, merciless arithmetic:
- Acquiring a new customer is expensive: campaigns, welcome discounts, free months. The industry rule of thumb is that acquiring a subscriber costs several times more than retaining an existing one (a common figure says between 5 and 7 times more, although the exact multiple depends on the business).
- Retaining is cheap... if you know who to retain: a phone call, a one-off discount, or a content recommendation may be enough. But offering discounts to every customer destroys margin; the key is targeting retention at those who are genuinely at risk.
- Churn compounds: 5% monthly churn sounds small, but sustained it means losing close to half the customer base in a year if it isn't offset by acquisition.
Hence the project: a model that, for each active subscriber, estimates the probability that they will cancel soon, so the retention team can concentrate its resources on the highest-risk customers. The business metric is clear (cancellations prevented and revenue retained versus campaign cost), and we'll discuss the ML metric critically further down — spoiler: the current notebook uses the wrong one.
All the project's data is fictional and synthetically generated: it represents plausible patterns of a streaming business, but no real customer. That's the right practice for training and for development: never use real people's data where it doesn't belong.
The dataset: churn_customers.csv
CineClick's data engineer periodically generates an extract with one row per subscriber and these columns:
| Column | Type | Description | Example values |
|---|---|---|---|
customer_id |
integer | Unique subscriber identifier (no predictive meaning) | 10482 |
tenure_months |
integer | Months since signup | 1, 8, 36 |
weekly_hours |
decimal | Average weekly viewing hours over the last month | 0.5, 6.2, 21.0 |
support_tickets |
integer | Support tickets opened in the last 3 months | 0, 1, 4 |
plan |
categorical | Subscription plan type | basic, standard, premium |
payment_method |
categorical | Subscription payment method | card, direct_debit, paypal |
active_discount |
boolean (0/1) | Whether they currently have any promotional discount | 0, 1 |
churned |
boolean (0/1) | Label: 1 if they cancelled within 30 days of the extract | 0, 1 |
Two traits of the dataset that will matter throughout the course:
- The classes are imbalanced: only around 15% of rows have
churned = 1. That's normal for churn (fortunately for CineClick, most customers stay), and it has direct consequences for which metrics are useful and which are misleading. - A mix of types: numeric (
tenure_months,weekly_hours,support_tickets), categorical (plan,payment_method), and binary (active_discount). The categoricals need encoding before training.
The initial notebook, complete and explained
This is the state of the art at CineClick today: a single notebook, churn_final_v3_FINAL.ipynb (the name alone tells a story), on the data scientist's laptop. Its contents, dumped as one block, are:
# churn_final_v3_FINAL.ipynb
import pandas as pd
import pickle
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# Load the data
df = pd.read_csv("C:/Users/laura/Desktop/datos/churn_customers.csv")
# Cleanup: drop nulls and some weird negatives that showed up in hours
df = df.dropna()
df = df[df["weekly_hours"] >= 0]
# Drop the id, it adds nothing
df = df.drop(columns=["customer_id"])
# Turn categoricals into 0/1 columns
df = pd.get_dummies(df, columns=["plan", "payment_method"])
# Split features and label
X = df.drop(columns=["churned"])
y = df["churned"]
# Split into train and test
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
# Train the model
model = RandomForestClassifier(n_estimators=100)
model.fit(X_train, y_train)
# Evaluate
predictions = model.predict(X_test)
print("Accuracy:", accuracy_score(y_test, predictions))
# Got 0.87, better than v2!
# Save the model
with open("C:/Users/laura/Desktop/modelos/churn_model.pkl", "wb") as f:
pickle.dump(model, f)Let's explain it step by step, because as exploration code it's perfectly reasonable, and it's worth understanding what each part does:
- Loading (
pd.read_csv): reads the CSV from a path on Laura's desktop into a pandas DataFrame — the tabular structure we'll always be working with. - Cleanup (
dropna, negatives filter): removes rows with null values and rows with negative viewing hours (a known bug in the logging system). Defensible decisions, but made without recording how many rows are lost or why. - Dropping the id (
drop): correct —customer_idis an arbitrary identifier; keeping it would let the model "memorize" customers instead of learning patterns. - One-hot encoding (
pd.get_dummies): converts each categorical into binary columns.planbecomesplan_basic,plan_standard,plan_premiumwith 0/1 values, and likewise forpayment_method. This is necessary because scikit-learn algorithms require numeric inputs. - X/y split:
Xholds the features (every column except the label) andythechurnedlabel. train_test_split(X, y, test_size=0.2): sets aside 20% of the rows to evaluate the model on data it hasn't seen during training. Pay attention to what's missing between those parentheses; we'll come back to it shortly.RandomForestClassifier(n_estimators=100): a random forest of 100 decision trees. A reasonable choice for tabular data: robust, handles a mix of feature types, and performs decently with hardly any tuning.model.fit(...): the actual training.- Evaluation with
accuracy_score: percentage of correct predictions on the test set. The comment# Got 0.87is literally the entire experimental record of the project. pickle.dump(...): serializes the trained model to a binary.pklfile on the desktop — which is what Laura would send over chat if anyone asked her for "the model".
Let's insist: this notebook is not the work of a bad professional. It's the natural outcome of the exploration phase, and as exploration it did its job: it proved there is signal in the data. The problem is that CineClick wants to use it in production as-is, and for that it accumulates every sin we're about to name.
Diagnosis: what's wrong with this notebook
Let's go down the list with the MLOps eye from the previous lessons. Each problem is concrete and has a concrete consequence:
| # | Problem | Where it shows | Consequence |
|---|---|---|---|
| 1 | Absolute, personal paths | C:/Users/laura/Desktop/... in loading and saving |
The code only works on Laura's laptop. On any other machine (or server) it fails at the first useful line. |
| 2 | No random seed | train_test_split without random_state; RandomForestClassifier without random_state |
Every run splits the data differently and trains different trees: the "0.87" is unrepeatable. Impossible to tell whether a change improved the model or it was chance. |
| 3 | Unversioned data | The desktop CSV gets overwritten with every extract | The current model can't be reproduced and trainings can't be compared: the "v3" dataset no longer exists. |
| 4 | Model with no versioning or metadata | churn_model.pkl saved by hand, overwriting the previous one |
Nobody knows which code, data, or parameters produced the .pkl in circulation. The "versions" live in the notebook's file name. |
| 5 | A single, misleading metric | Only accuracy with ~15% positives |
A useless model that always predicts "doesn't churn" already scores ~0.85 accuracy. The celebrated 0.87 could be detecting very few actual churners — which is the only thing retention cares about. |
| 6 | No tests of any kind | The whole notebook | Nothing checks that the cleanup does what's expected, that the CSV schema is the intended one, or that the model clears a minimum bar. Every change is validated "by eye". |
| 7 | No experiment tracking | The comment # Got 0.87, better than v2! |
The project's history lives in comments and memory. What was v2? On what data? Nobody can answer. |
| 8 | Implicit dependencies | No file declares pandas/scikit-learn versions | A colleague with different versions can get different results or errors. "Works on my machine", guaranteed. |
| 9 | Non-reusable preprocessing | Inline get_dummies and cleanup on the DataFrame |
To predict on new customers, the same transformation will have to be reimplemented in the service; any divergence (a missing category, columns in a different order) will silently degrade the model. |
| 10 | A 100% manual process | Everything | Retraining = locating Laura, her laptop, and her memory. Textbook level 0. |
Problem 5 deserves a pause, because it's the only one that deceives even with the notebook in front of you. With imbalanced classes (85% "doesn't churn"), accuracy is a nearly empty metric: it mostly measures the easy part (getting the stayers right) and ignores the valuable part (finding the leavers). The right metrics for this problem — precision and recall on the positive class, and their derivatives — will appear when we set up serious pipeline evaluation in module 2 and experiment comparisons in module 3. For now, hold on to the suspicion: that 0.87 doesn't say what Laura thinks it says.
The course stack: stage → tool → module
Every problem in the diagnosis has its answer in a lifecycle stage, and every stage has its tool in this course. All of them are open source or have a free tier, and all are de facto standards or serious contenders — but remember lesson 01-01: what matters is the practice they embody, not the brand.
| Lifecycle stage | Tool | What it fixes from the diagnosis | Module |
|---|---|---|---|
| Code versioning | Git | The foundation of everything; problems 1 and 10 start getting solved here | Cross-cutting (assumed known) |
| Reproducible structure and environment | Python packaging + dependency management | Problems 1, 8, and 9 | Module 2 |
| Data versioning | DVC | Problem 3 (and links data to code) | Module 2 |
| Training pipeline | DVC pipelines | Problems 2, 9, and 10 (a repeatable process) | Module 2 |
| Experiment tracking | MLflow | Problems 5 (multiple metrics) and 7 | Module 3 |
| Model registry | MLflow Model Registry | Problem 4 | Module 3 |
| Prediction service | FastAPI | Getting the model to whoever needs it | Module 4 |
| Packaging and deployment | Docker (+ Kubernetes/serverless) | Problems 1 and 8 in production | Module 4 |
| CI/CD | GitHub Actions | Problem 6 (tests) and deployment automation | Module 5 |
| Pipeline orchestration | Prefect | Running and scheduling the pipelines reliably | Module 5 |
| Monitoring and drift | Evidently (+ service metrics) | Watching the live model; closing the loop with retraining | Module 6 |
A word about expectations: this stack is one coherent, widely used combination, not the revealed truth. Your company may use a different orchestrator or tracking platform; the concepts you'll learn transfer almost one-to-one, because all the tools in each category solve the same lifecycle problem.
Roadmap for the rest of the course
Here is how CineClick's transformation will unfold, module by module:
flowchart TD
M1["Module 1 (here)<br/>Foundations: the notebook<br/>and its problems"] --> M2["Module 2<br/>Reproducible code:<br/>structured project, environments,<br/>DVC, training pipeline"]
M2 --> M3["Module 3<br/>Experiments and registry:<br/>MLflow tracking and model registry,<br/>feature stores"]
M3 --> M4["Module 4<br/>Serving in production:<br/>FastAPI, Docker,<br/>Kubernetes, optimization"]
M4 --> M5["Module 5<br/>Automation:<br/>CI/CD with GitHub Actions,<br/>Prefect, release strategies"]
M5 --> M6["Module 6<br/>Operations:<br/>monitoring, drift, retraining,<br/>governance and final project"]- Module 2 — From notebook to reproducible code: we'll refactor this notebook into a structured Python project, with a reproducible environment, the dataset under DVC control, and a training pipeline anyone can run. By the end, problems 1, 2, 3, 8, 9, and 10 will be solved or well on their way.
- Module 3 — Experiments and registry: every training run will be recorded in MLflow with its parameters and metrics (the right ones, not just accuracy), and models will be versioned and promoted in the model registry. Goodbye to problems 4, 5, and 7.
- Module 4 — Serving in production: the model will leave the desktop
.pklbehind to become a FastAPI service, packaged with Docker and deployed scalably. - Module 5 — Automation: code, data, and model tests in CI (problem 6), automated deployment, and pipeline orchestration — the jump from maturity level 1 to level 2.
- Module 6 — Operations: we'll monitor the service and the model, detect drift, automate retraining sensibly (remembering the feedback loops from lesson 01-02), and close with governance, documentation, and the integrative final project.
Common Mistakes and Tips
- Mistake: looking down on Laura's notebook. The notebook proved that the problem is solvable with the available data — that's the hard, irreplaceable part. MLOps doesn't replace exploration: it industrializes it. Treat exploratory code with respect and its author as an ally, not a culprit.
- Mistake: trusting accuracy with imbalanced classes. It's the most common modeling mistake in churn, fraud, or failure detection. Before celebrating a metric, always ask yourself: what would a trivial model that always predicts the majority class score?
- Mistake: "fixing" the notebook by adding cells. The temptation is to patch: one cell that pins the seed, another that copies the CSV with a date in the name... The notebook would still be a notebook, with its hidden state and out-of-order execution. Module 2's solution is structural, not cosmetic.
- Mistake: wanting to apply all eleven tools in the table by next week. The stack is adopted in the course's order for a reason: each piece rests on the previous one (you can't run CI on a pipeline that doesn't exist). In your real job, follow the same incremental order.
- Tip: keep a copy of this notebook exactly as it is. At the end of the course, comparing it with the final platform will be the best measure of what you've learned — and a useful exercise in humility: almost all of us have a
_final_v3_FINALin some drawer.
Exercises
Exercise 1
Without running anything, reason numerically: the dataset has 15% of customers with churned = 1. (1) What accuracy would a "model" that always predicts churned = 0 achieve? (2) How many of the customers who are going to churn would that model detect? (3) What does this tell you about the notebook's 0.87, and about what the retention team should ask Laura instead of "what's its accuracy?"?
Exercise 2
You run Laura's notebook twice in a row, without changing a single line or the CSV, and you get accuracy 0.87 the first time and 0.86 the second. (1) Point to the two exact lines of code responsible for this variability and explain the mechanism behind each. (2) Explain why this is serious beyond the annoyance: what fundamental question of experimental work becomes impossible to answer?
Exercise 3
Match each diagnosis problem with its remedy: for problems 3, 5, 6, and 9 from the diagnosis table, state which tool from the stack (and which course module) primarily addresses it, and write in one sentence what practice (beyond the tool) actually solves it.
Solutions
Solution 1:
- Always predicting "doesn't churn" gets every negative right (85%) and every positive wrong (15%): accuracy = 0.85.
- Zero. That model doesn't detect a single churner: it's perfectly useless for retention despite its 0.85.
- The notebook's 0.87 is only two points above the trivial model, so most of that accuracy is "getting the easy part right". It could be detecting many churners or almost none: accuracy can't tell the difference. Retention should ask: "of the customers who actually churn, what fraction does the model detect?" (recall on the positive class) and "of those the model flags as at risk, how many actually churn?" (precision) — because those two numbers determine, respectively, how many cancellations the campaign can prevent and how much money gets wasted on false positives.
Solution 2:
- The two lines:
train_test_split(X, y, test_size=0.2)— withoutrandom_state, each run shuffles the data with a different seed, so train and test contain different rows every time; andRandomForestClassifier(n_estimators=100)— withoutrandom_state, each tree's bootstrap sampling and the random feature selection at each split change between runs, producing different forests even on the same training data. - What's serious: it becomes impossible to answer the question "did this change improve the model?". If Laura adds a new feature tomorrow and accuracy goes from 0.86 to 0.875, there's no way to know whether the improvement came from the feature or from the same random noise that already produces ±0.01 swings with nothing touched. Without reproducibility there is no valid experimentation: the entire model improvement process rests on worthless comparisons.
Solution 3:
- Problem 3 (unversioned data) → DVC, module 2. The real practice: every training dataset gets immutably identified and linked to the version of the code that used it, so any past model can be rebuilt.
- Problem 5 (a single, misleading metric) → MLflow, module 3 (with the right metrics introduced already in module 2's pipeline). The real practice: defining, before training, a set of metrics aligned with the business and logging all of them in every experiment, so models are compared on what matters.
- Problem 6 (no tests) → GitHub Actions, module 5. The real practice: encoding expectations about code, data, and model as automated checks that run on every change — CI is just the engine that runs them.
- Problem 9 (non-reusable preprocessing) → Python project structure, module 2 (and its reuse in the service, module 4). The real practice: a single preprocessing module, tested and versioned, shared by training and inference, so it becomes impossible by construction for production to transform the data any other way.
Conclusion
We now have the complete, unvarnished starting point: a business with a real, quantifiable problem (churn compounds, and retaining is far cheaper than acquiring), a fictional but plausible dataset with its numeric and categorical features and its imbalanced label, and a monolithic notebook that proves there is signal in the data but accumulates ten concrete problems — personal paths, uncontrolled randomness, unversioned data and model, an accuracy that doesn't say what it seems to, zero tests, and a process that lives on one person's laptop. We also have the plan: a tool stack (Git, DVC, MLflow, FastAPI, Docker, GitHub Actions, Prefect, Evidently) mapped stage by stage onto the lifecycle, and a roadmap that walks that map module by module. This closes the fundamentals. In module 2 we roll up our sleeves: we'll take this notebook exactly as it is and turn it, step by step, into a structured, reproducible Python project — the first and most important stone on CineClick's road to production.
MLOps Course
Module 1: MLOps Fundamentals
- What MLOps is and why models die in the notebook
- The lifecycle of an ML model in production
- MLOps maturity levels and team roles
- The course project: from notebook to production
Module 2: From Notebook to Reproducible Code
- Structuring an ML project: from notebook to package
- Reproducible environments and dependency management
- Data versioning with DVC
- Reproducible training pipelines
Module 3: Experiments and Model Registry
- Experiment tracking with MLflow
- Model registry: versioning and promoting models
- Feature stores: when and what for
Module 4: Serving Models in Production
- Deployment patterns: batch, online and streaming
- A prediction service with FastAPI
- Packaging with Docker
- Scaling and deployment: Kubernetes and serverless
- Inference optimization: latency and cost
Module 5: Automation: CI/CD and Orchestration
- CI for ML: testing code, data and models
- CD: automating model deployment
- Orchestrating ML pipelines
- Release strategies: shadow, canary and A/B
