Skip to main content

Overview

Document ingestion is handled by the document transcoder in src/transcode/document.hpp / document.cpp. It decomposes an uploaded PDF or spreadsheet into an ordered list of LLM-consumable parts (text and/or images), strategy “text first, image (and OCR) fallback.” For the conceptual walkthrough see File Processing.
The document transcoder decomposes an uploaded document into text and image parts that a vision-capable LLM reads during the chat turn. (verify upload wiring against the current HTTP layer.)

Supported formats & build gates

Format is sniffed from magic bytes (detect_format), then routed: With a gate off, the format still sniffs but process() throws DocumentError("... not built").

The API

What each path emits

  • PDF, text page → a Text part with the extracted text layer.
  • PDF, scanned page (text below pdf_text_threshold) → rasterized at raster_dpi, PNG-encoded, run through image::Transcoder to fit image_limits, emitted as an Image part. In a MIROBODY_ENABLE_OCR build the same raster is also OCR’d and the recovered text appended as a Text part.
  • .xlsx / .xls → one Text part per worksheet, a GitHub-flavored Markdown table (## <sheet title> + table). Never an image.
  • .csv → a single Text part (RFC 4180 parse → Markdown table; UTF-8 BOM stripped).
to_markdown() flattens a Document: text parts inline, image parts as a ![page N image](...) placeholder line.
max_pages caps pages/sheets; when it trips, a trailing “(remaining … omitted: max_pages reached)” note is appended so nothing is silently dropped.

Threading (PDF)

PDFium’s library init/teardown is process-global and not thread-safe, and a loaded document may not be touched concurrently. The PDF path serializes all PDFium work behind an internal mutex and owns the one-time global init, so Transcoder is safe to call from multiple threads — PDF work simply isn’t parallel while a PDF is being processed. CSV / xlsx have no such constraint.

Try it — the document CLI

The cli/document.cpp tool (built when MIROBODY_BUILD_TOOLS is ON) exercises the transcoder end to end:
Image parts (rasterized scanned pages) are written next to <out>; text parts land in the Markdown. --target picks the vision preset for rasterized pages (default qwen).

Units

Values pulled from documents can be normalized to UCUM with the units engine (src/fhir/units/) — see Data Mapping.

Tests

tests/transcode/document_test.cpp, tests/transcode/file_test.cpp, tests/transcode/image_test.cpp, and tests/fhir/units_test.cpp (Catch2). Run the transcoder subset:
Writing extracted values back to the FHIR store as coded Observations (the document → indicator → FHIR pipeline) is in progress — the transcoder and UCUM normalization ship; terminology mapping and write-back are planned (src/fhir/README.md phases 3–4). (verify)

Next steps

File Processing (concept)

The pipeline at a glance

Data Flow

Where uploads fit

Data Mapping

UCUM normalization

Testing

Run the transcoder tests