All writing

OCR That Never Guesses: Turning Paper Farm Sheets into Records

How I built the OCR and AI health checks in Ekonect, a poultry-farm platform: the model reads, the farmer confirms, and nothing becomes official without a person.

Ekonect sign-in screen with a photo of a poultry house

Farmers already keep records. They write them on paper sheets in the poultry house, one row per day. The job was not to replace that habit. The job was to turn those sheets into records that an agribusiness company and a government agency could trust, without making the farmer type everything twice.

That is Ekonect, which I designed and built for an agribusiness client. Phase 1 went from a UI prototype to all modules in production in about five weeks. This post is about the two AI features in it, and the rules that keep them honest.

Phase 1~5 weeks
User roles3
Photos per health case1–5
Job attempts3

Three people, three needs

Every AI decision below follows from one question: who is responsible for this number? For the daily record, it is the farmer.

OCR: read the sheet, never invent a value

The farmer photographs the whole sheet. The photo is saved on the phone first and uploaded when there is signal, so a house with no coverage still works. The model then has a narrow task: find the row for the requested date, ignore total rows and summary blocks, cope with rotated photos and Indonesian decimal commas, and return each field with a confidence. Simplified, the answer looks like this:

{
  "date": "2026-10-12",
  "fields": {
    "eggs_kg":   { "value": 48.5, "confidence": 0.93 },
    "feed_kg":   { "value": 112,  "confidence": 0.88 },
    "mortality": { "value": null, "confidence": 0 }
  }
}

The important rule is in the last field. If the model cannot read a value, it returns null. It never guesses. A wrong number that looks plausible is far worse than an empty box, because nobody checks a box that is already filled.

The farmer then sees the photo and the pre-filled form side by side, corrects what is wrong, and submits. There is no admin approval step, and a high confidence score does not skip the check. The model saves typing; the farmer owns the record.

The model saves typing. The person who signs the record is still the person who checked it.

Keep every version

Each step is stored as a revision: the model's extraction, the farmer's correction, a retaken photo, a detected conflict. When someone later asks why a number is what it is, the answer is in the history, not in someone's memory.

Offline first, and conflicts that a person resolves

Drafts and an outbox live in the browser's IndexedDB with stable IDs, so a retry after a dropped connection cannot create a duplicate record. One real bug taught me to test this carefully: an outbox item could be sent more than once until I fixed it to send each item exactly once.

Official records are never overwritten. If a new submission arrives for a day that already has an official record, both versions are kept as a conflict. An admin compares them side by side, picks one and writes a reason. The other version stays in the history, and the decision is audited.

Health cases: the AI drafts, the admin decides

When a farmer sees sick birds, they report the date, a short story and one to five photos. The AI writes a pre-analysis: a possible condition, the evidence it used, a confidence from 0 to 100 and safe next steps.

The infrastructure is deliberately boring. The job queue is a Postgres table with a five-minute lease, three attempts, and retries after one and then five minutes. The "worker" is a tiny container that calls an internal endpoint every few minutes. No message broker, no new service to babysit.

Models are called through an OpenRouter client, with the model set by configuration. For OCR on farm records, requests are routed only to providers that do not retain the data.

What is not there yet

Honesty matters more than a good demo, so here is what Phase 1 does not do. Temperature and humidity are typed in by hand; there is no live sensor feed yet. The end-to-end tests run without the model, so they cover the OCR failure path but not a real extraction. Both are on the list.


The lesson I keep coming back to: the useful AI feature is rarely "the model decides". It is "the model does the tedious part, a person confirms, and the system remembers who did what". Design the null, the side-by-side check and the history first. The model is the easy part.

Share LinkedInXEmail