This last lesson builds nothing new — and that's exactly what makes it important. After six modules adding pieces, it's time to step back and see the whole system: the complete map of CineClick's platform, a real incident walked end to end that exercises every component, the repo inventory as a reusable template, and the closing of the arc we opened in module 1 — the ten problems from that diagnosis, revisited one by one. It ends with your project: replicating the full platform in another domain, because the proof that you know MLOps isn't having followed CineClick, but being able to build it without her.
Contents
- The complete platform map
- An end-to-end incident: the spring promo
- Repo inventory:
cineclick-churnas a template - Module 1's ten problems, revisited
- Your project: the "LuzYa" platform
- Adaptation guide: when your case isn't CineClick
- Synthesis exercises
The complete platform map
This is the system you've built, with the people drawn exactly where the system stops to wait for them:
flowchart TB
subgraph DEV["Development (M2-M3)"]
COD["Code in src/<br/>features.py single source"] --> DVCP["dvc repro<br/>reproducible pipeline"]
DVCP --> MLF["MLflow tracking<br/>churn-cineclick experiment"]
end
subgraph CI["CI (M5)"]
TESTS["ci.yml: code, data (pandera)<br/>and model tests<br/>threshold: recall>=0.60, precision>=0.50"]
end
subgraph REG["Registry (M3)"]
CHAL["@challenger"] -.-> CHAMP["@champion (v2)"]
end
subgraph CD["CD (M5)"]
IMG["cd-service.yml: image<br/>ghcr.io/cineclick/churn-api"]
PROM["Promotion PR +<br/>promote_champion.py"]
end
subgraph SRV["Serving (M4)"]
API["FastAPI on Kubernetes<br/>3-10 replicas, p95 92 ms"]
BATCH["weekly-churn-scoring<br/>Mondays 06:00 (Prefect)"]
end
subgraph OPS["Operations (M6)"]
MON["Monitoring 06-01<br/>2 planes + prediction table"]
DRIFT["Evidently 06-02<br/>Mondays 07:00, PSI"]
RETRAIN["churn-retraining 06-03<br/>monthly + extraordinary"]
GOV["Governance 06-04<br/>model card, ADRs, subgroups"]
end
COD --> TESTS
TESTS --> IMG
MLF --> CHAL
H1((("Human:<br/>PR review"))) --> TESTS
H2((("Human:<br/>environment<br/>approval"))) --> IMG
IMG --> API
CHAL -->|"shadow 2 wk +<br/>canary 5-25-100%"| PROM
H3((("Human:<br/>promotion<br/>review"))) --> PROM
PROM --> CHAMP
CHAMP --> API
CHAMP --> BATCH
API --> MON
BATCH --> MON
MON --> DRIFT
DRIFT -->|"alert"| H4((("Human:<br/>playbook")))
H4 --> RETRAIN
RETRAIN -->|"never @champion"| CHAL
GOV -.->|"traceability and<br/>documentation"| REG
GOV -.-> MONThree readings of the map before moving on:
- It's a loop, not a pipe. The arrow from
MONtoDRIFTtoRETRAINand back to the registry is what separates maturity level 2 from level 1: the system feeds back into itself. - Four humans, four judgments. Reviewing code, approving the service deployment, approving the model promotion, interpreting the drift. Everything mechanical between them is automatic; none of the four can be removed without losing a safeguard.
- Two cadences coexist: the service's (deployments when there are code changes) and the model's (promotions when there's a validated challenger). Decoupling them was module 5's central decision.
An end-to-end incident: the spring promo
Nothing proves a system like an incident. This scenario exercises, in order, almost every piece of the map. Every step uses commands and screens you already know — there's nothing new to learn, just watching it all work together.
Day 0 — the world changes. Marketing launches the spring promo: three months at half price. Nobody tells the ML team (they never do). Thousands of sign-ups start pouring in: young customers, basic plan, zero tenure. The service doesn't flinch — the HPA scales from 3 to 5 replicas, p95 stable at ~90 ms. Service plane: green. The 06-01 dashboard starts moving the proxies: the plan mix slides toward basic and the probability histogram fattens at the top.
Week 2 — the system watches, and sees. Monday 07:00, the churn-drift-detection flow compares the last week of the prediction table against the champion's versioned reference. Result: PSI 0.31 on tenure_months (> 0.25), drift also in plan and weekly_hours, drift_share above the threshold. Alert in #ml-alerts with the drifted features and the link to the Evidently HTML report. Nobody had to remember to look: that was the whole point of module 6.
Week 2, same day — playbook, not reflex. Laura opens the report and applies the 06-02 playbook. Upstream data error? No: the pandera customer_schema passes, the values are plausible, and the shifted distributions tell a coherent story (genuinely new customers). Legitimate cohort? Yes — two messages to marketing confirm it: it's the promo. Impact? The positive rate has risen to 23%, still inside the 10–25% band, but the model is extrapolating over a population it barely saw in training. Human decision: extraordinary retraining trigger, documented in the alert thread.
Weeks 2-3 — the retraining proposes. Laura launches churn-retraining from the Prefect UI with reason="extraordinary-PSI-tenure". The flow builds the new dataset with a point-in-time cutoff (the promo cohort enters with the labels it already has; the control group keeps the labels clean), does dvc repro, and evaluates: frozen test recall 0.69 / precision 0.55 (threshold cleared), fresh test recall 0.66. It registers v3 as @challenger with its lineage tags (dvc_data_md5, time window, flow run) and notifies #ml-alerts. At no point does it touch @champion.
Weeks 3-5 — the shadow listens. The promotion PR gets opened, but first: two mandatory weeks of shadow (ADR-007). SHADOW_MODE is enabled in the churn-api-config ConfigMap; every request is scored by v2 (which responds) and v3 (which only gets logged: shadow_probability, shadow_model_version). When the window closes, the prediction table analysis shows a 9% disagreement, concentrated almost entirely in the new cohort — v3 assigns lower probabilities to the promo's newcomers. It's exactly what should happen: v3 learned that "zero tenure + basic plan + promo" doesn't imply the risk v2 was extrapolating onto them. The subgroup evaluation (06-04) confirms no segment gets worse; v3's model card gets written with the cohort as a novelty and its still-young labels as a limitation.
Weeks 5-6 — canary and promotion. Human review of the promotion PR: docs/model-promotion.md checklist, shadow evidence attached, subgroups, model card. Approved. Model canary: 5% → 25% → 100%, 24-48 h per step, with the guardrail metrics watching (5xx, p95, positive rate within band). No surprises. promote_champion.py moves the alias: v3 = @champion. The /version endpoint confirms it; the drift detector's reference updates itself to v3's dataset (the mechanism from 06-02's exercise 3).
Week 6 — closure. A new ADR in docs/decisions/: context (uncommunicated promo), decision (extraordinary retraining and a policy agreed with marketing: campaigns announced two weeks in advance), consequences. The dashboard goes back to green with an annotation on the timeline: "v3 promoted". From day 0 to closure: the system detected on its own, the human decided four times, and no customer noticed a thing.
| Phase | Component exercised | Lesson |
|---|---|---|
| Detection | Proxy dashboard + Evidently + alert | 06-01, 06-02 |
| Diagnosis | Playbook + pandera + prediction table | 06-02, 05-01 |
| Response | Point-in-time retraining flow + registry | 06-03, 02-04, 03-02 |
| Validation | Shadow + subgroups + model card | 05-04, 06-04 |
| Delivery | Promotion PR + canary + alias | 05-02, 05-04 |
| Closure | ADR + annotation + updated reference | 06-04, 06-02 |
Repo inventory: cineclick-churn as a template
The repo you've watched grow is, by now, a reusable template. The full inventory, with the lesson where each piece was born:
| File / directory | What it's for | Lesson |
|---|---|---|
src/cineclick_churn/data.py |
Dataset loading and construction (point-in-time since 06-03) | 02-01, 06-03 |
src/cineclick_churn/features.py |
Single source of features (ratio_tickets, …) |
02-01, 03-03 |
src/cineclick_churn/train.py, evaluate.py |
Training and evaluation, invoked by the pipeline | 02-01, 02-04 |
src/cineclick_churn/validation.py |
The pandera customer_schema schema |
05-01 |
src/cineclick_churn/api/ (main.py, schemas.py, routing.py) |
FastAPI service: /predict, /predict-batch, /health, /version, /metrics |
04-02, 06-01 |
tests/ (test_features.py, test_api.py, test_model.py) |
Code, API, and model tests (model marker) |
05-01 |
dvc.yaml + storage remote |
Pipeline prepare_data → build_features → train → evaluate; versioned data | 02-03, 02-04 |
data/processed/test.csv |
Frozen, versioned test set | 02-03, 06-03 |
scripts/hyperparameter_search.py |
Search with MLflow tracking | 03-01 |
scripts/score_batch.py |
Batch scoring (invoked by the weekly flow) | 04-01 |
scripts/load_test.py |
Load testing (p95, req/s) | 04-05 |
scripts/promote_champion.py |
Alias movement, always after human review | 05-02 |
scripts/evaluate_subgroups.py |
Metrics by plan and tenure for every candidate | 06-04 |
flows/weekly_scoring.py |
weekly-churn-scoring flow, Mondays 06:00, cineclick-pool pool |
05-03 |
flows/drift_detection.py |
Weekly Evidently flow, Mondays 07:00 | 06-02 |
flows/retraining.py |
churn-retraining flow: dataset → repro → double evaluation → @challenger |
06-03 |
flows/ground_truth_evaluation.py |
Ground truth job: weekly real recall/precision | 06-01 |
deploy/k8s/ |
Manifests: Deployment, HPA, ConfigMap, Secret, ServiceMonitor, alerts | 04-04, 06-01 |
.github/workflows/ci.yml |
Code/data/model tests with a blocking threshold | 05-01 |
.github/workflows/cd-service.yml |
Image build and deployment with approval | 05-02 |
Dockerfile (multi-stage) |
The ghcr.io/cineclick/churn-api image |
04-03 |
docs/model-promotion.md |
Promotion process and checklist | 05-02 |
docs/decisions/ |
ADRs | 06-04 |
docs/model-card-churn-v2.md, datasheet-churn-customers.md |
Model card and datasheet | 06-04 |
For your next project, this table is the shopping list: each row is a piece you know how to build, and the order of the lessons is a reasonable build order.
Module 1's ten problems, revisited
In 01-04 we diagnosed CineClick's starting point: a promising model dying in Laura's notebook. Those ten problems, closed one by one:
| # | Problem (module 1) | Solution built | Where |
|---|---|---|---|
| 1 | The model lives in a notebook only Laura knows how to run | cineclick_churn package with src/, tests, and a locked environment |
M2 |
| 2 | "Works on my machine": unpinned dependencies | Reproducible environments + lock + multi-stage Docker image | 02-02, 04-03 |
| 3 | Nobody knows what exact data the model was trained on | DVC: data versioned in storage, hash in every version's lineage |
02-03, 06-03 |
| 4 | Experiment results in overwritten cells and spreadsheets | MLflow: churn-cineclick experiment, every run with params/metrics/artifacts |
03-01 |
| 5 | "Which model is in production?" — nobody can answer | Registry with @champion/@challenger aliases + /version endpoint |
03-02, 04-02 |
| 6 | Predictions were generated by hand when marketing asked | Orchestrated weekly batch (Prefect, Mondays 06:00) + online API for real time | 05-03, 04-02 |
| 7 | Deploying meant copying files to a server and crossing fingers | CI with a business threshold + decoupled CD with approval + shadow/canary with rollback | M5 |
| 8 | Features recomputed differently in each place (skew) | features.py as the single source, shared by training, API, and batch |
03-03 |
| 9 | Nobody knew whether the model was still right months later | Two monitoring planes, prediction table, real metrics, drift with Evidently, triggered retraining | M6 |
| 10 | If Laura goes on vacation, the system stops | Automation + documentation (model card, ADRs, playbook, checklist): the knowledge lives in the repo, not in a person | M5-M6 |
The whole arc of the course fits in this table. Notice that no row was solved with "more model": recall went from 0.43 (v1) to 0.68 (v2) in module 3, and everything else was turning that model into a system.
Your project: the "LuzYa" platform
Now it's your turn. The final project consists of replicating the complete platform with another case: LuzYa, a fictional electricity retailer that wants to predict invoice payment default to anticipate reminders and payment plans. Same skeleton, new domain — which is exactly the situation you'll face at work. All the data must be synthetic, generated by you (as CineClick's was).
The dataset (luzya_invoices.csv, generate ~20,000 synthetic rows with a ~12% default rate):
| Column | Type | Description |
|---|---|---|
contract_id |
internal id | Contract identifier |
tenure_months |
int | Months since signup |
monthly_kwh |
float | Average monthly consumption |
invoice_amount |
float | Amount of the invoice to predict |
prior_defaults |
int | Historical defaults (careful, point-in-time!) |
tariff |
cat | fixed / indexed / night |
payment_method |
cat | direct_debit / card / bank_transfer |
active_payment_plan |
bool | Whether a payment plan is already in place (an intervention!) |
defaulted |
0/1 | Label: the invoice wasn't paid within 30 days |
Phases, mapped to the course's modules, with self-assessment criteria:
- Phase 1 (M2) — Reproducibility. A
luzya-defaultrepo with a package, a locked environment, DVC, and a 4-stagedvc repropipeline.- [ ] A colleague (or you on another machine) reproduces the training with 3 commands.
- [ ]
dvc reprowith no changes re-runs nothing.
- Phase 2 (M3) — Experiments and registry. MLflow, at least two registered versions (baseline + improvement), a
@championalias,features.pyas the single source. Define YOUR business criterion (e.g. recall ≥ 0.65 with precision ≥ 0.45 — justify it: which costs more, one reminder too many or one undetected default?).- [ ] You can answer "what data and code was the champion trained with?" using only MLflow + DVC.
- Phase 3 (M4) — Serving. A FastAPI API (
/predict,/health,/version), multi-stage Docker, deployment on local Kubernetes (kind/minikube) with 2+ replicas; a monthly batch scoring the billing run.- [ ] p95 measured, and a latency budget defined and documented.
- Phase 4 (M5) — Automation. CI with code/data (pandera)/model tests and your blocking threshold; decoupled CD; a scheduled scoring flow; a written release plan (shadow? canary? which guardrail metrics?).
- [ ] A PR that degrades recall below the threshold gets blocked on its own.
- Phase 5 (M6) — Operations.
/metrics+ proxies, a prediction table, scheduled drift detection with Evidently, a retraining flow with point-in-time correctness (prior_defaultsandactive_payment_planare your leakage and intervention traps: solve them the way CineClick solved hers — temporal cutoff and control group), a model card, two ADRs, and the 06-04 governance checklist completed.- [ ] Simulate a drift (generate one month of data with a different
tariffdistribution) and verify your detector sees it. - [ ] Answer the audit drill from 06-04's exercise 1, LuzYa edition.
- [ ] Simulate a drift (generate one month of data with a different
Scoping advice: do it in series, not in parallel, and don't move past a phase without meeting its checklist — the module order was also a dependency order. A project with phases 1-3 done impeccably is worth more than all five done halfway.
Adaptation guide: when your case isn't CineClick
The skeleton is almost always the same; these are the typical adjustments, in broad strokes:
- Deep learning models: the loop doesn't change (registry, challenger, shadow, drift), but training gets expensive — checkpoint tracking comes in, datasets that don't fit in a CSV (DVC still works; the remote and the format change), and costlier evaluation. The modeling fundamentals are covered by the portal's deep_learning course.
- With GPUs: it affects two places — training (GPU queues and nodes in the orchestrator; the retraining flow requests different resources) and sometimes inference (request batching, and the latency/cost budget from 04-05 gets recomputed entirely). On Kubernetes, node pools and GPU
resources.limits: the kubernetes course. - A large team: what at CineClick was "Laura and you" becomes a shared platform — centralized multi-project registry and tracking, per-team environments, and human approvals formalized in CODEOWNERS and protected environments. Branch discipline, reviews, and pipelines at scale: the ci_cd and docker courses to reinforce the delivery foundation.
- Heavy regulation (banking, health, insurance): module 6-04 goes from "good practice" to hard requirement; the checklist grows with independent model validation and committees — but notice: the structure is the same, only the bar for each box changes.
Common Mistakes and Tips
- Starting the LuzYa project at phase 4 or 5. The temptation to "skip to the interesting part" (CI/CD, drift) without a solid phase 1 reproduces CineClick's original mistake: automating on sand. If
dvc reprodoesn't work on a clean machine, nothing you build on top can be trusted. - Copying CineClick's numbers instead of deriving your own. The recall ≥ 0.60/precision ≥ 0.50 threshold, the 10-25% positive band, or the 300 ms budget come from CineClick's business. In LuzYa the cost of a false negative (an undetected default) and of a false positive (an unfair reminder to a good customer) are different — do the math before setting the threshold, as in phase 2.
- Treating the dataset's traps as optional details.
prior_defaultswithout a temporal cutoff is pure leakage (you'll be counting defaults that hadn't happened yet) andactive_payment_planis an intervention that contaminates the label, just like CineClick's retention discount. If your phase 2 model gives suspiciously good metrics, you've almost certainly fallen into the first one. - Confusing "having the pieces" with "having the platform". The promo scenario taught it: the value isn't in Evidently or Prefect separately, but in the alert leading to a playbook, the playbook to a retraining with lineage, and that to a safe release path. When self-assessing, walk an invented incident end to end — that's the checklist that really matters.
- Skipping the humans on the map. If in your replica the retraining flow ends up moving
@championdirectly "because it's just me anyway", you've returned to level 0 with more YAML. The challenger + review discipline gets trained in small projects so that it exists in the big ones.
Synthesis exercises
On the spring promo scenario (no code required):
-
The counterfactual. Walk through the incident imagining CineClick had stayed as it was at the end of module 4 (no module 5 or 6): at what point would the problem have been detected, who would have detected it, and what would have happened to the retention campaign during those weeks? Point out the two pieces whose absence would have hurt the most.
-
The four humans. During the incident there are four human interventions (playbook, launching the retraining, reviewing the promotion PR, deciding on the canary). For each one, explain what judgment that person contributes that the system cannot automate with what the course has built — and which of the four you think would be the first candidate for automation at a hypothetical maturity level 3, with what safeguard in exchange.
-
The piece that failed silently. In the scenario, marketing didn't announce the promo and the system detected it through drift two weeks later. The closing ADR adds advance notice of campaigns as policy. What does this teach you about the limits of the technical solution — that is, what class of problems in a production ML system doesn't get solved with more monitoring?
Solutions
-
Without module 6 there is no detection: no proxies, no Evidently, no alert. The problem would have been discovered when someone in marketing noticed — weeks or months later — that the retention campaign was "acting weird": too many new customers flagged at risk (v2 extrapolating zero tenure as danger), the discount budget blown on a cohort that maybe didn't need it, and at-risk veteran customers competing for campaign slots with false positives. Without module 5, moreover, the response would have been artisanal: retraining by hand on Laura's laptop, no shadow or canary, with the risk of swapping a known problem for an unknown one. The two most painful absences: the prediction table (without it there isn't even a retrospective diagnosis: you wouldn't even know how many promo customers were flagged) and the safe challenger → shadow → canary path (without it, the fix is as risky as the problem).
-
Playbook: it distinguishes causes that require context external to the system (is it a promo? a data-team bug?) — the system sees that the distribution changed, not why; that why lives in conversations with other teams. Launching the retraining: it decides whether the change deserves the expensive response (compute, risk, review) — a cost/benefit judgment with business information. PR review: accountability — someone with a name takes on that the evidence (shadow, subgroups, model card) justifies the change; it's as much control as accountability. Canary: interpreting guardrail metrics in context (is that p95 spike the model, or the football match?). The natural automation candidate at a level 3 is the canary progression (the thresholds are already objective and the automatic rollback already exists): you'd automate the 5→25→100 advance when the guardrail metrics have been clean for N hours, with the safeguard that any alert freezes the progression and notifies — the human moves from approving each step to supervising by exception. The other three involve external context or accountability, and automating them would remove the judgment, not the busywork.
-
That monitoring is a symptom detector, not a substitute for communication between teams: the perfect technical system detected the promo with a two-week delay because the problem wasn't technical — it was organizational (a business decision that affects the data never reached the people operating the model). No amount of PSI turns a surprise into advance notice. That's why the incident's closure is an ADR with a communication policy and not a new tool: there is a whole class of problems (business changes, product decisions, foreseeable external events) whose cheap solution is telling people beforehand and whose technical solution is only the airbag. MLOps, like its relative DevOps, is ultimately a sociotechnical discipline: half the platform is pipelines; the other half is agreements between people — the four humans on the map, the ADRs, the playbook, and a promo communicated in time.
Conclusion
Here the journey ends. It began in module 1 with a notebook on Laura's laptop: a promising random forest with 0.43 recall and ten problems keeping it from touching reality. It ends with the platform on this lesson's map: a reproducible package with versioned data (M2); tracked experiments and a registry where v2 — and now v3 — carry their full lineage (M3); a service on Kubernetes with a 92 ms p95 and a weekly batch feeding the retention campaign (M4); CI that turns the business criterion into a blocking threshold, decoupled CD with humans at the points of accountability, and releases with shadow, canary, and a rehearsed rollback (M5); and a system that watches on its own — two monitoring planes, drift detected every Monday, retraining with triggers and point-in-time correctness, governance auditable by design (M6). CineClick crossed into maturity level 2, and you crossed with her.
What you now know how to do, concretely: structure an ML project as a reproducible package with a pinned environment; version data and pipelines with DVC; track experiments and manage the champion/challenger cycle in MLflow; serve models online with FastAPI, package them with Docker, and operate them on Kubernetes with a latency budget; set up CI with code, data, and model tests, and decoupled CD for service and model; orchestrate with Prefect; design releases with shadow, canary, and A/B; monitor on two planes and build an auditable prediction table; detect drift with PSI and Evidently and react with a playbook; automate retraining without surrendering the promotion decision; and document and govern the system — model cards, ADRs, subgroups, privacy — to the point of passing an audit drill in minutes.
To keep going deeper, three natural paths: toward modeling (the portal's deep_learning course, if your models grow faster than your pipelines), toward infrastructure (the kubernetes, docker, and ci_cd courses, if you want to operate the platform others use), and toward practice — which is the path that truly consolidates: the LuzYa project is waiting, and after it, your team's first real model that today is dying in a notebook. You now know exactly what to do with it.
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
