PDF to CSV Extraction
PythonData extractionPulls 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 pdfplumberFiles: 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)
| file | account | date | description | qty | amount |
|---|---|---|---|---|---|
| statement-1.pdf | NW-1042 | 2026-09-01 | Toner cartridge | 4 | 194.53 |
| statement-1.pdf | NW-1042 | 2026-09-02 | Label rolls | 6 | 441.17 |
| statement-1.pdf | NW-1042 | 2026-09-03 | Boxes, large | 2 | 99.26 |
| statement-1.pdf | NW-1042 | 2026-09-04 | Toner cartridge | 8 | 183.76 |
| statement-1.pdf | NW-1042 | 2026-09-05 | Label rolls | 4 | 318.66 |
| statement-1.pdf | NW-1042 | 2026-09-06 | Label rolls | 9 | 606.67 |
| statement-1.pdf | NW-1042 | 2026-09-07 | Toner cartridge | 7 | 365.46 |
| statement-1.pdf | NW-1042 | 2026-09-08 | Boxes, large | 4 | 207.54 |
| statement-2.pdf | NW-2210 | 2026-09-01 | Paper, case | 9 | 297.23 |
| statement-2.pdf | NW-2210 | 2026-09-02 | Packing tape | 2 | 30.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'}")