Chapter 6 of 8
Take-home projects that pass
A take-home is graded against a rubric, and the single strongest move is to grade your own submission the way the reviewer will before you send it. This chapter's lab flips you into the reviewer's seat: given a spec, a set of weighted checks, and a candidate function, it runs the checks and produces a score. That mindset, build to the checks, is what passes take-homes. The strongest structural signal you can send in an AI take-home is to build the eval harness first. Most candidates build the feature and bolt on a few tests at the end. The candidate who builds a small golden dataset and a grader before the feature signals exactly the production maturity these roles want, because in real LLM work you cannot tell if a change helped without an eval. A real instruction from a take-home: "Build a classifier for these support tickets." The passing submission does not just train something. It defines the metric, builds a held-out set, reports the number, documents the ambiguous calls it made (what counts as which class) and why, and names what it would do next with more time. Handling that ambiguity without emailing five clarifying questions is itself part of the test.
The lab: read it, then run it
#!/usr/bin/env python3
"""
LAB IP6: A mock take-home grader.
Take-home projects are graded against a rubric, and the strongest thing you can
do is grade your OWN submission the way the reviewer will before you send it.
This lab flips to the reviewer's seat: given a spec (a set of weighted checks)
and a candidate implementation, run the checks and produce a score. It makes the
grading criteria concrete, which is exactly the mindset that passes take-homes:
build to the checks, not to a vibe.
The spec here grades a `tokenize` function on correctness, edge-case handling,
and hygiene (a docstring). You PROVE:
- a good candidate scores full marks,
- a buggy candidate scores strictly lower and fails the specific checks it
should fail,
- the grader is deterministic and the good submission outranks the buggy one.
Run: python3 modules/academy-content/labs/interview-prep/ip6-take-home-grader.py
"""
import sys
# ---- Two candidate submissions to the same spec ---------------------------
def good_tokenize(text):
"""Lowercase and split on whitespace, returning a list of tokens."""
if not isinstance(text, str):
raise TypeError("text must be a string")
return text.lower().split()
def buggy_tokenize(text):
# No docstring, no type guard, and it forgets to lowercase.
return text.split()
# ---- The rubric: weighted, self-checking criteria -------------------------
def build_spec():
"""Each check returns True/False for a candidate. Weights sum to 1.0 so the
score is a clean fraction, which is how a real rubric reports."""
def basic_split(fn):
return fn("the cat sat") == ["the", "cat", "sat"]
def lowercases(fn):
return fn("The CAT Sat") == ["the", "cat", "sat"]
def handles_empty(fn):
try:
return fn("") == []
except Exception:
return False
def guards_type(fn):
try:
fn(123)
return False # should have raised
except TypeError:
return True
except Exception:
return False
def has_docstring(fn):
return bool(fn.__doc__ and fn.__doc__.strip())
return [
("splits on whitespace", basic_split, 0.30),
("lowercases input", lowercases, 0.25),
("handles empty string", handles_empty, 0.15),
("guards bad input type", guards_type, 0.20),
("documents the function", has_docstring, 0.10),
]
def grade(candidate, spec):
"""Run every check against the candidate, return (score, per-check results)."""
results = []
score = 0.0
for name, check, weight in spec:
try:
passed = bool(check(candidate))
except Exception:
passed = False
if passed:
score += weight
results.append((name, passed, weight))
return round(score, 4), results
spec = build_spec()
good_score, good_results = grade(good_tokenize, spec)
buggy_score, buggy_results = grade(buggy_tokenize, spec)
print("STEP 1: grade the good submission")
for name, passed, weight in good_results:
print(f" [{'x' if passed else ' '}] {name:24s} (weight {weight:.2f})")
print(f" score = {good_score}")
print("")
print("STEP 2: grade the buggy submission")
for name, passed, weight in buggy_results:
print(f" [{'x' if passed else ' '}] {name:24s} (weight {weight:.2f})")
print(f" score = {buggy_score}")
# The buggy one must fail exactly: lowercasing, type guard, and docstring.
buggy_failed = {name for name, passed, _ in buggy_results if not passed}
expected_failures = {"lowercases input", "guards bad input type", "documents the function"}
good_full = (good_score == 1.0)
buggy_lower = (buggy_score < good_score)
buggy_expected = (buggy_failed == expected_failures)
deterministic = (grade(good_tokenize, spec)[0] == good_score)
print("")
print(f" good scores full marks : {good_full}")
print(f" buggy scores strictly lower : {buggy_lower} ({buggy_score} < {good_score})")
print(f" buggy fails the right checks : {buggy_expected}")
print(f" grader is deterministic : {deterministic}")
ok = good_full and buggy_lower and buggy_expected and deterministic
print("")
print(f"TAKE-HOME GRADER SCORES THE CANDIDATE CORRECTLY: {'YES' if ok else 'NO'}")
if not ok:
sys.exit(1)
print("Grade yourself before they do. Next round: behavioral and safety.")
Runnable lab
ip6-take-home-grader.pyGrade a candidate function against a weighted spec and prove the grader scores good and buggy submissions correctly.
Proves: TAKE-HOME GRADER SCORES THE CANDIDATE CORRECTLY: YES
Runs in your browser via Pyodide. First run loads the runtime once; no install, no server.
The grader gave the clean implementation full marks and the buggy one a strictly lower score, failing it on exactly the checks it should: no lowercasing, no type guard, no docstring. Run this lens on your own submission before you hand it in. Write down the rubric you think the reviewer is using, then score yourself honestly against it. Two rules make a take-home pass. First, the README carries the grade: state the problem, your metric, the number you hit, the decisions you made under ambiguity, and what you would do with another day. A reviewer often reads the README and the eval before the code. Second, ship something that runs. A smaller, working, evaluated system beats an ambitious half-built one every time. Next you prepare for the behavioral round, where you talk about systems that fail.
Check your understanding
1. What is the strongest structural move in an AI take-home?
2. How should you handle ambiguity in a take-home spec?
3. Why grade your own submission before sending it?