unit U3 — 3 of 5
PDFs & extraction
text layer vs scan, when OCR, what to trust
PDFs come in two kinds that look identical to a human and could not be more different to a flow. A digital PDF carries a text layer — the characters are really in the file, and extraction is fast, free and exact. A scanned PDF is a photograph of a page wrapped in a PDF container; it contains no text at all, and extraction returns an empty string. Every document pipeline needs to tell them apart before it decides what to do.
The test is simple: extract the text layer and measure it. A meaningful character count means a digital PDF, and you should use that text — it is the ground truth. Near-zero means a scan, and now you need OCR, which is slower, costs money, and is approximate: it will confuse `0` with `O`, `1` with `l`, and lose table structure. That approximation is fine for search and routing, and it is not fine for an amount you are about to post to a ledger. Route those to a human, or to a second check.
Where it breaks: assuming extraction succeeded because it did not throw. An empty string is a perfectly valid return value from a scanned page, and a flow that carries it forward produces a document with no content, an LLM asked to summarise nothing, and a confidently empty result. Assert a minimum character count before trusting extraction, and branch when it fails.
worked example
Telling a digital PDF from a scan, and treating OCR output as approximate.
const MIN_CHARS = 40; // below this, treat as "no text layer"
export const code = async (inputs) => {
const text = await extractTextLayer(inputs.pdfBytes);
// An empty string is a VALID return for a scan. Not throwing proves nothing.
if (text.trim().length >= MIN_CHARS) {
return { source: 'text-layer', text, exact: true };
}
const ocr = await ocrPages(inputs.pdfBytes); // slower, costs money
return {
source: 'ocr',
text: ocr.text,
exact: false, // 0/O and 1/l are guesses
confidence: ocr.confidence,
// Anything financial from OCR goes to a human, not straight to the ledger.
needsReview: ocr.confidence < 0.9 || /total|amount|iban/i.test(ocr.text),
};
};field checklist
- Extract the text layer first and measure what came back.
- Treat a near-empty extraction as "scanned", not as success.
- Reach for OCR only when there is no text layer.
- Mark OCR output approximate and carry its confidence forward.
- Route OCR-derived financial values to a human before they are posted.
common failure — A summariser given an empty document
An invoice pipeline extracted PDF text and passed it to a model for structuring. Scanned invoices returned an empty string, the extraction step did not throw, and the model confidently produced a plausible invoice with invented values from nothing. Assert a minimum character count before trusting extraction, branch scans to OCR, and mark anything OCR-derived as approximate so a human sees it before it reaches a ledger.
check your understanding
Text extraction from a scanned PDF returns an empty string without throwing. Which assertion belongs before the text is trusted?
next unit opens once this is passed
sandbox validation
The check above confirms you followed the unit. Marking the module COMPLETED takes more: build the automation in your own engine and submit the exported flow and its run evidence, signed, to your unique validation URL. See the module page for that spec.