← All samples

PDF to CSV Extraction

PythonData extraction

Pulls line items out of PDF statements (including a two-page one) into a single CSV, and checks each PDF's printed total against the extracted rows.

Run it

cd samples/pdf-to-csv
python make_sample_pdfs.py   # fictional statements
python extract.py   # needs pdfplumber

Files: line_items.csv · extract.py · make_sample_pdfs.py

Sample input: statement-1.pdf · statement-3.pdf (2 pages)

Output (first 10 of 67 rows)

fileaccountdatedescriptionqtyamount
statement-1.pdfNW-10422026-09-01Toner cartridge4194.53
statement-1.pdfNW-10422026-09-02Label rolls6441.17
statement-1.pdfNW-10422026-09-03Boxes, large299.26
statement-1.pdfNW-10422026-09-04Toner cartridge8183.76
statement-1.pdfNW-10422026-09-05Label rolls4318.66
statement-1.pdfNW-10422026-09-06Label rolls9606.67
statement-1.pdfNW-10422026-09-07Toner cartridge7365.46
statement-1.pdfNW-10422026-09-08Boxes, large4207.54
statement-2.pdfNW-22102026-09-01Paper, case9297.23
statement-2.pdfNW-22102026-09-02Packing tape230.55

Run log: all 3 statements matched their printed totals (8, 14 and 45 rows).

Code

"""Extracts line items from PDF statements into one CSV and checks each PDF's printed total against the extracted rows.
Run: python extract.py   (needs pdfplumber; make_sample_pdfs.py creates fictional sample statements)"""
import csv, glob, re, pdfplumber
def money(s): return float(s.replace("$","").replace(",",""))
out=[]; problems=[]
for path in sorted(glob.glob("statements/*.pdf")):
    with pdfplumber.open(path) as pdf:
        text="\n".join(p.extract_text() or "" for p in pdf.pages)
        acct=re.search(r"Account:\s*(\S+)",text).group(1)
        rows=[]
        for p in pdf.pages:
            for t in p.extract_tables():
                for r in t:
                    if r and re.match(r"\d{4}-\d{2}-\d{2}",r[0] or ""): rows.append(r)
        total=money(re.search(r"Total due:\s*(\$[\d,]+\.\d\d)",text).group(1))
        s=round(sum(money(r[3]) for r in rows),2)
        ok="OK" if abs(s-total)<0.005 else "MISMATCH"
        if ok!="OK": problems.append(path)
        print(f"{path}: account {acct}, {len(rows)} rows, extracted {s:.2f} vs printed {total:.2f} -> {ok}")
        for r in rows: out.append({"file":path.split("/")[-1],"account":acct,"date":r[0],"description":r[1],"qty":int(r[2]),"amount":money(r[3])})
with open("line_items.csv","w",newline="") as f:
    w=csv.DictWriter(f,fieldnames=list(out[0])); w.writeheader(); w.writerows(out)
print(f"wrote line_items.csv with {len(out)} rows; problems: {problems or 'none'}")