Document use boundaries
- Lesson 26
- 55 min
- Module 8: Prepare the model for handoff
Lesson 26 of 27 · Machine Learning: From Problem to Reliable ModelDocument use boundaries
1. Understand machine learning
2. Frame the prediction problem
3. Evaluate supervised models
4. Build the first models
5. Try other model families
6. Tune and finish the model
7. Understand and choose the model
8. Prepare the model for handoff
What you will be able to do
Write down what the model is for, what it is not for, and how good it is. Then people outside the project use it only where the evidence supports it.
NotebookDo this lesson in a notebook
The notebook trains the model with the course’s train.py, runs evaluate.py once, and computes the numbers of the card from the files. Then it writes MODEL_CARD.md and checks that the card matches the artifact. It needs no local setup.
Colab and Kaggle open your own copy. They need a free Google or Kaggle account. Stuck? See the solution notebook.
Before you startWhat to know and to have ready
What you need to know
- The baselines and the improvement target, from Define a baseline.
- The test report, made once with
evaluate.py, from Thresholds and calibration: Test once with evaluate.py. - The threshold 0.19 and the calibration gap after the policy change, from Thresholds and calibration.
- Why the simpler model was chosen, from Tune hyperparameters: The one-standard-error rule and When a simple model is better.
- Slices and their sample sizes, from Slice analysis. Why coefficients are not causes, from Explain predictions.
What you need on your computer (only for the local route; the notebook needs none of it)
- The project
~/projects/ticket-modelwith.venvactive, from Define a baseline: Set up your project. - The folder
model/with its three files, frompython train.pyin Package artifacts: Worked example: train.py. If it is missing, runpython train.py. If you changedREVIEW_CAPACITY, set it back to0.20first. reports/test_report.json, frompython evaluate.pyin Thresholds and calibration: Test once with evaluate.py. If it is missing, runpython evaluate.pyonce. Ifevaluate.pyis missing, download the course’s file:curl -fsSL -O https://raw.githubusercontent.com/Nextia-Intelligence/nextia-learning-labs/main/C05/project/evaluate.py(curl.exeon Windows).
Tested on macOS with Python 3.14.6, pandas 3.0.6 and scikit-learn 1.9.1. The notebook was tested in Jupyter.
The leaflet in the medicine box
Section titled “The leaflet in the medicine box”Open a box of medicine from a pharmacy, and you find a folded leaflet. It says what the medicine is for, and who must not take it. It gives the dose, the side effects, and what the maker tested. It names the maker, and it gives the date of the last change. The pharmacist is not at home with you, but the leaflet answers the questions that you would ask.
The leaflet exists because a medicine is used far from the people who made it. A model is the same. The files in model/ tell a program how to run the model. They do not tell a person what the model is for, how good it is, or where it must not be used. Priya will not sit next to every person who uses the scores.
The problem: a model gets a second job
Section titled “The problem: a model gets a second job”Omar, the product manager, asks a fair question: “The scores are useful. Can we use them for other things too?” One team lead already has an idea: rank the support agents by the scores of their tickets, for the yearly performance review.
Nothing in the files stops this. The scores are numbers, and a spreadsheet can average them by agent. But the model never learned anything about agents. It scores a ticket when the ticket is created, before any agent touches it. A ranking of agents by these scores would measure which tickets they got, not how well they worked. People could lose a bonus because of a number that means something else.
This is common: a model built for one decision is used for a second decision that nobody tested. The problem is not bad intent. The problem is that the limits were in Priya’s head, not next to the model. The answer must be written down, next to the model, before somebody tries.
What a model card is
Section titled “What a model card is”A model card is a short document that comes with a model, like the leaflet in the medicine box. It says what the model is for, who uses it, and what it is not for. It also gives the training data, the results, the weak spots and the owners. The name comes from a 2019 research paper, Model Cards for Model Reporting, by Margaret Mitchell and colleagues. Today many teams publish a card with each model.
Start with something small: a card for the rule “flag priority 1”, the baseline from Define a baseline. Recall the counts. In May, 163 of the 1,179 validation tickets have priority 1, and 61 of them escalated. The validation set has 183 escalations in all.
| What it does | Flags every ticket with priority 1. |
| For | Sending tickets to the senior team when no model is available. |
| Not for | Deciding which customers matter more. |
| Evidence (May) | Flags 163 of 1,179 tickets (13.8%). Precision 61 ÷ 163 = 0.374. Recall 61 ÷ 183 = 0.333. |
| Limit | Misses 2 of 3 escalations: every escalation with priority 2 or 3. |
| Owner | Grace |
Check the numbers by hand: 163 ÷ 1,179 = 0.138, 61 ÷ 163 = 0.374 and 61 ÷ 183 = 0.333. Remember what the two shares mean. Precision answers “when the rule flags a ticket, how often does it escalate?” Recall answers “of all the escalations, how many did the rule flag?”
With six rows, a new colleague knows what the rule does, how well it works, and where not to use it. A model card for the escalation model has the same parts, with more evidence.
The sections of a card
Section titled “The sections of a card”A good card answers the questions that a careful user asks, in the order that they ask them. What is this, and who owns it? What is it for? What data trained it, and why this kind of model? How good is it, compared with something simple? Where is it weaker? What must I know to use it safely? What must I never use it for?
Each answer comes from evidence that you already have. You do not invent anything new for the card: you collect it. The diagram shows where each section’s evidence comes from.
Text description of the diagram
The modelling brief from Module 2 gives the intended use and the data. model/model_info.json gives the header (the artifact’s SHA-256 and the threshold), the validation results and the reason for this model. reports/test_report.json adds the test result, made once. The slice analysis from Module 7 gives the slices with their counts. The known problems (the calibration gap, channels the model never saw, guests without history) give the limits. Your judgement gives the uses the model is not for and the ethical notes.
The table lists the same mapping, with the question that each section answers:
| Section | The question it answers | Where the evidence comes from |
|---|---|---|
| Header | Which model file, which threshold, who owns it, when is the next review? | model/model_info.json (artifact_sha256, threshold) |
| Intended use | Which decision, made by whom, at which prediction time, for which target? | Module 2: the decision and the target |
| Not for | Which uses are unsafe, or not supported by the evidence? | Your judgement, with a reason for each |
| Data | Which tickets, from when, with which features? | The split, and the description of the data (dataset.md in the labs repository) |
| Why this model | Why this kind of model, and not a more complex one? | The comparison and the one-standard-error rule (Modules 5 to 7) |
| Evaluation | How good is it, compared with what? | model_info.json (valid), reports/test_report.json (test, once), the baseline |
| Slices | Where is it weaker? | Module 7, with the number of tickets in each slice |
| Limits | What do the users need to know to use it safely? | Calibration (Module 6), new categories, data problems (Module 7) |
| Ethical and practical notes | Who can be harmed, and how do you prevent it? | The decision and its costs |
The Not for section is the one that people skip, and the one that prevents the most harm. Remember the agent ranking: a likely second use, which the evidence does not support. Each item needs a reason, so that a reader can apply the reason to a new case that the card does not list.
But why not just a README? Tomás asks: “We already have a README with the commands. Why another document?” A README tells a developer how to run the code. A card tells a decision maker what the numbers mean and where they stop meaning anything. Grace and Omar read the card; they will never read train.py. The two documents have different readers, so they hold different things.
Worked example: Priya’s model card
Section titled “Worked example: Priya’s model card”Priya collects the numbers first. She takes them from the files, not from her memory or from an old slide, so the card describes this exact artifact:
import json
info = json.load(open("model/model_info.json"))test = json.load(open("reports/test_report.json"))print("threshold:", info["threshold"], "| artifact:", info["artifact_sha256"][:16])print("valid:", info["valid"])print("test:", {k: test[k] for k in ("flag_rate", "precision", "recall", "roc_auc", "average_precision")})print("rule on test:", test["baseline_priority_1"])The output:
threshold: 0.19 | artifact: a4acd259a398e7c8valid: {'rows': 1179, 'escalated': 183, 'roc_auc': 0.752, 'average_precision': 0.391, 'flagged': 227, 'flag_rate': 0.193, 'precision': 0.379, 'recall': 0.47}test: {'flag_rate': 0.196, 'precision': 0.357, 'recall': 0.446, 'roc_auc': 0.749, 'average_precision': 0.345}rule on test: {'flagged': 150, 'flag_rate': 0.15, 'precision': 0.367, 'recall': 0.35}For the slices, she uses the validation set at the threshold 0.19, with the count of tickets and escalations next to each recall. Why the counts? A recall of 1.000 on 1 escalation means nothing: one more escalation could make it 0.500. So a slice with too few tickets says “too few to measure”, as you learned in Slice analysis.
Then she writes MODEL_CARD.md in the project folder, next to model/:
# Model card: ticket escalation model
| | ||---|---|| Model | Logistic regression in a scikit-learn pipeline (`model/escalation_model.joblib`) || Artifact SHA-256 | `a4acd259a398e7c84517b98bf50d811197f90b0f2f1ea15f1f9e168ba3aff736` || Threshold | 0.19, chosen on the validation set for a review capacity of 20% || Card date | 2026-07-01 || Owners | Priya (data scientist): the model and this card. Grace (support lead): the decision and the review capacity. Mei (data engineer): the input data and the input checks. || Next review | 2026-10-01, or earlier when a retraining trigger fires |
## Intended use
- **Decision:** when a support ticket is created, decide if it goes straight to the senior team.- **How:** `predict.py` scores each new ticket. A ticket with a score of at least 0.19 is flagged and routed to the senior team. Other tickets stay in the normal queue.- **Users:** Larkfield's support team. Grace's senior team reviews the flagged tickets.- **Prediction time:** when the ticket is created. The model uses only facts known at that moment.- **Target:** `escalated_72h`, 1 if the ticket is escalated to a senior agent within 72 hours of creation.
## Not for
- **Staff performance reviews.** A score describes a ticket, not the agent who handled it.- **Refusing, delaying or reducing service** to any customer. A ticket without a flag gets the normal service, never less.- **Showing a score as "the chance" of escalation** to customers or staff. Since the policy change of 2026-05-01, the scores are too low (see *Limits*). This needs recalibration on recent data first.- **Other shops, other languages, or other prediction times** (for example, after the first reply). The model learned Larkfield's tickets at creation only.- **Ranking customers.** One prediction is for one ticket, not for a customer.
## Data
Synthetic data made for the course: it has no real people. One row is one ticket, with only the facts known at creation.
| Part | Tickets | Escalated | Dates ||---|---|---|---|| Train | 16,939 | 1,983 (11.7%) | 2024-07-01 to 2026-04-30 || Validation | 1,179 | 183 (15.5%) | May 2026 || Test | 1,002 | 157 (15.7%) | 2026-06-01 to 2026-06-27 |
Features (11): `channel`, `team`, `segment`, `region`, `priority`, `order_value`, `word_count`, `customer_tenure_days`, `prior_tickets_90d`, `created_hour`, `prior_escalations_90d`. The checksums of the data files are in `model/model_info.json`.
## Why this model
Priya compared logistic regression with decision trees, random forests and gradient boosting, with 5-fold cross-validation on the train set. The best boosting setting had an average precision of 0.3424, and logistic regression 0.3393. The difference, 0.003, is smaller than one standard error (0.008), so it is noise. By the one-standard-error rule, she chose the simpler model. Its coefficients show how each feature moves a score, so the owners can explain a flag and find problems in the inputs.
## Evaluation
The threshold was chosen on the validation set. The test set was used once, after every choice, with `python evaluate.py`.
| At threshold 0.19 | Validation (May) | Test (June) | Rule "flag priority 1" (test) ||---|---|---|---|| Flagged | 227 (19.3%) | 196 (19.6%) | 150 (15.0%) || Precision | 0.379 | 0.357 | 0.367 || Recall | 0.470 | 0.446 | 0.350 || ROC AUC | 0.752 | 0.749 | || Average precision | 0.391 | 0.345 | |
Target: flag at most 20% of new tickets and catch at least 40% of escalations. The model meets it on validation and test. The rule catches 35% on test (33.3% on validation).
Slices (validation set, threshold 0.19):
| Slice | Tickets | Escalated | Flagged | Recall ||---|---|---|---|---|| Team payment | 206 | 48 | 91 | 0.771 || Team warranty | 168 | 48 | 50 | 0.521 || Team delivery | 348 | 42 | 39 | 0.310 || Team returns | 291 | 33 | 40 | 0.303 || Team account | 166 | 12 | 7 | 0.083 || Priority 3 | 617 | 59 | 19 | 0.102 || Segment guest | 9 | 1 | 1 | too few to measure || Channel unknown | 22 | 5 | 5 | too few to measure |
## Limits
- **Scores are too low since 2026-05-01.** After the new warranty policy, the mean score on the validation set is 0.120, but 15.5% of tickets escalated. For scores from 0.2 to 0.3, the mean score is 0.241 and the observed rate is 0.315. The ranking still works, so the threshold for the capacity still works.- **Precision is about 0.36 to 0.38.** About 2 of 3 flagged tickets do not escalate. The senior team must expect this.- **Weak slices.** The model rarely flags `account` tickets and priority 3 tickets, so it misses most of their escalations.- **Categories the model never saw.** A new channel (for example `social`) gets 0 in every channel column. The model still scores the ticket, but with less information. `predict.py` warns and counts these tickets.- **Guests and new customers.** Guests have no customer history (202 guest tickets in train). Small slices like guests and `channel = unknown` have too few validation tickets to measure.- **A hidden customer tendency.** Some repeat customers escalate more often, and no column shows it. This limits any model on these features.
## Ethical and practical notes
- A flag adds a review by a senior agent. No flag must never mean worse service.- Coefficients and feature importance describe the model, not the causes of escalation.- The owners check the monitoring signals every week (flag rate, mean score, unknown categories) and recall when the labels arrive.- With real data, the features would include personal data about customers. Then privacy rules apply to the data, the artifact and the predictions.
## How to reproduce
```shpython -m pip install -r requirements.txtpython get_data.pypython train.pypython predict.py --check```
`python train.py` prints `Trained on 16939 tickets. Threshold 0.19 for a review capacity of 20%.` `python predict.py --check` prints `Check passed: 20 scores are identical after reloading`.Keep this file. You use it again in the final assignment.
Four choices in this card matter most:
- The test column is next to the validation column and the baseline. A number alone means nothing. The reader sees that the model beats the rule on recall (0.446 against 0.350 on test) with a similar precision, and that test is a little lower than validation.
- Each slice shows its counts. The
accountteam’s recall of 0.083 is 1 of 12 escalations. That is a weak slice with few cases, and the card says both. Guests have 9 tickets: the card does not pretend to know. - Each “Not for” item has a reason. “Staff performance reviews” is not in the data at all, but it is a likely second use. The reason (a score describes a ticket, not an agent) also answers similar requests that the card does not list.
- “Why this model” gives the reason for a simple model. A reader may ask: “Why not the most powerful model?” The card answers it. The difference was smaller than the noise, and a simple model is easier to explain and check. When a simple model is better gives more reasons of this kind.
Guided practice: write and check your card
Section titled “Guided practice: write and check your card”- In
~/projects/ticket-model, activate.venv. - Run
python train.py. You should seeThreshold 0.19. - If
reports/test_report.jsonis missing, runpython evaluate.pyonce. - Save
card_numbers.pyfrom the worked example, and run it. You should see the four lines of the output above. - Create
MODEL_CARD.mdnext tomodel/. Copy Priya’s card into it. - Compare each number in the Evaluation table with the output of step 4.
- Compare the artifact SHA-256 in the card with
artifact_sha256inmodel/model_info.json. On another system, yours can differ: then write yours into the card. - Read the Not for list. Add one more item, with a reason, that fits Larkfield.
Example of an extra “Not for” item
- Choosing which tickets to close without a reply. The model predicts escalation, not whether a reply is needed. A low score does not mean an unimportant ticket.
Other good answers: deciding refunds or compensation (the model knows nothing about what a customer is owed); routing at another prediction time, for example after the customer’s second message (the features describe the ticket at creation).
Your turn: Omar’s request
Section titled “Your turn: Omar’s request”Omar wants to add a line to each customer’s confirmation email: “There is a 24% chance that a senior agent will handle your ticket.” The 24% would be the ticket’s score. Use the card to decide. Before you read the answer, write two or three sentences: yes or no, and why, with numbers from the card.
Show the answer
No, not with these scores. The card lists it under Not for: showing a score as “the chance”. The reason is in Limits: since the policy change, the scores are too low. In May, tickets with a score from 0.2 to 0.3 (mean 0.241) escalated at a rate of 0.315. So “24%” would be about 31% in reality. A number shown to a customer reads as a promise, and this one is wrong in a known direction.
What would make it possible: recalibrate the scores on recent data, or retrain with it (the next lesson plans that). Then measure again, and update the card. Even then, Omar and Grace must decide if customers should see this number at all. The card is where that decision is written down.
Common mistake
A card that does not match the artifact
Symptom: Tomás tries the 15% capacity from Package artifacts and forgets to set it back. The model folder now has the threshold 0.22. The card still says 0.19, 227 tickets flagged in May and a recall of 0.470. Grace plans the senior team’s week with the card. Nothing shows an error.
Cause: the card was written by hand, once. The artifact changed after it. The model file did not even change: its SHA-256 is still a4acd259…, because the pipeline is the same. Only the threshold in model_info.json changed, and the real recall on May is now 0.41.
Fix: treat the card as part of the artifact. Name the exact model file and the threshold in it, and check both before every handoff:
import jsonfrom pathlib import Path
card = Path("MODEL_CARD.md").read_text(encoding="utf-8")info = json.loads(Path("model/model_info.json").read_text(encoding="utf-8"))problems = []if info["artifact_sha256"] not in card: problems.append("the card names another model file")if f"| Threshold | {info['threshold']}," not in card: problems.append(f"the card does not give the threshold {info['threshold']}")print("The card matches the artifact." if not problems else "The card does not match: " + "; ".join(problems) + ".")With the 15% artifact:
The card does not match: the card does not give the threshold 0.22.With the course’s artifact (capacity 20%):
The card matches the artifact.When the check fails, update the card: all its numbers, not only the threshold. Or make the artifact again with the settings that the card describes.
Consequence in AI work: an old card is worse than no card, because people trust it. Teams that retrain often make the card from model_info.json and the test report with a script, so the numbers cannot drift apart.
Knowledge check
Section titled “Knowledge check”Check your understanding
Give three uses that the escalation model is not for, each with its reason.
Show the answer
Any three of these, each with its reason:
- Staff performance reviews: a score describes a ticket, not the agent.
- Refusing, delaying or reducing service: the model only adds a review; no flag must never mean worse service.
- Showing the score to customers or staff as “the chance”: the scores are too low since the policy change of 2026-05-01 (scores 0.2 to 0.3 escalated at 0.315).
- Other shops, languages or prediction times: the model learned Larkfield’s tickets at creation only.
- Ranking customers: the unit of a prediction is one ticket.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Check your understanding
Omar reads average precision 0.391 in the card and says: so the model catches 39% of escalations. What is wrong, and which numbers answer his question?
Show the answer
Average precision summarises the ranking over all thresholds. It is not the share of escalations that the model catches. The share caught is the recall at the threshold that is used, 0.19: 0.470 on the validation set (86 of 183) and 0.446 on the test set (70 of 157). With it goes the flag rate (19.3% and 19.6%), which Grace needs for her capacity, and the precision (0.379 and 0.357).
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Check your understanding
Why does the card show the number of tickets and escalations next to each slice's recall?
Show the answer
A rate from a small slice is unreliable. The account team’s recall of 0.083 is 1 of 12 escalations; with so few cases, a few tickets more or less change it a lot. Guests have 9 validation tickets and 1 escalation, so their recall of 1.000 means nothing. The counts tell the reader how much to trust each number. They also show where the card cannot say anything yet.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Recap
You can now hand over a model with its use boundaries written down:
- A model card says what the model is for, who uses it, and what it is not for, with reasons. It also gives the data, why this model, the results next to the baseline, the slices with counts, the limits, the owners and the review date.
- The numbers come from the files (
model_info.json,test_report.json), not from memory: test recall 0.446 against the rule’s 0.350, at 19.6% flagged. - The card names the exact artifact (its SHA-256 and threshold), and a short check proves that they match.
The card says when the model may no longer fit. Next, you plan the checks that tell you when that happens: input checks, monitoring signals and a retraining trigger.
Next: Plan production checks