Lab · Intermediate
Make it run on someone else's laptop
You are handed a working analysis in a single notebook with hard-coded paths, no environment file and a chart exported by hand. Turn it into something a colleague can clone and run — and prove it by running it somewhere it has never been.
The analysis works. It produces a GAM estimate by commune, a chart, and a paragraph for the cluster report, and it has been doing so for four months.
It is one notebook. The path to the data is /Users/aline/Downloads/muac.csv,
the chart was exported by right-clicking it, the seed is wherever numpy’s default
left it, and the person who wrote it is leaving in three weeks.
Your job is to make it survive that.
The file
muac-screening-artibonite-2024.v1.csv — 4,218 screenings across twelve communes.
Treat it as the monthly export: it arrives, it is read-only, and next month’s will be
the same shape with different rows.
Part one: the layout
Build the structure from lesson 1 and move the analysis into it. Nothing in src/
may contain an absolute path, and nothing may be produced by hand.
project/
data/raw/ the CSV, read-only, with CHECKSUMS
src/ one function per step
outputs/ everything regenerated
tests/
run.py the whole pipeline, one command
.gitignore written first
README.md
Commit .gitignore before anything else, and make sure data/raw/* is excluded
with !data/raw/CHECKSUMS letting the fingerprint back in.
Part two: the environment
Initialise uv, add what the analysis imports, commit pyproject.toml and uv.lock.
Pin the Python version as a range.
Then delete your virtual environment and run uv sync. If anything is missing,
the analysis was importing something it never declared.
Part three: the four sources of difference
The original notebook has at least three of the four from lesson 4. Find them and fix them:
- the clock — anything using today’s date;
- the seed — the analysis bootstraps a confidence interval;
- file order — if it globs anything;
- the locale — the CSV read and any date parsing.
Then prove it. Run the pipeline twice into different directories and diff them. The diff must be empty.
python run.py && cp -r outputs outputs-first
rm -rf outputs && python run.py && diff -r outputs outputs-first
Part four: the checks
Add the loader checks from lesson 6. At minimum: required columns present, row count
in range, commune values within the known set, muac_mm within a plausible range,
and an assertion that the summary’s denominators sum to the number of screenings.
Then break the data on purpose — a copy with a renamed column, a copy truncated to
500 rows, a copy with Anse Rouge instead of Anse-Rouge — and confirm each one
stops the pipeline with a message naming the problem.
Part five: prove it somewhere else
git clone . /tmp/handover-test && cd /tmp/handover-test
uv sync
python run.py
diff -r outputs/ ~/project/outputs/
A fresh clone in a temporary directory is the minimum. A colleague’s laptop is better. If you have CI available, add a workflow that does exactly this on every push.
Part six: the handover
Write HANDOVER.md using the seven sections from lesson 7. Sections 4 and 6 are the
graded ones:
Section 4 must record every decision your pipeline makes that the code does not explain — which communes are normalised and why, what happens to screenings with no age, whether oedema cases are counted, and what the case definition is.
Section 6 must name the finding that will be challenged. You already know what it is if you did the visualisation course: the commune ranking.
Check your numbers
| Expected | |
|---|---|
| Screenings | 4,218 |
| Communes | 12 |
| Screenings, smallest and largest commune | 189 and 768 |
| GAM, whole district (MUAC or oedema) | 10.3% |
| GAM, MUAC below 125 mm only | 9.3% |
| Highest and lowest commune | 15.3% and 5.6% |
| Communes whose interval clears the district median | 3 |
| Second pipeline run | byte-identical to the first |
| Fresh-clone run | byte-identical to both |
If your two runs differ only in one figure file, you have found the timestamp that lesson 4 is about — SVG and PNG writers both embed one unless told not to.
The questions to answer in prose
Three sentences each.
1. Name the sources of run-to-run difference you found and how you found each. For any of the four you did not find, say why the analysis was already immune.
2. You broke the data three ways and each stopped the pipeline. For the truncated file, say what the analysis would have reported had the check not been there, and how plausible that number would have looked.
3. Your handover section 4 records decisions. Pick the one a successor is most likely to change without realising it matters, and say what the consequence would be.
What to hand in
- the restructured project, running from a fresh clone with two commands
uv.lock, committedtests/with the loader checks, and evidence that each catches its defectHANDOVER.md, seven sections- the output of the two-run diff and the fresh-clone diff, both empty
- the three prose answers
How to know you are done
Give it to someone else and watch them run it without helping. Every question they ask is a line missing from the README or the handover, and every failure is a dependency you did not declare.
Failing that: delete your local checkout entirely, clone from the remote into a new directory, and run it. That catches everything except what is installed globally on your machine — which is the one thing only another machine finds.
What this lab is not
It is not a nutrition analysis. The GAM estimate is one the nutrition course already produced, and reproducing it is the point rather than the finding.
It is also not about tooling preference. uv and renv are the tools this course
uses; conda, poetry, pip-tools and packrat all satisfy the same requirement,
which is that a lock file exists and is committed. The requirement is the lesson;
the tool is a detail that will change.