Document-AI

Prepare documents for AI

A practical guide to getting a repository of files ready for search, retrieval-augmented generation, and other language-model workflows.

Why preparation matters

This guide is for people who already have a pile of files: a shared drive, an exported mailbox, a wiki dump, a folder of contracts. Search and language models can only use the text you give them. They do not know which copy is current. They cannot read a photograph of a page until something has recognized the letters. They will repeat a password, a home address, or an old policy if that text is in the index.

Most bad answers start in the files, before anyone writes a prompt. Duplicate drafts compete with the real document. Scans have no text layer. Tables get read across the wrong way. Every email reply contains the whole thread again. Dates are missing, so nothing can tell the 2019 draft from the rule in force.

Preparation is ordinary library work. You decide what is in scope, keep one current copy, turn it into clean text with a title and a source, and check that a real question finds the right passage. The steps below are enough to start with common tools. Work them in order. Skip a conversion step when you do not have that kind of file, and still run the checklist so the skip was a choice.

Tool names below are starting points. Commands change between versions. If a flag differs from what you have installed, the project’s own --help wins.

Target formats

Pick one text form for the index and keep the original file as the record people still edit or sign. The text form is what search and the model read.

Recommended target for each kind of file. Keep the original next to the text.
You have Store this for the index Why this form
Text PDF Markdown or UTF-8 text, plus the PDF The words are already in the file. The PDF stays the record.
Scanned PDF A searchable PDF, then Markdown or UTF-8 text OCR adds a text layer. Models cannot read the picture alone.
Email .eml or .mbox, plus a cleaned text copy Headers stay intact. The text copy is what you index.
Word, ODT, RTF, HTML Markdown Headings survive and give you sections to split on.
Plain text UTF-8 text with LF line endings Stops encoding damage and noisy diffs.
Spreadsheet CSV, one table per file, plus a column note Each row means one thing, and each column has a name.
Slides Markdown, one section per slide Titles, body text, and speaker notes become readable.
Image or photo of a page The image, a caption, and OCR text if it contains words Search sees the caption and the recognized text.

Step 1

Inventory and scope

Why You can only prepare a collection you have listed. Indexing everything also pulls in drafts, personal mail, and files a smaller group was meant to see alone.

How

  • List every place documents live: shared drives, SharePoint or Google Drive exports, wikis, ticket exports, local project folders, and mail archives.
  • For each collection, write the path, the kinds of files, the date range, the owner, and whether people still treat that place as the source of record.
  • Mark each collection in or out. Out includes drafts you will not stand behind, personal mailboxes, copies of a system you already included, and anything legal or contract staff say must stay out.
  • Name one owner for the prepared corpus, and keep the owner of each source collection. Someone has to answer “is this the current file?”

A spreadsheet is enough. Suggested columns: location, file types, date range, owner, in or out, reason, sensitivity. This is a decision record. It does not need to be a cataloging system.

Step 2

Clean up

Why Duplicates and old drafts compete with the current document. Search then returns several answers, and the longest one is often the wrong version.

How

  • Find exact copies by file content. fdupes groups files that are byte-for-byte identical. Keep one copy of each group.
  • Find near copies by hand where the names give them away: final, v7, latest, dates, and (1). Ask the owner which file is current. If nobody will say, keep the latest approved copy and write that rule in the inventory.
  • Move rejected copies to a folder you do not index, or delete them when retention rules allow. Leave them out of the folder you will convert.
  • Remove junk an indexer would treat as documents: .DS_Store, Thumbs.db, desktop.ini, Word lock files (~$*), .tmp files, empty files, and installer logs.
List identical files under a folder
fdupes -r ./documents

Step 3

Folders and file names

Why The path becomes part of the citation. A person and a retrieval system both need to see which document a passage came from.

How

  • Use one collection per top folder: policies, contracts, meetings, support.
  • Stay shallow. Collection, then year or topic, then the file is enough.
  • Use lowercase names with hyphens. Put an ISO date first when the date identifies the document: 2024-11-01_travel-policy.md.
  • Leave final, latest, and new out of names. The current folder holds the current files. Older copies live in an archive folder you do not index.
  • Add a short README in each collection: what it contains, who owns it, and what “current” means.
  • Keep secrets and personal data out of file names. Names show up in logs, citations, and search results.
A layout that cites cleanly
policies/travel/2024-11-01_travel-policy.md
meetings/2024/2024-03-12_board-minutes.md
contracts/acme/2023-06-01_master-services-agreement.pdf
mail/2024/thread-9f3a/

Step 4

Metadata

Why Title, date, author, source, type, and permissions tell you what a retrieved paragraph is and who may see it. A file’s modified time is a weak substitute for the date on the document.

How

Put a header at the top of each text file, or a sidecar with the same stem, such as travel-policy.meta.txt. Use the date printed on the document when it differs from the file timestamp.

  • title — the name a person would use.
  • date — the document date, as YYYY-MM-DD.
  • author — a person or the team that issued it.
  • source — where this copy came from: a URL, a library path, or a message id.
  • type — a short list you actually use: policy, contract, email, minutes, spreadsheet, how-to.
  • permissions — the audience: public, internal, or a named group such as hr.

Keep a stable id if you rename the file, so a later update replaces the same document. A permission label does nothing until the search application filters on it. Step 11 is where that filter has to hold.

A header block in the text file
---
title: Travel policy
date: 2024-11-01
author: People team
source: sharepoint://policies/travel.docx
type: policy
permissions: internal
id: travel-policy
---

Step 5

PDFs

Why A PDF is a picture of a page plus, sometimes, a hidden text layer. If the text layer is missing or scrambled, extraction returns blank pages or nonsense, and the model has nothing true to quote.

How

Text PDFs and scanned PDFs

Open the file and try to select a sentence. If you can copy real words, it is a text PDF (sometimes called born-digital). If selection fails, or the copied text is gibberish, the page is a scan or a broken export.

Confirm from the command line with Poppler’s pdftotext. A text PDF prints words. A scan prints almost nothing.

Read a PDF that already contains text
pdftotext -eol unix policy.pdf policy.txt

Default pdftotext rebuilds reading order, which is what you want for prose. Add -layout when you need to see columns while you check a page. -layout pads lines with spaces, so it is a poor file to chunk as-is. Page breaks are form-feed characters; leave them in if you want to cite a page number. -nopgbrk removes them.

Some PDFs have a text layer that is still wrong, because the font encoding is damaged. If the words look garbled, treat the file like a scan and OCR it.

OCR with Tesseract and OCRmyPDF

Tesseract reads an image of text. OCRmyPDF runs Tesseract on a PDF and writes a searchable PDF with the text hidden behind the page image. Keep the original scan, and write the OCR result beside it.

Set the language. Quality drops when the language is wrong. Combine languages with a plus sign when a document mixes them. --deskew straightens pages that were scanned slightly crooked. --sidecar also writes a plain-text file.

OCR a scanned PDF and write a text sidecar
ocrmypdf -l eng --deskew --sidecar policy.txt scan.pdf policy-searchable.pdf

OCRmyPDF stops when a PDF already has text, so a mixed file (some real text, some scans) needs --skip-text. That option OCRs only the pages that have no text yet.

OCR only the scanned pages in a mixed PDF
ocrmypdf -l eng --skip-text mixed.pdf mixed-searchable.pdf

For one image, call Tesseract directly. For a PDF of many scans, prefer OCRmyPDF so the text stays tied to pages.

OCR a single image
tesseract page.png stdout -l eng

Install the Tesseract language packs you name with -l. After OCR, read a few pages. Skew, handwriting, stamps, and tiny footnotes still fail, and those pages need a person.

Tables and layout

Simple extractors read left to right. Two columns become one scrambled column. A table becomes a sequence of cell values with the row and column breaks gone. Headers and footers get glued onto the first and last line of every page.

For a report whose meaning is in the tables, use a tool that emits a Markdown table or structured cells, then spot-check every important table against the PDF. If a table is the contract, keep the PDF page number in the text so someone can open the original.

Extraction tools

  • pdftotext (Poppler). Fast, and the right start for ordinary text PDFs. Weak on complex layout.
  • PyMuPDF. A Python library for page-by-page text, images, and positions. Use it when you want page numbers and a script you control.
  • Docling. Converts PDFs and several office formats to Markdown or JSON, with attention to reading order and tables. Current docs show docling convert policy.pdf --to md; older releases used docling policy.pdf. Trust docling --help on your install.
  • Unstructured. Splits a document into elements such as title, paragraph, and table, which are natural boundaries for chunking.
  • Marker. Converts a PDF to Markdown in one pass, including many tables and multi-column pages.

Start with pdftotext for simple text PDFs. Move to Docling, Unstructured, or Marker when columns or tables come out wrong. A short PyMuPDF script is a good middle step when you only need page-labeled text:

Page-labeled text with PyMuPDF
import pymupdf

doc = pymupdf.open("policy.pdf")
for number, page in enumerate(doc, start=1):
    print(f"\n\n--- page {number} ---\n")
    print(page.get_text())

Step 6

Emails

Why Decisions often live in mail, and a raw export is full of signatures, legal footers, and the same quoted history repeated under every reply. Indexed that way, one thread crowds out everything else.

How

Export .eml, .mbox, or .pst

  • .eml is one message per file. It is the easiest form to process and to cite.
  • .mbox is many messages in one file. Thunderbird and Google Takeout mail use it.
  • .pst is an Outlook archive. Convert it before ingestion. readpst (from libpst) can write one file per message with an extension such as .eml.
Turn a PST into separate message files
readpst -e -o mail/ archive.pst

Keep the exported originals. Build a separate text copy for the index.

Keep threads together

Group messages with the Message-ID, In-Reply-To, and References headers. Sort each thread by date, and write one Markdown file with a heading per message. When those headers are missing, group by subject after you strip prefixes such as Re:, Fw:, Fwd:, and Aw:, and expect a few unrelated messages to land together. Check those.

One file for the thread, messages in time order
# Q1 pricing
permissions: internal

## 2024-02-03 Ada Lovelace to Ben Smith
message-id: <abc@example.com>

Can we hold last year's rate through March?

## 2024-02-04 Ben Smith to Ada Lovelace
message-id: <def@example.com>

Yes, through 31 March. The attached schedule is the one to use.

Attachments

Save attachments next to the parent message, with the message id or date in the file name. List those file names in the message text so the link survives. Convert or OCR an attachment the same way you would if it had arrived on its own: a contract PDF follows step 5, a sheet follows step 8. Skip logos, tracking pixels, and decorative images, and note that you skipped them.

Signatures, disclaimers, and quoted replies

Clean the text copy, and leave the .eml untouched.

  • Drop lines that start with >. They repeat an earlier message that you already store once in the thread file.
  • Drop a trailing block that begins with the conventional signature mark -- followed by a space, when that mark is present.
  • Drop legal footers you have listed, once you can see the same paragraph on message after message.

Read a sample of threads after the cleaner runs. Some clients quote without >. Sometimes the only statement of a decision sits in a signature or a quoted blob. Fix those by hand, or rely on the thread file, which already contains the earlier message a single time.

Keep sender, recipient, and date

Copy From, To, Cc, Date, Subject, and Message-ID into the text header. The body cleaner has to leave those fields alone. Bcc is often missing on mail you received; leave it out rather than guessing. These fields are how someone checks an answer against the real message.

Step 7

Text, Word, and office files

Why A .docx or .odt file is a package of XML. Models need the words and the headings. The wrong encoding turns accents into junk and can split a word so search misses it.

How

Convert Word, ODT, RTF, and HTML to Markdown so headings stay headings. Pandoc is the general tool. --wrap=none avoids hard line breaks in the middle of sentences.

Word to Markdown with Pandoc
pandoc -f docx -t markdown --wrap=none -o policy.md policy.docx

MarkItDown converts Office files to Markdown for text pipelines, including Word, PowerPoint, and Excel. Use it when you want one command across those formats. For slides, see step 9. For a large sheet, prefer the CSV rules in step 8.

Office file to Markdown with MarkItDown
markitdown policy.docx -o policy.md

Use plain text when the source is already prose with no headings. Keep the original Office file if people still edit there. The Markdown is the build you index.

Encoding and line endings

  • Save every text file as UTF-8.
  • On Linux and many other Unix systems, file --mime-encoding prints a guess. Open the file and look at accents, quotes, and dashes before you trust the guess.
  • Convert a known legacy encoding with iconv. Let the command fail if the bytes do not match, so a bad guess is visible.
  • Use LF line endings (\n). dos2unix converts a file from CRLF. In Git, a .gitattributes line of * text=auto normalizes text files on commit.
Check an encoding, then convert Windows-1252 to UTF-8
file --mime-encoding notes.txt
iconv -f WINDOWS-1252 -t UTF-8 notes.txt > notes.utf8.txt

Step 8

Spreadsheets and CSV

Why A worksheet is often several tables, a chart, and a title stuffed into one grid. Retrieved row by row, that mix has no stable meaning.

How

  • Put one table in each sheet you export. If a sheet stacks two tables under a chart, split them into two CSV files and describe the chart in a sentence, or leave the chart out.
  • Use a single header row. Names are short, unique, and readable: invoice_date, not Column1 and not two columns both called Date.
  • Unmerge cells inside the table. Keep totals out of the data file. If you need a total, write it in the column note.
  • Export each table to CSV in UTF-8. Write dates as YYYY-MM-DD. Store numbers without currency signs, and put the unit in the column name or the note (amount_usd).
  • Describe the columns in a short note beside the CSV: what one row represents, what each column means, and the allowed values when they are a small set.
  • When the formulas themselves matter, keep the workbook and also export the values.
A column note stored next to the CSV
# invoices.csv
One row is one invoice.

invoice_id   Identifier in the billing system
issued_on    Invoice date, YYYY-MM-DD
amount_usd   Total in US dollars, no currency symbol
status       open, paid, or void

Step 9

Images, slides, and scans

Why A chart, a photo, and a slide made of pictures are invisible to text search until someone describes them or OCR reads the words on them.

How

  • Write alt text or a caption for every image that carries meaning. Say what it shows and the figure that matters (“network diagram: failover path is backup-east, added March 2024”). A file name is not a caption.
  • Store the caption in a sidecar text file (network-diagram.txt) or under the figure in the Markdown you already produced.
  • For slides, write one Markdown section per slide. Include the slide number, the title, the body text, and the speaker notes. MarkItDown reads PPTX files. Add your own caption when a slide is mostly a picture.
  • OCR page scans with Tesseract or OCRmyPDF, as in step 5. A photograph of a whiteboard often comes back garbled. Transcribe the parts you need when the OCR text is wrong.
Slides to Markdown
markitdown slides.pptx -o slides.md

Step 10

Chunking

Why Retrieval works on passages, and models read a limited amount of text at once. A whole manual as one blob is hard to retrieve. A single sentence with no heading is hard to understand.

How

  • Split on headings and sections first. If a section is still very long, split it into passages of a few hundred words. Test that size with real questions (step 12) before you tune it.
  • When a split would cut a sentence or a rule in half, repeat the last sentence or two at the start of the next passage. A large overlap mostly stores the same text twice.
  • Put the heading path on every chunk (“Travel policy > Hotels > International”), plus the document title, the source path or URL, and a page or slide number when you have one.
  • Keep a small table in one chunk. For a large table, chunk by row and repeat the column header and the table name on each piece.
  • Keep a warning with the rule it qualifies. Keep the sentence that introduces a list with that list.
  • Give each chunk a stable id, such as travel-policy#hotels-international, so an update replaces that passage instead of adding another copy.
What one chunk carries besides the words
id: travel-policy#hotels-international
source: policies/travel/2024-11-01_travel-policy.md
title: Travel policy
section: Hotels > International
page: 4
permissions: internal

International hotel stays need approval from the traveler's manager
before booking. The cap is the city rate in the finance sheet.

Step 11

Privacy and security

Why A retrieval system quotes what you indexed. Anything in the corpus can show up in an answer, and sending that text to a hosted model is a disclosure. This section is practical hygiene, not legal advice.

How

Decide the audience before you add files. An assistant for one team and a bot on a public site are different corpora. Match the corpus to the people who can query it.

Redact personal data you do not need

Remove national ID numbers, payment card numbers, bank details, home addresses, health details, passwords, and API keys when the document still answers questions without them. Presidio can flag likely personal data. Treat its hits as a review queue. It misses things, and it also flags names you may need to keep, such as the author of a policy. A person reviews high-risk collections.

Honor access rights

If a folder is limited to HR, leave it out of a company-wide index. Store the permission label from step 4, and enforce it in the application that runs the query. A label in the file is not a control on its own. Filters belong in the search layer, where a user only retrieves chunks they are allowed to read.

Leave these out

  • Passwords, keys, tokens, and connection strings.
  • A full personal mailbox, when the task only needed a shared team inbox.
  • Documents whose readers are a smaller group than the people who can query the index.
  • Material a contract, a statute, or a legal hold says you cannot process this way. Ask the people who own that decision.
  • Customer content your agreement does not allow you to send to a model provider.

Know where the index and the model run. Text sent to a third-party API leaves your network. Prefer a setup that matches the rule you already follow for that data.

Step 12

Quality checks

Why Extraction fails quietly. A script can finish with empty pages, reversed columns, or a running header glued into every chunk.

How

  • Spot-check a sample of each file type, including the awkward ones: a scan, a two-column PDF, a spreadsheet, a long email thread, and a slide deck.
  • Open the original beside the text. Read the start, a page from the middle, one table, and one page that has a header, a footer, or footnotes.
  • Look for empty output, damaged characters (a UTF-8 file read as Latin-1, or the reverse), missing headings, a table read across the row instead of down it, and the same disclaimer repeated on every page.
  • Write questions people actually ask. For each one, record the document and the passage that should answer it. Run the questions through search or your retrieval tool.
  • On a miss, fix the file or the chunk first. Typical causes are missing text, a bad split, or an old copy still in the index beside the new one. Change the prompt after those are fixed. Keep the question list and run it again after each pipeline change.

Step 13

Keeping it fresh

Why A stale corpus answers with last year’s policy. Two editable copies drift apart the week someone updates only one of them.

How

  • Keep a single source of truth. People edit the policy site, the contract folder, or the sheet. The text you index is a build from that source, not a second place to edit.
  • When a source changes, re-extract that document and replace its chunks under the same id. Leave the old text out of the index.
  • Log each change: date, document id, old and new hash, and who or what job ran it. Git history is enough when the text files live in Git. Otherwise append one line to a log.
  • When a document is superseded, remove it from the index or mark it superseded and exclude that mark from normal search. Keep the file in an archive folder if retention says you must hold it.
  • Review collections that change often, such as prices and procedures, on a schedule you can keep. Signed contracts usually change only when someone replaces the file.
An append-only change log
2026-04-02  travel-policy        replaced  sha256:9c1e…  people-team-bot
2026-04-02  2019-travel-policy   removed   superseded by travel-policy

Step 14

One-page checklist

Why A short list is what you can actually run before you call a corpus ready. Print this section, or keep it in the collection README.

Scope

  • Locations are listed: drives, mail, wikis, local folders.
  • Each collection has an owner, and the corpus has an owner.
  • In and out of scope are written down, with a reason.

Cleanup, names, and metadata

  • Exact duplicates are removed or parked outside the index.
  • One current version is chosen for each document.
  • Junk files are gone (.DS_Store, Thumbs.db, lock files, empties).
  • Folders are shallow, and names are stable. ISO dates lead when the date matters.
  • Each document has title, date, author, source, type, and permissions.

Conversion

  • Text PDFs are extracted. Scans are OCR’d before extraction.
  • Tables were checked against the original, not only the body text.
  • Mail is .eml or .mbox. PST files are converted.
  • Threads stay together. Attachments sit beside the message.
  • Signatures, disclaimers, and quoted replies are stripped on the text copy only.
  • Sender, recipients, and date are kept.
  • Office files are Markdown or UTF-8 text. Encoding is UTF-8. Line endings are LF.
  • Each sheet export is one CSV table, with a column note.
  • Images and slides have a caption, alt text, or OCR text.

Chunks, access, and upkeep

  • Chunks follow headings. Each chunk has its heading path, source, and id.
  • Personal data you do not need is redacted. A person reviewed the high-risk sets.
  • Secrets and out-of-audience material are left out.
  • Permission labels are stored, and search enforces them.
  • A sample of each file type was compared with the original.
  • Real questions return the right passage. Misses were rechecked after fixes.
  • The corpus is rebuilt from the source of truth.
  • Changes are logged. Superseded documents are out of normal search.