Free open-source exam grading for convention or classroom use: build the answer sheet, write the key, print and scan, grade, and fix unclear marks.
Where data is stored: Your setup, answer keys, thresholds, and results are saved locally in this browser on this device. Uploaded scan PDFs are temporarily sent to the grading server for processing, then deleted; they are not retained on the server.
Ask whoever set the server up.
An invite link fills these two boxes in for whoever opens it. The passphrase is inside the link — anyone who has it can grade, so send it the way you would send the passphrase itself, and not somewhere public. Step 2 has a second button that adds your sheet design to the link.
You can change the wording and Latin levels. The bubbles and corner marks are fixed. Reprint after any change.
Allowed or excluded tests use these names, so renaming one here renames it everywhere.
These are not read by the software — only the bubbles are. They exist in case the Student ID goes wrong.
That link contains the invite from step 1 as well as everything in this step. Send this link rather than the plain invite if you have customized any wording.
How dark the bubbles themselves are printed. An empty bubble is not blank paper — it is a printed ring with a letter inside it — so this is the floor every reading sits on, and printing it lighter leaves more room between an untouched bubble and a lightly-pencilled one. Every sheet in a batch must be printed at the same setting.
The sheet already shows a filled bubble beside the three ways it is usually got wrong — a cross, a tick, and a half-scribble — which is read without reading. This adds a line of your own underneath. Most sheets do not need one.
Test ID: the 4-digit number for this test. Print this number on the test booklet.
Levels: who may take the test. Two tests may share a Test ID as long as the levels do not overlap.
| Test name | Test ID | Levels that may take it |
|---|
Enter the correct answer for questions 1–80 for each test.
Pick one of the two options.
Paste directly into each box, and it will try to parse whatever you have.
Download the template, type the answers, then bring the file back.
Every answer is editable here. Edited cells are shown in bold.
| C | The answer is C. |
| ABD | The answer is A, B and D. The student must bubble all three. |
| A|BD | Either is accepted: A on its own, or B and D together. |
| X | Exclude this question from scoring. Use if you discover after the test that a question should not count. |
| - | You have not entered this answer yet (placeholder). |
X means the question is finished and will not be
scored, while - just means you have not typed it in yet.
abd and
ABD are the same answer, and so are x and
X.BD and
DB are the same answer.A|B|CD accepts A, or B, or C and D together. There is
no limit on how many you list.AA is
rejected, as is a stray | such as A|, and
any letter outside A–E.Keys.csv. The first
column has headers Name, Test ID, Excluded, then 1 to 80, with one further column per test.Excluded may be written as
Allowed instead, naming the levels that
may sit each test rather than the ones that may not. Use
one row or the other, not both. Excluded is left blank
by default, which bars nobody and stays right when you add or
rename a Latin level later.You don’t need to change this for most runs.
Every test on the sheet is graded by default. An unticked test is not read at all: no results, no answer key needed, and nothing on the review sheets. Useful when a test is scored elsewhere, or when its key is not ready yet.
Scanning one side only is for when a test is being re-marked on its own: the pages are then read one at a time instead of in front-and-back pairs, and a page of the other side is set aside rather than read. The front carries Test 1 and the back carries Tests 2 and 3, so a side you leave out takes its tests with it.
A duplex scanner produces an empty page for the back of a one-sided original, giving printed, blank, printed, blank. With this setting on, those empty pages are skipped. With it off, each one is reported as something that should not be there.
Marked-up scans show which bubbles were read as filled, along with the correct answer. They make a run noticeably slower, so leave this off unless you are demoing or debugging.
Every cell the reader laid over the page, not just the bubbles it acted on. This is what to look at when a sheet has been read wrongly and it is not obvious why.
Every bubble is measured for darkness on a scale from 0 (blank paper) to 1 (solid black). A bubble darker than the cutoff counts as filled. One that lands between the review threshold and the cutoff goes to step 7 for a person to decide. Anything fainter is ignored.
Where those two cutoffs fall depends on the scanner, the paper, and how hard the students pressed. So the server measures them from each batch of scans rather than using fixed numbers.
A higher number is stricter: it demands a darker mark. Leave a box empty to compute that one automatically — you can pin one number and let the other three be measured.
After a run, Calibration.txt shows the
computed thresholds, so you can copy those numbers here to repeat it.
A batch is one PDF from the scanner, holding whole sheets. Each is graded on its own and keeps its own results, so you can start grading the first stack while the rest is still being scanned.
If a mark was read wrong or the key was wrong, fix it in step 7: that re-scores from the results already here, without re-reading the scans. If the paper itself needs re-reading, scan it again and add it as a new batch — a batch that has been graded stays as it was.
An unclear bubble is too faint to be definitively filled. A missing bubble is a required field it could not read at all. A question left blank is not an error: that is the student’s choice.
Each file asks for one correction per row. Open it, check the paper, fill
in the answer, mark the row Done, and upload it below.
The Done column should read TRUE. A row you
have not ticked is left exactly as it was and comes back as a shorter
file to finish next time, so you can do half now and half later.
You can add rows too. A student whose answer was read confidently wrong never reaches these files, and a row you write by hand is how to fix it.
A row needs enough of File, Page,
Test, Test ID and Student ID
to pick out exactly one test. The Student ID and the Test ID
normally do it. Then the question number and the answer:
| Student ID | Test ID | Question | A | B | C | D | E | Done |
|---|---|---|---|---|---|---|---|---|
| 06767 | 1067 | 17 | FALSE | TRUE | FALSE | FALSE | FALSE | TRUE |
That says: on student 06767’s test 1067, question 17 is B.
Leave every option FALSE to record that the student
answered nothing. If a row names no test, or names more than one, it
is reported back rather than guessed at.
In Google Sheets, CAJCL → Add a correction row sets one up with the checkboxes already in place.
Any editor that saves CSV will do, even a text editor. Nothing here needs Google Sheets.
Sheets is worth setting up when several people are correcting at
once. Everyone opens the same file and works through different
rows at the same time, and you download it once
at the end. The script below is a convenience on top of that — it adds
checkboxes so nobody has to type TRUE, restores the leading
zeros on Student IDs that a spreadsheet would otherwise eat, fits the
columns, and trims the blank rows.
onSpreadsheetChange