Quadra core
Reads a supermarket till receipt from a photo. No ML model, no network calls: Tesseract does the OCR, then geometry and arithmetic do the rest.
from quadra_core import parse_image
receipt = parse_image("receipt.jpg")
receipt.status # "ok", "review" or "failed"
receipt.document # the items, discounts and totals, ready for JSON
pip install quadra-core
sudo apt install tesseract-ocr tesseract-ocr-ita
One status to look at
| status | meaning |
|---|---|
ok |
Every line adds up to the receipt's total, and nothing on the receipt contradicts it. Nothing to check. |
review |
Usable, but a person should check the fields listed in document["review"]. Only those. |
failed |
No items were found. |
Each review entry names the field, the item it concerns, and what went wrong, in simple terms:
"review": [
{"code": "total_mismatch", "field": "total", "item": null,
"message": "The lines add up to 90,09, but the total is 101,25."},
{"code": "price_missing", "field": "price", "item": null,
"message": "No price read for “BANANE”."},
{"code": "price_unconfirmed", "field": "price", "item": 3,
"message": "Read as 1,98; 1,95 would make the receipt add up."}
]
Items with an entry also carry "needs_review": true, so a review screen can highlight just those rows. Everything else in warnings is for debugging and never changes the status.
How it decides
The line totals plus the discounts have to equal the total, so the parser can check its own work.
When they don't, it reads the price column again on its own, three different ways, and looks for the prices that make the receipt add up. The arithmetic only proposes. A correction is accepted without review only when both of these hold:
- the total was confirmed by a second reading (the printed total and the payment line agree, or you typed it in), and
- every re-read of that price agrees on the new value.
Anything less is kept as a suggestion, and that line goes to review. Prices are never adjusted towards a total that the payment line contradicts.
When the total is unreadable
Type it in, and every line is checked against it:
receipt = parse_image("receipt.jpg", total=10125) # minor units: 101,25
Command line
quadra-core parse receipt.jpg # prints the JSON
quadra-core parse receipt.jpg --total 101,25
quadra-core profiles # the shops supported
The exit code is the status: 0 ok, 1 review, 2 failed, 3 error.
The document
| key | what it holds |
|---|---|
status, review |
as above |
line_items |
description, quantity (a decimal string), unit price, line total, the discount printed under it, VAT code, and where it sits on the photo |
adjustments |
the discounts, each pointing at the item it applies to |
totals |
subtotal, discounts, the computed total, the total it was checked against and where that came from (printed, payments or supplied), and the difference |
warnings, ocr |
debugging detail |
Money is always in integer minor units (cents). A field the parser could not read is null; it is never guessed. The JSON Schema is exported as quadra_core.SCHEMA_PATH.
receipt.tsv and receipt.image hold the OCR and the prepared photo that item boxes refer to, if you want to archive them. You can re-parse the TSV later with parse_tsv, without the photo.
Adding a shop
Everything shop-specific is a TOML profile in src/quadra_core/profiles/. The code knows how to find a price column, and the profile says what a price looks like.
Start with a stub, since calibrate.py needs a profile to report against:
# src/quadra_core/profiles/myshop.toml
[profile]
id = "myshop"
version = 1
calibrated = false
[format]
money = '\d{1,3},\d{2}'
Then point it at a photo:
python tools/calibrate.py receipt.jpg --profile myshop
It prints what Tesseract read, where the price column landed, which rule matched each line, and what a person would be asked to review. Add [[rules]] and [regions] anchors until the lines stop coming back as unknown. Rules are tried in order and the first match wins, so put the specific ones first.
Add [fingerprint] anchors so the shop is recognised without --profile. A VAT number is more stable than a branch address. Save the OCR as a fixture with --save-fixture NAME so later changes have something to regress against. Set calibrated = true once it balances, otherwise every parse carries a profile_not_calibrated warning.
Before committing a fixture, check it for personal data: the address, the loyalty card number, the points balance. tools/scrub.py covers the Esselunga ones.
Development
uv run pytest
Licence
This project is licensed under the MIT License. See the LICENSE file for details.
Metadata
Release files for quadra-core 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| quadra_core-0.2.0.tar.gz | 94.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| quadra_core-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 177.8 kB
Release files / quadra_core-0.2.0.tar.gz
| Download URL | quadra_core-0.2.0.tar.gz |
|---|---|
| Size | 94.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
53ae124453465f9bf581c673c42bf272519850ad133abe217ca59cea8365d7d3
|
|
BLAKE2b-256 checksum How to use checksums |
f3e5505cfab80568fff00863998acdcc4d457d7d5128f061a457ed1ceaf4a607
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency logRelease files / quadra_core-0.2.0-py3-none-any.whl
| Download URL | quadra_core-0.2.0-py3-none-any.whl |
|---|---|
| Size | 83.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c30c0f57f02a64760f524e51ef2fd71a299fd7da01dcf98ec80b14e3efbaf3f2
|
|
BLAKE2b-256 checksum How to use checksums |
17584b8e20a73e4e975f53c38d8eb231dc9c7bf683dd517a4fc5a6e95da2073c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency log