Ovillo Download
How it works · Reading documents

How it works

Reading documents

Every document you add is read on your Mac before anything is filed. This page follows a file from the moment it arrives to the moment it is searchable, and says where the reading can go wrong. It is for people who want to know what the app does with their files, and for developers.

The pipeline

The page works through documents one at a time in the background. For each one:

  1. Store. The original is encrypted and saved as a blob. Originals stay where they were; Ovillo keeps encrypted copies.
  2. Extract text. Digital text is read directly; scans and photos go through OCR. See below.
  3. Tables. If the Apple silicon helper is ready, PDFs and images also go to it for table structure, which the lab reader uses as a second opinion.
  4. Fields. The document type, date, facility and clinicians' names are picked out of the text.
  5. Whose is it? The patient check compares the name on the page with the person whose record this is.
  6. Lab values. The lab reader finds results. Routine ones are filed, marked and undoable; anything doubtful is held. See How the record is built.
  7. Index. The text is cut into passages and added to an encrypted search index.

If you close the window part-way, the next unlock picks up where it stopped. Reading and indexing never need a network connection. Plain text, pages, passages and vectors exist only in memory and in encrypted blobs.

File types

KindFilesHow it is read
PDF.pdfpdf.js reads the text layer. A page with no real text is rendered and sent to OCR. Typed values in fillable forms are included. Password-protected PDFs open when you type the password in the app; it is checked on this computer.
Photos and scans.jpg .jpeg .png .webp .gif .bmp .tif .tiff .heic .heifOCR. HEIC and TIFF are decoded on this computer first.
Word and similar.docx .odt .rtfOvillo unpacks and reads them with its own code. Nothing is executed.
Email and web.eml .html .htmText only. Attached text files in an email are read in place; an attached PDF is listed, and you add it as its own document.
Plain text.txt .text .log .md .markdown .csv .tsv .json .xmlRead as text. Older text encodings (Spanish Latin-1 files) are handled.

Not read: old .doc, spreadsheets, slides, Pages, Numbers, Keynote, archives, audio and video. A folder drop lists these as left out, with a reason. An older or password-protected Word file gets a message asking you to save it as .docx or PDF without a password. A file named .pdf that is really a JPEG or PNG is read as the image it is. A file is limited to 100 MB, and a PDF to its first 500 pages.

PDF text

For each page, Ovillo asks pdf.js for the text layer. If the layer has fewer than 25 characters, or less than half of them are letters or digits, the page counts as a scan. Judging by the text layer alone means a fillable cover sheet laid over a scanned page does not hide the scan.

OCR for scans and photos

OCR is Tesseract, running inside the page, with English and Spanish together (eng+spa). Other languages are not supported. Scanned PDF pages are drawn at twice size; phone photos are shrunk so the longest side is 2,500 pixels, since more than that does not help.

A phone photo is often sideways, upside down or at an angle, and Tesseract reads those badly. So each page gets a confidence score, and a page scoring under 60 gets a second look:

  1. Apple's reader first, if available. If the helper is ready, it reads the page. Its text replaces Tesseract's only when it has clearly more real words.
  2. Probe the orientations. On a small copy (700 pixels), Ovillo tries the page turned 90 and 270 degrees, upside down, mirrored, and skewed 15 degrees either way, stopping early once a result is good enough.
  3. Re-read the winner. If one orientation is clearly better, that version is read again at full size, and kept only if it is surer and has not lost most of the words.

The extra effort is capped at 20 seconds per page and 90 seconds per document, so a bad 500-page scan cannot run for hours. Pages that are read cleanly cost nothing extra.

Important. OCR can misread. A value from a page that OCR read poorly (under 60 percent, or rescued by Apple's reader) is held for you to check unless a recognised table on the page confirms it. Always look at the page itself, which every record entry links to.

The lab reader

Lab results are read from the document's own text by plain rules, with no model. src/labs.mjs knows 50 built-in tests, with the names English and Spanish reports print for them (for example "ferritin" and "ferritina"). For each match it keeps:

It understands several layouts: a line of text, a result with the unit after the range, and rows of cells. It will not read a layout it does not know; that becomes a new rule, not a guess. Short abbreviations such as K or Na only count when a number follows right after, so a bacterium name or a surname is never read as a result.

Several things make the reader hold a value instead of filing it: a test run on another specimen (urine, sweat, spinal fluid and others, named anywhere on the page or in a table title), a line that prints the value about someone else (a parent, a donor, cord blood), an unknown unit, a value far outside its range, and two readers disagreeing. When the helper supplies tables, the table reader and the line reader are compared; if they read a value differently, both readings are shown. Ovillo also never converts a value in place: another unit is shown beside it.

The patient check

src/identity.mjs reads the patient field of each document, so a relative's report in the same folder does not land in this person's record. It looks for labels such as "Patient", "Patient name", "Paciente" and "Nombre del paciente" in English and Spanish. A bare "Name:" is ignored because letters use it for doctors. The result is one of four states:

A first name that looks like a scan damaging the real one ("Luci" for "Lucía", surnames exact) is held too, but in a gentler "hard to read" state. Only labelled numeric birth dates are read. See Review for how held documents are decided.

Duplicates

The local search index

Text is cut into passages of up to about 1,400 characters, with some overlap. Each passage is indexed twice: by words (BM25) and by meaning, using multilingual-e5-small, a small model that runs in the page and covers Spanish and English. The two result lists are merged. The model loads from the app's own files and cannot download anything. The index is stored as encrypted blobs. If the saved index was made by a different model, or cannot be loaded, Ovillo rebuilds it from the stored text. If the model cannot load, documents are still searchable by words.

The local name model

A second small model, bert-base-multilingual-cased-ner-hrl, finds the names of people written without a title ("came with her daughter Lucía Fernández"). Its only job is to teach the scrubber which names to replace before an assistant request leaves. It runs on this Mac and cannot download anything. See Privacy and security.

What the Apple silicon helper adds

The bundled helper, offload, uses Apple's on-device models and exists only in the Apple silicon app, on macOS 27. On an Intel Mac, or an Apple silicon Mac on an earlier macOS, the built-in readers described above do all the work and the helper features are absent. It adds:

The helper starts on first use, never at launch. It runs inside the macOS App Sandbox with no network permission, so macOS itself refuses any connection it tries. It receives document bytes in memory, through a pipe, writes no temporary files, keeps nothing between requests, and stops when the vault locks or after a minute without a call. The first read after an install can take a long while, because Apple compiles its models then.