Field

The Standard Stays Yours — What a Grading Tool Does Not Do

By makemind · Jul 21, 2026

Stated up front. In March 2026 this magazine ran a piece about a teacher building a grading tool. This is not a specific school. We constructed the shape exactly as it actually works, and we do not name where. What follows was built to that shape, and the answers and the rubric are entirely invented.

The original's problem was elsewhere. It told you the tool gave marks, but not what it gave them on.

In grading that is not a small omission. A mark has to be something a student can appeal, and a teacher has to be able to answer that appeal. A mark with no reason can neither be appealed nor answered.

The rubric is data

Start with what this tool does not have. There is no code that judges what a good answer is. That is the rubric, and the rubric is data the teacher wrote.

/// One rubric line, written by the teacher.
///
/// `accept` is the list of things that earn the mark. It is deliberately a list
/// of strings rather than a regular expression or a scoring function: the
/// teacher has to be able to read their own standard back, and change it
/// without asking anyone.

The reason it is not a regex or a scoring function is right there. The teacher has to be able to read their own standard back. That reading-back is in the execution log.

rubric: Q1(10) accept=[24] | Q2(10) accept=[x=3, x = 3, 3]
      | Q3(20) accept=[-5, x=-5] | Q4(10) accept=[12cm, 12 cm]

Note that Q2 has three entries in accept. x=3, x = 3, 3. How far to be forgiving about spacing and notation is a marking standard, and that is the teacher's judgment, not the tool's generosity.

A partial mark comes out with its reason

This is the centre of the piece. What turns a mark scheme from a lookup table into a judgment is partial credit.

/// Mark one answer against one rubric line, and say which branch was taken.
///
/// The reason for returning the branch and not just the number: a teacher
/// overriding a mark needs to see what the scheme decided, not guess at it.
(int, String) _mark(RubricItem r, String? given) {
  if (given == null || given.isEmpty) return (0, 'blank');
  if (r.accept.contains(given)) return (r.points, 'accepted');
  if (r.partial.containsKey(given)) {
    return (r.partial[given]!, 'partial (${r.trap})');
  }
  return (0, 'wrong');
}

It does not return only the number; it returns which branch was taken alongside it. Here is one real paper, verbatim.

paper 02: Q1 "24" -> 10/10 (accepted)
        | Q2 "x=3" -> 10/10 (accepted)
        | Q3 "5" -> 8/20 (partial (drops the minus sign on the root))
        | Q4 "12 cm" -> 10/10 (accepted)

Q3 is 8 out of 20, and why it is 8 is attached. They dropped the minus sign on the root. If the student asks, the teacher shows them that line.

The verification looks at that too.

# A partial mark must say which branch of the rubric gave it. A number with
# no reason cannot be argued with, and a teacher must be able to argue.
grep -q 'partial (drops the minus sign on the root)' captures/run.log \
  || { echo "a partial mark arrived without the reason that produced it"; exit 1; }
Papers — 40.5 average, 8 papers marked against the rubric. A real render capture
Papers — 40.5 average, 8 papers marked against the rubric. A real render capture

This content requires Developer or above

Sign in and upgrade your plan to continue reading.

View Plans
Twitter