feat(ocr): real-receipt fixture corpus and image preprocessing #76

Merged
TimCane merged 10 commits from feat/ocr-real-receipt-corpus into main 2026-07-07 08:55:37 +01:00
TimCane commented 2026-07-06 23:17:54 +01:00 (Migrated from github.com)

Summary

  • grow the ReceiptParser fixture corpus from 7 synthetic seeds to 33
    fixtures
    , adding 26 real receipts transcribed from public receipt images
    (UK/US/EU: pubs, diners, Lightspeed/Eats365/Kobas tills) with hand-authored
    expected.json; the whole corpus is green
  • drive the parser heuristics the real layouts demand:
    • two-pass grand total; drop tax/tip/service and noise printed below it
      (VAT breakdowns, "divide by N" hints, payment lines)
    • trailing VAT-class codes (4.00 B), per-unit price columns
      (2 Bread 2.00 4.00), @ 6.50 unit prices, 2 @ $35.50 detail lines
    • split service-charge lines (label above the amount), gratuities, category
      rollups (8 DRINK, 3 FOOD), Total Taxes/AMOUNT/AMT/Balance
      totals, Spanish IVA, VAT-rate breakdown rows
    • park bare/columnar amounts as warnings rather than dropping them silently
  • replace the brittle HaveCount(7) corpus assertion with a named-seeds check
  • preprocess images in the sidecar before inference (grayscale, denoise,
    autocontrast, upscale, angle classifier; opt-in Otsu binarize) — pure Pillow
    so it unit-tests without the numpy/paddle wheels
  • add tools/record_fixture.py, a dev-only helper that POSTs an image at the
    sidecar and writes ocr.json into the corpus
  • docs: preprocessing pipeline + config flags (06), recording loop (11)

Test plan

  • dotnet test BillSplitter.Tests - 85/85 green (33 corpus fixtures + seeds)
  • ruff check clean on the OCR changes; py_compile clean
  • OCR unit tests (preprocessing + shaping) run in CI - this dev container's
    interpreter is missing stdlib so pytest can't run locally, matching the
    repo's OCR-is-CI-only setup

Notes

  • images are public Google-image-search receipts; the /ocr response shape is
    unchanged, so no docs/04 change
  • ocr.json is transcribed from the images (no paddle sidecar in the dev
    container); re-record with tools/record_fixture.py when one is available
  • a few genuinely messy receipts (Toby Carvery product-group summaries, the
    Ding Dong pizza modifier) parse imperfectly by design - the corpus captures
    these as the documented limits of a line-based parser
  • a follow-up will evaluate refactoring ReceiptParser toward a pluggable
    detector/rule + scoring + validation architecture (design docs pending)

Closes #75

## Summary - grow the `ReceiptParser` fixture corpus from 7 synthetic seeds to **33 fixtures**, adding 26 real receipts transcribed from public receipt images (UK/US/EU: pubs, diners, Lightspeed/Eats365/Kobas tills) with hand-authored `expected.json`; the whole corpus is green - drive the parser heuristics the real layouts demand: - two-pass grand total; drop tax/tip/service and noise printed **below** it (VAT breakdowns, "divide by N" hints, payment lines) - trailing VAT-class codes (`4.00 B`), per-unit price columns (`2 Bread 2.00 4.00`), `@ 6.50` unit prices, `2 @ $35.50` detail lines - split service-charge lines (label above the amount), gratuities, category rollups (`8 DRINK`, `3 FOOD`), `Total Taxes`/`AMOUNT`/`AMT`/`Balance` totals, Spanish `IVA`, VAT-rate breakdown rows - park bare/columnar amounts as warnings rather than dropping them silently - replace the brittle `HaveCount(7)` corpus assertion with a named-seeds check - preprocess images in the sidecar before inference (grayscale, denoise, autocontrast, upscale, angle classifier; opt-in Otsu binarize) — pure Pillow so it unit-tests without the numpy/paddle wheels - add `tools/record_fixture.py`, a dev-only helper that POSTs an image at the sidecar and writes `ocr.json` into the corpus - docs: preprocessing pipeline + config flags (06), recording loop (11) ## Test plan - `dotnet test BillSplitter.Tests` - 85/85 green (33 corpus fixtures + seeds) - `ruff check` clean on the OCR changes; `py_compile` clean - OCR unit tests (preprocessing + shaping) run in CI - this dev container's interpreter is missing stdlib so pytest can't run locally, matching the repo's OCR-is-CI-only setup ## Notes - images are public Google-image-search receipts; the `/ocr` response shape is unchanged, so no `docs/04` change - `ocr.json` is transcribed from the images (no paddle sidecar in the dev container); re-record with `tools/record_fixture.py` when one is available - a few genuinely messy receipts (Toby Carvery product-group summaries, the Ding Dong pizza modifier) parse imperfectly by design - the corpus captures these as the documented limits of a line-based parser - a follow-up will evaluate refactoring `ReceiptParser` toward a pluggable detector/rule + scoring + validation architecture (design docs pending) Closes #75
Sign in to join this conversation.
No description provided.