2026
NetTune
- Python
- OpenAI Vision
- Raspberry Pi
- Samba
- SQLite
- python-docx
- pytest
Built for a shopping centre's security and maintenance team, and in use there. Staff scan the day's handwritten log sheets into a shared folder on a Raspberry Pi. Every sheet is read twice, independently, by an OpenAI vision model, then dated, put in shift order and filtered for routine. An email says when a batch is ready. A member of the team checks each sheet beside its lines in a web dashboard, and one click writes the centre's monthly Word report. A hard monthly budget is checked before every API call; at about five pages a day it costs roughly €1.20 a month.
How it works
One batch, end to end
A colleague scans the day's handwritten sheets into a shared folder on a Raspberry Pi. Each page of the PDF is rasterised at 300 dpi and read twice, independently, by OpenAI's vision model.
The readings are compared line by line, and every date is checked against the rest of the batch. Lines that pass both checks go straight into the monthly report. Anything doubtful branches off, in teal, to a person in the review dashboard, who confirms or corrects it before the report is written.
Why every page is read twice
A model reading cursive can be confidently wrong, and a single reading gives no way to tell. So every page is read twice, independently, and the two readings are compared word by word, each word under its pair.
Where they agree, the line is trusted. Where one word differs, here "air" against "copper", the whole line is flagged with both readings, so a person checks exactly that line instead of the whole sheet. It is not infallible: both readings can make the same mistake, which is why a person still sees every sheet.
pass 1: Checked the air conditioning on floor 1, no incidents. pass 2: Checked the copper conditioning on floor 1, no incidents.
consensus: 8 of 9 words agree · "air" / "copper" differ → line flagged ⚠ for a person
The date that didn't fit
A scanned batch is one session of consecutive days, which gives a second check for free: every page's date is compared with its neighbours.
A page dated months away from the pages around it has been misread, even when both readings agree on it. It is lifted out and flagged instead of being filed in the wrong month.
page 16 reads 16-04-2026 in both readings; its neighbours are 15-07 and 17-07 → misread, flagged ⚠
What the Pi prints while it works
The service runs on the Raspberry Pi under systemd. It watches the share, waits for a scan to finish copying, skips anything it has already processed, reads and checks every page, and files the result in that month's folder.
Once no new scan has arrived for a couple of minutes, one email per month tells the team how many sheets are in and how many lines need checking, with a link to the dashboard.
Never over budget
Every API call is checked against a hard monthly cap before it is made. When the cap is reached, scans simply wait in the folder until the month rolls over: nothing is lost and nothing is overspent.
At about five pages a day, a month costs roughly €1.20 of a €5 cap. Each bar is one day's spend against that day's share of the cap.
month so far €1.21 of €5.00 cap · [#####...............]
What went wrong
Handwritten cursive cannot be transcribed perfectly, and the failures are silent. On a real sheet, the word “aire” (air) was read as “cobre” (copper) by five of six passes across two models, confidently and unanimously, even with both words in the prompt’s glossary. A single reading that looks right is not evidence that it is right.
That changed the design. No reading is trusted on its own: every page is read twice and any disagreement goes to a person with both readings (figure 2), and every date is checked against its batch (figure 3). The date check earned its place on a real 25-page July batch, where it caught a sheet read unanimously as 16-04-2026, something comparing readings could never have caught.