Clean production data from human review, and the shortcuts people take

Blog post #99

Poem manuscripts being checked line by line before entering a digital archive.

AI-generated illustration.


The Josef Hofbauer archive is my neighbour Peter’s grandfather’s poems, diary and books, put online for his family and for a historian in Prague. Most of the poems are typewritten pages that I ran through OCR. OCR on 1940s typewriter paper gives you text that is roughly right and wrong in a hundred small places. So the site has a review tab. Peter and Robert from the family, a librarian and a proofreader open a poem, fix the text and save it as a corrected version.

Today Peter told me he had corrected a batch of poems and asked me to put the approved ones on the poem page. That turned into three things: a clean path from a correction to the live site, a scheduled task that keeps doing it every morning, and a check for the shortcuts people take when they review.


What shipped

  • The latest approved version wins. Every correction is stored in Supabase with an id and a status. For each poem the build now takes the newest correction, and it only uses it once that version is approved. If the newest version is still waiting, the poem waits too. An older approved version does not get used in its place. Peter had saved two versions of “Atombombe” and the second one is what went live.
  • Corrections reach the reviewed poems too. Sixteen poems had already been checked against an earlier edition. Peter fixed small things in three of them, but the build skipped corrections on poems marked as reviewed. Nobody noticed, because those corrections showed up as approved in the database. They just never reached the page. Now they do.
  • Every poem that moves over gets a picture and a voice. When a poem moves from “machine read” to “corrected”, it gets an AI illustration made with Codex and a reading with my ElevenLabs voice clone. If the text of a poem that already has a reading changes, the audio is made again and the old file is kept.
  • A scheduled task at 07:00 every morning. It fetches newly approved poems, writes a picture prompt for each one, makes the image and the reading, looks at the image, deploys and reports how many poems are left. When no machine-read poems are left, it turns itself off.

What’s unclear or broken

  • “Corrected” did not always mean corrected. When I listened through the new readings, “Dank” sounded wrong. The first line on the site read Auf Silberai stein Leicht sich wisgend, which is OCR noise for something like “on silver thistles, gently swaying”. The poem was marked as corrected by Robert. I compared his version with the raw OCR. He had changed one line: the title, from DA NK . to D A N K. The other 23 lines were the OCR text, untouched.
  • The tool made the shortcut easy. The review tab sends the whole poem when you save. Robert fixed the title he saw first and pressed save, which is a perfectly human thing to do. To the system it looked exactly like a full review, so the poem moved into the “readable” section, labelled as corrected, and got a picture and my voice reading the OCR noise aloud.

Decisions made

  • Assume half reviews will happen and build for them. When something is marked as approved, that does not prove anyone looked at all of it. Volunteers have twenty minutes, see the obvious error, fix it and move on. I can’t fix that with a rule in an email, so the build has to notice it.
  • Diff against the original, not against the last save. Each poem now keeps its raw OCR text next to the corrected one. A new correction to a machine-read poem is compared line by line with that original. If it changes two lines or fewer, it counts as a title fix. The poem stays among the unreviewed poems with its new title, and it gets no picture and no reading. On today’s data the split was clear: Dank changed 1 line, the real corrections changed between 6 and 22.
  • Keep the work, hide it. The illustration and audio for Dank are still on disk. When someone corrects the full text and it is approved, the morning task links the picture again and records a new reading from the real text.
  • Report it instead of hiding it. The morning task lists title-only fixes and who sent them, so I can ask Peter or Robert to finish the poem rather than have it quietly sit there.
  • Readings are the exception to “leave the text alone”. For the audio I fix obvious OCR typos so my voice doesn’t read Bombemwurf instead of Bombenwurf. That list only affects the reading, never the text on the page. The page shows what a person approved.

Tooling & process

  • The scheduled task is a prompt, not a cron script. It runs in the Claude desktop app as a scheduled skill. Each run starts with no memory of this conversation, so the prompt says everything: the folder, the rule that the latest approved version wins, which voice and model to use and to stop rather than switch voice if ElevenLabs is down, what to check in each image, and when to turn itself off. Writing it felt like a handover note to a new colleague who will do the job alone at seven in the morning.
  • The parts that need judgement are left to the agent. Fetching, building and deploying are plain Python. Writing a picture prompt from a German poem, spotting OCR typos before the voice reads them, and rejecting an image with letters in it need judgement, so the scheduled agent does those steps.
  • Codex hung after making the images. All four pictures were generated, then the Codex run hung on copying the file and timed out after 25 minutes. The images were in Codex’s own folder the whole time. The script now picks them up from there if the copy step fails.

— Stefan