Title: Lab 15 — Lock It, Package It, Track It
Course: AIINFRA 100: AI Systems & Development Environments > Section: Week 15 — Reproducibility, Testing & Responsible AI — Final

🛠️ Lab 15 — Lock It, Package It, Track It (50 pts)

Goal: Take a small existing Python script from an earlier week and turn it into a properly packaged, dependency-locked uv project, add a test for it, and track a run with MLflow — the capstone quality loop of this course.

Steps:

1. Pick a small Python script you already wrote earlier (a scikit-learn or numpy script works well). Copy it into a new, empty folder.
2. In a terminal in that folder, run uv init to scaffold a pyproject.toml. Open it and note the project name and empty dependencies section.
3. Add the packages your script actually needs, one at a time, with uv add <package> (e.g. uv add scikit-learn or uv add numpy). Watch each uv add update pyproject.toml.
4. Run your script through uv with uv run <your_script>.py. The first run resolves every dependency (direct and transitive) and generates uv.lock. Open uv.lock in a text editor just to see it — do not hand-edit it.
5. Export a classic requirements file for comparison: uv export --format requirements.txt > requirements.txt. Open uv.lock and requirements.txt side by side and write down at least two differences you notice.
6. Add a test. Pull one small pure function out of your script (or use the normalize example below), create test_<name>.py in the same folder, and write 2–3 pytest tests with plain assert, including at least one edge case. Run uv run pytest -v (add pytest first with uv add --dev pytest) and confirm they pass. Then run ruff check . and ruff format . and note anything that changed.

	def normalize(values: list[float]) -> list[float]:
	    if not values:
	        return []
	    lo, hi = min(values), max(values)
	    if lo == hi:
	        return [0.0 for _ in values]
	    return [(v - lo) / (hi - lo) for v in values]

Example tests: normalize([0, 5, 10]) == [0.0, 0.5, 1.0]; the edge case normalize([]) == []; optionally normalize([3, 3, 3]).

1. Track a run with MLflow. Add it with uv add mlflow, then wrap your key parameter and result in tracking calls — e.g. mlflow.log_param("n_estimators", 100) before the model runs and mlflow.log_metric("accuracy", accuracy) after. Re-run your script two or three times with a different parameter value each time via uv run <your_script>.py.
2. Run mlflow ui, open http://localhost:5000, and take a screenshot showing your logged runs listed side-by-side.
3. Write a short reflection (5–8 sentences): what pyproject.toml declares, what uv.lock guarantees that requirements.txt alone does not, what your test checks (and why the edge case matters), and what comparing your MLflow runs told you.

Deliverables: Submit your project folder (including pyproject.toml, uv.lock, requirements.txt, and your test_*.py) as a zipped upload, plus your written reflection and your MLflow UI screenshot as a text entry / attachment.