A weekend project: 10 scans go through Tesseract OCR into JSON, Parquet and DuckDB, with all 40 of 40 fields extracted. The top supplier is Northwind Office Supply at $1,449.00, and Part 2 adds questions in plain English.
python3 --version.pip install "pandas>=2.2"pip install "pyarrow>=15"pip install "duckdb>=1.0"pip install "pytesseract>=0.3.10"pip install "pillow>=10"pip install "fastembed>=0.4"pip install "lancedb>=0.10"brew install tesseractgit clone https://github.com/DayanEbrar0X/data-anatomy.ai.git cd data-anatomy.ai
python3 -m venv .venv source .venv/bin/activate
pip install -r requirements.txt # or just this lesson: pip install "pandas>=2.2" "pyarrow>=15" "duckdb>=1.0" "pytesseract>=0.3.10" "pillow>=10" "fastembed>=0.4" "lancedb>=0.10"
brew install tesseract # macOS sudo apt install tesseract-ocr # Debian / Ubuntu tesseract --version
cd data-engineering/build-lab-02-documents-to-data cd docs_pipeline python3 main.py # Part 1 python3 search.py # Part 2
import json
import duckdb
import pandas as pd
from ocr import ocr_folder
from parse import parse
texts = ocr_folder("invoices")
records = [parse(t) for t in texts.values()]
with open("data/docs.json", "w") as f:
json.dump(records, f, indent=2)
df = pd.DataFrame(records)
df["date"] = pd.to_datetime(df["date"])
df.to_parquet("data/docs.parquet", index=False)
ok = df.drop(columns="text").notna()
n, t = ok.sum().sum(), ok.size
print(f"{len(df)} invoices read, {n}/{t} fields")
sql = """SELECT vendor, sum(total) AS spend
FROM 'data/docs.parquet'
GROUP BY vendor ORDER BY spend DESC LIMIT 3"""
for vendor, spend in duckdb.sql(sql).fetchall():
print(f"{vendor:<26}{spend:>9,.2f}")These invoices are just pictures. In three files, they become data you can query with SQL. Here's a project you can build over a weekend. Pixels to text.
Text to fields. Fields to a table. Our folder holds ten scanned invoices from six vendors. A small script drew them with Pillow, so every run is identical.
File one: ocr.py. To a computer, a scan is only pixels: a grid of brightness values, with no letters inside. Pytesseract calls Tesseract, a free OCR engine.
Optical character recognition: pixels in, characters out. For every PNG in the folder, we open it and convert it to grayscale. Tesseract finds each line of text, then recognizes it character by character. We keep each file's text, keyed by its name.
File two: parse.py. Raw text is one long string. We want a schema: the same named fields for every invoice.
Each field is a pattern. Invoice number: INV, then digits. The date: year, month, day. The total: a dollar amount.
In parse, we drop the blank lines, and the first line is the vendor. Each pattern searches the text, ignoring case. OCR read one capital I as a small i. The total becomes a real number, and we keep the full text.
Part two needs it. File three: main.py. OCR every scan, parse every text, and save the records as JSON.
Then pandas builds a table, dates become real dates, and we write Parquet. Parquet stores each column together, typed and compressed, so tools read only the columns they need. A quick check: how many of the forty fields did we find? And DuckDB runs SQL right on the Parquet file.
Spend by vendor, no database server. Let's run it. Ten invoices read. Forty out of forty fields found.
Northwind Office Supply leads with $1,449, then Cobalt Cloud, then Summit Freight. Here's the real payoff. This one table can now serve two consumers. SQL analytics, like this query.
And AI retrieval: next time, we embed the same text into a vector database. Pixels, text, fields, table. Scans in, SQL out.
Read the full article →