Skip to content

Extracting Grades and Comments From a Report Card

9 min read · updated August 11, 2026

Subject, grade, comment looks like a three-field extraction. It is four, and the field people leave out is the one that makes the other three interpretable: which scale the mark was written in.

The triplet is really a quadruple

A report card is a grid of subject rows against term columns, so a single subject row commonly carries several marks — a first quarter, a second quarter, a semester result and sometimes an examination mark — and each of those is a separate record. The row is not the unit; the cell is. Extract one record per subject and term, and the transposed layouts that some schools use, with terms as rows, stop being a special case.

To that add the scale. A mark of 3 is a strong result on a four-point standards-based scale and a weak one on a four-point grade point average. A B is meaningless without the letter-to-percentage table the school prints, which differs between schools and sometimes between departments. So the minimum record is subject, term, mark as printed, and a scale identifier — plus the usual span and page for traceability.

{
  "subject": "Mathematics",
  "term": "Q2",
  "mark_raw": "3",
  "scale_id": "standards_4pt_2026",
  "mark_normalised": null,
  "comment_codes": ["12", "27"],
  "page": 1
}

Leaving mark_normalised null at extraction time is the point of this page.

Scales, and the mid-year change

Schools use several scale families and frequently more than one on the same document. Letter grades with plus and minus modifiers; percentages; a four-point standards-based scale where the top value means exceeding the standard rather than perfect work; a word scale such as exceeding, meeting, progressing, beginning; pass and fail; and in the elementary years a separate behaviour or effort scale printed alongside the academic one. A single subject row can legitimately show a letter for the course and a set of one-to-four marks for the individual skills beneath it, which is two scales in one row.

Now the case that decides the design. A school changes its reporting scale between the first and second half of the year — moving to standards-based reporting, or adjusting the letter thresholds. The report card issued at the end of the year prints marks from both regimes side by side in the same row. A pipeline that normalised each mark to a common numeric scale as it read it has produced a row of numbers that are not comparable and no longer carries the information needed to fix that, because the raw marks are gone. The transformation is lossy and it is applied before anyone knows the scale changed.

So: capture the mark exactly as printed, capture a scale identifier resolved from the legend on the document rather than from a global configuration, and normalise later in a separate mapping that is versioned and can be re-run when someone discovers the mapping was wrong. This is the same argument the appraisal page makes about condition ratings and the permit page makes about occupancy codes, and it applies with more force here because the report card usually prints its own legend, which means the scale is recoverable from the document if you read it and unrecoverable if you do not.

A grade point average printed on the card is a derived value and a useful check, but only where you know the weighting policy — honours and advanced courses commonly carry extra weight, and unweighted and weighted averages both appear. If you do not hold the policy, record the printed average and do not assert a discrepancy.

The grade column contains things that are not grades

This is where a strict schema fails in a way that looks like a model error and is really a design error. The mark column holds, routinely:

  • Incomplete, pending work, often with a deadline after which it converts.
  • Pass and fail on a course graded that way, in a document where every other row is a letter.
  • No grade, not graded, or a dash, for a course a student enrolled in late or one not assessed this term.
  • Exempt or medically excused markers, especially in physical education.
  • Withdrawn, sometimes combined with a mark, as in withdrawn-failing.
  • A blank, which means the cell was left empty and is not the same as any of the above.

Constrain the field to letters A to F and one of two things happens: a structured-output layer rejects the generation and you lose the row, or the model supplies the nearest letter and you have invented a grade for a student. The second is worse and it is the one that happens silently. The correct shape is a union — a mark value plus a mark kind, where the kind distinguishes graded, administrative and absent — and an enumeration wide enough to hold the administrative codes the school actually uses, which is discoverable from the legend. Designing for the values you have not seen yet is the subject of schema design for unseen variants.

Comment codes point somewhere else on the page

Most report cards do not print comment text next to the grade. They print numbers, which index a legend of standard comments elsewhere on the document — commonly on the reverse, in small type, in two columns. Resolving them is a cross-reference within one document, and three things go wrong.

The legend is on a different page from the codes, so an extraction that processes pages independently never sees both. A code with no corresponding legend entry — because the legend was cropped in scanning, or because the school reused last year’s form — must stay as an unresolved code; a model asked to produce comment text for code 27 with no legend in context will write a plausible teacher comment, and that is a fabrication attached to a named child. And handwritten free-text comments coexist with the coded ones, so a field that holds only codes silently discards the part a parent actually reads.

Keep three fields: the codes as printed, the resolved text where the legend supplied it, and any free text verbatim. The general handwriting problem is covered in handwriting recognition with LLMs; what is specific here is that a teacher comment is short, personal and unverifiable from context, which makes it the worst possible place for a model to smooth over an illegible word.

Grid problems specific to this document

  • The attendance block looks like a grade block. Days absent and times tardy per term sit in a grid with the same shape as the grades grid, often directly beneath it. A table extractor with no semantic anchor will merge them, producing a subject called “Days Absent” with a grade of 4. Two grids of different kinds on one page is the case multi-entity document schema design is about.
  • Standards sub-rows are indented, not labelled. The relationship between a course row and the skill rows beneath it is expressed by indentation alone. Losing it flattens a hierarchy into a list of unrelated marks, which is the trade nested and flat extraction schemas weighs up, and the skill descriptors are frequently standards citations — extracting standards references deals with what to do with them.
  • Teacher names in the subject column. A cell reading a course name, a period number and a teacher name is three fields written as one line.
  • A report card is not a transcript. One is a term snapshot, the other cumulative with credits and a class rank. They look similar enough that a pipeline built for one will accept the other and produce records with the wrong meaning of “term”.
  • Student identity. The card names a child, and often a student number, a homeroom and a date of birth. This is exactly the data to minimise before a page reaches any third-party service; see PII redaction. The grades can be extracted from a page whose identity block has been masked, with the identifier reattached from the scanning workflow.